Skip to content

Loss Prevention: transparent benchmarking for real-world retail AI on Intel® hardware

Part of the Intel® Retail AI Suite: open-source, runnable AI workloads that let you measure real performance on Intel hardware and decide whether it fits your deployment.

Warning

The main branch is work-in-progress and not guaranteed stable. For the latest stable release 👉 Releases

Important

Migrating from Automated Self-Checkout (ASC)? The automated-self-checkout repo is End-of-Life (retiring end of September 2026). Its pipelines and all future updates, issues, and contributions moved here. The ASC use cases (object detection, object-detection + classification, age verification) run in this repo: see Use cases and Walkthroughs.

Table of Contents

  1. Why this exists / what it is
  2. Use cases in this repo
  3. What to expect when you run it
  4. How to think about performance & stream density
  5. Prerequisites
  6. Quick start
  7. Per-use-case walkthroughs (run · benchmark · density)
  8. Advanced usage
  9. Architecture, services & project layout
  10. Getting help

Why this exists / what it is

We build these workloads to demonstrate real-world retail scenarios and to measure their performance on different Intel hardware platforms. They’re released as open source so partners can read the code and the metrics instrumentation, run the benchmarks themselves to reproduce the numbers we publish, or swap in their own models, pipelines, and business logic and see exactly how performance changes. These are open-source reference pipelines built on Intel® hardware and software, GStreamer, and OpenVINO™, running real-time object detection and classification at the edge.

It answers one question: “How do I know if Intel’s hardware will actually perform for my workload?” So it’s a hardware-sizing tool, not a production starting point.

Transparent Full source + metrics instrumentation. A window into how we did it, not a starting point for your production code.
Representative Off-the-shelf, non-fine-tuned models; sample datasets included; easy to customize.
Measurable Latency · FPS · utilization (CPU/GPU/NPU) · power · stream density.

The conversation we want: “Here’s a real workload. Run it. Compare it. Let’s talk about what you’re actually seeing”, not a TOPS spec sheet.

Use cases in this repo

The workloads build on each other. Start with a basic self-checkout, add a concurrent age-verification pipeline for a real-world lane, then the loss-prevention scenarios themselves, and finally an LVLM enhancement. Each layer adds compute, so the progression is also a hardware-sizing story (see use-case density). Each is selected by a CAMERA_STREAM + WORKLOAD_DIST config pair in configs/, with CPU / iGPU / NPU / hetero variants where available (matrix).

1 · Self-checkout

The base everything else builds on. Migrated from the EOL ASC repo.

A self-checkout vision pipeline that identifies products as they’re scanned. YOLO11n detects the item, EfficientNet-B0 classifies it (asc_object_detection_classification).

Business takeaway: the entry point. Can this Intel box run a basic self-checkout lane at the frame rate it needs? Everything below adds load on top of this.

2 · Self-checkout + age verification

Builds on 1. Adds a parallel face pipeline for a real lane. Add a second, customer-facing pipeline so the same lane can clear age-restricted purchases (alcohol, tobacco) without a staff override. A realistic lane now runs two pipelines at once: one camera scanning products (yolo11n then efficientnet-b0), one camera watching the customer (face detection then age and gender). That is the multi-pipeline-per-lane step.

It ships in four placements, selected by the WORKLOAD_DIST you pass: everything on the iGPU, everything on the CPU, everything on the NPU, or split across the iGPU and NPU with one stage of each pipeline on each accelerator. Running the density test on the iGPU-only and the split versions and comparing them is how you see what accelerator placement is worth on your hardware (see the walkthrough).

Business takeaway: shows whether a box can run a complete lane (scanning plus the age-verification pipeline for unattended alcohol/tobacco sales) at the frame rates the lane requires.

3 · Loss prevention

Builds on 1. Six-camera lane, one shrink scenario per camera. Now layer the actual loss-prevention scenarios on top. As a retail operator, I want to detect common loss scenarios at the lane so that I can reduce shrink without adding staff. The default workload models a single lane as six cameras, each running a scenario detector:

