feat: add Qdrant _faces collection embedding push

- Add qdrant_faces.py utility module for _faces collection operations
- Modify face_processor.py to push embeddings to Qdrant (CoreML extraction re-enabled)
- Modify store_traced_faces.py to update trace_id in Qdrant after face tracking
- Collection schema: 512D vectors, Cosine distance, fixed name '_faces'
- Payload: file_uuid, frame, trace_id, bbox, confidence, identity_id/uuid, stranger_id
- Batch size: 100 (default), configurable via QDRANT_BATCH_SIZE env var
- Error handling: face_processor.py exits with error if Qdrant push fails
This commit is contained in:
Accusys
2026-06-25 00:23:20 +08:00
parent 074cdcdbed
commit 9fbb4f9b48
3 changed files with 355 additions and 3 deletions

View File

@@ -30,7 +30,9 @@ from pathlib import Path
import coremltools as ct
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "utils"))
from redis_publisher import RedisPublisher
from qdrant_faces import push_face_embeddings_batch
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
SWIFT_BIN = os.path.join(SCRIPT_DIR, "swift_processors", ".build", "debug", "swift_face_pose")
@@ -199,6 +201,7 @@ class FaceProcessorVision:
embed_count = 0
total_face_count = 0
last_pct = -1
all_embeddings = [] # Collect embeddings for Qdrant push
for frame_info in frames:
frame_num = frame_info["frame"]
@@ -225,11 +228,18 @@ class FaceProcessorVision:
if face_img.size == 0:
continue
# CoreML embedding - TODO: push to Qdrant _faces collection instead
# emb = self.extract_face_embedding(face_img)
emb = None
# CoreML embedding - push to Qdrant _faces collection
emb = self.extract_face_embedding(face_img)
if emb is not None:
embed_count += 1
# Collect for batch Qdrant push
all_embeddings.append({
"frame": frame_num,
"trace_id": 0, # Initial, updated by face_tracker
"bbox": {"x": x, "y": y, "width": w, "height": h},
"confidence": face.get("confidence", 0.5),
"embedding": emb,
})
# Pose classification
pose_info = face.get("pose", {})
@@ -292,6 +302,18 @@ class FaceProcessorVision:
with open(self.output_path, "w") as f:
json.dump(output, f, indent=2, ensure_ascii=False)
# Push embeddings to Qdrant _faces collection
if all_embeddings:
try:
pushed = push_face_embeddings_batch(self.uuid, all_embeddings, self.publisher)
if pushed != len(all_embeddings):
raise RuntimeError(
f"Qdrant push incomplete: {pushed}/{len(all_embeddings)} embeddings pushed"
)
except Exception as e:
print(f"[FACE_V2] ERROR: Qdrant push failed: {e}", file=sys.stderr)
raise RuntimeError(f"Qdrant push failed: {e}")
elapsed = time.time() - t0
print(f"[FACE_V2] Done: {len(frames_list)} frames, {embed_count} embeddings, {elapsed:.0f}s")