Views
No views yet
| Property | Value |
|---|---|
| Category | Face Detection + Re-Identification |
| Base Model | face-detection-adas-0001 + face-reidentification-retail-0095 (Open Model Zoo) |
| Source Framework | Caffe / PyTorch (Open Model Zoo) |
| Supported Precisions | FP32, FP16 |
| Inference Engine | OpenVINO |
| Hardware | CPU, GPU, NPU |
| Detected Class(es) | Human faces (detection) + 256-d face embeddings (re-identification) |
gvadetect + gvaclassify pipeline.Privacy Note: Facial recognition involves biometric data. Ensure your deployment complies with applicable privacy regulations (GDPR, BIPA, etc.) and has proper consent mechanisms in place.
1python3 -m venv .venv --system-site-packages
2source .venv/bin/activateNote: The--system-site-packagesflag is required so the virtual environment can access the system-installed OpenVINO and DLStreamer Python packages.
1chmod +x export_and_quantize.sh
2./export_and_quantize.shopenvino.face-detection-adas-0001 (FP16) into ./intel/face-detection-adas-0001/FP16/.face-reidentification-retail-0095 (FP16) into ./intel/face-reidentification-retail-0095/FP16/.test_video.mp4).ID <n>, and the result is saved to output_openvino.mp4.
Change the device string to run on CPU, GPU, or NPU.1import cv2
2import numpy as np
3import openvino as ov
4
5DETECTION_MODEL = "intel/face-detection-adas-0001/FP16/face-detection-adas-0001.xml"
6REID_MODEL = "intel/face-reidentification-retail-0095/FP16/face-reidentification-retail-0095.xml"
7INPUT_VIDEO = "test_video.mp4"
8CONF_THRESHOLD = 0.6
9MATCH_THRESHOLD = 0.5
10
11core = ov.Core()
12
13# Change device to "GPU" or "NPU" to run on integrated GPU or NPU.
14det_model = core.compile_model(core.read_model(DETECTION_MODEL), "CPU")
15reid_model = core.compile_model(core.read_model(REID_MODEL), "CPU")
16
17det_input = det_model.input(0)
18det_h, det_w = det_input.shape[2], det_input.shape[3]
19reid_input = reid_model.input(0)
20reid_h, reid_w = reid_input.shape[2], reid_input.shape[3]
21
22
23def detect_faces(img):
24 h0, w0 = img.shape[:2]
25 blob = cv2.resize(img, (det_w, det_h))
26 blob = blob.transpose(2, 0, 1)[np.newaxis, ...].astype(np.float32)
27 detections = det_model([blob])[det_model.output(0)][0][0]
28 boxes = []
29 for det in detections:
30 if float(det[2]) < CONF_THRESHOLD:
31 continue
32 x1 = max(0, int(det[3] * w0))
33 y1 = max(0, int(det[4] * h0))
34 x2 = min(w0, int(det[5] * w0))
35 y2 = min(h0, int(det[6] * h0))
36 if x2 > x1 and y2 > y1:
37 boxes.append((x1, y1, x2, y2))
38 return boxes
39
40
41def get_embedding(img, bbox):
42 x1, y1, x2, y2 = bbox
43 crop = img[y1:y2, x1:x2]
44 blob = cv2.resize(crop, (reid_w, reid_h))
45 blob = blob.transpose(2, 0, 1)[np.newaxis, ...].astype(np.float32)
46 emb = reid_model([blob])[reid_model.output(0)].flatten()
47 return emb / np.linalg.norm(emb)
48
49
50# Gallery of (numeric_id, embedding). recognize() returns an existing ID for a
51# known face or enrolls a new one, keeping each person's ID stable over time.
52gallery = []
53next_id = 1
54
55
56def recognize(embedding):
57 global next_id
58 best_index, best_sim = -1, 0.0
59 for index, (_, gallery_emb) in enumerate(gallery):
60 sim = float(np.dot(embedding, gallery_emb))
61 if sim > best_sim:
62 best_sim, best_index = sim, index
63 if best_sim >= MATCH_THRESHOLD:
64 person_id, gallery_emb = gallery[best_index]
65 # Blend the embedding into the gallery entry to stay robust to pose.
66 updated = 0.9 * gallery_emb + 0.1 * embedding
67 gallery[best_index] = (person_id, updated / np.linalg.norm(updated))
68 return person_id
69 person_id = next_id
70 next_id += 1
71 gallery.append((person_id, embedding))
72 print(f"Enrolled ID {person_id}")
73 return person_id
74
75
76cap = cv2.VideoCapture(INPUT_VIDEO)
77fps = cap.get(cv2.CAP_PROP_FPS) or 12
78frame_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
79frame_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
80writer = cv2.VideoWriter(
81 "output_openvino.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, (frame_w, frame_h))
82
83while True:
84 ok, frame = cap.read()
85 if not ok:
86 break
87 for bbox in detect_faces(frame):
88 person_id = recognize(get_embedding(frame, bbox))
89 x1, y1, x2, y2 = bbox
90 cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
91 cv2.putText(frame, f"ID {person_id}", (x1, max(15, y1 - 8)),
92 cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
93 writer.write(frame)
94
95cap.release()
96writer.release()
97print(f"Total identities recognized: {len(gallery)}")
98print("Saved: output_openvino.mp4")"CPU" -- default, works on all Intel platforms."GPU" -- Intel integrated or discrete GPU."NPU" -- Intel NPU; face-detection-adas-0001 FP16 is NPU-compatible.
gvadetect and the
re-identification model via gvaclassify on the video. Frames are pulled through
an appsink, where each face's embedding is matched against a gallery to assign
a stable numeric ID (new people are enrolled, returning people keep their ID).
Every face is annotated with its ID <n> and the result is saved to
output_dlstreamer.mp4.Notes on running this sample:
ExportPYTHONPATHso the DLStreamer Python modules (gi,gstgva) are importable:bash1source /opt/intel/openvino_2026/setupvars.sh 2source /opt/intel/dlstreamer/scripts/setup_dls_env.sh 3export PYTHONPATH=/opt/intel/dlstreamer/python:\ 4/opt/intel/dlstreamer/gstreamer/lib/python3/dist-packages:${PYTHONPATH:-} The re-identification embedding is attached as a tensor on each face's region-of-interest metadata. Convert the stream toBGRbeforegvadetect/gvaclassifyso a downstream format conversion does not strip those tensors before theappsinkreads them.
1import gi
2
3gi.require_version("Gst", "1.0")
4from gi.repository import Gst
5
6Gst.init([])
7
8import numpy as np
9import cv2
10from gstgva import VideoFrame
11
12INPUT_VIDEO = "test_video.mp4"
13OUTPUT_VIDEO = "output_dlstreamer.mp4"
14DETECTION_MODEL = "intel/face-detection-adas-0001/FP16/face-detection-adas-0001.xml"
15REID_MODEL = "intel/face-reidentification-retail-0095/FP16/face-reidentification-retail-0095.xml"
16# For CPU: change "GPU" to "CPU". For NPU: change "GPU" to "NPU".
17DEVICE = "GPU"
18DET_THRESHOLD = 0.6
19MATCH_THRESHOLD = 0.5
20
21# Gallery of (numeric_id, embedding). recognize() returns an existing ID for a
22# known face or enrolls a new one, keeping each person's ID stable over time.
23gallery = []
24next_id = 1
25
26
27def recognize(embedding):
28 global next_id
29 best_index, best_sim = -1, 0.0
30 for index, (_, gallery_emb) in enumerate(gallery):
31 sim = float(np.dot(embedding, gallery_emb))
32 if sim > best_sim:
33 best_sim, best_index = sim, index
34 if best_sim >= MATCH_THRESHOLD:
35 person_id, gallery_emb = gallery[best_index]
36 # Blend the embedding into the gallery entry to stay robust to pose.
37 updated = 0.9 * gallery_emb + 0.1 * embedding
38 gallery[best_index] = (person_id, updated / np.linalg.norm(updated))
39 return person_id
40 person_id = next_id
41 next_id += 1
42 gallery.append((person_id, embedding))
43 print(f"Enrolled ID {person_id}", flush=True)
44 return person_id
45
46
47def face_embeddings(video_frame):
48 """Yield ((x, y, w, h), normalized_embedding) for each classified face."""
49 for region in video_frame.regions():
50 rect = region.rect()
51 emb = None
52 for tensor in region.tensors():
53 if tensor.is_detection():
54 continue
55 data = np.array(tensor.data(), dtype=np.float32)
56 if data.size >= 256:
57 emb = data[:256]
58 if emb is None:
59 continue
60 emb = emb / (np.linalg.norm(emb) + 1e-9)
61 yield (int(rect.x), int(rect.y), int(rect.w), int(rect.h)), emb
62
63
64# Convert to BGR before inference so gvaclassify's embedding tensors survive to
65# the appsink (a later format-changing videoconvert would strip them).
66pipeline = Gst.parse_launch(
67 f"filesrc location={INPUT_VIDEO} ! decodebin3 ! "
68 "videoconvert ! video/x-raw,format=BGR ! "
69 f"gvadetect model={DETECTION_MODEL} device={DEVICE} "
70 f"threshold={DET_THRESHOLD} ! queue ! "
71 f"gvaclassify model={REID_MODEL} device={DEVICE} ! queue ! "
72 "appsink name=sink emit-signals=true sync=false max-buffers=4 drop=false"
73)
74sink = pipeline.get_by_name("sink")
75
76writer = {"w": None}
77
78
79def on_video(sink):
80 sample = sink.emit("pull-sample")
81 if sample is None:
82 return Gst.FlowReturn.OK
83 vf = VideoFrame(sample.get_buffer(), caps=sample.get_caps())
84 labeled = []
85 for (x, y, w, h), emb in face_embeddings(vf):
86 labeled.append((x, y, w, h, recognize(emb)))
87
88 with vf.data() as mat:
89 frame = mat.copy()
90
91 if writer["w"] is None:
92 frame_h, frame_w = frame.shape[:2]
93 structure = sample.get_caps().get_structure(0)
94 ok_fr, fps_n, fps_d = structure.get_fraction("framerate")
95 fps = fps_n / fps_d if ok_fr and fps_d else 12
96 writer["w"] = cv2.VideoWriter(
97 OUTPUT_VIDEO, cv2.VideoWriter_fourcc(*"mp4v"), fps, (frame_w, frame_h))
98
99 for x, y, w, h, person_id in labeled:
100 cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
101 cv2.putText(frame, f"ID {person_id}", (x, max(15, y - 8)),
102 cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
103 writer["w"].write(frame)
104 return Gst.FlowReturn.OK
105
106
107sink.connect("new-sample", on_video)
108pipeline.set_state(Gst.State.PLAYING)
109pipeline.get_bus().timed_pop_filtered(
110 Gst.CLOCK_TIME_NONE, Gst.MessageType.EOS | Gst.MessageType.ERROR)
111pipeline.set_state(Gst.State.NULL)
112if writer["w"] is not None:
113 writer["w"].release()
114print(f"Total identities recognized: {len(gallery)}", flush=True)
115print(f"Saved: {OUTPUT_VIDEO}", flush=True)DEVICE = "GPU" -- default in the sample code.DEVICE = "CPU" -- change "GPU" to "CPU".DEVICE = "NPU" -- change "GPU" to "NPU"; use batch-size=1 and nireq=4 for best NPU utilization.