Camera Scenario detector Camera Scenario detector
cam1 Items-in-Basket cam4 Multi-Product Identification
cam2 Hidden Items cam5 Product Switching
cam3 Fake-Scan Detection cam6 Sweethearting

Run with make run-lp. (This 6-camera lane is the concrete example of use-case density.)

4 · Loss prevention with an LVLM

Builds on 3. Adds a vision-language model at the edge. Push item-recognition accuracy beyond traditional CV with a local Large Vision-Language Model, invoked by an agent only when CV is too generic or uncertain, with no custom training (vlm). E.g. “bottle” → “Heinz Ketchup 32 oz”, seasonal/regional packaging, produce varieties, partially obscured or bulk-bagged items, age-restricted variants with changed packaging.

Business takeaway: fewer false positives (less customer friction, fewer staff interventions), better real-loss capture, and adaptability to new/seasonal/regional products without a retraining cycle. It’s the suite’s GenAI-at-the-edge workload, and it reports the metrics that matter for LLM/LVLM sizing (TTFT, token throughput, CPU/GPU/NPU utilization). Runs entirely locally, no cloud dependency.

Scope & related repositories

This repo covers loss prevention at the self-checkout and point of sale, built on the Automated Self-Checkout foundation above. Store-wide loss prevention is a separate effort in the Intel Retail AI Suite:

Each use case in this repo is selected by a CAMERA_STREAM + WORKLOAD_DIST config pair (configs/). Device variants — CPU / iGPU / NPU / hetero — vary by use case; see the matrix.

What to expect when you run it

  • Visual mode (RENDER_MODE=1 DISPLAY=:0) opens a video window with detection overlays/alerts; the pipeline runs until the video completes. Headless mode runs the same pipeline for servers and automated benchmarking.
  • These are off-the-shelf, non-fine-tuned models: expected misclassifications under real-world conditions are part of an authentic evaluation, not defects to hide. The goal is a faithful performance picture, not a flawless demo.
  • For a 15 fps source, a healthy stream holds ~15 fps per stream; throughput, latency, and utilization vary by platform and configuration (see §4).
  • Output files (visual + headless): results/pipeline_stream*.log (per-stream FPS) and results/gst-launch_*.log (full GStreamer output). First run downloads videos, models, and images, so it takes a while.

How to think about performance & stream density

The metrics that matter: FPS, end-to-end latency, CPU/GPU/NPU utilization, power, and stream density (for GenAI/LVLM use cases also TTFT and token throughput).

  • Stream density = the most concurrent streams a box sustains at a target FPS. Because an LP lane is multi-camera (six cameras at potentially different frame-rate needs), density is best read as use-case instances: “how many shopping lanes with this use case running per box”, judging each camera against its own FPS target (use-case density, not a raw stream count).
  • Reading the result: a stream cannot sustain more than its source FPS, so any per-stream reading above the source rate is a measurement artifact rather than real throughput. Two things cause it. The counter tallies whole frames inside a fixed window, and the window boundary does not line up with frame arrivals, so a window catches one frame more or fewer than expected. On a 15 fps source that shows up as readings of 14, 15 and 16 on a stream whose average is exactly 15, and it happens on a perfectly healthy stream. Under load a second cause appears: a pipeline that has fallen behind processes its buffered frames in a burst as it recovers. That is real work, but it is not a rate the pipeline can hold.
  • How we measure it: we ramp the number of streams, let each step settle, then read the sustained per-stream FPS against the target to find the most streams that hold it. This method has known limitations (a short measurement window can misread under noise) and we’re actively improving it toward a steadier measurement.

