Skip to content

Real-Time Video Pipeline (CPU & Metal GPU)

In video surveillance (NVR), autonomous vehicles, robotics, and broadcast servers, vision processing engines must ingest and process multiple high-definition video streams simultaneously in real time (e.g. 16 to 64 camera streams at 60+ FPS).

A standard real-time vision pre-processing pipeline consists of three successive stages per pixel:

1. Spatial Feature Extraction (\(3 \times 3\) Sobel Gradient)

Detects structural edges and boundaries across neighboring pixels:

\[\begin{aligned} G_x &= (p_{02} + 2p_{12} + p_{22}) - (p_{00} + 2p_{10} + p_{20}) \\ G_y &= (p_{20} + 2p_{21} + p_{22}) - (p_{00} + 2p_{01} + p_{02}) \\ G_{\text{spatial}} &= \frac{1}{8}\left(G_x^2 + G_y^2\right) \end{aligned}\]

2. Temporal Motion Estimation (Frame Differencing)

Measures dynamic variation between the current frame \(I_t\) and previous frame \(I_{t-1}\):

\[M_{\text{temporal}} = \left(I_t(r, c) - I_{t-1}(r, c)\right)^2\]

3. Motion-Edge Fusion

Blends spatial boundaries and temporal activity into a unified perception mask:

\[O(r, c) = \alpha \cdot G_{\text{spatial}} + \beta \cdot M_{\text{temporal}}\]

Figure: Real-Time Multi-Stream Processing — Raw Input Feed (Left) vs Legolas DLI Sobel Edge & Motion Detection running at 17,264 FPS (Right).


The Vectorization Challenge in Multi-Stream Video

In conventional planar or interleaved frame layouts, processing \(N\) separate camera feeds processes each frame independently: - Spatial stencils: Within a single image, 3x3 2D stencils suffer from unaligned memory loads, image border conditions, and horizontal reduction overhead. - Cache pollution: Reading multiple disparate frames destroys CPU cache line locality.


Legolas++ DLI Solution

With Legolas++ Data Layout Interleaving (DLI), multiple video feeds are interleaved at compile-time:

// N camera streams, H x W pixels per stream, interleaved with vector pack size P:
Legolas::Array<float, 2, PACK_SIZE, 2> curr_frames(NUM_STREAMS, H * W);

At every spatial coordinate \((r, c)\), the hardware SIMD vector register (ARM NEON, x86 AVX2, AVX-512) holds the corresponding pixel from \(P\) distinct camera feeds.

Key benefits: - 100% Contiguous SIMD Loads: Vectors are loaded across streams, perfectly aligned. - Sliding Window Optimization: By keeping 2 columns in SIMD registers and loading only 1 new column, memory bandwidth is reduced from 9 loads to 3 loads per pixel. - Zero External Dependencies: Pure standard C++14. No OpenCV, no FFmpeg runtime, no IPP.


Implementation Example

#include <iostream>
#include "Legolas/Array/Array.hxx"
#include "Legolas/Array/Map.hxx"

struct VideoSpatialTemporalPipeline {
    int H, W;
    VideoSpatialTemporalPipeline(int h, int w) : H(h), W(w) {}

    template <class InCurrA2D, class InPrevA2D, class OutA2D>
    void operator()(int begin, int end, InCurrA2D curr_frames, InPrevA2D prev_frames, OutA2D out_frames) const {
        typedef typename InCurrA2D::RealType Scalar;
        const Scalar alpha(0.7f);
        const Scalar beta(0.3f);

        for (int s = begin; s < end; ++s) {
            auto curr = curr_frames[s];
            auto prev = prev_frames[s];
            auto out  = out_frames[s];

            for (int r = 1; r < H - 1; ++r) {
                const int row_prev = (r - 1) * W;
                const int row_curr = r * W;
                const int row_next = (r + 1) * W;

                // Prime sliding window
                Scalar p00 = curr[row_prev];     Scalar p01 = curr[row_prev + 1];
                Scalar p10 = curr[row_curr];     Scalar p11 = curr[row_curr + 1];
                Scalar p20 = curr[row_next];     Scalar p21 = curr[row_next + 1];

                for (int col = 1; col < W - 1; ++col) {
                    // Only 3 new loads per pixel!
                    Scalar p02 = curr[row_prev + col + 1];
                    Scalar p12 = curr[row_curr + col + 1];
                    Scalar p22 = curr[row_next + col + 1];

                    Scalar gx = (p02 + Scalar(2.0f) * p12 + p22) - (p00 + Scalar(2.0f) * p10 + p20);
                    Scalar gy = (p20 + Scalar(2.0f) * p21 + p22) - (p00 + Scalar(2.0f) * p01 + p02);

                    Scalar spatial_edge = (gx * gx + gy * gy) * Scalar(0.125f);
                    Scalar diff = p11 - prev[row_curr + col];
                    Scalar temporal_motion = diff * diff;

                    out[row_curr + col] = alpha * spatial_edge + beta * temporal_motion;

                    // Shift window forward
                    p00 = p01; p01 = p02;
                    p10 = p11; p11 = p12;
                    p20 = p21; p21 = p22;
                }
            }
        }
    }
};

int main() {
    const int NUM_STREAMS = 32;
    const int H = 720, W = 1280;
    VideoSpatialTemporalPipeline pipeline(H, W);

    // Single-thread or Multi-threaded SIMD execution:
    Legolas::Array<float, 2, 4, 2> curr(NUM_STREAMS, H * W);
    Legolas::Array<float, 2, 4, 2> prev(NUM_STREAMS, H * W);
    Legolas::Array<float, 2, 4, 2> out(NUM_STREAMS, H * W);

    // Multi-core parallel execution across all CPU cores:
    Legolas::parmap(pipeline, curr, prev, out);

    std::cout << "Processed 32 HD streams in parallel!" << std::endl;
    return 0;
}

Measured Performance (Apple M1 Max)

Processing 32 concurrent 720p HD (\(1280 \times 720\)) video feeds:

Backend Cores Throughput Bandwidth Speedup Max Error
CPU Scalar Baseline 1 Core 1,579 FPS 1.46 GPix/s 1.00x Reference
Legolas DLI NEON 1 Core 2,012 FPS 1.85 GPix/s 1.27x \(< 10^{-7}\)
Legolas Parallel + DLI NEON 8 Cores 8,442 FPS 7.78 GPix/s 5.34x \(< 10^{-7}\)
Apple Silicon Metal GPU 32 GPU Cores 17,263 FPS 15.91 GPix/s 57.15x \(< 10^{-7}\)

🌿 Green Computing & Energy Footprint Analysis

In broadcast production centers, edge surveillance, and autonomous robotics, processing high-density video feeds is strictly constrained by thermal dissipation and battery life.

Metric CPU Scalar Baseline Legolas CPU (8 Cores) Legolas Metal GPU Impact vs Scalar
Active Package Power ~30 W ~30 W ~42 W Standard Package TDP
Frame Throughput 1,579 FPS 8,442 FPS 17,264 FPS 5.34× / 10.9×
Energy per HD 720p Frame 19.00 µJ 3.55 µJ 2.43 µJ -87.2% Energy per Frame
Compute Density 0.048 GPix/Watt 0.259 GPix/Watt 0.379 GPix/Watt +690% Density per Watt

[!TIP] At 60 FPS per camera, Legolas CPU processes 140 continuous HD feeds in real time on a laptop processor without a GPU. The Metal GPU backend scales this to 287 concurrent HD feeds at 60 FPS using Apple Silicon's zero-copy Unified Memory, consuming only 2.43 µJ per frame.