| --- |
| license: mit |
| license_link: LICENSE |
| library_name: opencv |
| tags: |
| - opencv |
| - intel |
| - scene-change-detection |
| - histogram |
| - edge-ai |
| - metro |
| language: |
| - en |
| --- |
| |
| # Scene Change Detection |
|
|
| | Property | Value | |
| |---|---| |
| | **Category** | Scene Analytics (classical computer vision) | |
| | **Base Model** | Not applicable -- uses frame histogram comparison | |
| | **Source Framework** | OpenCV | |
| | **Supported Precisions** | Not applicable | |
| | **Inference Engine** | OpenCV (CPU) | |
| | **Hardware** | CPU, GPU (OpenCV UMat optional) | |
| | **Detected Class(es)** | Scene-change events | |
|
|
| --- |
|
|
| ## Overview |
|
|
| Scene Change Detection is a Metro Analytics use case that flags abrupt or |
| sustained changes in what a camera is showing, such as a shot cut, a camera |
| being repositioned, or a large change in the field of view. |
| It compares the color-histogram signature of each frame against the previous |
| frame using the Bhattacharyya distance and raises an event when the distance |
| exceeds a threshold. |
|
|
| Histogram and similarity scoring is more robust and far cheaper than running |
| an object detector for this signal, so this use case intentionally avoids a |
| neural model. |
| For semantic scene understanding (for example "platform" versus "concourse"), |
| pair this with the [object-detection](../object-detection/) use case. |
|
|
| Typical Metro deployments include: |
|
|
| - **Camera Repositioning Alerts** -- detect when a PTZ camera moves to a new view. |
| - **Video Segmentation** -- split long recordings into scenes for indexing. |
| - **Content Validation** -- confirm a feed switched to the expected source. |
| - **Pre-filter for Analytics** -- re-initialize trackers when the scene changes. |
|
|
| --- |
|
|
| ## Prerequisites |
|
|
| - Python 3.11+ |
| - [Install OpenVINO](https://docs.openvino.ai/2026/get-started/install-openvino.html) (latest version) |
| - `ffmpeg` (used by `export_and_quantize.sh` to build the sample montage) |
|
|
| Create and activate a Python virtual environment before running the scripts: |
|
|
| ```bash |
| python3 -m venv .venv --system-site-packages |
| source .venv/bin/activate |
| ``` |
|
|
| > **Note:** The `--system-site-packages` flag is required so the virtual |
| > environment can access the system-installed OpenVINO Python packages |
| > (which provide OpenCV). |
|
|
| --- |
|
|
| ## Getting Started |
|
|
| ### Download the Sample Video |
|
|
| This use case does not export or quantize a model. |
| Run the provided script to prepare the sample test video: |
|
|
| ```bash |
| chmod +x export_and_quantize.sh |
| ./export_and_quantize.sh |
| ``` |
|
|
| A single continuous shot never triggers a scene change, so the script |
| downloads several distinct sample clips and joins them with hard cuts into |
| `test_video.mp4` (four 2-second scenes). This produces a clear scene change |
| every two seconds for the detector to flag. The script requires `ffmpeg` to |
| build the montage. |
|
|
| ### OpenCV Sample |
|
|
| The sample below computes a normalized HSV histogram for each frame, compares |
| it to the previous frame with the Bhattacharyya distance, and flags a scene |
| change when the distance exceeds `CHANGE_THRESHOLD`. |
| The annotated frames are written to `output_opencv.mp4`. |
|
|
| ```python |
| import cv2 |
| import numpy as np |
| |
| INPUT_VIDEO = "test_video.mp4" |
| CHANGE_THRESHOLD = 0.45 # Bhattacharyya distance in [0, 1]; higher = more change |
| |
| cap = cv2.VideoCapture(INPUT_VIDEO) |
| fps = cap.get(cv2.CAP_PROP_FPS) or 30.0 |
| width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) |
| height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) |
| writer = cv2.VideoWriter( |
| "output_opencv.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, (width, height)) |
| |
| |
| def frame_histogram(bgr): |
| hsv = cv2.cvtColor(bgr, cv2.COLOR_BGR2HSV) |
| hist = cv2.calcHist([hsv], [0, 1], None, [50, 60], [0, 180, 0, 256]) |
| cv2.normalize(hist, hist, 0, 1, cv2.NORM_MINMAX) |
| return hist |
| |
| |
| prev_hist = None |
| frame_idx = 0 |
| scene_changes = 0 |
| while True: |
| ok, frame = cap.read() |
| if not ok: |
| break |
| frame_idx += 1 |
| hist = frame_histogram(frame) |
| |
| distance = 0.0 |
| changed = False |
| if prev_hist is not None: |
| distance = cv2.compareHist(prev_hist, hist, cv2.HISTCMP_BHATTACHARYYA) |
| changed = distance >= CHANGE_THRESHOLD |
| prev_hist = hist |
| |
| if changed: |
| scene_changes += 1 |
| print(f"Frame {frame_idx}: SCENE CHANGE (distance={distance:.3f})", |
| flush=True) |
| color = (0, 0, 255) if changed else (0, 255, 0) |
| label = f"dist={distance:.3f}" + (" CHANGE" if changed else "") |
| cv2.putText(frame, label, (10, 30), |
| cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2) |
| writer.write(frame) |
| |
| cap.release() |
| writer.release() |
| print(f"Scene changes detected: {scene_changes}", flush=True) |
| ``` |
|
|
| **Device targets:** |
|
|
| - `"CPU"` -- default for OpenCV histogram comparison. |
| - `"GPU"` -- wrap frames in `cv2.UMat` to use the OpenCV transparent API on Intel GPUs. |
| - `"NPU"` -- not applicable; histogram comparison is not a neural workload. |
|
|
| ### Scene-Change Terminal Logging |
|
|
| Every time the Bhattacharyya distance crosses `CHANGE_THRESHOLD`, the sample |
| treats it as a new scene and prints a line to the terminal with the frame |
| number and the distance that triggered it. A running total is printed when the |
| video ends. This makes the terminal a lightweight event log you can pipe to a |
| file or another process without inspecting the annotated video. |
|
|
| The relevant lines in the sample are: |
|
|
| ```python |
| if changed: |
| scene_changes += 1 |
| print(f"Frame {frame_idx}: SCENE CHANGE (distance={distance:.3f})", |
| flush=True) |
| ``` |
|
|
| #### Expected Terminal Output |
|
|
| Running the sample against the four-scene montage produces one log line per cut |
| (at ~2s, ~4s, and ~6s), followed by the summary: |
|
|
| ```text |
| Frame 61: SCENE CHANGE (distance=0.949) |
| Frame 121: SCENE CHANGE (distance=0.988) |
| Frame 181: SCENE CHANGE (distance=0.854) |
| Scene changes detected: 3 |
| ``` |
|
|
| #### Expected Output |
|
|
| The annotated video draws each frame's distance in green and turns the label |
| red on the frame where a scene change is detected: |
|
|
|  |
|
|
| --- |
|
|
| ## License |
|
|
| Licensed under the MIT License. See [LICENSE](LICENSE) for details. |
|
|
| ## References |
|
|
| - [OpenCV Histogram Comparison](https://docs.opencv.org/4.x/d8/dc8/tutorial_histogram_comparison.html) |
| - [OpenCV calcHist Reference](https://docs.opencv.org/4.x/d6/dc7/group__imgproc__hist.html) |
| - [OpenVINO Documentation](https://docs.openvino.ai/) |
|
|