Prerequisites

  • Ubuntu 24.04 or newer (Linux recommended), Desktop edition (or Server + GUI).
  • Docker
  • Make (sudo apt install make)
  • Python 3 (sudo apt install python3) - required for video download and validation scripts
  • Intel hardware (CPU, iGPU, dGPU, NPU) + drivers:
  • 32 GB RAM . 300 GB Storage
  • Corporate proxy / optional RTSP config:
    # HTTP/HTTPS Proxy settings
    export HTTP_PROXY=<HTTP PROXY>
    export HTTPS_PROXY=<HTTPS PROXY>
    export NO_PROXY=localhost,127.0.0.1,rabbitmq,minio-service,rtsp-streamer
    # Optional RTSP Configuration (defaults shown)
    export RTSP_STREAM_HOST=rtsp-streamer  # Hostname of RTSP server
    export RTSP_STREAM_PORT=8554           # RTSP port
    export RTSP_MEDIA_DIR=../performance-tools/sample-media  # Video source directory
    export STREAM_LOOP=false               # Set to 'true' to loop video streams indefinitely

QuickStart

git clone -b <release-or-tag> --single-branch https://github.com/intel-retail/loss-prevention  # e.g. v4.0.0
cd loss-prevention
RENDER_MODE=1 DISPLAY=:0 make run-lp      # visual: see detections live (recommended first run)
make run-lp                               # headless
make down-lp                              # stop the application

First run downloads videos, models, and images (several minutes). By default make run-lp pulls pre-built images, runs headless, and uses the Loss Prevention (CPU) workload. Add REGISTRY=false to build images locally instead (see Advanced).

Per-use-case walkthroughs

Each use case supports three actions: run it, benchmark a fixed load, and measure stream density. They are listed below in the same order they’re introduced above. The device target is selected by the WORKLOAD_DIST config, but not every use case ships every device variant, so check the matrix below before swapping a suffix.

Use case WORKLOAD_DIST variants available
ASC — object detection _cpu · _gpu · _npu
ASC — object detection + classification _cpu · _gpu · _npu · _hetero
ASC — age verification _cpu · _gpu · _npu · _hetero
Loss Prevention (core, 6-camera) _cpu · _gpu · _gpu-npu · _hetero (no _npu)
LVLM-enhanced none — single workload_to_pipeline_vlm.json; devices are set inside the JSON (device, vlm_device)

Run ls configs/workload_to_pipeline_* to confirm what your checkout provides. Passing a variant that doesn't exist fails fast — make run-lp validates the pair first and reports Configuration file not found: configs/<name>.json.

View results for any benchmark/density run: make consolidate-metricscat benchmark/metrics.csv and make plot-metrics → utilization chart. For Advanced Benchmark settings, 👉 Benchmarking Guide

1 · Self-checkout

Run it, detection + classification on the iGPU

make run-lp CAMERA_STREAM=camera_to_workload_asc_object_detection_classification.json WORKLOAD_DIST=workload_to_pipeline_asc_object_detection_classification_gpu.json RENDER_MODE=1 DISPLAY=:0

Measure its stream density

make benchmark-stream-density CAMERA_STREAM=camera_to_workload_asc_object_detection_classification.json WORKLOAD_DIST=workload_to_pipeline_asc_object_detection_classification_gpu.json

See the results

make consolidate-metrics && cat benchmark/metrics.csv

2 · Self-checkout + age verification

Run scanning and age verification together, both on the iGPU. Two cameras: one scanning products, one doing face detection and age prediction.

make run-lp CAMERA_STREAM=camera_to_workload_asc_age_verification.json WORKLOAD_DIST=workload_to_pipeline_asc_age_verification_gpu.json

Measure the use-case density of that lane (how many of these lanes the box sustains)

make benchmark-stream-density CAMERA_STREAM=camera_to_workload_asc_age_verification.json WORKLOAD_DIST=workload_to_pipeline_asc_age_verification_gpu.json

Run the same lane with the work split across the iGPU and NPU. Same two cameras and same models, but each pipeline now puts one stage on the iGPU and the other on the NPU, so you can see what the split costs or buys.

