더 이상 배포, 유지보수 하지 않는 기록용 레포지토리입니다.
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtsource .venv/bin/activate
AI_PRIVACY_DEVICE=cpu \
AI_PRIVACY_REQUIRE_GPU=false \
AI_PRIVACY_USE_HALF=false \
python -m uvicorn app.main:app --host 127.0.0.1 --port 8000 --reloadMac 로컬 실행은 위와 같이 CPU 모드를 사용한다. 운영 환경은 앱 시작 시 모델을
미리 로드하고 CUDA 또는 ONNX Runtime CUDA가 준비되지 않았으면 즉시 실패하도록
AI_PRIVACY_REQUIRE_GPU=true를 사용한다.
privacy_blur는 모든 WebRTC 스트림이 하나의 GPU 런타임과 YOLO11s face pose,
SFace 모델 세트를 공유한다. 각 스트림은 독립적인 얼굴 추적 상태와 client_id별
참조 얼굴 설정만 따로 가진다. 공유 프레임 워커가 짧은 시간 동안 들어온 프레임을
최대 AI_PRIVACY_MAX_BATCH_SIZE개까지 모아 한 번에 추론하며, 대기열은
AI_PRIVACY_MAX_PENDING_FRAMES로 제한된다.
처리 순서는 다음과 같다.
- YOLO FP16 배치로 얼굴과 5개 랜드마크를 탐지한다.
- 얼굴 추적 상태를 갱신하고, 식별 재확인이 필요한 트랙만 비동기 식별 큐에 넣는다. 랜드마크가 유효하지 않은 얼굴은 본인 제외 후보로 사용하지 않는다.
- 현재까지 확정된 본인 트랙을 제외한 얼굴에 확장 타원 마스크와 Gaussian blur를 GPU에서 적용한다. 프레임 처리는 SFace 식별 결과를 기다리지 않는다.
- 별도 식별 워커가 YOLO 랜드마크로 얼굴을 정렬하고 SFace 배치로 참조 얼굴과 비교한다. 결과는 이후 프레임의 해당 트랙 상태에 반영된다.
- 처리 결과를 원래 WebRTC 트랙으로 반환한다.
GET /health에서 장치, ONNX provider, 프레임 큐와 식별 큐 깊이, 배치 수, 평균
배치 크기와 평균 처리 지연을 확인할 수 있다. 얼굴 대조 수와 지연, YOLO 랜드마크
사용 수, 랜드마크 누락 수, 식별 큐 포화로 드롭된 요청 수도 별도 지표로 제공한다.
identity_yunet_fallback_faces는 이전 클라이언트와의 호환성을 위해 남아 있지만,
YuNet fallback은 더 이상 사용하지 않으므로 항상 0이다.
본인 트랙은 AI_PRIVACY_IDENTITY_RECHECK_INTERVAL, 비본인 또는 아직 불확실한 트랙은
AI_PRIVACY_IDENTITY_NEGATIVE_RECHECK_INTERVAL 주기로 재확인한다. 기본값은 둘 다
12프레임이라 화면 밖에서 들어온 본인이 첫 대조에서 실패해도 짧은 간격으로 다시 확인한다.
새 얼굴이 한 번에 많이 들어오면 현재 프레임은 기존 추적 상태로 바로 처리하고, 식별 워커가
처리한 결과가 다음 프레임들에 반영된다.
/reference-face API는 client_id별로 블러 제외 기준 얼굴을 분리한다.
client_id는 쿼리, multipart/form-data 필드, 또는 X-Client-ID 헤더로 전달할 수
있다. 값이 없으면 기존 클라이언트 호환을 위해 default 클라이언트를 사용한다.
POST /reference-face
GET /reference-face
DELETE /reference-face
DELETE /reference-face/{face_id}
POST /reference-face는 JPEG, PNG, WebP 이미지를 최대 20장까지 받는다. 기존 단일
파일 필드 image만 보내면 기존 동작처럼 해당 클라이언트의 참조 얼굴을 교체한다.
새 다중 파일 필드 images를 보내면 해당 클라이언트에 얼굴을 추가한다. 응답에는
client_id, count, faces[].face_id, faces[].registered_at이 포함된다.
DELETE /reference-face는 해당 클라이언트의 참조 얼굴 전체를 삭제하고,
DELETE /reference-face/{face_id}는 해당 클라이언트에서 지정한 한 장만 삭제한다.
참조 이미지는 data/reference_faces/{client_id}/{face_id}{ext} 형식으로 저장된다.
예전 단일 파일 data/reference_face.jpg는 default 클라이언트 조회 시에만
호환용으로 읽는다.
WebRTC 세션은 POST /sessions 요청의 metadata.client_id로 참조 얼굴을 선택한다.
브라우저 테스트 클라이언트는 이 값을 자동으로 넣는다. API 클라이언트가 세션을
직접 생성할 때 metadata.client_id를 생략하면 default 클라이언트의 참조 얼굴을
사용한다.
호스트에 NVIDIA 드라이버와 NVIDIA Container Toolkit을 설치한 뒤 .env.example을
기준으로 .env를 구성한다. 기본 운영값은 cuda:0, FP16, 최대 배치 8개다.
고정 참조 얼굴을 환경 변수로 넣고 싶으면 AI_PRIVACY_ME_IMAGE_PATH를 설정할 수
있지만, 일반적인 운영 흐름은 클라이언트가 /reference-face API로 자기
client_id의 참조 얼굴을 등록하는 방식이다.
docker build -t innolive-server .
docker run --rm -i --gpus all --env-file .env innolive-server python - <<'PY'
import onnxruntime as ort
import torch
print(torch.cuda.is_available(), torch.cuda.get_device_name(0))
print(ort.get_available_providers())
PY첫 출력은 True와 GPU 이름, 두 번째 출력은 CUDAExecutionProvider를 포함해야
한다. 한 GPU에는 애플리케이션 프로세스를 하나만 실행한다. Uvicorn worker를 여러
개 띄우면 프로세스마다 모델과 GPU 메모리가 중복되고 스트림 간 동적 배치가 분리된다.
The server serves a browser client at:
http://127.0.0.1:8000/client/
Do not open app/static/client/index.html with a file:// URL. Browsers can
deny camera access on file origins. Start the server and use the /client/
HTTP(S) URL instead.
On the deployed server, open:
https://innolive.duckdns.org/client/
Use Start WebRTC to create a session, open the camera, send a WebRTC offer
over /signaling, trickle local ICE candidates, and receive the server's
processed video track. The page also exposes health check, session create/list,
session polling, deletion, signaling error probing, connection timing, media
state diagnostics, and client-scoped reference face registration/deletion.
Signaling uses a WebSocket endpoint:
ws://127.0.0.1:8000/signaling
Send an offer:
{
"type": "offer",
"session_id": "<session_id>",
"sdp": "v=0\n..."
}The server responds with an answer:
{
"type": "answer",
"session_id": "<session_id>",
"sdp": "v=0\n..."
}Send ICE candidates on the same socket:
{
"type": "ice_candidate",
"session_id": "<session_id>",
"candidate": "candidate:...",
"sdpMid": "0",
"sdpMLineIndex": 0
}Send the end-of-candidates signal with a null or empty candidate:
{
"type": "ice_candidate",
"session_id": "<session_id>",
"candidate": null
}The server acknowledges received candidates:
{
"type": "ice_candidate_added",
"session_id": "<session_id>",
"end_of_candidates": false,
"ice_connection_state": "checking",
"connection_state": "connecting"
}Peer connection timing is logged when the connection reaches connected and when
ICE reaches completed. The same timing snapshot is included in session API
responses under timing.
Errors are sent on the same socket:
{
"type": "error",
"error": {
"code": "bad_request",
"message": "Invalid signaling message."
}
}The browser client loads ICE servers from:
GET /webrtc/config
Configure the server with environment variables:
WEBRTC_STUN_URLS=stun:stun.l.google.com:19302
WEBRTC_TURN_URLS=turn:innolive.duckdns.org:3478?transport=udp,turn:innolive.duckdns.org:3478?transport=tcp,turns:innolive.duckdns.org:5349?transport=tcp
WEBRTC_TURN_USERNAME=
WEBRTC_TURN_CREDENTIAL=
WEBRTC_ANNOUNCED_IP=
WEBRTC_UDP_PORT_MIN=50000
WEBRTC_UDP_PORT_MAX=60000WEBRTC_STUN_URLS and WEBRTC_TURN_URLS are comma-separated. For reliable
connections across unrelated networks, run a TURN server and set the TURN URL,
username, and credential. STUN alone can expose a server-reflexive candidate, but
it is not sufficient for every NAT/firewall combination.
If this server is behind NAT and aiortc advertises only a private host candidate
such as 192.168.x.x, set WEBRTC_ANNOUNCED_IP to the public IPv4 address that
forwards UDP media traffic to this process. This rewrites host candidates in the
answer SDP sent to browsers. The NAT/router must forward the WebRTC UDP range
1:1 to the same container/host ports; if the public port is remapped, use TURN.
The default server UDP media range is 50000-60000/udp. Open and forward:
sudo ufw allow 8000/tcp
sudo ufw allow 50000:60000/udpThe production Compose file uses host networking so aiortc binds the configured UDP range directly on the host. Keep router port forwarding, the host firewall, and the environment range aligned.
HTTPS/WSS signaling may pass through a reverse proxy such as Nginx Proxy Manager. WebRTC media UDP does not pass through a normal HTTP reverse proxy or Cloudflare orange-cloud proxy. Media must reach the server directly over UDP, or it must go through a TURN relay.
When the connection is healthy in chrome://webrtc-internals, the selected
candidate pair for an external client should normally include one of:
local: hosttoremote: srflxor an announced public host candidate when direct UDP forwarding works.local: relayorremote: relaywhen TURN is used.
If the selected pair only shows private addresses such as 192.168.x.x for a
peer outside the LAN, the media path is still not externally reachable.