vagheshpatel commited on
Commit
ee6192f
·
verified ·
1 Parent(s): 0267b95

Sync face-matching from metro-analytics-catalog

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ expected_output_dlstreamer.gif filter=lfs diff=lfs merge=lfs -text
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) Intel Corporation.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE
README.md ADDED
@@ -0,0 +1,378 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ license_link: LICENSE
4
+ library_name: openvino
5
+ pipeline_tag: image-classification
6
+ tags:
7
+ - openvino
8
+ - intel
9
+ - face-detection
10
+ - face-matching
11
+ - face-reidentification
12
+ - edge-ai
13
+ - metro
14
+ - dlstreamer
15
+ language:
16
+ - en
17
+ ---
18
+
19
+ # Face Matching
20
+
21
+ | Property | Value |
22
+ |---|---|
23
+ | **Category** | Face Detection + One-to-One Verification |
24
+ | **Base Model** | [face-detection-adas-0001](https://docs.openvino.ai/2024/omz_models_model_face_detection_adas_0001.html) + [face-reidentification-retail-0095](https://docs.openvino.ai/2024/omz_models_model_face_reidentification_retail_0095.html) (Open Model Zoo) |
25
+ | **Source Framework** | Caffe / PyTorch (Open Model Zoo) |
26
+ | **Supported Precisions** | FP32, FP16 |
27
+ | **Inference Engine** | OpenVINO |
28
+ | **Hardware** | CPU, GPU, NPU |
29
+ | **Detected Class(es)** | Human faces (detection) + 256-d face embeddings (matching) |
30
+
31
+ ---
32
+
33
+ ## Overview
34
+
35
+ Face Matching is a Metro Analytics use case that verifies a specific identity:
36
+ given a reference face image, it locates that person inside a scene that may
37
+ contain several people and highlights only the matching face. Each detected
38
+ face is compared to the reference by cosine similarity of its embedding vector.
39
+
40
+ It uses the same two-stage pipeline as [facial-recognition](../facial-recognition/):
41
+
42
+ - **face-detection-adas-0001** -- detects every face in the scene.
43
+ - **face-reidentification-retail-0095** -- computes a 256-d embedding per face.
44
+
45
+ The difference from full facial recognition is scope: face matching verifies a
46
+ single reference identity and highlights only that person, without maintaining a
47
+ gallery database.
48
+
49
+ Typical Metro deployments include:
50
+
51
+ - **Badge Verification** -- compare a live face to a badge photo at entry gates.
52
+ - **Document Verification** -- match a passport or ID photo to the holder.
53
+ - **Duplicate Detection** -- check if two records belong to the same person.
54
+ - **Re-identification Confirmation** -- confirm a person flagged by the search system.
55
+
56
+ > **Privacy Note:** Face matching involves biometric data.
57
+ > Ensure your deployment complies with applicable privacy regulations
58
+ > (GDPR, BIPA, etc.) and has proper consent mechanisms in place.
59
+
60
+ ---
61
+
62
+ ## Prerequisites
63
+
64
+ - Python 3.11+
65
+ - [Install OpenVINO](https://docs.openvino.ai/2026/get-started/install-openvino.html) (latest version)
66
+ - [Install Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/get_started/install/install_guide_ubuntu.html) (latest version)
67
+
68
+ Create and activate a Python virtual environment before running the scripts:
69
+
70
+ ```bash
71
+ python3 -m venv .venv --system-site-packages
72
+ source .venv/bin/activate
73
+ ```
74
+
75
+ > **Note:** The `--system-site-packages` flag is required so the virtual
76
+ > environment can access the system-installed OpenVINO and DLStreamer Python
77
+ > packages.
78
+
79
+ ---
80
+
81
+ ## Getting Started
82
+
83
+ ### Download Models
84
+
85
+ Run the provided script to download the face detection and re-identification
86
+ models from the Open Model Zoo:
87
+
88
+ ```bash
89
+ chmod +x export_and_quantize.sh
90
+ ./export_and_quantize.sh
91
+ ```
92
+
93
+ The script downloads `face-detection-adas-0001` and
94
+ `face-reidentification-retail-0095` in FP16, downloads the sample video, and
95
+ captures a reference face image (`face_a.jpg`) of the left subject from it.
96
+
97
+ ### OpenVINO Sample
98
+
99
+ The sample below verifies identity inside a scene: it loads the captured
100
+ reference image of the subject (`face_a.jpg`, the woman who pauses on the left of
101
+ the sample video), computes its embedding, then reads a frame from the video that
102
+ contains several people. It detects every face in the frame, embeds each one, and
103
+ draws a green box only on the face whose similarity to the reference is highest
104
+ and above the match threshold. Change the `device` string to run on CPU, GPU, or NPU.
105
+
106
+ ```python
107
+ import cv2
108
+ import numpy as np
109
+ import openvino as ov
110
+
111
+ DETECTION_MODEL = "intel/face-detection-adas-0001/FP16/face-detection-adas-0001.xml"
112
+ REID_MODEL = "intel/face-reidentification-retail-0095/FP16/face-reidentification-retail-0095.xml"
113
+ REFERENCE_IMAGE = "face_a.jpg" # reference: left subject captured from the video
114
+ SCENE_VIDEO = "test_video.mp4" # scene containing the reference subject plus others
115
+ SCENE_FRAME = 480 # frame index of the two-person scene (~40s)
116
+ CONF_THRESHOLD = 0.5
117
+ MATCH_THRESHOLD = 0.5
118
+
119
+ core = ov.Core()
120
+
121
+ # Change device to "GPU" or "NPU" to run on integrated GPU or NPU.
122
+ det_compiled = core.compile_model(core.read_model(DETECTION_MODEL), "CPU")
123
+ reid_compiled = core.compile_model(core.read_model(REID_MODEL), "CPU")
124
+
125
+ det_input = det_compiled.input(0)
126
+ det_h, det_w = det_input.shape[2], det_input.shape[3]
127
+ reid_input = reid_compiled.input(0)
128
+ reid_h, reid_w = reid_input.shape[2], reid_input.shape[3]
129
+
130
+
131
+ def detect_faces(img):
132
+ h0, w0 = img.shape[:2]
133
+ blob = cv2.resize(img, (det_w, det_h))
134
+ blob = blob.transpose(2, 0, 1)[np.newaxis, ...].astype(np.float32)
135
+ dets = det_compiled([blob])[det_compiled.output(0)][0][0]
136
+ faces = []
137
+ for d in dets:
138
+ if float(d[2]) < CONF_THRESHOLD:
139
+ continue
140
+ x1 = max(0, int(d[3] * w0))
141
+ y1 = max(0, int(d[4] * h0))
142
+ x2 = min(w0, int(d[5] * w0))
143
+ y2 = min(h0, int(d[6] * h0))
144
+ if x2 > x1 and y2 > y1:
145
+ faces.append((x1, y1, x2, y2))
146
+ return faces
147
+
148
+
149
+ def get_embedding(img, bbox):
150
+ x1, y1, x2, y2 = bbox
151
+ crop = img[y1:y2, x1:x2]
152
+ blob = cv2.resize(crop, (reid_w, reid_h))
153
+ blob = blob.transpose(2, 0, 1)[np.newaxis, ...].astype(np.float32)
154
+ emb = reid_compiled([blob])[reid_compiled.output(0)].flatten()
155
+ return emb / np.linalg.norm(emb)
156
+
157
+
158
+ # 1. Embed the reference face.
159
+ reference = cv2.imread(REFERENCE_IMAGE)
160
+ ref_faces = detect_faces(reference)
161
+ if not ref_faces:
162
+ raise SystemExit("No face detected in the reference image")
163
+ ref_bbox = max(ref_faces, key=lambda b: (b[2] - b[0]) * (b[3] - b[1]))
164
+ ref_emb = get_embedding(reference, ref_bbox)
165
+
166
+ # 2. Read a scene frame that contains several people.
167
+ cap = cv2.VideoCapture(SCENE_VIDEO)
168
+ cap.set(cv2.CAP_PROP_POS_FRAMES, SCENE_FRAME)
169
+ ok, frame = cap.read()
170
+ cap.release()
171
+ if not ok:
172
+ raise SystemExit(f"Could not read frame {SCENE_FRAME} from {SCENE_VIDEO}")
173
+
174
+ # 3. Compare every face in the scene to the reference; keep the best match.
175
+ best_bbox = None
176
+ best_sim = 0.0
177
+ for bbox in detect_faces(frame):
178
+ sim = float(np.dot(get_embedding(frame, bbox), ref_emb))
179
+ if sim > best_sim:
180
+ best_sim = sim
181
+ best_bbox = bbox
182
+
183
+ if best_bbox is not None and best_sim >= MATCH_THRESHOLD:
184
+ x1, y1, x2, y2 = best_bbox
185
+ cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
186
+ cv2.putText(frame, f"MATCH {best_sim:.2f}", (x1, max(15, y1 - 8)),
187
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
188
+ print(f"Matched reference identity, similarity {best_sim:.4f}")
189
+ else:
190
+ print(f"No matching face found (best similarity {best_sim:.4f})")
191
+
192
+ cv2.imwrite("output_openvino.jpg", frame)
193
+ print("Saved: output_openvino.jpg")
194
+ ```
195
+
196
+ **Device targets:**
197
+
198
+ - `"CPU"` -- default, works on all Intel platforms.
199
+ - `"GPU"` -- Intel integrated or discrete GPU.
200
+ - `"NPU"` -- Intel NPU; face-detection-adas-0001 FP16 is NPU-compatible.
201
+
202
+ #### Expected Output
203
+
204
+ ![OpenVINO expected output](expected_output_openvino.jpg)
205
+
206
+ ### DLStreamer Sample
207
+
208
+ The pipeline below runs the face detector via `gvadetect` and the
209
+ re-identification model via `gvaclassify` on the video. Frames are pulled through
210
+ an `appsink`, where each detected face's embedding is compared to the reference
211
+ embedding computed from `face_a.jpg`. Only faces that match the reference are
212
+ boxed, so the annotated `output_dlstreamer.mp4` highlights just the reference
213
+ subject even when other people are present.
214
+
215
+ > **Notes on running this sample:**
216
+ >
217
+ > - Export `PYTHONPATH` so the DLStreamer Python modules (`gi`, `gstgva`) are
218
+ > importable:
219
+ >
220
+ > ```bash
221
+ > source /opt/intel/openvino_2026/setupvars.sh
222
+ > source /opt/intel/dlstreamer/scripts/setup_dls_env.sh
223
+ > export PYTHONPATH=/opt/intel/dlstreamer/python:\
224
+ > /opt/intel/dlstreamer/gstreamer/lib/python3/dist-packages:${PYTHONPATH:-}
225
+ > ```
226
+ >
227
+ > - The re-identification embedding is attached as a tensor on each face's
228
+ > region-of-interest metadata. Convert the stream to `BGR` **before**
229
+ > `gvadetect`/`gvaclassify` so a downstream format conversion does not strip
230
+ > those tensors before the `appsink` reads them.
231
+
232
+ ```python
233
+ import gi
234
+
235
+ gi.require_version("Gst", "1.0")
236
+ from gi.repository import Gst
237
+
238
+ Gst.init([])
239
+
240
+ import numpy as np
241
+ import cv2
242
+ from gstgva import VideoFrame
243
+
244
+ INPUT_VIDEO = "test_video.mp4"
245
+ REFERENCE_IMAGE = "face_a.jpg"
246
+ OUTPUT_VIDEO = "output_dlstreamer.mp4"
247
+ DETECTION_MODEL = "intel/face-detection-adas-0001/FP16/face-detection-adas-0001.xml"
248
+ REID_MODEL = "intel/face-reidentification-retail-0095/FP16/face-reidentification-retail-0095.xml"
249
+ # For CPU: change "GPU" to "CPU". For NPU: change "GPU" to "NPU".
250
+ DEVICE = "GPU"
251
+ DET_THRESHOLD = 0.6
252
+ MATCH_THRESHOLD = 0.4
253
+
254
+
255
+ def face_embeddings(video_frame):
256
+ """Yield ((x, y, w, h), normalized_embedding) for each classified face."""
257
+ for region in video_frame.regions():
258
+ rect = region.rect()
259
+ emb = None
260
+ for tensor in region.tensors():
261
+ if tensor.is_detection():
262
+ continue
263
+ data = np.array(tensor.data(), dtype=np.float32)
264
+ if data.size >= 256:
265
+ emb = data[:256]
266
+ if emb is None:
267
+ continue
268
+ emb = emb / (np.linalg.norm(emb) + 1e-9)
269
+ yield (int(rect.x), int(rect.y), int(rect.w), int(rect.h)), emb
270
+
271
+
272
+ def run_pipeline(source_desc, on_frame):
273
+ # Convert to BGR before inference so gvaclassify's embedding tensors survive
274
+ # to the appsink (a later format-changing videoconvert would strip them).
275
+ pipeline = Gst.parse_launch(
276
+ f"{source_desc} ! videoconvert ! video/x-raw,format=BGR ! "
277
+ f"gvadetect model={DETECTION_MODEL} device={DEVICE} "
278
+ f"threshold={DET_THRESHOLD} ! queue ! "
279
+ f"gvaclassify model={REID_MODEL} device={DEVICE} ! queue ! "
280
+ "appsink name=sink emit-signals=true sync=false max-buffers=4 drop=false"
281
+ )
282
+ sink = pipeline.get_by_name("sink")
283
+ sink.connect("new-sample", on_frame)
284
+ pipeline.set_state(Gst.State.PLAYING)
285
+ pipeline.get_bus().timed_pop_filtered(
286
+ Gst.CLOCK_TIME_NONE, Gst.MessageType.EOS | Gst.MessageType.ERROR)
287
+ pipeline.set_state(Gst.State.NULL)
288
+
289
+
290
+ # 1. Compute the reference embedding from the reference image.
291
+ ref = {"emb": None, "area": 0}
292
+
293
+
294
+ def on_reference(sink):
295
+ sample = sink.emit("pull-sample")
296
+ if sample is None:
297
+ return Gst.FlowReturn.OK
298
+ vf = VideoFrame(sample.get_buffer(), caps=sample.get_caps())
299
+ for (x, y, w, h), emb in face_embeddings(vf):
300
+ if w * h > ref["area"]:
301
+ ref["area"] = w * h
302
+ ref["emb"] = emb
303
+ return Gst.FlowReturn.OK
304
+
305
+
306
+ run_pipeline(f"filesrc location={REFERENCE_IMAGE} ! jpegdec", on_reference)
307
+ if ref["emb"] is None:
308
+ raise SystemExit("No face detected in the reference image")
309
+ ref_emb = ref["emb"]
310
+
311
+ # 2. Process the video, boxing only faces that match the reference identity.
312
+ writer = {"w": None}
313
+ match_frames = 0
314
+
315
+
316
+ def on_video(sink):
317
+ global match_frames
318
+ sample = sink.emit("pull-sample")
319
+ if sample is None:
320
+ return Gst.FlowReturn.OK
321
+ vf = VideoFrame(sample.get_buffer(), caps=sample.get_caps())
322
+ matches = []
323
+ for (x, y, w, h), emb in face_embeddings(vf):
324
+ similarity = float(np.dot(emb, ref_emb))
325
+ if similarity >= MATCH_THRESHOLD:
326
+ matches.append((x, y, w, h, similarity))
327
+
328
+ with vf.data() as mat:
329
+ frame = mat.copy()
330
+
331
+ if writer["w"] is None:
332
+ frame_h, frame_w = frame.shape[:2]
333
+ structure = sample.get_caps().get_structure(0)
334
+ ok_fr, fps_n, fps_d = structure.get_fraction("framerate")
335
+ fps = fps_n / fps_d if ok_fr and fps_d else 12
336
+ writer["w"] = cv2.VideoWriter(
337
+ OUTPUT_VIDEO, cv2.VideoWriter_fourcc(*"mp4v"), fps, (frame_w, frame_h))
338
+
339
+ for x, y, w, h, similarity in matches:
340
+ cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
341
+ cv2.putText(frame, f"MATCH {similarity:.2f}", (x, max(15, y - 8)),
342
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
343
+ if matches:
344
+ match_frames += 1
345
+ writer["w"].write(frame)
346
+ return Gst.FlowReturn.OK
347
+
348
+
349
+ run_pipeline(f"filesrc location={INPUT_VIDEO} ! decodebin3", on_video)
350
+ if writer["w"] is not None:
351
+ writer["w"].release()
352
+ print(f"Frames with a matched face: {match_frames}", flush=True)
353
+ print(f"Saved: {OUTPUT_VIDEO}", flush=True)
354
+ ```
355
+
356
+ **Device targets:**
357
+
358
+ - `DEVICE = "GPU"` -- default in the sample code.
359
+ - `DEVICE = "CPU"` -- change `"GPU"` to `"CPU"`.
360
+ - `DEVICE = "NPU"` -- change `"GPU"` to `"NPU"`; use `batch-size=1` and `nireq=4` for best NPU utilization.
361
+
362
+ #### Expected Output
363
+
364
+ ![DLStreamer expected output](expected_output_dlstreamer.gif)
365
+
366
+ ---
367
+
368
+ ## License
369
+
370
+ Licensed under the MIT License. See [LICENSE](LICENSE) for details.
371
+
372
+ ## References
373
+
374
+ - [face-detection-adas-0001](https://docs.openvino.ai/2024/omz_models_model_face_detection_adas_0001.html)
375
+ - [face-reidentification-retail-0095](https://docs.openvino.ai/2024/omz_models_model_face_reidentification_retail_0095.html)
376
+ - [Open Model Zoo](https://github.com/openvinotoolkit/open_model_zoo)
377
+ - [OpenVINO Documentation](https://docs.openvino.ai/)
378
+ - [Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/index.html)
expected_output_dlstreamer.gif ADDED

Git LFS Details

  • SHA256: dee3365f39546addca8e7fd028fc756187926ade33b77a72860dc57080556aed
  • Pointer size: 133 Bytes
  • Size of remote file: 13.2 MB
expected_output_openvino.jpg ADDED
export_and_quantize.sh ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (C) Intel Corporation
4
+ #
5
+ # Download face detection and re-identification models from Open Model Zoo
6
+ # for the face-matching use case.
7
+ # Usage: ./export_and_quantize.sh
8
+
9
+ set -euo pipefail
10
+
11
+ # Official Open Model Zoo public model storage (versioned, immutable).
12
+ OMZ_BASE="https://storage.openvinotoolkit.org/repositories/open_model_zoo/2023.0/models_bin/1"
13
+
14
+ echo "--- Installing dependencies ---"
15
+ pip install -qU openvino opencv-python numpy
16
+
17
+ # Download both the IR topology (.xml) and weights (.bin) for an OMZ model
18
+ # from the official storage into intel/<model>/<precision>/.
19
+ download_omz_model() {
20
+ local model="$1"
21
+ local precision="$2"
22
+ local dest="intel/${model}/${precision}"
23
+ mkdir -p "${dest}"
24
+ local ext
25
+ for ext in xml bin; do
26
+ if [[ ! -f "${dest}/${model}.${ext}" ]]; then
27
+ wget -q -O "${dest}/${model}.${ext}" \
28
+ "${OMZ_BASE}/${model}/${precision}/${model}.${ext}"
29
+ fi
30
+ done
31
+ }
32
+
33
+ # Ask for approval before downloading models and sample files
34
+ echo ""
35
+ echo "This script will download:"
36
+ echo " - Model weights and/or sample files"
37
+ echo ""
38
+ read -p "Continue with downloads? (yes/no): " APPROVAL
39
+ if [[ "${APPROVAL}" != "yes" ]]; then
40
+ echo "Download cancelled by user."
41
+ exit 0
42
+ fi
43
+ echo ""
44
+
45
+ echo "--- Downloading face-detection-adas-0001 (FP16) ---"
46
+ download_omz_model face-detection-adas-0001 FP16
47
+ echo "Ready: face-detection-adas-0001"
48
+
49
+ echo "--- Downloading face-reidentification-retail-0095 (FP16) ---"
50
+ download_omz_model face-reidentification-retail-0095 FP16
51
+ echo "Ready: face-reidentification-retail-0095"
52
+
53
+ echo "--- Downloading sample test video ---"
54
+ if [[ ! -f test_video.mp4 ]]; then
55
+ wget -q -O test_video.mp4 \
56
+ "https://github.com/intel-iot-devkit/sample-videos/raw/master/face-demographics-walking-and-pause.mp4"
57
+ echo "Downloaded: test_video.mp4"
58
+ else
59
+ echo "Already present: test_video.mp4"
60
+ fi
61
+
62
+ echo "--- Capturing the reference face of the left subject from the video ---"
63
+ if [[ ! -f face_a.jpg ]]; then
64
+ python3 - <<'PY'
65
+ import cv2
66
+ import numpy as np
67
+ import openvino as ov
68
+
69
+ DET = "intel/face-detection-adas-0001/FP16/face-detection-adas-0001.xml"
70
+ core = ov.Core()
71
+ det = core.compile_model(core.read_model(DET), "CPU")
72
+ inp = det.input(0)
73
+ det_h, det_w = inp.shape[2], inp.shape[3]
74
+
75
+
76
+ def left_face_portrait(frame, thr=0.6):
77
+ """Return a head-and-shoulders crop of the left-most detected face."""
78
+ h, w = frame.shape[:2]
79
+ blob = cv2.resize(frame, (det_w, det_h))
80
+ blob = blob.transpose(2, 0, 1)[np.newaxis, ...].astype(np.float32)
81
+ out = det([blob])[det.output(0)][0][0]
82
+ boxes = [(int(d[3] * w), int(d[4] * h), int(d[5] * w), int(d[6] * h))
83
+ for d in out if float(d[2]) >= thr]
84
+ if not boxes:
85
+ return None
86
+ x1, y1, x2, y2 = min(boxes, key=lambda b: (b[0] + b[2]) / 2) # left-most
87
+ bw, bh = x2 - x1, y2 - y1
88
+ cx1, cx2 = max(0, int(x1 - 0.6 * bw)), min(w, int(x2 + 0.6 * bw))
89
+ cy1, cy2 = max(0, int(y1 - 0.7 * bh)), min(h, int(y2 + 1.6 * bh))
90
+ return frame[cy1:cy2, cx1:cx2]
91
+
92
+
93
+ # During the 37-42s segment a woman pauses on the left of the frame; capture
94
+ # a head-and-shoulders portrait of her as the reference face to search for.
95
+ cap = cv2.VideoCapture("test_video.mp4")
96
+ cap.set(cv2.CAP_PROP_POS_FRAMES, 468)
97
+ ok, frame = cap.read()
98
+ crop = left_face_portrait(frame) if ok else None
99
+ if crop is None or crop.size == 0:
100
+ raise SystemExit("Could not capture a reference face at frame 468")
101
+ cv2.imwrite("face_a.jpg", crop)
102
+ print("Captured face_a.jpg from frame 468")
103
+ cap.release()
104
+ PY
105
+ else
106
+ echo "Already present: face_a.jpg"
107
+ fi
108
+
109
+ echo "--- Done ---"
110
+ echo "Detector : intel/face-detection-adas-0001/FP16/face-detection-adas-0001.xml"
111
+ echo "ReID : intel/face-reidentification-retail-0095/FP16/face-reidentification-retail-0095.xml"
112
+ echo "Reference : face_a.jpg (left subject captured from the sample video)"
113
+ echo "Scene : test_video.mp4 (contains the reference subject plus others)"