make run-lp CAMERA_STREAM=camera_to_workload_asc_age_verification.json WORKLOAD_DIST=workload_to_pipeline_asc_age_verification_hetero.json

Measure the use-case density of the split lane

make benchmark-stream-density CAMERA_STREAM=camera_to_workload_asc_age_verification.json WORKLOAD_DIST=workload_to_pipeline_asc_age_verification_hetero.json

See the results

make consolidate-metrics && cat benchmark/metrics.csv

3 · Loss prevention

The default six-camera lane. No config pair needed.

Run it, with video output

RENDER_MODE=1 DISPLAY=:0 make run-lp

Benchmark a fixed load

make benchmark

Measure the maximum sustainable lanes

make benchmark-stream-density

See the results

make consolidate-metrics && cat benchmark/metrics.csv

4 · Loss prevention with an LVLM

Set the credentials it needs

export MINIO_ROOT_USER=<...> MINIO_ROOT_PASSWORD=<...>
export RABBITMQ_USER=<...> RABBITMQ_PASSWORD=<...>
export GATED_MODEL=true HUGGINGFACE_TOKEN=<...>

Run it

make run-lp CAMERA_STREAM=camera_to_workload_vlm.json STREAM_LOOP=false

Benchmark it

make benchmark CAMERA_STREAM=camera_to_workload_vlm.json WORKLOAD_DIST=workload_to_pipeline_vlm.json

What to Expect

  • Visual Mode

    • Opens a video window with retail footage and detection overlays.

    • The pipeline runs until the input video finishes.

      Note: The pipeline runs until the video completes

  • Visual and Headless Mode

    • Verify that these output files are created and contain data:

      • <loss-prevention-workspace>/results/pipeline_stream*.log: per-stream FPS metrics (one value per line)
      • <loss-prevention-workspace>/results/gst-launch_*.log: full GStreamer logs
    • Expected result:

      • Files exist with content
      • Files are non-empty

If a run fails, see TroubleShooting

Stop the application

make down-lp

Advanced Usage

Build images locally (instead of pulling pre-built):

#Download the models
make download-models REGISTRY=false
#Update github performance-tool submodule
make update-submodules REGISTRY=false
#Download sample videos used by the performance tools
make download-sample-videos REGISTRY=false
#Run the LP application for visual mode
make run-render-mode DISPLAY=:0 REGISTRY=false RENDER_MODE=1
or
#Run the LP application for headless mode
make run REGISTRY=false
  • Or simply:
  • Visual Mode
make run-lp DISPLAY=:0 REGISTRY=false RENDER_MODE=1
  • Headless Mode
make run-lp REGISTRY=false

Configuration & user-defined workloads

  • Workloads are configured by JSON in configs/ plus the CAMERA_STREAM / WORKLOAD_DIST env vars (CPU/GPU/NPU/hetero variants, availability varies by use case). You can also define your own: map cameras to custom pipelines by editing camera_to_workload_*.json (which cameras run which workloads) and workload_to_pipeline_*.json (each workload’s pipeline, a sequence of GStreamer elements + models, e.g. gvadetect / gvaclassify, on a chosen device). See the Documentation Guide for pre-configured workloads and User-Defined Workloads for the full definitions.

  • Inference interval

    The INFERENCE_INTERVAL environment variable controls how often the detector runs inference (the gvadetect inference-interval property). A value of N runs inference on every Nth frame and relies on the tracker for the frames in between.

    • Default: 3
    • 1: run inference on every frame (smoothest, most consistent detections; highest load)
    • 2, 3, and up: infer less frequently (better performance, more reliance on the tracker between frames)
make run-lp CAMERA_STREAM=camera_to_workload_asc_object_detection_classification.json WORKLOAD_DIST=workload_to_pipeline_asc_object_detection_classification_gpu.json RENDER_MODE=1 DISPLAY=:0 INFERENCE_INTERVAL=1

