--- 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: ![OpenCV expected output](expected_output_openvino.gif) --- ## 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/)