Skip to content
This repository was archived by the owner on Sep 10, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ AI_PRIVACY_ENABLE_IDENTITY_EXCLUSION=true
AI_PRIVACY_IDENTITY_RECHECK_INTERVAL=12
AI_PRIVACY_IDENTITY_NEGATIVE_RECHECK_INTERVAL=90
AI_PRIVACY_ME_IMAGE_PATH=me.jpeg
AI_PRIVACY_FACE_MODEL_PATH=models/yolov8n-face.pt
AI_PRIVACY_FACE_MODEL_PATH=models/yolov12n-face.pt
AI_PRIVACY_FACE_DETECTION_MODEL_PATH=models/face_detection_yunet_2023mar.onnx
AI_PRIVACY_FACE_RECOGNITION_MODEL_PATH=models/face_recognition_sface_2021dec.onnx
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Mac 로컬 실행은 위와 같이 CPU 모드를 사용한다. 운영 환경은

## AI privacy runtime

`privacy_blur`는 모든 WebRTC 스트림이 YOLOv8n-Face, YuNet, SFace 모델 세트를
`privacy_blur`는 모든 WebRTC 스트림이 YOLOv12n-Face, YuNet, SFace 모델 세트를
공유하고 각 스트림은 독립적인 얼굴 추적 상태만 유지한다. 공유 워커가 짧은 시간
동안 들어온 프레임을 최대 `AI_PRIVACY_MAX_BATCH_SIZE`개까지 모아 한 번에 추론하며,
대기열은 `AI_PRIVACY_MAX_PENDING_FRAMES`로 제한된다.
Expand Down
2 changes: 1 addition & 1 deletion app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Settings(BaseSettings):
ai_privacy_identity_recheck_interval: int = 12
ai_privacy_identity_negative_recheck_interval: int = 90
ai_privacy_me_image_path: str | None = None
ai_privacy_face_model_path: str = "models/yolov8n-face.pt"
ai_privacy_face_model_path: str = "models/yolov12n-face.pt"
ai_privacy_face_detection_model_path: str = (
"models/face_detection_yunet_2023mar.onnx"
)
Expand Down
Binary file added models/yolov12n-face.pt
Binary file not shown.
2 changes: 1 addition & 1 deletion privacy_blur/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

@dataclass(frozen=True, slots=True)
class PrivacyBlurConfig:
face_model_path: Path = Path("models/yolov8n-face.pt")
face_model_path: Path = Path("models/yolov12n-face.pt")
face_imgsz: int = 640
face_conf_threshold: float = 0.22
face_iou_threshold: float = 0.45
Expand Down
27 changes: 20 additions & 7 deletions privacy_blur/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,7 @@ def _blur_frames(
y = torch.arange(height, device=self.device, dtype=dtype).view(height, 1)
x = torch.arange(width, device=self.device, dtype=dtype).view(1, width)

max_face_size = 0.0
for batch_index, frame_index in enumerate(indices):
for box in boxes_per_frame[frame_index]:
box_x, box_y, box_w, box_h = box[:4].astype(float)
Expand All @@ -1039,21 +1040,33 @@ def _blur_frames(
masks[batch_index, 0] = torch.maximum(
masks[batch_index, 0], ellipse.to(dtype)
)
max_face_size = max(max_face_size, max(box_w, box_h))

blurred = self._separable_gaussian(
images, _make_odd(self.config.blur_kernel)
)
feathered = self._separable_gaussian(
masks,
_make_odd(self.config.feather * 2 + 1),
).clamp(0, 1)
blur_kernel = self._adaptive_blur_kernel(max_face_size)
blurred = self._separable_gaussian(images, blur_kernel)
feather_kernel = _make_odd(self._adaptive_feather(max_face_size))
feathered = self._separable_gaussian(masks, feather_kernel).clamp(0, 1)
protected = images * (1.0 - feathered) + blurred * feathered
protected = protected.clamp(0, 255).round().to(torch.uint8)
protected_numpy = protected.permute(0, 2, 3, 1).contiguous().cpu().numpy()
for batch_index, frame_index in enumerate(indices):
outputs[frame_index] = protected_numpy[batch_index]
return outputs

def _adaptive_blur_kernel(self, max_face_size: float) -> int:
base = self.config.blur_kernel
reference = float(self.config.small_face_threshold)
ratio = max_face_size / reference
scaled = base * max(1.0, min(ratio, 3.0))
capped = min(scaled, 151.0)
return _make_odd(int(capped))

def _adaptive_feather(self, max_face_size: float) -> int:
base = self.config.feather
reference = float(self.config.small_face_threshold)
ratio = max_face_size / reference
return int(base * max(1.0, min(ratio, 2.0)))

def _separable_gaussian(
self, tensor: torch.Tensor, kernel_size: int
) -> torch.Tensor:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_performance_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

def make_test_config(**overrides) -> PrivacyBlurConfig:
defaults = {
"face_model_path": ROOT / "models/yolov8n-face.pt",
"face_model_path": ROOT / "models/yolov12n-face.pt",
"me_image_path": ROOT / "me.jpeg",
"face_detection_model_path": ROOT / "models/face_detection_yunet_2023mar.onnx",
"face_recognition_model_path": ROOT
Expand Down
2 changes: 1 addition & 1 deletion tests/test_privacy_blur.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def _build_config(**overrides):
from privacy_blur import PrivacyBlurConfig

defaults = {
"face_model_path": ROOT / "models/yolov8n-face.pt",
"face_model_path": ROOT / "models/yolov12n-face.pt",
"face_detection_model_path": ROOT / "models/face_detection_yunet_2023mar.onnx",
"face_recognition_model_path": ROOT
/ "models/face_recognition_sface_2021dec.onnx",
Expand Down
Loading