Objetivo y caso de uso
Qué construirás: Un sistema de inspección en línea con NVIDIA Jetson Orin Nano que detecta defectos sobre una cinta transportadora usando DeepStream y acciona un expulsor por servo (PCA9685) cuando se detecta un defecto en el punto de expulsión.
Para qué sirve
- Inspección de productos en tiempo real en una línea de ensamblaje (por ejemplo, botellas con tapón mal cerrado o etiqueta desprendida).
- Clasificación automática OK/DEFECT y expulsión mediante un actuador en una banda transportadora.
- Detección de anomalías visuales (p. ej., manchas rojas en una superficie blanca) en empaques o piezas.
- Control de calidad no destructivo para productos de consumo desplazándose a 0.3–1.0 m/s.
Resultado esperado
- Tasa de procesamiento sostenida ≥ 25 FPS con la cámara CSI IMX477 a 1280×720.
- Latencia de decisión (captura → decisión → señal a servo) < 120 ms en promedio.
- Precisión en la zona de inspección basada en umbral de color: tasa de falsos positivos < 5% tras calibración del umbral HSV y ROI.
- Activación del expulsor (servo) sincronizada con la detección de defectos.
Público objetivo: Ingenieros de automatización; Nivel: Avanzado
Arquitectura/flujo: Captura de imagen → procesamiento en Jetson Orin Nano → decisión de expulsión → activación de servo.
Nivel: Avanzado
Prerrequisitos (SO, SDK y toolchain exactos)
- Hardware: NVIDIA Jetson Orin Nano Developer Kit.
- Sistema operativo base: Ubuntu 20.04 LTS (L4T).
- JetPack (L4T) y dependencias:
- JetPack 5.1.2 (L4T R35.4.1).
- CUDA 11.4.
- cuDNN 8.6.
- TensorRT 8.5.x (instalado con JetPack 5.1.2).
- DeepStream SDK:
- DeepStream 6.2 para Jetson.
- Python:
- Python 3.8 (por defecto en Ubuntu 20.04).
- Bindings de DeepStream para Python (pyds).
- Librerías de cámara y I/O:
- GStreamer 1.16+ (incluido).
- Adafruit Blinka y adafruit-circuitpython-pca9685 (para controlar el PCA9685 sobre I2C).
Verificación inicial de versiones en tu Jetson Orin Nano:
# Identificar JetPack/L4T
cat /etc/nv_tegra_release
# Ejemplo esperado: # R35 (release), REVISION: 4.1, GCID: ..., BOARD: t186ref, EABI: aarch64, DATE: ...
# Kernel y paquetes NVIDIA
uname -a
dpkg -l | grep -E 'nvidia|tensorrt|deepstream'
# DeepStream (tras la instalación, ver sección de instalación)
deepstream-app --version-all
Notas:
– Este caso práctico asume JetPack 5.1.2 (L4T 35.4.1) y DeepStream 6.2, combinación estable y ampliamente utilizada en Jetson Orin Nano.
– Si tu placa está con JetPack 6.x, ajusta rutas y versiones de DeepStream a la correspondiente (por ejemplo, 6.3). El flujo y el código no cambian sustancialmente.
Materiales
- Dispositivo principal:
- NVIDIA Jetson Orin Nano Developer Kit.
- Cámara:
- Arducam IMX477 MIPI Camera (Sony IMX477), interfaz CSI-2.
- Actuación/Expulsión:
- Adafruit 16-Channel PWM Driver (PCA9685).
- Un microservo estándar (p. ej., SG90 o similar) o solenoide con driver apropiado, alimentado a 5–6 V (V+ del PCA9685).
- Cables y alimentación:
- Cables jumper dupont para 40 pines (SDA/SCL/3V3/GND y el canal PWM del servo).
- Fuente 5–6 V independiente para V+ del PCA9685 si vas a mover servos/solenoides. GND común con Jetson.
- Soporte mecánico:
- Cinta transportadora de prueba (banda pequeña o simulada), un “punto de expulsión” ubicado a una distancia conocida desde la zona de inspección.
- Acceso remoto (opcional):
- SSH habilitado y red local.
Preparación y conexión
Conexiones físicas
- Cámara IMX477:
- Conectar el cable plano MIPI CSI-2 al puerto CAM0 (o CAM1) del Jetson Orin Nano Developer Kit, asegurando la orientación correcta y el bloqueo del conector.
-
Usaremos sensor_id=0 (CAM0). Si la cámara se conectó a CAM1, cambia a sensor_id=1 en los comandos.
-
PCA9685 (I2C y potencia):
- Tabla de conexión (GPIO header de 40 pines del Jetson Orin Nano):
| Componente | Señal | Jetson Orin Nano (Header) | PCA9685 | Notas |
|---|---|---|---|---|
| I2C | SDA | Pin 3 (I2C-1 SDA) | SDA | Bus /dev/i2c-1 |
| I2C | SCL | Pin 5 (I2C-1 SCL) | SCL | Dirección por defecto 0x40 |
| Lógica | 3V3 | Pin 1 | VCC | Alimenta la lógica del PCA9685 |
| GND | GND | Pin 6 (o 9/14/20/25/30/34/39) | GND | Tierra común |
| Servo | PWM | Canal 0 (por ejemplo) | CH0 PWM + GND | V+ a fuente 5–6 V independiente |
| Potencia servos | 5–6 V | Fuente externa | V+ | GND de fuente unido a GND Jetson |
- Asegúrate de:
- No alimentar V+ del PCA9685 desde el Jetson; usa una fuente 5–6 V separada para los actuadores.
- Unir GND del Jetson y GND de la fuente externa del PCA9685.
Preparación del sistema
1) Actualizar e instalar dependencias base:
sudo apt update
sudo apt -y upgrade
sudo apt -y install python3-pip python3-venv git i2c-tools v4l-utils gstreamer1.0-tools
2) Habilitar I2C y permisos:
# Verificar bus I2C-1
ls /dev/i2c-1
# Usuario en grupo i2c
sudo usermod -aG i2c $USER
# Probar descubrimiento PCA9685 (0x40)
sudo i2cdetect -y -r 1
# Deberías ver 40 en la tabla
3) Instalar DeepStream 6.2 (si no está ya instalado):
# Repositorio NVIDIA para DeepStream 6.2
sudo apt -y install \
deepstream-6.2 \
deepstream-6.2-dev \
deepstream-6.2-doc
# Verificar
/opt/nvidia/deepstream/deepstream-6.2/sources/deepstream_python_apps/deepstream_python_apps_install.sh
# El script anterior instala dependencias para las apps Python, incluidas pyds ruedas precompiladas.
4) Instalar librerías Python adicionales:
python3 -m pip install --upgrade pip wheel
python3 -m pip install opencv-python==4.7.0.72 numpy==1.23.5
python3 -m pip install adafruit-blinka==8.23.1 adafruit-circuitpython-pca9685==3.4.7
5) Probar la cámara IMX477 con GStreamer:
# Vista previa rápida (Esc para cerrar si usas una terminal con X/Wayland)
gst-launch-1.0 nvarguscamerasrc sensor_id=0 ! \
'video/x-raw(memory:NVMM),width=1280,height=720,framerate=30/1,format=NV12' ! \
nvvideoconvert ! nveglglessink -e
Si la cámara no abre, revisa Troubleshooting.
Código completo
Objetivo del código: Pipeline DeepStream en Python usando la cámara IMX477, inferencia ligera (Primary Detector con modelo de ejemplo de DeepStream), análisis de una ROI en HSV para detectar anomalías rojas (simularemos “DEFECT” como presencia de parches rojos significativos) y activación del expulsor (servo en PCA9685) sincronizado con la posición del objeto.
Puntos clave:
– Pipeline GStreamer: nvarguscamerasrc → nvvideoconvert → nvinfer (PGIE) → nvdsosd → nveglglessink.
– Hook de pad-probe para:
– Leer NvBufSurface y extraer ROI.
– Calcular proporción de píxeles rojos en ROI (umbral HSV).
– Añadir un metadato/OSD “OK” o “DEFECT” y, si es “DEFECT”, planificar activación de expulsor.
– Control PCA9685:
– I2C en /dev/i2c-1, dirección 0x40.
– Frecuencia 50 Hz para servo.
– Pulsos en µs convertidos a valores de 12 bits.
Ajusta los parámetros ROI, umbral y cinemática de la banda (velocidad y distancia a expulsión) a tu montaje.
#!/usr/bin/env python3
# deepstream_conveyor_defect_detection.py
# Modelo de dispositivo exacto: NVIDIA Jetson Orin Nano Developer Kit + Arducam IMX477 MIPI Camera (Sony IMX477) + Adafruit 16-Channel PWM Driver (PCA9685)
# Toolchain: JetPack 5.1.2 (L4T 35.4.1), DeepStream 6.2, TensorRT 8.5.x, CUDA 11.4, Python 3.8
import sys
import os
import gi
gi.require_version('Gst', '1.0')
gi.require_version('GstBase', '1.0')
from gi.repository import Gst, GObject
import pyds
import numpy as np
import cv2
import threading
import time
from collections import deque
# I2C + PCA9685 (Adafruit)
import board
import busio
from adafruit_pca9685 import PCA9685
# -----------------------------
# Parámetros de hardware/sistema
# -----------------------------
SENSOR_ID = 0 # IMX477 en CAM0
DISPLAY = True # Si no tienes display, usa fakesink
INFER_CONFIG = "/opt/nvidia/deepstream/deepstream-6.2/samples/configs/deepstream-app/source1_primary_detector.txt"
# -----------------------------
# Parámetros del expulsor
# -----------------------------
I2C_ADDRESS = 0x40
PWM_CHANNEL = 0 # Canal 0 del PCA9685
PWM_FREQ_HZ = 50 # Frecuencia servo
SERVO_MIN_US = 500 # microsegundos (ángulo mínimo)
SERVO_MAX_US = 2500 # microsegundos (ángulo máximo)
SERVO_NEUTRAL_US = 1500 # microsegundos (reposo)
SERVO_KICK_US = 2000 # microsegundos (expulsión)
KICK_MS = 220 # duración del pulso de expulsión
# Cinemática cinta
BELT_SPEED_MPS = 0.40 # m/s
DIST_CAM_TO_KICK_M = 0.25 # metros desde ROI a expulsor
PROC_LATENCY_MS = 60 # latencia estimada pipeline
# Tiempo de vuelo aproximado
TOF_MS = int(1000.0 * DIST_CAM_TO_KICK_M / max(0.05, BELT_SPEED_MPS)) - PROC_LATENCY_MS
TOF_MS = max(0, TOF_MS)
# -----------------------------
# ROI & umbrales de defecto
# -----------------------------
# ROI en porcentajes de la imagen (x0, y0, x1, y1) relativos a [0,1]
ROI = (0.35, 0.55, 0.65, 0.85)
# Detección de "rojo" en HSV (dos rangos por wrap-around del tono)
LOWER_RED1 = np.array([0, 90, 50], dtype=np.uint8)
UPPER_RED1 = np.array([10, 255, 255], dtype=np.uint8)
LOWER_RED2 = np.array([170, 90, 50], dtype=np.uint8)
UPPER_RED2 = np.array([180, 255, 255], dtype=np.uint8)
RED_RATIO_THRESHOLD = 0.06 # 6% de pixeles rojos en ROI => DEFECT
# -----------------------------
# PCA9685 Helper
# -----------------------------
def us_to_pwm12bits(us, freq_hz=PWM_FREQ_HZ):
period_us = 1_000_000.0 / freq_hz
val = int(4096 * (us / period_us))
return np.clip(val, 0, 4095)
class Ejector:
def __init__(self):
self.i2c = busio.I2C(board.SCL, board.SDA)
self.pca = PCA9685(self.i2c, address=I2C_ADDRESS)
self.pca.frequency = PWM_FREQ_HZ
self.lock = threading.Lock()
self.last_kick = 0.0
def set_us(self, channel, us):
on = 0
off = us_to_pwm12bits(us, self.pca.frequency)
self.pca.channels[channel].duty_cycle = off # Adafruit lib usa 16 bits, internamente maneja
# Nota: La lib abstrae on/off de 12b, aquí usamos duty_cycle 16b proporcional.
def neutral(self):
self.set_us(PWM_CHANNEL, SERVO_NEUTRAL_US)
def kick(self, duration_ms=KICK_MS):
with self.lock:
t = time.time()
# Anti re-disparo demasiado frecuente
if (t - self.last_kick) < 0.2:
return
self.last_kick = t
self.set_us(PWM_CHANNEL, SERVO_KICK_US)
time.sleep(duration_ms / 1000.0)
self.neutral()
# Cola de eventos de expulsión (tiempo futuro en epoch s)
eject_queue = deque()
ejector = None
def eject_worker():
while True:
now = time.time()
if eject_queue and eject_queue[0][0] <= now:
_, reason = eject_queue.popleft()
print(f"[EJECT] Activando expulsor: {reason}")
try:
ejector.kick()
except Exception as e:
print(f"[EJECT][ERROR] {e}")
time.sleep(0.005)
# -----------------------------
# DeepStream helpers
# -----------------------------
def make_element(factory, name=None, **props):
el = Gst.ElementFactory.make(factory, name if name else factory)
if not el:
raise RuntimeError(f"Fallo al crear elemento {factory}")
for k, v in props.items():
el.set_property(k, v)
return el
def osd_sink_pad_buffer_probe(pad, info, u_data):
# Extrae lote e itera frames
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
while l_frame:
try:
frame_meta = pyds.NvDsFrameMeta.cast(l_frame.data)
except StopIteration:
break
# Obtener superficie de imagen del frame
n_frame = pyds.get_nvds_buf_surface(hash(gst_buffer), frame_meta.batch_id)
# NvBufSurface => numpy (H, W, 4) RGBA
frame_rgba = np.array(n_frame, copy=False, dtype=np.uint8)
h, w, _ = frame_rgba.shape
# ROI absoluta
x0 = int(ROI[0] * w); y0 = int(ROI[1] * h)
x1 = int(ROI[2] * w); y1 = int(ROI[3] * h)
roi = frame_rgba[y0:y1, x0:x1, :3] # ignorar alfa
# HSV y máscara de rojo
roi_hsv = cv2.cvtColor(roi, cv2.COLOR_RGB2HSV)
mask1 = cv2.inRange(roi_hsv, LOWER_RED1, UPPER_RED1)
mask2 = cv2.inRange(roi_hsv, LOWER_RED2, UPPER_RED2)
mask = cv2.bitwise_or(mask1, mask2)
red_ratio = float(np.count_nonzero(mask)) / max(1, mask.size)
is_defect = red_ratio >= RED_RATIO_THRESHOLD
# OSD: dibujar ROI y texto
display_meta = pyds.nvds_acquire_display_meta_from_pool(batch_meta)
display_meta.num_rects = 1
rect_params = display_meta.rect_params[0]
rect_params.left = x0; rect_params.top = y0
rect_params.width = (x1 - x0); rect_params.height = (y1 - y0)
rect_params.border_width = 3
rect_params.border_color.set(1.0, 1.0, 0.0, 1.0) # amarillo
text_params = display_meta.text_params[0]
text_params.display_text = f"{'DEFECT' if is_defect else 'OK'} | red_ratio={red_ratio:.3f}"
text_params.x_offset = x0
text_params.y_offset = y0 - 10 if y0 > 20 else y0 + 20
text_params.font_params.font_name = "Serif"
text_params.font_params.font_size = 16
if is_defect:
text_params.font_params.font_color.set(1.0, 0.0, 0.0, 1.0) # rojo
else:
text_params.font_params.font_color.set(0.0, 1.0, 0.0, 1.0) # verde
text_params.set_bg_clr = 1
text_params.text_bg_clr.set(0.0, 0.0, 0.0, 0.4)
pyds.nvds_add_display_meta_to_frame(frame_meta, display_meta)
# Programar expulsión si DEFECT
if is_defect and TOF_MS >= 0:
t_fire = time.time() + TOF_MS / 1000.0
eject_queue.append((t_fire, f"ratio={red_ratio:.3f}"))
# Opción: marcar visualmente evento pendiente
# (ya reflejado en texto OSD)
try:
l_frame = l_frame.next
except StopIteration:
break
return Gst.PadProbeReturn.OK
def main():
global ejector
# Inicialización GStreamer/GLib
Gst.init(None)
loop = GObject.MainLoop()
# Elementos pipeline
pipeline = Gst.Pipeline()
src = make_element("nvarguscamerasrc", "src", sensor_id=SENSOR_ID)
caps = make_element("capsfilter", "caps")
caps.set_property("caps", Gst.Caps.from_string(
"video/x-raw(memory:NVMM), width=1280, height=720, framerate=30/1, format=NV12"
))
conv = make_element("nvvideoconvert", "conv")
pgie = make_element("nvinfer", "primary-infer")
# Config de ejemplo (resnet10 detector), proporciona metadata pero no es el núcleo de la decisión
if not os.path.exists(INFER_CONFIG):
raise FileNotFoundError(f"No se encuentra {INFER_CONFIG}")
pgie.set_property("config-file-path", INFER_CONFIG)
pgie.set_property("batch-size", 1)
nvosd = make_element("nvdsosd", "osd")
if DISPLAY:
sink = make_element("nveglglessink", "sink", sync=False)
else:
sink = make_element("fakesink", "sink")
for el in [src, caps, conv, pgie, nvosd, sink]:
pipeline.add(el)
if not Gst.Element.link_many(src, caps, conv, pgie, nvosd, sink):
raise RuntimeError("Error al enlazar elementos")
# Probe para análisis de ROI + expulsión
osd_sink_pad = nvosd.get_static_pad("sink")
if not osd_sink_pad:
raise RuntimeError("No se pudo obtener sink pad de nvdsosd")
osd_sink_pad.add_probe(Gst.PadProbeType.BUFFER, osd_sink_pad_buffer_probe, 0)
# Iniciar expulsor (PCA9685) y thread
ejector = Ejector()
ejector.neutral()
thr = threading.Thread(target=eject_worker, daemon=True)
thr.start()
# Señales
def bus_call(bus, message, loop):
t = message.type
if t == Gst.MessageType.EOS:
print("EOS recibido")
loop.quit()
elif t == Gst.MessageType.ERROR:
err, debug = message.parse_error()
print(f"Error: {err}, debug: {debug}")
loop.quit()
return True
bus = pipeline.get_bus()
bus.add_signal_watch()
bus.connect("message", bus_call, loop)
# Ejecutar
print(f"Iniciando pipeline. TOF_MS={TOF_MS} ms, ROI={ROI}, Threshold red={RED_RATIO_THRESHOLD}")
pipeline.set_state(Gst.State.PLAYING)
try:
loop.run()
except KeyboardInterrupt:
pass
finally:
pipeline.set_state(Gst.State.NULL)
ejector.neutral()
print("Finalizado.")
if __name__ == "__main__":
main()
Explicación breve de partes clave:
– Extracción de NvBufSurface con pyds.get_nvds_buf_surface: accedemos a la imagen RGBA en CPU para análisis de color. Es rápido y suficiente para 720p a 30 FPS en Orin Nano.
– Doble umbral HSV para rojo: el tono rojo cruza 0/180, por eso hay dos rangos (0–10 y 170–180).
– Programación de expulsión: en lugar de accionar el servo inmediatamente, calculamos TOF_MS según velocidad de la banda y distancia desde la ROI al expulsor. Añadimos el evento a una cola y un hilo lo ejecuta justo a tiempo.
– nvinfer: usamos el modelo de detector primario de ejemplo de DeepStream para mantener una ruta acelerada por GPU y disponer de metadatos/OSD. La decisión de defecto la hemos centrado en una regla visual (rojo en ROI), típica para defectos cromáticos en líneas de producción, y que sirve como “stand-in” de un clasificador OK/DEFECT. Para producción, reemplaza esta parte por un nvinfer con tu modelo ONNX/TensorRT binario (OK/DEFECT).
Compilación/flash/ejecución
No hay compilación C++; todo es Python con GStreamer/DeepStream. Pasos:
1) Poner el Jetson en modo de máximo rendimiento (opcional, vigila termals):
# Consultar modo actual
sudo nvpmodel -q
# MAXN (modo 0 en Orin Nano Dev Kit)
sudo nvpmodel -m 0
# Bloquear clocks
sudo jetson_clocks
2) Métricas en vivo (en otra terminal):
sudo tegrastats
3) Probar DeepStream nativo con cámara (opcional, para verificar stack):
/opt/nvidia/deepstream/deepstream-6.2/samples/configs/deepstream-app/deepstream-app -c \
/opt/nvidia/deepstream/deepstream-6.2/samples/configs/deepstream-app/source1_primary_detector.txt
4) Ejecutar el caso práctico:
# Clonar o copiar el script
nano deepstream_conveyor_defect_detection.py # pega el código anterior
# Ejecutar
python3 deepstream_conveyor_defect_detection.py
5) Finalizar y revertir clocks (si lo deseas):
# Parar tegrastats con Ctrl+C
# Revertir a modo por defecto (por ejemplo, modo 1 moderado)
sudo nvpmodel -m 1
Validación paso a paso
1) Verificación de hardware:
– Cámara: Ejecuta el pipeline gst-launch-1.0 (sección Preparación). Debes ver vídeo estable a 30 FPS, latencia baja.
– I2C/PCA9685: i2cdetect debe mostrar 0x40. Conecta temporalmente el servo y prueba un pulso corto con un script mínimo:
# test_pca9685_servo.py
import time, board, busio
from adafruit_pca9685 import PCA9685
i2c = busio.I2C(board.SCL, board.SDA)
pca = PCA9685(i2c, address=0x40)
pca.frequency = 50
def set_us(ch, us):
period_us = 1_000_000/50
duty = int(65535 * (us/period_us))
pca.channels[ch].duty_cycle = duty
ch=0
set_us(ch,1500); time.sleep(1)
set_us(ch,2000); time.sleep(0.4)
set_us(ch,1500)
- El servo debe moverse y volver a neutral.
2) Métricas de rendimiento:
– Con el script principal en ejecución y tegrastats abierto:
– FPS esperado: ~30 (720p).
– CPU: 10–30% total.
– GPU (GR3D%): 20–50% dependiendo del OSD y conversión.
– EMC (memoria) estable, sin throttling.
– Si FPS < 20, revisa nvpmodel/jetson_clocks y que no haya sobrecalentamiento.
3) Señales visibles en pantalla:
– Un rectángulo amarillo delimitando la ROI.
– Texto OSD: “OK | red_ratio=0.xxx” o “DEFECT | red_ratio=0.xxx”.
– Coloca un objeto con una zona roja dentro de la ROI (por ejemplo, una etiqueta roja). El OSD debe cambiar a “DEFECT” cuando el porcentaje supere el umbral (0.06 por defecto).
4) Activación del expulsor:
– Mide la distancia desde el borde inferior de la ROI al punto del expulsor (DIST_CAM_TO_KICK_M).
– A una velocidad de 0.40 m/s, el tiempo de vuelo típico con 0.25 m es ~625 ms. Con PROC_LATENCY_MS=60 ms, el script programará el kick ~565 ms después del frame que vio el defecto.
– Observa en consola: líneas “[EJECT] Activando expulsor: ratio=0.xxx” aproximadamente TOF_MS después de ver el cambio a DEFECT. El servo debe moverse justo cuando el objeto alcance el expulsor.
– Ajusta BELT_SPEED_MPS y DIST_CAM_TO_KICK_M hasta que la expulsión ocurra justo a tiempo.
5) Criterios de éxito cuantificables:
– ≥ 25 FPS, latencia percibida baja.
– Errores de sincronización de expulsión < ±2 cm respecto a la posición deseada a la velocidad configurada.
– Estabilidad: sin pérdidas de frames prolongadas ni errores de GStreamer durante 10 minutos continuos.
6) Limpieza:
– Ctrl+C para cerrar el script.
– Apaga jetson_clocks o reduce nvpmodel si no estás midiendo rendimiento.
Troubleshooting
1) La cámara IMX477 no abre (nvarguscamerasrc ERROR):
– Asegura conexión correcta (flex bien asentado) y sensor_id adecuado (0 para CAM0, 1 para CAM1).
– Reinicia el servicio del stack Argus:
– sudo systemctl restart nvargus-daemon
– Verifica con v4l2-ctl –list-devices y carga de módulos. Si usas cable muy largo o doblado, prueba otro cable FFC.
2) deepstream-app o pyds no encontrado:
– Verifica instalación: ls /opt/nvidia/deepstream/deepstream-6.2
– Reinstala paquetes: sudo apt install deepstream-6.2 deepstream-6.2-dev
– Ejecuta el instalador de Python apps: /opt/nvidia/deepstream/deepstream-6.2/sources/deepstream_python_apps/deepstream_python_apps_install.sh
3) PCA9685 no responde (i2cdetect no ve 0x40):
– Revisa cableado SDA/SCL (pines 3/5), VCC (3V3), GND.
– Asegura pertenencia al grupo i2c (id -nG) y reinicia sesión.
– Si hay otro dispositivo en la misma dirección, cambia jumpers A0–A5 en PCA9685 (dirección base 0x40–0x7F).
4) Servo vibra o no se mueve:
– Fuente insuficiente: alimenta V+ con 5–6 V y 1–2 A según servo/solenoide.
– Une GND de la fuente y GND del Jetson.
– Ajusta SERVO_MIN_US/SERVO_MAX_US al rango de tu servo.
5) FPS bajos o lag:
– Habilita modo máximo rendimiento: sudo nvpmodel -m 0; sudo jetson_clocks.
– Reduce resolución a 960×540 cambiando capsfilter en el script.
– Cierra otras apps gráficas. Verifica throttling térmico (temperatura).
6) El OSD no muestra texto/ROI:
– Comprueba que el pad-probe esté en el sink de nvdsosd.
– Revisa dependencias de fuentes/OSD. Cambia font_params.font_name.
7) Expulsiones desincronizadas:
– Calibra BELT_SPEED_MPS con una marca y cronómetro (tiempo para recorrer una distancia conocida).
– Mide DIST_CAM_TO_KICK_M desde la línea de ROI exacta hasta el punto de contacto del expulsor.
– Ajusta PROC_LATENCY_MS si usas displays o si tegrastats muestra carga alta.
8) Error al abrir INFER_CONFIG:
– Verifica la ruta exacta a source1_primary_detector.txt:
– /opt/nvidia/deepstream/deepstream-6.2/samples/configs/deepstream-app/source1_primary_detector.txt
– Si no existe, instala deepstream-6.2-doc o deepstream-6.2-dev. Alternativamente, copia otra config válida de PGIE.
Mejoras/variantes
- Sustituir la lógica de color por un modelo OK/DEFECT con DeepStream:
- Entrena con TAO Toolkit un clasificador binario con tus datos de defectos.
- Exporta a ONNX y crea un engine TensorRT (FP16/INT8).
-
Reemplaza nvinfer por un clasificardor Sgie sobre la ROI detectada o recortada con nvdspreprocess.
-
Pipeline más eficiente:
- Trabajar en NV12 directamente con CUDA/NPP para análisis, evitando conversión a CPU.
-
Reducir el tamaño de la ROI o muestrear para bajar uso de CPU.
-
Sincronización avanzada:
-
Añade un encoder incremental o sensor óptico de paso para medir posición de la banda y disparar el expulsor por posición y no por tiempo.
-
Telemetría:
-
Publica eventos OK/DEFECT via nvmsgbroker a MQTT/Kafka y guarda imágenes de defectos.
-
Aceleración y ahorro energético:
- INT8 en nvinfer para el modelo de detectores reales.
-
Ajuste de nvpmodel para equilibrio entre rendimiento y consumo, con control térmico activo.
-
Integración mecánica:
- Sustituir servo por solenoide con MOSFET driver y diodo flyback, controlado por PCA9685 (o GPIO con driver dedicado).
Checklist de verificación
- [ ] JetPack 5.1.2 (L4T R35.4.1) verificado con cat /etc/nv_tegra_release.
- [ ] DeepStream 6.2 instalado y deepstream-app –version-all OK.
- [ ] Cámara Arducam IMX477 en CAM0, gst-launch-1.0 nvarguscamerasrc funciona.
- [ ] PCA9685 visible en i2cdetect con dirección 0x40.
- [ ] Script de prueba de servo mueve el actuador y vuelve a neutral.
- [ ] Script main corre a ≥ 25 FPS, OSD muestra ROI y estado OK/DEFECT.
- [ ] Con objeto rojo en la ROI, aparece “DEFECT” y el expulsor actúa con la sincronización prevista.
- [ ] Ajustados BELT_SPEED_MPS, DIST_CAM_TO_KICK_M y PROC_LATENCY_MS hasta expulsión precisa.
- [ ] Revertido nvpmodel/jetson_clocks tras las pruebas (opcional).
Apéndice: Comandos útiles y mediciones
- Consulta de modo de potencia y ajuste:
- sudo nvpmodel -q
-
sudo nvpmodel -m 0; sudo jetson_clocks
-
Métricas:
- sudo tegrastats
-
nvidia-smi no aplica en Jetson; usa tegrastats.
-
Pipeline de cámara mínimo para diagnóstico:
gst-launch-1.0 nvarguscamerasrc sensor_id=0 ! \
'video/x-raw(memory:NVMM),width=1280,height=720,framerate=30/1,format=NV12' ! \
nvvideoconvert ! 'video/x-raw,format=RGBA' ! fakesink -e
- Listado de paquetes NVIDIA/TensorRT:
dpkg -l | grep -E 'nvidia|tensorrt|cuda|deepstream'
Notas finales:
– Este caso práctico mantiene coherencia con el modelo exacto: NVIDIA Jetson Orin Nano Developer Kit + Arducam IMX477 MIPI Camera (Sony IMX477) + Adafruit 16-Channel PWM Driver (PCA9685), usando DeepStream 6.2 en JetPack 5.1.2. El núcleo del proyecto deepstream-conveyor-defect-detection está implementado con una pipeline de DeepStream y control en tiempo de vuelo del expulsor mediante I2C/PCA9685. Para producción, sustituye el análisis de color por un clasificador/segmentador entrenado con tus defectos reales y optimizado con TensorRT.
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.




