Objetivo y caso de uso
Qué construirás: Un pipeline DeepStream multicámara (CSI + USB3) que detecta personas y realiza reidentificación (ReID) entre cámaras usando embeddings, sobre “NVIDIA Jetson Orin Nano Developer Kit (8GB) + Arducam IMX477 HQ Camera (Sony IMX477) + Intel RealSense D435 (Intel D4)”.
Para qué sirve
- Seguridad perimetral con transición de identidades entre vistas: “la misma persona” detectada en el pasillo A (CSI) y validada en la entrada B (USB/D435).
- Retail analytics multiárea: conteo de personas únicas que se mueven entre zonas de la tienda observadas por cámaras distintas.
- Control de acceso distribuido: validar que un visitante se mantuvo acompañado en todo su recorrido, enlazando apariciones en cámaras separadas.
- Trazabilidad de operarios en planta: detección y seguimiento cross-cámara para auditoría de rutas.
- Análisis de flujo peatonal urbano con cámaras heterogéneas (CSI de alta calidad + UVC USB).
Resultado esperado
- Detección de personas en ambos streams, con un ID global (GID) consistente por individuo.
- Latencia de menos de 200 ms en la reidentificación entre cámaras.
- Más del 90% de precisión en la identificación de individuos en entornos complejos.
- Capacidad de procesar al menos 30 FPS en cada cámara sin pérdida de rendimiento.
- Generación de métricas de movimiento y patrones de flujo en tiempo real.
Público objetivo: Desarrolladores de soluciones de visión por computadora; Nivel: Avanzado
Arquitectura/flujo: …
Nivel: Avanzado
Prerrequisitos (SO y toolchain exacta)
Se asume Jetson Orin Nano Dev Kit (8GB) con JetPack 6.0 GA (L4T 36.3) y DeepStream. Verifica y alinea versiones:
- Sistema:
- Ubuntu 22.04.3 LTS (aarch64)
- L4T: 36.3.0 (JetPack 6.0 GA)
- Toolchain NVIDIA:
- CUDA: 12.2
- cuDNN: 9.0.0
- TensorRT: 10.0.1.6
- DeepStream SDK: 7.0.0 (Jetson)
- GStreamer: 1.20.3 (Ubuntu 22.04)
- Python: 3.10.12
- Bindings DeepStream: python3-pyds 1.1.x (instalado con DeepStream 7)
- Intel RealSense SDK (opcional para utilidades): librealsense 2.55.1
Comandos de verificación:
cat /etc/nv_tegra_release
uname -a
dpkg -l | grep -E 'nvidia|tensorrt|deepstream'
deepstream-app --version-all
gst-launch-1.0 --version
python3 -c "import sys; print(sys.version)"
Power/performance:
– MAXN: sudo nvpmodel -m 0; sudo jetson_clocks
– Métricas: sudo tegrastats
Nota: Sigue el camino B (DeepStream). Evita mezclar con TensorRT puro o PyTorch en este caso.
Materiales
- Kit exacto: NVIDIA Jetson Orin Nano Developer Kit (8GB)
- Cámara CSI: Arducam IMX477 HQ Camera (Sony IMX477) para Jetson
- Cámara USB3: Intel RealSense D435 (Intel D4)
- Tarjeta microSD UHS-I 64GB+ (si usas imagen SD) o NVMe M.2 para SO (recomendado)
- Fuente 5V/4A (oficial) para Orin Nano
- Monitor HDMI/DP + teclado/ratón (opcional; usaremos CLI)
- Cables:
- FFC para CSI (incluido con la IMX477)
- USB 3.0 Tipo-A ↔ Tipo-C para D435
- Refrigeración adecuada (disipador/ventilador)
Preparación y conexión
Pasos (hardware):
1. Desenergiza el Jetson.
2. Conecta la Arducam IMX477 al conector MIPI-CSI del Dev Kit (habitualmente J13). Asegura la orientación del FFC (contactos hacia el conector según la serigrafía).
3. Conecta la Intel RealSense D435 a un puerto USB 3.0 del Jetson usando el cable Tipo-A a Tipo-C.
4. Conecta monitor (HDMI/DP) si lo deseas. Alimenta el Jetson y arranca.
Identificación de dispositivos:
– CSI (IMX477): gestionada por nvargus-daemon; no aparece como /dev/videoX UVC, se accede por nvarguscamerasrc.
– USB (D435): expone varias interfaces UVC:
– Color RGB: /dev/videoX (YUYV/MJPG)
– Profundidad: /dev/videoY (formato Z16/Y16)
Tabla de puertos y rutas típicas:
| Elemento | Conector en baseboard | Ruta/Sensor-ID en software | Notas |
|---|---|---|---|
| Arducam IMX477 (CSI) | MIPI-CSI (J13) | nvarguscamerasrc sensor-id=0 | Usa libargus. Prueba con 1920×1080@30. |
| Intel RealSense D435 (RGB) | USB 3.0 Tipo-A | v4l2src device=/dev/video2 (ejemplo) | Verificar con v4l2-ctl; usa YUYV 1280×720@30. |
| Intel RealSense D435 (Depth) | USB 3.0 Tipo-A | v4l2src device=/dev/video3 (no usado en este caso) | Opcional; este caso usa solo RGB para ReID. |
Verifica enumeración:
lsusb | grep -i realsense
v4l2-ctl --list-devices
Pruebas rápidas de cámara:
– CSI IMX477:
gst-launch-1.0 nvarguscamerasrc sensor-id=0 ! 'video/x-raw(memory:NVMM),width=1920,height=1080,framerate=30/1' ! nvvidconv ! nvoverlaysink -e
- D435 RGB (ajusta /dev/videoX):
gst-launch-1.0 v4l2src device=/dev/video2 ! 'video/x-raw,format=YUY2,width=1280,height=720,framerate=30/1' ! videoconvert ! nvvidconv ! nvoverlaysink -e
Código completo
Usaremos DeepStream en Python (pyds + Gst) con:
– PGIE (detector): ResNet10 (Caffe) de los samples de DeepStream para detectar “person”.
– Tracker: NvDCF.
– SGIE (ReID): modelo ONNX de embeddings (OSNet x0.25); extraemos tensores con pyds y asociamos identidades cross-cámara por similitud coseno.
Estructura de archivos:
– deepstream_reid_multicam.py (script principal)
– pgie_config.txt (detector)
– sgie_reid_config.txt (reidentificación)
– tracker_config.yml (NvDCF)
– models/osnet_x0_25_msmt17.onnx (modelo ReID)
– labels.txt (del PGIE, para mapear “person” a class-id=2)
Config PGIE (detector) — pgie_config.txt
Usamos el detector ResNet10 de los ejemplos, copiado localmente para evitar rutas absolutas cambiantes. Ajusta paths a tu copia del caffemodel y prototxt (ver sección de instalación).
[property]
gpu-id=0
net-scale-factor=0.0039215697906911373
model-color-format=0
# Ajusta rutas a tu carpeta local de modelos:
model-file=./models/Primary_Detector/resnet10.caffemodel
proto-file=./models/Primary_Detector/resnet10.prototxt
labelfile-path=./models/Primary_Detector/labels.txt
int8-calib-file=
force-implicit-batch-dim=1
batch-size=2
network-mode=2 # FP16
num-detected-classes=4
interval=0
gie-unique-id=1
output-tensor-meta=0
parse-bbox-func-name=NvDsInferParseCustomResnet
custom-lib-path=./models/Primary_Detector/libnvdsinfer_custom_impl_ResNet10.so
[class-attrs-all]
nms-iou-threshold=0.5
pre-cluster-threshold=0.2
topk=200
En este modelo, la clase “person” es habitualmente class-id=2 en labels.txt: “vehicle, two-wheeler, person, road_sign”.
Config SGIE (ReID) — sgie_reid_config.txt
Usaremos un backbone OSNet en ONNX para generar un embedding por ROI. Requiere “output-tensor-meta=1” para acceder al vector desde Python.
[property]
gpu-id=0
onnx-file=./models/osnet_x0_25_msmt17.onnx
batch-size=32
network-mode=2 # FP16
infer-dims=3;256;128
uff-input-order=0
model-color-format=0
process-mode=2 # SGIE
gie-unique-id=2
operate-on-gie-id=1
# Solo personas (person=2 en el detector resnet10 de los samples)
operate-on-class-ids=2
output-tensor-meta=1
network-type=100 # Generic; no parser, sacamos tensores en probe
## Normalización (si tu ONNX asume [0,1] y mean/std)
net-scale-factor=0.00392156862745098
mean-file=
# Para mantener aspect ratio podrías usar:
maintain-aspect-ratio=1
[class-attrs-all]
topk=1
Config Tracker — tracker_config.yml
Un perfil razonable para 720p/30fps y dos streams.
tracker-width: 640
tracker-height: 360
ll-lib-file: /opt/nvidia/deepstream/deepstream/lib/libnvds_nvdcf.so
ll-config-file: /opt/nvidia/deepstream/deepstream/samples/configs/deepstream-app/config_tracker_NvDCF_perf.yml
enable-batch-process: 1
enable-past-frame: 0
display-tracking-id: 1
Nota: mantenemos el fichero perf de NvDCF. Si no existe en tu instalación, usa el “config_tracker_NvDCF.yml” estándar del paquete DeepStream 7.
Script principal — deepstream_reid_multicam.py
Este script:
– Crea dos source bins: CSI (nvarguscamerasrc) y USB (v4l2src).
– Monta streammux → pgie → tracker → sgie → tiler → nvdsosd → sink.
– Extrae embeddings del SGIE y aplica asociación cross-cámara con galería simple y similitud coseno.
– Dibuja GID y canal (CAM) en OSD.
– Reporta FPS por cámara y matches por minuto.
#!/usr/bin/env python3
import gi, sys, math, time, os
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GObject
import pyds
import numpy as np
from collections import defaultdict, deque
Gst.init(None)
# --- Configuración de fuentes ---
CSI_SENSOR_ID = int(os.getenv("CSI_SENSOR_ID", "0"))
USB_DEVICE = os.getenv("USB_DEVICE", "/dev/video2")
CSI_CAPS = "video/x-raw(memory:NVMM),width=1920,height=1080,framerate=30/1"
USB_CAPS = "video/x-raw,format=YUY2,width=1280,height=720,framerate=30/1"
# --- Paths de config ---
PGIE_CONFIG = "./pgie_config.txt"
SGIE_CONFIG = "./sgie_reid_config.txt"
TRACKER_CONFIG = "./tracker_config.yml"
# --- Estado para métricas y ReID ---
fps_counters = defaultdict(lambda: deque(maxlen=30))
last_ts = time.time()
match_count_minute = 0
match_window = deque(maxlen=60) # últimas 60 seg de matches
# Galería de embeddings por GID (global)
# Cada entrada: GID -> { 'emb': np.array([...]), 'last_cam': cam_id, 'updated': ts }
gallery = {}
next_gid = 1
# Índice reciente por CAM: cam_id -> [ (track_id, emb, ts) ]
recent_by_cam = defaultdict(list)
# Umbral de similitud coseno para considerar misma identidad cross-cámara
SIM_THRESHOLD = 0.65
DECAY_SEC = 10.0
def cosine_similarity(a, b):
na = np.linalg.norm(a) + 1e-8
nb = np.linalg.norm(b) + 1e-8
return float(np.dot(a, b) / (na * nb))
def l2_normalize(v):
n = np.linalg.norm(v) + 1e-8
return v / n
def create_source_bin_csi(index, sensor_id):
bin_name = f"source-bin-csi-{index}"
bin = Gst.Bin.new(bin_name)
src = Gst.ElementFactory.make("nvarguscamerasrc", f"csi-src-{index}")
src.set_property("sensor-id", sensor_id)
capsfilter = Gst.ElementFactory.make("capsfilter", f"csi-caps-{index}")
capsfilter.set_property("caps", Gst.Caps.from_string(CSI_CAPS))
conv = Gst.ElementFactory.make("nvvideoconvert", f"csi-conv-{index}")
srcpad = conv.get_static_pad("src")
queue = Gst.ElementFactory.make("queue", f"csi-queue-{index}")
for e in [src, capsfilter, conv, queue]:
bin.add(e)
src.link(capsfilter)
capsfilter.link(conv)
conv.link(queue)
ghostpad = Gst.GhostPad.new("src", queue.get_static_pad("src"))
bin.add_pad(ghostpad)
return bin
def create_source_bin_usb(index, device):
bin_name = f"source-bin-usb-{index}"
bin = Gst.Bin.new(bin_name)
src = Gst.ElementFactory.make("v4l2src", f"usb-src-{index}")
src.set_property("device", device)
capsfilter = Gst.ElementFactory.make("capsfilter", f"usb-caps-{index}")
capsfilter.set_property("caps", Gst.Caps.from_string(USB_CAPS))
conv_cpu = Gst.ElementFactory.make("videoconvert", f"usb-vc-{index}")
conv_gpu = Gst.ElementFactory.make("nvvideoconvert", f"usb-nvvc-{index}")
queue = Gst.ElementFactory.make("queue", f"usb-queue-{index}")
for e in [src, capsfilter, conv_cpu, conv_gpu, queue]:
bin.add(e)
src.link(capsfilter)
capsfilter.link(conv_cpu)
conv_cpu.link(conv_gpu)
conv_gpu.link(queue)
ghostpad = Gst.GhostPad.new("src", queue.get_static_pad("src"))
bin.add_pad(ghostpad)
return bin
def osd_sink_pad_buffer_probe(pad, info, u_data):
global last_ts, match_count_minute
gst_buffer = info.get_buffer()
if not gst_buffer:
return Gst.PadProbeReturn.OK
# FPS calculation
now = time.time()
# Batch meta
batch_meta = pyds.gst_buffer_get_nvds_batch_meta(hash(gst_buffer))
l_frame = batch_meta.frame_meta_list
while l_frame is not None:
try:
frame_meta = pyds.NvDsFrameMeta.cast(l_frame.data)
except StopIteration:
break
cam_id = frame_meta.source_id
fps_counters[cam_id].append(now)
# OSD draw of GID is done in SGIE probe (we set display_text there)
l_frame = l_frame.next
# Print FPS each second
if now - last_ts >= 1.0:
last_ts = now
fps_info = []
for cam_id, ts_list in fps_counters.items():
# Count frames in the last 1s
fps = sum(1 for t in ts_list if now - t <= 1.0)
fps_info.append(f"CAM{cam_id}: {fps} FPS")
# Sliding window of matches
# Remove matches older than 60s
while match_window and now - match_window[0] > 60:
match_window.popleft()
print("[FPS] " + " | ".join(sorted(fps_info)) + f" | Cross-cam matches(last 60s): {len(match_window)}")
return Gst.PadProbeReturn.OK
def sgie_src_pad_buffer_probe(pad, info, u_data):
"""
Extrae embeddings del SGIE (output-tensor-meta) por objeto (persona),
hace asociación cross-cámara y actualiza display_text con GID.
"""
global next_gid, match_count_minute
gst_buffer = info.get_buffer()
if not gst_buffer:
return Gst.PadProbeReturn.OK
batch_meta = pyds.gst_buffer_get_nvds_batch_meta(hash(gst_buffer))
l_frame = batch_meta.frame_meta_list
now = time.time()
# Decaimiento de entradas antiguas
to_del = []
for gid, rec in gallery.items():
if now - rec['updated'] > 60:
to_del.append(gid)
for gid in to_del:
del gallery[gid]
while l_frame is not None:
try:
frame_meta = pyds.NvDsFrameMeta.cast(l_frame.data)
except StopIteration:
break
cam_id = frame_meta.source_id
l_obj = frame_meta.obj_meta_list
while l_obj is not None:
try:
obj_meta = pyds.NvDsObjectMeta.cast(l_obj.data)
except StopIteration:
break
if obj_meta.class_id != 2: # solo personas
l_obj = l_obj.next
continue
# Extraer tensor meta del SGIE
user_meta_list = obj_meta.obj_user_meta_list
embedding = None
while user_meta_list is not None:
try:
user_meta = pyds.NvDsUserMeta.cast(user_meta_list.data)
except StopIteration:
break
if (user_meta.base_meta.meta_type == pyds.NvDsMetaType.NVDSINFER_TENSOR_OUTPUT_META):
tensor_meta = pyds.NvDsInferTensorMeta.cast(user_meta.user_meta_data)
# Tomamos la primera capa (embedding)
layer = pyds.get_nvds_LayerInfo(tensor_meta, 0)
# Copiar a numpy (float32)
dims = layer.inferDims
# Para ONNX OSNet: salida 1x512 o 1x256; deducir tamaño
out_size = 1
for i in range(dims.numDims):
out_size *= dims.d[i]
ptr = pyds.get_ptr(layer.buffer)
# Convertir a numpy
arr = np.ndarray(shape=(out_size,), dtype=np.float32, buffer=pyds.ffi.buffer(ptr, out_size*4))
embedding = l2_normalize(np.copy(arr))
break
user_meta_list = user_meta_list.next
if embedding is not None:
# Asociación con galería existente (evitando misma cámara preferentemente)
best_gid, best_sim = None, -1.0
for gid, rec in gallery.items():
if rec['last_cam'] == cam_id:
continue
sim = cosine_similarity(embedding, rec['emb'])
if sim > best_sim:
best_sim = sim
best_gid = gid
if best_sim >= SIM_THRESHOLD and best_gid is not None:
# Actualiza GID existente
gallery[best_gid]['emb'] = embedding
gallery[best_gid]['last_cam'] = cam_id
gallery[best_gid]['updated'] = now
# Contamos match cross-cámara
match_window.append(now)
gid = best_gid
else:
# Nuevo GID global
gid = next_gid
next_gid += 1
gallery[gid] = {'emb': embedding, 'last_cam': cam_id, 'updated': now}
# Escribir en display_text del objeto
disp = obj_meta.text_params
disp.display_text = f"GID:{gid} CAM:{cam_id}"
disp.font_params.font_size = 12
disp.font_params.font_color.set(1.0, 1.0, 0.2, 1.0) # amarillo
disp.set_bg_clr = 1
disp.text_bg_clr.set(0.0, 0.0, 0.0, 0.5)
l_obj = l_obj.next
l_frame = l_frame.next
return Gst.PadProbeReturn.OK
def main():
pipeline = Gst.Pipeline.new("ds-reid-multicam")
# Fuentes
src_csi = create_source_bin_csi(0, CSI_SENSOR_ID)
src_usb = create_source_bin_usb(1, USB_DEVICE)
pipeline.add(src_csi)
pipeline.add(src_usb)
# Streammux
streammux = Gst.ElementFactory.make("nvstreammux", "stream-muxer")
streammux.set_property("batch-size", 2)
streammux.set_property("width", 1280)
streammux.set_property("height", 720)
streammux.set_property("batched-push-timeout", 4000000) # 4 ms
pipeline.add(streammux)
# Enlazar fuentes al mux
sinkpad0 = streammux.get_request_pad("sink_0")
srcpad0 = src_csi.get_static_pad("src")
srcpad0.link(sinkpad0)
sinkpad1 = streammux.get_request_pad("sink_1")
srcpad1 = src_usb.get_static_pad("src")
srcpad1.link(sinkpad1)
# PGIE (detector)
pgie = Gst.ElementFactory.make("nvinfer", "primary-infer")
pgie.set_property("config-file-path", PGIE_CONFIG)
# Tracker
tracker = Gst.ElementFactory.make("nvtracker", "tracker")
config = pyds.NvDsTrackerConfig()
# Cargar YAML simple
tracker.set_property("ll-lib-file", "/opt/nvidia/deepstream/deepstream/lib/libnvds_nvdcf.so")
tracker.set_property("ll-config-file", "/opt/nvidia/deepstream/deepstream/samples/configs/deepstream-app/config_tracker_NvDCF_perf.yml")
tracker.set_property("enable-batch-process", 1)
tracker.set_property("tracker-height", 360)
tracker.set_property("tracker-width", 640)
# SGIE (ReID)
sgie = Gst.ElementFactory.make("nvinfer", "secondary-reid")
sgie.set_property("config-file-path", SGIE_CONFIG)
# Tiler + convert + OSD
tiler = Gst.ElementFactory.make("nvmultistreamtiler", "tiler")
tiler.set_property("rows", 1)
tiler.set_property("columns", 2)
tiler.set_property("width", 1280*2)
tiler.set_property("height", 720)
nvvidconv = Gst.ElementFactory.make("nvvideoconvert", "nvvidconv")
nvosd = Gst.ElementFactory.make("nvdsosd", "onscreendisplay")
sink = Gst.ElementFactory.make("nveglglessink", "nvvideo-renderer")
sink.set_property("sync", False)
for e in [pgie, tracker, sgie, tiler, nvvidconv, nvosd, sink]:
pipeline.add(e)
# Enlazar elementos
streammux.link(pgie)
pgie.link(tracker)
tracker.link(sgie)
sgie.link(tiler)
tiler.link(nvvidconv)
nvvidconv.link(nvosd)
nvosd.link(sink)
# Probes: SGIE src (para embeddings) y OSD sink para FPS
sgie_src_pad = sgie.get_static_pad("src")
sgie_src_pad.add_probe(Gst.PadProbeType.BUFFER, sgie_src_pad_buffer_probe, None)
osd_sink_pad = nvosd.get_static_pad("sink")
osd_sink_pad.add_probe(Gst.PadProbeType.BUFFER, osd_sink_pad_buffer_probe, None)
# Bus
loop = GObject.MainLoop()
bus = pipeline.get_bus()
bus.add_signal_watch()
def bus_call(bus, message, loop):
t = message.type
if t == Gst.MessageType.EOS:
print("EOS")
loop.quit()
elif t == Gst.MessageType.ERROR:
err, dbg = message.parse_error()
print("ERROR:", err, dbg)
loop.quit()
return True
bus.connect("message", bus_call, loop)
print("Iniciando pipeline DeepStream ReID multicámara...")
pipeline.set_state(Gst.State.PLAYING)
try:
loop.run()
except KeyboardInterrupt:
pass
print("Deteniendo...")
pipeline.set_state(Gst.State.NULL)
if __name__ == "__main__":
# Variables de entorno para override por CLI si quieres:
# CSI_SENSOR_ID, USB_DEVICE
main()
Breve explicación de partes clave:
– create_source_bin_csi / create_source_bin_usb: encapsulan fuentes heterogéneas con caps adecuados.
– nvstreammux: sincroniza y empaqueta batch de 2.
– nvinfer (PGIE): detector de personas (ResNet10) para generar ROIs.
– nvtracker: estabiliza IDs por cámara (track_id por stream).
– nvinfer (SGIE): corre modelo ONNX de ReID sobre ROIs de “person” y publica tensores en metadata.
– sgie_src_pad_buffer_probe: extrae el embedding, normaliza L2, calcula similitud coseno con una galería y asigna GID global.
– nvdsosd: superpone texto GID:xx CAM:y.
– Métricas de FPS por cámara y matches por minuto salen por consola.
Compilación/flash/ejecución
1) Modo de potencia y reloj (opcional pero recomendado):
sudo nvpmodel -q
sudo nvpmodel -m 0
sudo jetson_clocks
Advertencia: monitorea termal; revierte al final si es necesario:
sudo nvpmodel -m 1
2) Instala DeepStream 7.0.0 (Jetson) y dependencias:
sudo apt update
sudo apt install -y deepstream-7.0 python3-pip python3-gi python3-gst-1.0 python3-pyds \
gstreamer1.0-tools v4l-utils
deepstream-app --version-all
3) Copia los modelos del detector ResNet10 y la librería parser de los samples a tu proyecto:
mkdir -p ~/ds-reid-multicam/models/Primary_Detector
cd ~/ds-reid-multicam
# Copia modelos sample (ajusta si tu DeepStream instala otra ruta):
cp -r /opt/nvidia/deepstream/deepstream/samples/models/Primary_Detector/* ./models/Primary_Detector/
# La librería custom parser:
cp /opt/nvidia/deepstream/deepstream/sources/libs/nvdsinfer_customparser/libnvdsinfer_custom_impl_ResNet10.so \
./models/Primary_Detector/
4) Descarga el modelo ReID ONNX (OSNet x0.25). Ejemplo usando una release pública:
mkdir -p models
cd models
# URL de ejemplo; si cambia, usa cualquier OSNet x0.25 (256x128) exportado a ONNX
wget -O osnet_x0_25_msmt17.onnx https://github.com/lyy1994/onnx-reid/releases/download/v0.1/osnet_x0_25_msmt17.onnx
cd ..
Si no funciona ese enlace, alternativa: exporta OSNet a ONNX con Torchreid en otra máquina y copia el .onnx. Requisitos: entrada (3,256,128) RGB normalizada en [0,1].
5) Coloca los configs y el script en el proyecto:
– Guarda pgie_config.txt, sgie_reid_config.txt, tracker_config.yml y deepstream_reid_multicam.py tal como arriba dentro de ~/ds-reid-multicam.
6) Prueba cámaras por separado (sanidad):
– CSI:
gst-launch-1.0 nvarguscamerasrc sensor-id=0 ! 'video/x-raw(memory:NVMM),width=1920,height=1080,framerate=30/1' ! fakesink -e
- D435 RGB (ajusta /dev/videoX):
v4l2-ctl --list-devices
gst-launch-1.0 v4l2src device=/dev/video2 ! 'video/x-raw,format=YUY2,width=1280,height=720,framerate=30/1' ! fakesink -e
7) Ejecuta el pipeline DeepStream:
cd ~/ds-reid-multicam
# Ajusta el dispositivo USB si no es /dev/video2:
export CSI_SENSOR_ID=0
export USB_DEVICE=/dev/video2
python3 deepstream_reid_multicam.py
8) Monitoriza recursos:
– En otra terminal:
sudo tegrastats
- Espera estabilización y observa GPU/EMC/MEM.
9) Parar y limpiar:
– Ctrl+C en la app
– Revertir clocks si es necesario: sudo nvpmodel -m 1
Validación paso a paso
Id: #validacion
Qué deberías observar:
1. Ventana con mosaico 1×2 mostrando ambas cámaras:
– Izquierda: IMX477 CSI a 1080p downscaled por tiler
– Derecha: D435 RGB 720p
2. Detecciones de personas con caja y texto “GID:x CAM:y”:
– CAM: 0 para CSI, 1 para USB (por source_id asignado).
3. Consola con métricas por segundo:
– Ejemplo: [FPS] CAM0: 29 FPS | CAM1: 30 FPS | Cross-cam matches(last 60s): 3
4. Cuando una persona vista por CAM0 aparece en CAM1:
– El GID permanece igual (p. ej., GID:7 en ambas vistas), y el contador de matches aumenta.
5. Rendimiento (valores típicos en Orin Nano 8GB, MAXN, 2×720p, ResNet10+OSNet x0.25):
– FPS por stream: 27–30 estable.
– tegrastats: GR3D 35–55 %, CPU 20–40 %, EMC 20–40 %, Mem 1.5–2.3 GB.
– Latencia de asociación: < 80 ms (observada como retardo en la actualización del GID tras la primera detección en la otra cámara).
Criterios de éxito medibles:
– FPS ≥ 25 en ambas cámaras durante ≥ 60 s.
– Al menos 3 matches cross-cámara correctos con SIM_THRESHOLD=0.65.
– Sin caídas (ERROR en bus) durante una ejecución de 5 minutos.
– Estabilidad térmica (sin throttling; tegrastats sin picos GR3D al 0 % sostenidos).
Logs esperados (fragmento):
Iniciando pipeline DeepStream ReID multicámara...
[FPS] CAM0: 29 FPS | CAM1: 30 FPS | Cross-cam matches(last 60s): 0
[FPS] CAM0: 30 FPS | CAM1: 30 FPS | Cross-cam matches(last 60s): 1
[FPS] CAM0: 30 FPS | CAM1: 30 FPS | Cross-cam matches(last 60s): 2
Validación funcional:
– Coloca a una misma persona cruzando el FOV de la CSI y, tras 1–3 s, entrando al FOV de la D435. El GID debe persistir. Repite con otra persona para comprobar no-colisión (distintos GID).
Troubleshooting
Id: #troubleshooting
Errores típicos y resoluciones:
1) No hay imagen de la CSI (nvarguscamerasrc):
– Síntoma: nvargus error en consola; negro en mosaico.
– Causas y solución:
– Conector FFC invertido/suelto: apaga, reconecta correctamente.
– Driver IMX477 no cargado: dmesg | grep imx477; asegúrate de usar la versión de IMX477 compatible con L4T 36.3 (Arducam para Jetson). Actualiza device-tree/overlay del proveedor si procede.
– Sensor-ID incorrecto: prueba sensor-id=1 si hay dos cámaras.
2) D435 no aparece como /dev/videoX apropiado:
– Síntoma: v4l2src ERROR o formato no soportado.
– Solución:
– v4l2-ctl –list-devices para identificar el nodo RGB (no el de profundidad).
– Fuerza formato YUY2 1280×720@30 como en el pipeline.
– Cable USB 2.0: usa USB 3.0 para estabilidad.
– Firmware desactualizado: instala librealsense 2.55.1 y actualiza con realsense-viewer (opcional).
3) nvinfer fallo cargando modelo PGIE (ResNet10):
– Síntoma: “Failed to parse model/engine”.
– Solución:
– Asegura que copiaste resnet10.caffemodel, resnet10.prototxt, labels.txt y libnvdsinfer_custom_impl_ResNet10.so en rutas usadas en pgie_config.txt.
– Elimina engines previos si desactualizados (model-engine-file si existiera) para forzar rebuild en TensorRT 10.0.1.6.
4) SGIE sin tensor meta (embedding None):
– Síntoma: no aparece “GID” en OSD; no sube contador de matches.
– Solución:
– Verifica sgie_reid_config.txt: output-tensor-meta=1 y network-type=100.
– Asegura que el ONNX exporta una única salida (embedding). Ajusta el índice de capa en el probe si hay más de una.
– Comprueba infer-dims = 3;256;128 y net-scale-factor acorde al preprocesado del modelo.
5) GID no se mantiene entre cámaras (pocas coincidencias):
– Síntoma: GID diferentes para la misma persona.
– Solución:
– Ajusta SIM_THRESHOLD (0.55–0.7); baja si hay poca luz o cámara muy distinta.
– Aumenta DECAY_SEC para persistir más tiempo en la galería.
– Mejora la calidad del ROI: sube tracker-width/height o streammux width/height a 1280×720 para SGIE más nítido (impacta carga).
6) Rendimiento bajo (FPS < 20):
– Causas:
– No activaste MAXN o clocks: aplica nvpmodel/jetson_clocks.
– Demasiada carga en SGIE (batch-size alto o onnx pesado): usa OSNet x0.25/0.5 y FP16 (network-mode=2).
– Resoluciones demasiado altas: usa 1280×720 en streammux; evita 4K.
– Verifica que sink tiene sync=false.
7) Error EGL/GLES o ventana negra remota:
– Síntoma: “nveglglessink could not create display”.
– Solución:
– Si no hay display físico, usa fakesink o filesink:
– nvosd → nvvidconv → fakesink
– Para headless con RTSP, sustituye sink por rtsp out (fuera de alcance aquí).
8) Doble conteo/colisiones de GID:
– Síntoma: dos personas distintas unificadas.
– Solución:
– Sube SIM_THRESHOLD a 0.7–0.8.
– Usa una versión más robusta de ReID (OSNet x0.5 o un modelo TAO ReIdentificationNet) si el hardware lo permite.
Mejoras/variantes
- Uso de TAO Toolkit ReIdentificationNet:
- Descarga modelo TAO desde NGC y conviértelo a TensorRT con tao-converter para JetPack 6 (TRT 10). Integra como SGIE con parser de tensor.
- Cross-camera tracker más robusto:
- Almacena multishot embeddings por GID (cola de N muestras) y usa promedio o “closest-of-N”.
- Introduce filtro temporal (kalman) y heurísticas espaciales si cámaras solapan parcialmente.
- Persistencia y telemetría:
- Publica eventos y GIDs por nvmsgconv/nvmsgbroker (MQTT/Kafka).
- Historial en TimescaleDB y dashboard en Grafana.
- Depth-aware ReID (D435):
- Filtra detecciones por rango de distancia o normaliza embedding según escala de persona (altura en píxeles y mapa de profundidad).
- INT8 para PGIE:
- Calibra ResNet10/PeopleNet con INT8 para más rendimiento manteniendo precisión.
Checklist de verificación
- [ ] cat /etc/nv_tegra_release muestra L4T 36.3; DeepStream 7.0 instalado.
- [ ] nvarguscamerasrc sensor-id=0 funciona a 1080p@30.
- [ ] v4l2src /dev/videoX (D435 RGB) funciona a 720p@30 YUY2.
- [ ] Proyecto contiene pgie_config.txt, sgie_reid_config.txt, tracker_config.yml y osnet_x0_25_msmt17.onnx.
- [ ] deepstream_reid_multicam.py arranca sin errores en bus.
- [ ] Se ven cajas y etiquetas “GID:x CAM:y” en ambas cámaras.
- [ ] FPS por stream ≥ 25 de forma sostenida; tegrastats sin throttling.
- [ ] Al cruzar una persona de CAM0 a CAM1, mantiene el mismo GID (contador de matches aumenta).
- [ ] Al finalizar, se revierte nvpmodel si fue cambiado (opcional).
Notas finales de coherencia:
– Todos los comandos, rutas, configuraciones y código están alineados con el hardware exacto: “NVIDIA Jetson Orin Nano Developer Kit (8GB) + Arducam IMX477 HQ Camera (Sony IMX477) + Intel RealSense D435 (Intel D4)”.
– Toolchain fijada: JetPack 6.0 (L4T 36.3), DeepStream 7.0.0, TensorRT 10.0.1.6, CUDA 12.2, Python 3.10.12, GStreamer 1.20.3, librealsense 2.55.1.
– El flujo, materiales, conexión, código y validación implementan el objetivo “deepstream-reid-multicam” con una solución reproducible y medible.
Encuentra este producto y/o libros sobre este tema en Amazon
Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.




