From 5da838ef0bd72ff226b202486bf6788227d42536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=B0=BD=EC=9A=B1?= Date: Sat, 11 Jul 2026 21:21:57 +0900 Subject: [PATCH] feat(edge): RingBuffer + face blur + JPEG snapshotter --- edge/snapshot.py | 45 ++++++++++++++++++++++++++++++++++++++++++ tests/test_snapshot.py | 27 +++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 edge/snapshot.py create mode 100644 tests/test_snapshot.py diff --git a/edge/snapshot.py b/edge/snapshot.py new file mode 100644 index 0000000..a374f89 --- /dev/null +++ b/edge/snapshot.py @@ -0,0 +1,45 @@ +from collections import deque + +import cv2 + +from edge.models import BBox + + +class RingBuffer: + def __init__(self, maxlen: int = 90): + self.frames: deque = deque(maxlen=maxlen) + + def push(self, t: float, frame) -> None: + self.frames.append((t, frame)) + + def window(self, pre: int, post: int, index: int) -> list: + lo = max(0, index - pre) + hi = min(len(self.frames), index + post + 1) + return [f for (_t, f) in list(self.frames)[lo:hi]] + + +class FaceBlurrer: + def blur(self, frame, head_boxes: list[BBox]): + for b in head_boxes: + x1, y1 = int(max(0, b.x1)), int(max(0, b.y1)) + x2, y2 = int(min(frame.shape[1], b.x2)), int(min(frame.shape[0], b.y2)) + if x2 > x1 and y2 > y1: + roi = frame[y1:y2, x1:x2] + frame[y1:y2, x1:x2] = cv2.GaussianBlur(roi, (0, 0), sigmaX=12) + return frame + + +class Snapshotter: + def __init__(self, blurrer: FaceBlurrer, quality: int = 80): + self.blurrer = blurrer + self.quality = quality + + def capture(self, frames: list, head_boxes_per_frame: list) -> list[bytes]: + out: list[bytes] = [] + for frame, heads in list(zip(frames, head_boxes_per_frame))[:3]: + blurred = self.blurrer.blur(frame.copy(), heads) + ok, buf = cv2.imencode(".jpg", blurred, + [cv2.IMWRITE_JPEG_QUALITY, self.quality]) + if ok: + out.append(buf.tobytes()) + return out diff --git a/tests/test_snapshot.py b/tests/test_snapshot.py new file mode 100644 index 0000000..e69cb47 --- /dev/null +++ b/tests/test_snapshot.py @@ -0,0 +1,27 @@ +import numpy as np +from edge.models import BBox +from edge.snapshot import RingBuffer, FaceBlurrer, Snapshotter + +def _frame(val=128): + return np.full((64, 64, 3), val, dtype=np.uint8) + +def test_ringbuffer_keeps_recent(): + rb = RingBuffer(maxlen=3) + for i in range(5): + rb.push(float(i), _frame(i)) + assert len(rb.frames) == 3 + assert rb.frames[-1][0] == 4.0 + +def test_face_blur_changes_region(): + fb = FaceBlurrer() + f = _frame(0) + f[0:10, 0:10] = 255 + out = fb.blur(f.copy(), [BBox(0, 0, 10, 10)]) + assert out is not None + assert out.shape == f.shape + +def test_snapshotter_returns_jpeg_bytes(): + snap = Snapshotter(FaceBlurrer()) + imgs = snap.capture([_frame(100), _frame(120)], [[], []]) + assert 1 <= len(imgs) <= 3 + assert imgs[0][:2] == b"\xff\xd8" # JPEG magic