libfacedetection

repository·master·Indexed 11 days ago

https://github.com/shiqiyu/libfacedetection

An open-source, dependency-free C++ library for CNN-based face detection. It is highly optimized for Intel (AVX2/AVX512) and ARM (NEON) architectures using SIMD and Google Highway. The project includes a pure Rust implementation via the libfacedetection_rs crate (v0.1.0) and supports deployment via OpenCV's FaceDetectorYN API (OpenCV 4.5.4+). It provides specialized integration guides for iOS and Android.

Tokens
26.3K
Snippets
71
Records
117
Agent score
95%

What's inside libfacedetection

  1. Implement SIMD boundaries in Rust

    master

    The project follows a specific organizational pattern for SIMD to keep unsafe code auditable and safe:

    • kernels::mod: Handles platform detection and fallback logic.
    • kernels::x86: Contains the actual AVX2 intrinsics.
    • kernels::scalar: Provides a readable, cross-platform reference implementation used as a fallback.
    • Upper Layers: layers and network modules interact with the kernels through safe abstractions and do not touch intrinsics directly.

    Each AVX2 entry point assumes specific shape preconditions (e.g., channel padding, specific output channel widths).

  2. Manage memory with HwNetworkWorkspace and HwBlob

    master

    Efficient memory management in the Highway implementation relies on reusing large temporary buffers (scratchpads) and output blobs to minimize allocator churn.

    • HwNetworkWorkspace: A persistent container that owns reusable scratch blobs for the backbone and FPN/head stages, as well as reusable backbone outputs for full-network calls.
    • HwBlob::ResizeForOverwrite: A method used to resize a blob while preserving its vector capacity. This is particularly useful when reusing shapes, as it avoids the overhead of clearing large outputs that will be completely overwritten by the next operation.
    • Steady-state reuse: For optimal performance in a loop (e.g., video processing), use thread_local instances of the input blob, network workspace, raw head outputs, and decoded outputs to ensure memory is allocated once and reused.
  3. How the detection pipeline processes images

    master

    The Detector implements an end-to-end scalar pipeline that transforms an input image into structured face detections. The lifecycle of a detection call is:

    1. Image Transform: The input (RGB/BGR) is transformed into an initial Blob (e.g., a 96x96 input becomes a 48x48x32 initial tensor).
    2. Backbone Forward Pass: The network runs through the convolutional backbone (filters 0..=22).
    3. FPN (Feature Pyramid Network): Uses fused upsample_x2_add_to operations to combine backbone outputs into multiple levels (stride 8, 16, and 32).
    4. Raw Heads: Computes classification (cls), regression (reg), keypoints (kps), and objectness (obj) for each level.
    5. Post-processing:
      • Decodes tensors using meshgrids.
      • Applies sigmoid to scores.
      • Performs confidence filtering and NMS (Non-Maximum Suppression).
      • Sorts results by score and packs them into the final Face structures or the C-compatible result buffer.
  4. Understand the Hybrid Ceiling strategy for kernel selection

    master

    The library does not use a single SIMD implementation for all operations. Instead, it employs a Hybrid Ceiling strategy, selecting the fastest backend based on the operation type to balance cross-platform abstraction (Highway) with raw performance (Intrinsics).

    Current selection logic:

    • Pointwise 1x1: Uses Highway packed pointwise.
    • Depthwise 3x3: Uses intrinsics depthwise.
    • Maxpool 2x2: Uses intrinsics maxpool.
    • Vector Add: Uses Highway.

    Note that for pointwise operations, a strategy selector is used to decide between packed and primitive implementations based on channel counts: packed when channels >= 16 and out_channels >= 16; primitive otherwise.

  5. How the `Blob` tensor abstraction works

    master

    The Blob type is the core tensor abstraction used for HWC (Height-Width-Channel) f32 storage. It is designed to be performance-oriented and AVX2-friendly.

    Key Features:

    • Padded Storage: Uses a channel_step to provide default channel padding (8 floats) to ensure deterministic SIMD reads.
    • Memory Management:
      • resize: Clears allocation.
      • resize_for_overwrite: Reuses existing capacity/shape to avoid new allocations during workspace reuse.
    • Views: Supports both immutable and mutable view types for kernel operations.

    Kernels in this library (e.g., relu_in_place, add_to, pointwise_1x1_to) write directly into caller-owned BlobViewMut outputs to avoid internal allocations.

  6. Understand the Highway detection pipeline stages

    master

    The Highway-optimized (hw) detection pipeline is composed of several distinct stages. Understanding these helps in profiling and identifying performance bottlenecks:

    1. Image Transform: Converts input to 32-channel initial feature blobs.
    2. Backbone: The primary computational load (e.g., conv_head, conv0 through conv5).
    3. FPN + Raw Heads: Fused layers (using UpsampleX2AddHw) that add lateral connections to the backbone outputs.
    4. Decode + Concat: Post-processing tensors using helpers like MeshgridHw, BboxDecodeHw, and SigmoidHw.
    5. NMS: Non-Maximum Suppression to filter overlapping detections.
  7. How the Pointwise Strategy Selector works

    master

    The Highway implementation uses a PointwiseStrategy to choose the most efficient kernel based on the layer dimensions. This prevents the overhead of 'packed' kernels on very small layers where padding and tail handling would degrade performance.

    Selection Logic:

    • packed strategy: Used when channels >= 16 AND out_channels >= 16. This is optimized for backbone and neck layers (e.g., 16/32/64-output layers).
    • primitive strategy: Used otherwise. This is optimized for tiny detection heads (e.g., 1/4/10-output layers).

    This strategy is encapsulated in PointwisePlan objects, which are created once during model initialization (via HwFilter) to avoid per-call overhead.

  8. How `Filter` and `PointwisePlan` optimize convolution

    master

    To minimize runtime overhead, the library moves pointwise packing to the model loading stage.

    Filter and Model Structure:

    • Filter: Owns padded weights and compact biases. It records whether with_relu is enabled.
    • PointwisePlan: A persistent plan created during Filter::load to handle pointwise convolutions efficiently.
      • Primitive: Used for tiny heads or layers with small output counts.
      • Packed: Used when channels >= 16 && out_channels >= 16. It stores weights as [input_channel][padded_output_channel] with 8-lane packing to match AVX2 baselines.

    Runtime Dispatch: During the forward pass, the Model dispatches convolution through these pre-computed plans, ensuring that large pointwise layers use optimized paths without changing kernel signatures.

  9. Benchmark strategies for performance optimization

    master

    The project uses a three-tier benchmarking hierarchy to isolate bottlenecks and validate optimizations. Using only one type of benchmark can lead to incorrect conclusions about where performance gains are coming from.

    Benchmark TypeTargetTypical Question
    micro benchmarkIndividual kernel shapesIs packed pointwise faster than primitive pointwise?
    stage benchmarkNetwork stagesWhich stage (backbone, FPN/head, decode, NMS) is the bottleneck?
    hotspot kernel-only benchmarkKernel execution (excluding overhead)Excluding allocation/lifecycle noise, how slow is the conv2 pointwise1 kernel itself?

    Optimization Workflow:

    1. Use stage benchmarks to locate the bottleneck stage.
    2. Use hotspot kernel-only benchmarks to analyze the specific kernel within that stage.
    3. Implement focused optimizations.
    4. Validate with correctness tests and real-image benchmarks to ensure end-to-end parity.
  10. Understand the facedetect_rs detection pipeline

    master

    The detection process follows a specific sequence to ensure correctness before optimization. The pipeline includes:

    1. Image Transform: Pre-processing the input image.
    2. Neural Network Inference: Executing the backbone, FPN, and various heads (cls/reg/kps/obj).
    3. Post-processing:
      • Meshgrid generation
      • Bbox/Keypoint decoding
      • Sigmoid activation
      • Confidence filtering
      • Stable descending score sorting
      • Non-Maximum Suppression (NMS)
      • Keep-top-k selection
    4. Output Generation: Packing results into either Rust-structured Face objects or C-compatible result buffers.
  11. Optimize pointwise convolution with packed weight layout

    master

    A major performance bottleneck in 1x1 (pointwise) convolutions is the horizontal reduction required when using standard SIMD primitives. Instead of performing a dot product for each output channel individually, use a packed weight layout to enable multi-channel parallel accumulation.

    Transformation:

    • Original Layout: [output_channel][input_channel] (requires one dot product per output channel).
    • Packed Layout: [input_channel][output_channel_block] (allows broadcasting a single input scalar to update multiple output channels simultaneously).

    Algorithm Logic:

    for pixel:
      for output_channel_block:
        acc[0..N] = bias[0..N]
        for input_channel:
          input_scalar = input[input_channel]
          acc[0..N] += input_scalar * packed_weights[input_channel][0..N]

    This approach allows Highway to match or exceed hand-written AVX2/FMA intrinsics by improving data locality and reducing instruction count.

  12. Understand the Highway (hw) Architecture

    master

    The hw implementation is structured to separate high-level model flow from low-level SIMD kernels. The execution flow follows this hierarchy:

    facedetect_hw_cnn $\rightarrow$ hw_objectdetect_cnn $\rightarrow$ hw convolution flow $\rightarrow$ hw kernels.

    Available Primitive Kernels:

    • hw_dot_product(const float* a, const float* b, int n)
    • hw_mul_add(const float* a, const float* b, float* acc, int n)
    • hw_add(const float* a, const float* b, float* out, int n)
    • hw_relu(float* data, int n)
    • hw_maxpool_2x2s2(...)
    • hw_pointwise_1x1(...)
    • hw_depthwise_3x3(...)
    • hw_element_add(...)