The story has a very ordinary beginning: 'my home surveillance racked up a few hundred GB, can it be compressed?' The requirement was plain — don't keep frames where nothing happens. Who knew that sentence would carry me from freezedetect to YOLOv8n, from doubting every result to 240 FPS? This is that full trajectory, every digit recorded on my little Core Ultra 5 235H.
Box: Core Ultra 5 235H / Arc 140T / NPU / OpenVINO 2026.5.0.dev / ffmpeg 9.0.1 / Windows. Timeline 2026-09-05 → 09-13.
Act 1: freezedetect vs. sensor noise
Version one was pure common sense: use ffmpeg's freezedetect to find 'frozen' windows and cut them. The demo worked — 180s shrank to 66s — and I thought we were done. Then the real night footage went in and it fell apart:
- night footage is all noise: sensor jitter makes 'static' look active, freezedetect lets everything through
- the n-sweep showed -48dB at a 26% valley while neighbors sat at 40–78% — hypersensitive and non-monotonic
- it can't tell 'still' from 'has a person' — a still human and an empty room look identical to frame diff
At first I tried smarter thresholds and wider windows for hours, until one sentence from the user nailed the direction: 'the rule is keep-it-if-there-is-a-person.' It was never a threshold problem — it was semantic. Motion detection got its death sentence.
Act 2: hand-written OpenVINO pre/post-processing, wiped out
Semantic means object detection. The NPU was sitting there, so I exported yolov8n.pt to OpenVINO IR and then — hand-wrote letterbox, hand-wrote BGR, hand-wrote NMS coordinate decode. Not one variant worked: bounding boxes everywhere, confidence garbage, FCOS decode broken too.
After nearly two days I surrendered completely: inference goes through the official ultralytics path model.predict(device="intel:NPU"), all pre/post-processing owned by it. Every 'DIY parser' hour was wasted — the most expensive lesson in this story, and the one I most want you to skip.
Act 3: 'the GPU is slow' was fake news
Once the official path worked I measured throughput: NPU 32 FPS, GPU 65.6 — the iGPU felt mediocre. Then I swapped the feeding from cv2 full-decode to ffmpeg frames into an in-memory pipe… and the story flipped again.
The truth: cv2's hwdownload readback saturated the CPU while the GPU starved. After switching to ffmpeg sampling, single-process GPU held 65.6, 4-process hit 147.8, and the hybrid iGPU+NPU pool reached 240 FPS. This 'underpowered' box turned out to be 7× stronger than I'd been tricked into believing by my own test method.
Act 4: from bottleneck to an afterthought
Detection was faster yet still 40s per hour (~99× realtime), and slicing was still slow re-encoding. Three changes took the pipeline to its final form:
- stream-copy cutting replaces re-encoding: 44 slices in 2.8s vs 87.2s, 24× faster
- rawvideo pipe + 640x360: nothing to disk, 4× fewer bytes, detect 32.6s→23.2s
- gpu_ratio 0.6 + 3s sampling: capability-split kills idle-wait, detection at 8s per hour (~440×)
The magic isn't that AI is brilliant — it's that 'how much each chip does' got calculated exactly right. Detection ended up faster than decoding itself, shrinking from bottleneck to the least interesting step in the whole flow.
Final act: one night, 502GB → 235GB
The final battle was the full D: drive: 1,677 directories, ~80k files, 502.5GB, read-only, nothing deleted. A scheduled task spawned a self-contained Python launcher, 23:51 → 06:26:
| Item | Value |
|---|
| Source | 502.5GB / 1,677 目录 / 79,027 文件 |
| Output | 235.7GB / 1,554 个(123 个无人目录无成片) |
| Shrink | −53% |
| Duration | ~6.5 小时 |
| Errors | 0 |
Checking the log at 6am: 1,677/1,677 done, zero errors. That moment felt — honestly — just 'sweet'. Hundreds of GB of disk-hogging surveillance became one manageable overnight watch.
Looking back: was it worth the detours
| Phase | Outcome | Lesson |
|---|
| freezedetect | 180s→66s demo | motion ≠ presence; no threshold solves semantic |
| DIY OpenVINO | 全部失败 | official path or bust |
| cv2 bench | 假 'GPU慢' | wrong method, wrong verdict |
| hybrid + tuning | 240 张/s | multi-process + capability split |
| full-disk batch | 502→235GB,0 错误 | task scheduler + resume for safety |