Architecture & services

The system runs as a set of Docker containers orchestrated by docker-compose. AI inference runs on OpenVINO™ across Intel® CPU / iGPU / NPU; the video-analytics pipeline is built with GStreamer (Intel® DLStreamer gvadetect / gvaclassify elements) and generated dynamically from the config files; and video is fed in over RTSP. The sections below cover that streaming source, the container services, and the repository layout.

RTSP streaming architecture

An integrated MediaMTX RTSP server (rtsp-streamer container) streams the sample video files for testing and development.

How it works

  1. RTSP server container (rtsp-streamer): auto-starts MediaMTX on port 8554; streams every .mp4 in performance-tools/sample-media/; each video becomes an RTSP stream at rtsp://rtsp-streamer:8554/<video-name>.

  2. Pipeline consumption: GStreamer pipelines connect via the rtspsrc element, with TCP transport, configurable latency, and automatic retry/timeout handling.

  3. Stream naming convention: e.g. video items-in-basket-32658421-1080-15-bench.mp4 → stream rtsp://rtsp-streamer:8554/items-in-basket-32658421-1080-15-bench.

RTSP server features

  • Loop playback: videos restart automatically when finished
  • TCP transport: reliable streaming over corporate networks
  • Low latency: 200 ms default for real-time processing
  • Multiple streams: supports concurrent camera streams
  • Proxy support: works through corporate HTTP/HTTPS proxies

Docker Services

Service Purpose Port Notes
rtsp-streamer RTSP video streaming server 8554 Streams videos from sample-media
rabbitmq Message broker for VLM workload 5672, 15672 Requires credentials
minio-service Object storage for frames 4000, 4001 S3-compatible storage
model-downloader Downloads AI models - Runs once at startup
lp-vlm-workload-handler VLM inference processor - GPU/CPU inference
vlm-pipeline-runner VLM pipeline orchestrator - Requires DISPLAY variable
lp-pipeline-runner Main inference pipeline - Supports CPU/GPU/NPU

All services run on my_network bridge network for DNS resolution;use rtsp-streamer, rabbitmq, minio-service as hostnames for inter-service communication

Project Structure

  • configs/: Configuration files (camera/workload mapping, pipeline mapping)
  • docker/: Dockerfiles for downloader and pipeline containers
  • docs/: Documentation (HLD, LLD, system design)
  • download-scripts/: Scripts for downloading models and videos
  • src/: Main source code and pipeline runner scripts
  • src/rtsp-streamer/: RTSP server container (MediaMTX + FFmpeg)
  • src/gst-pipeline-generator.py: Dynamic GStreamer pipeline generator
  • src/docker-compose.yml: Multi-container orchestration
  • performance-tools/sample-media/: Video files for RTSP streaming
  • Makefile: Build automation and workflow commands

Useful Information

  • Make Commands
    • make validate-all-configs: Validate all configuration files
    • make clean-images: Remove dangling Docker images
    • make clean-containers: Remove stopped containers
    • make clean-all: Remove all unused Docker resources
  • Known Issues
    • On EMT OS, containers built on Alpine base images (e.g., MinIO) may report as unhealthy despite the service functioning normally. Docker health checks are failing with OCI runtime errors, preventing proper container orchestration and monitoring.

Getting help

Pick the use case closest to your needs, run the included benchmarks, and reach out to your Intel Solution Manager to talk about what you found. Full docs: the Loss Prevention Documentation Guide. In case of failure, see Troubleshooting.

About

The Loss Prevention Pipeline System is an open-source reference implementation for building and deploying video analytics pipelines for retail loss prevention use cases. It leverages Intel® hardware and software, GStreamer, and OpenVINO™ to enable scalable, real-time object detection and classification at the edge.

Resources

Code of conduct

Contributing

Security policy

Stars

7 stars

Watchers

5 watching

Forks

Releases

Packages

Used by

Contributors

Languages