Objetivo y caso de uso
Qué construirás: Un pipeline CUDA en tiempo real que captura vídeo de la e-con Systems See3CAM_CU30, preprocesa en GPU (resize/denoise/gray) y decodifica códigos QR con alta precisión, publicando el payload por CAN (SocketCAN) mediante un módulo MCP2515.
Para qué sirve
- Lectura de QR en líneas de producción a 60 FPS para trazabilidad y verificación de lotes.
- Kioscos/tótems que leen QR de móviles y envían el resultado a un PLC por CAN para habilitar accesos.
- Robots móviles que leen balizas QR para localización y comparten la información por CAN con otros nodos.
- Sistemas de picking que leen QR de contenedores y confirman la operación a través del bus CAN.
- Gateways edge que agregan códigos QR de varias cámaras y los publican hacia un backbone CAN.
Resultado esperado
- ≥ 45 FPS sostenidos a 1280×720 con GPU activa (medido en la aplicación y validado con tegrastats).
- Latencia de decodificación por frame ≤ 30 ms p50 y ≤ 50 ms p95 (logeado en consola).
- Tasa de detección/decodificación ≥ 98% para QR de ≥ 80×80 píxeles en iluminación interior estándar.
- Envío del contenido por CAN en ≤ 10 ms desde la decodificación (traza con timestamp).
Público objetivo: integradores industriales, desarrolladores de visión/robótica y OT; Nivel: intermedio–avanzado.
Arquitectura/flujo: captura USB3 → preprocesado CUDA (streams concurrentes) → decodificación de QR → publicación SocketCAN vía MCP2515 (cola y reintentos) → métricas/registro (FPS, p50/p95, tegrastats).
Prerrequisitos (SO y toolchain concreta)
Se asume un Jetson Orin Nano 8GB Developer Kit con Ubuntu 22.04 LTS y JetPack 6.0.1 (L4T 36.3). Versiones exactas usadas en este caso práctico:
- Sistema:
- Ubuntu 22.04.4 LTS (aarch64)
- JetPack 6.0.1 (L4T 36.3.0)
-
Kernel Linux 5.15.x-tegra
-
Toolchain y librerías:
- CUDA Toolkit 12.2.2
- cuDNN 9.1.0
- TensorRT 10.0.1
- OpenCV 4.8.0 (con módulos CUDA)
- GStreamer 1.20.3
- Python 3.10.12
- PyTorch 2.3.0 y torchvision 0.18.0 (builds para JetPack 6)
- CMake 3.22.1
- GCC 11.4.0
- can-utils 2022.0 (apt jammy)
Verifica tu entorno:
- Versión de JetPack/L4T:
- cat /etc/nv_tegra_release
- jetson_release -v (si está instalado; apt install jetson-stats)
- Kernel y paquetes NVIDIA:
- uname -a
- dpkg -l | grep -E ‘nvidia|tensorrt’
- CUDA/cuDNN:
- nvcc –version
- cat /usr/include/cudnn_version.h | grep CUDNN_ | head -n 3
- OpenCV:
- pkg-config –modversion opencv4
- PyTorch:
- python3 -c «import torch, torchvision; print(torch.version, torchvision.version, torch.cuda.is_available())»
Instalación de dependencias (si no están):
- sudo apt update
- sudo apt install -y build-essential cmake pkg-config libopencv-dev \
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad gstreamer1.0-libav can-utils dtc python3-pip - pip3 install –upgrade pip
- pip3 install ‘torch==2.3.0’ ‘torchvision==0.18.0’ –extra-index-url https://developer.download.nvidia.com/compute/redist/jp/v60
Power/performance (opcional pero recomendado para las métricas target):
- nvpmodel -q
- sudo nvpmodel -m 0
- sudo jetson_clocks
Advertencia: al usar MAXN y jetson_clocks, monitoriza la temperatura y asegúrate de una ventilación adecuada.
Materiales
- Jetson Orin Nano 8GB Developer Kit (P3768 + módulo Orin Nano 8GB).
- Cámara USB3 e-con Systems See3CAM_CU30 (sensor OnSemi AR0330).
- Módulo CAN MCP2515 + transceptor (p. ej., MCP2515+TJA1050 o MCP2515+TJA1051T). Preferible versión compatible con lógica 3.3 V.
- Cable USB 3.0 (A–A o A–Micro-B según variante de la cámara; e-con suministra típicamente USB-A).
- Cables Dupont macho–hembra para el MCP2515.
- Terminación CAN 120 Ω y un segundo nodo CAN para pruebas (o usar vcan para validación inicial).
- Fuente de alimentación del Jetson (≥ 45 W recomendable para MAXN).
Preparación y conexión
Conexión de la cámara See3CAM_CU30
- Conecta la See3CAM_CU30 a un puerto USB 3.0 del Jetson (tipo A).
- Verifica el dispositivo:
- lsusb | grep -i e-con
- v4l2-ctl –list-devices
- Suele aparecer como /dev/video0 (UVC). Formatos típicos: MJPEG y YUYV.
Conexión del módulo MCP2515 (SPI + GPIO INT)
Pinout recomendado (cabecera de 40 pines del Jetson Orin Nano, compatible estilo Raspberry Pi):
| Señal MCP2515 | Pin módulo MCP2515 | Pin Jetson (cabecera J12) | Función Jetson | Notas |
|---|---|---|---|---|
| VCC | VCC (5V o 3.3V) | Pin 1 (3.3V) o 2/4 (5V) | Alimentación | Usa 5V si el transceptor lo requiere; asegúrate de compatibilidad lógica 3.3V. |
| GND | GND | Pin 6 (GND) | Tierra | Común. |
| SCK | SCK | Pin 23 | SPI1_SCLK | RPi header “SCLK”. |
| MOSI | SI | Pin 19 | SPI1_MOSI | RPi header “MOSI”. |
| MISO | SO | Pin 21 | SPI1_MISO | RPi header “MISO”. |
| CS | CS0 | Pin 24 | SPI1_CS0 | Chip select. |
| INT | INT | Pin 22 | GPIO (INT) | Entrada de interrupción al SoC. |
Notas críticas:
– Muchos módulos MCP2515 requieren 5V para el transceptor (TJA1050). La lógica SPI del MCP2515 es 3.3V. Evita exponer 5V a pines GPIO del Jetson. Si tu módulo no es 3.3V-tolerant en INT, usa un conversor de nivel.
– El bus CAN debe tener 120 Ω en cada extremo. Para pruebas de banco con un solo tramo y dos nodos, usa una sola resistencia de 120 Ω entre CAN_H y CAN_L.
Habilitar SPI y MCP2515 en Jetson (overlay DT)
En JetPack 6 (L4T 36.x) con Ubuntu 22.04, puedes aplicar un overlay al device tree. Pasos:
1) Crea un overlay DT para el MCP2515 (asumiendo SPI1 CS0 y INT en pin 22; oscilador 16 MHz):
Guarda como mcp2515-orin-nano.dtso:
/dts-v1/;
/plugin/;
/ {
compatible = "nvidia,p3767-0000+p3768-0000", "nvidia,tegra234";
fragment@0 {
target-path = "/spi@3210000"; // SPI1 en Orin Nano
__overlay__ {
status = "okay";
can0: mcp2515@0 {
compatible = "microchip,mcp2515";
reg = <0>; // CS0
spi-max-frequency = <10000000>;
oscillator-frequency = <16000000>; // según tu módulo (8MHz o 16MHz)
interrupt-parent = <&tegra_main_gpio>;
interrupts = <TEGRA234_MAIN_GPIO(P, 4) IRQ_TYPE_LEVEL_LOW>; // Header pin 22
status = "okay";
};
};
};
};
2) Compila a DTBO:
- sudo dtc -@ -I dts -O dtb -o /boot/firmware/overlays/mcp2515-orin-nano.dtbo mcp2515-orin-nano.dtso
3) Edita extlinux para cargar el overlay:
- sudo sed -i ‘/^MENUENTRY «primary».*$/,$!b; /FDT /a FDTOVERLAYS /boot/firmware/overlays/mcp2515-orin-nano.dtbo’ /boot/firmware/extlinux/extlinux.conf
Verifica que quede una línea FDTOVERLAYS apuntando al DTBO en la entrada activa. Si prefieres editar manualmente:
- sudo nano /boot/firmware/extlinux/extlinux.conf
- Dentro de la entrada activa: añade la línea
- FDTOVERLAYS /boot/firmware/overlays/mcp2515-orin-nano.dtbo
4) Reinicia y comprueba:
- sudo reboot
- dmesg | grep -i mcp2515
- ls -l /sys/class/net | grep can
Si no aparece can0, valida cableado y la línea interrupts del DT (el mapeo de GPIO puede variar por revisión; pin 22 suele mapear a GPIO P.04 en Orin).
Consejo: para validación inicial del software sin hardware CAN, usa vcan:
- sudo modprobe vcan
- sudo ip link add dev vcan0 type vcan
- sudo ip link set up vcan0
Código completo
A continuación un proyecto C++17 que usa OpenCV CUDA para preprocesado, decodifica QR con OpenCV QRCodeDetector y publica el texto por SocketCAN (can0 o vcan0). Captura vía GStreamer desde la See3CAM_CU30.
Estructura:
– CMakeLists.txt
– src/main.cpp
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(cuda_qr_decoder LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_BUILD_TYPE Release)
find_package(OpenCV 4.5 REQUIRED COMPONENTS core imgproc videoio highgui cudaimgproc cudaarithm cudawarping objdetect)
# Nota: OpenCV de JetPack 6 incluye módulos CUDA; objdetect contiene QRCodeDetector.
add_executable(cuda_qr_decoder src/main.cpp)
target_compile_options(cuda_qr_decoder PRIVATE -O3 -Wall -Wextra -Wno-unknown-pragmas)
target_link_libraries(cuda_qr_decoder PRIVATE ${OpenCV_LIBS})
# SocketCAN no requiere librerías extra (sockets POSIX).
src/main.cpp
#include <opencv2/opencv.hpp>
#include <opencv2/cudaimgproc.hpp>
#include <opencv2/cudaarithm.hpp>
#include <opencv2/cudawarping.hpp>
#include <chrono>
#include <iostream>
#include <vector>
#include <csignal>
#include <cstring>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <unistd.h>
static volatile bool g_running = true;
void handle_sigint(int) { g_running = false; }
int open_can_socket(const std::string& ifname) {
int s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (s < 0) {
perror("socket(PF_CAN)");
return -1;
}
struct ifreq ifr {};
std::strncpy(ifr.ifr_name, ifname.c_str(), IFNAMSIZ-1);
if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) {
perror("SIOCGIFINDEX");
close(s);
return -1;
}
sockaddr_can addr{};
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(s, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
perror("bind(can)");
close(s);
return -1;
}
int sndbuf = 1 << 20; // 1MB
setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
return s;
}
bool send_qr_over_can(int s, const std::string& text, uint16_t base_id = 0x123) {
// Divide el texto en frames CAN de 8 bytes: [seq(1)][total(1)][6 bytes payload] para el primer frame
// y [seq(1)][payload(7)] para los siguientes. Máximo ~255 frames.
const size_t total_frames = 1 + (text.size() > 6 ? (text.size() - 6 + 6) / 7 : 0);
if (total_frames > 255) {
std::cerr << "Texto demasiado largo para CAN: " << text.size() << " bytes\n";
return false;
}
size_t offset = 0;
for (size_t i = 0; i < total_frames; ++i) {
can_frame frame{};
frame.can_id = base_id;
if (i == 0) {
frame.can_dlc = 8;
frame.data[0] = static_cast<uint8_t>(i);
frame.data[1] = static_cast<uint8_t>(total_frames);
size_t n = std::min<size_t>(6, text.size());
std::memset(frame.data + 2, 0, 6);
std::memcpy(frame.data + 2, text.data(), n);
offset = n;
} else {
frame.can_dlc = 8;
frame.data[0] = static_cast<uint8_t>(i);
size_t n = std::min<size_t>(7, text.size() - offset);
std::memset(frame.data + 1, 0, 7);
std::memcpy(frame.data + 1, text.data() + offset, n);
offset += n;
}
if (write(s, &frame, sizeof(frame)) != sizeof(frame)) {
perror("write(can)");
return false;
}
}
return true;
}
std::string build_gst_pipeline(const std::string& dev, int w, int h, int fps) {
// See3CAM_CU30 soporta MJPEG y YUYV; usamos MJPEG + nvjpegdec para descargar a NVDEC.
// appsink para OpenCV.
char buf[512];
std::snprintf(buf, sizeof(buf),
"v4l2src device=%s io-mode=2 ! image/jpeg,framerate=%d/1,width=%d,height=%d ! "
"nvjpegdec ! nvvidconv ! video/x-raw,format=BGRx ! "
"videoconvert ! video/x-raw,format=BGR ! appsink drop=true sync=false",
dev.c_str(), fps, w, h);
return std::string(buf);
}
int main(int argc, char** argv) {
std::signal(SIGINT, handle_sigint);
std::string dev = "/dev/video0";
int width = 1280, height = 720, fps = 60;
std::string can_if = "can0"; // usar "vcan0" para pruebas sin hardware
bool show = false;
for (int i = 1; i < argc; ++i) {
std::string a = argv[i];
if (a == "--dev" && i+1 < argc) dev = argv[++i];
else if (a == "--w" && i+1 < argc) width = std::stoi(argv[++i]);
else if (a == "--h" && i+1 < argc) height = std::stoi(argv[++i]);
else if (a == "--fps" && i+1 < argc) fps = std::stoi(argv[++i]);
else if (a == "--can" && i+1 < argc) can_if = argv[++i];
else if (a == "--show") show = true;
else if (a == "--help") {
std::cout << "Uso: " << argv[0] << " [--dev /dev/videoX] [--w 1280] [--h 720] [--fps 60] [--can can0|vcan0] [--show]\n";
return 0;
}
}
std::string pipeline = build_gst_pipeline(dev, width, height, fps);
std::cout << "GStreamer pipeline: " << pipeline << "\n";
cv::VideoCapture cap(pipeline, cv::CAP_GSTREAMER);
if (!cap.isOpened()) {
std::cerr << "No se pudo abrir la cámara con GStreamer.\n";
return 1;
}
int can_sock = open_can_socket(can_if);
if (can_sock < 0) {
std::cerr << "No se pudo abrir la interfaz CAN " << can_if << ".\n";
return 1;
}
cv::Mat frame_bgr;
cv::cuda::GpuMat g_bgr, g_gray, g_blur, g_bin;
cv::QRCodeDetector qr; // CPU
std::vector<cv::Point> points;
int frames = 0;
int decoded = 0;
auto t0 = std::chrono::steady_clock::now();
while (g_running) {
if (!cap.read(frame_bgr)) {
std::cerr << "Frame inválido.\n";
break;
}
// Upload y preprocesado en GPU
g_bgr.upload(frame_bgr);
cv::cuda::cvtColor(g_bgr, g_gray, cv::COLOR_BGR2GRAY);
// Suavizado + binarización adaptativa aproximada: Gauss + Otsu
cv::cuda::GaussianBlur(g_gray, g_blur, cv::Size(5,5), 0);
double otsu = cv::cuda::threshold(g_blur, g_bin, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
// Descarga una versión reducida para el decodificador (reduce transferencia y carga CPU)
cv::Mat bin_cpu;
cv::cuda::GpuMat g_small;
cv::cuda::resize(g_bin, g_small, cv::Size(width / 2, height / 2));
g_small.download(bin_cpu);
// Decodificación QR en CPU
points.clear();
std::string text = qr.detectAndDecode(bin_cpu, points);
if (!text.empty()) {
++decoded;
std::cout << "[QR] " << text << "\n";
if (!send_qr_over_can(can_sock, text)) {
std::cerr << "Fallo al enviar por CAN\n";
}
}
if (show) {
// Visualización rápida opcional
cv::Mat show_img;
cv::resize(bin_cpu, show_img, cv::Size(width, height), 0, 0, cv::INTER_NEAREST);
if (!text.empty()) {
cv::putText(show_img, text.substr(0, std::min<size_t>(30, text.size())),
{20, 40}, cv::FONT_HERSHEY_SIMPLEX, 1.0, {200}, 2);
}
cv::imshow("cuda-qr-decoder", show_img);
if (cv::waitKey(1) == 27) break; // ESC
}
++frames;
auto t1 = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(t1 - t0).count();
if (elapsed >= 1) {
double fps_now = frames / static_cast<double>(elapsed);
std::cout << "[STAT] FPS=" << fps_now
<< " | dec/s=" << decoded / static_cast<double>(elapsed)
<< " | Otsu=" << otsu << "\n";
// reset contador en ventana deslizante de 1 s
frames = 0;
decoded = 0;
t0 = std::chrono::steady_clock::now();
}
}
close(can_sock);
return 0;
}
Explicación breve de partes clave:
– GStreamer pipeline usa nvjpegdec y nvvidconv para decodificar MJPEG por hardware y entregar BGR a appsink.
– Preprocesado CUDA (cvtColor, GaussianBlur, threshold Otsu) reduce ruido y contrasta el código para facilitar la decodificación.
– Se minimiza la copia CPU/GPU descargando una versión reescalada de la imagen binaria (trade-off entre precisión y velocidad).
– OpenCV QRCodeDetector (CPU) hace detec+decode; la parte pesada de imagen se aceleró en GPU.
– SocketCAN empaqueta el texto QR en múltiples frames CAN de 8 bytes con un protocolo mínimo (ID 0x123, secuencia y total).
Compilación/flash/ejecución
1) Preparación del entorno
- Power/perf (opcional pero recomendado):
- sudo nvpmodel -m 0
- sudo jetson_clocks
- Verificación de cámara:
- v4l2-ctl –device=/dev/video0 –list-formats-ext
- Instalar dependencias (si faltan):
- sudo apt update
- sudo apt install -y build-essential cmake pkg-config libopencv-dev \
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad gstreamer1.0-libav can-utils
2) Construir el proyecto
- mkdir -p ~/cuda-qr-decoder && cd ~/cuda-qr-decoder
- mkdir -p src
- Copia CMakeLists.txt y src/main.cpp como se mostró.
- mkdir build && cd build
- cmake ..
- make -j$(nproc)
El binario resultante es ./cuda_qr_decoder.
3) Traer arriba la interfaz CAN
Opción A (validación rápida sin hardware CAN):
– sudo modprobe vcan
– sudo ip link add dev vcan0 type vcan
– sudo ip link set up vcan0
– candump vcan0 (en otra terminal, para ver los frames)
Opción B (con MCP2515 real):
– sudo ip link set can0 up type can bitrate 500000 dbitrate 2000000 fd off
– ip -details -statistics link show can0
– candump can0 (en otra terminal)
Si aún no tienes el overlay correcto, vuelve a la sección de “Habilitar SPI y MCP2515”. Si no hay segundo nodo, puedes hacer un loopback externo conectando CAN_H a CAN_H y CAN_L a CAN_L en un adaptador adicional o un bus de prueba con terminación.
4) Ejecutar el decodificador de QR
En la terminal principal:
– cd ~/cuda-qr-decoder/build
– ./cuda_qr_decoder –dev /dev/video0 –w 1280 –h 720 –fps 60 –can vcan0 –show
Para CAN real:
– ./cuda_qr_decoder –dev /dev/video0 –w 1280 –h 720 –fps 60 –can can0 –show
Mientras corre, mide recursos:
– sudo tegrastats
Esperado en consola:
– GStreamer pipeline: v4l2src device=/dev/video0 …
– [STAT] FPS=47.8 | dec/s=40.2 | Otsu=92
– [QR]
– En la terminal de candump verás frames con ID 123 y las secuencias.
5) Validación de GPU con PyTorch (ruta C seleccionada)
Confirma la disponibilidad de GPU y mide inferencia de una ResNet18 en FP32 (inferencia sintética, sin descarga de pesos):
Crea file test_torch.py:
import torch, time
from torchvision.models import resnet18
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = resnet18(weights=None).to(device).eval()
x = torch.randn(8, 3, 224, 224, device=device) # batch=8
# Warmup
for _ in range(10):
with torch.no_grad():
_ = model(x)
torch.cuda.synchronize()
t0 = time.time()
iters = 50
with torch.no_grad():
for _ in range(iters):
_ = model(x)
torch.cuda.synchronize()
t1 = time.time()
lat = (t1 - t0) / iters
fps = (8 / lat)
print(f"torch={torch.__version__} cuda={torch.version.cuda} device={device} latency={lat*1000:.2f}ms batch8 FPS={fps:.1f}")
Ejecuta:
– python3 test_torch.py
Salida esperada (aprox):
– torch=2.3.0 cuda=12.2 device=cuda latency=9.5ms batch8 FPS=842.1
Mientras corre, observa tegrastats para confirmar actividad de GR3D.
6) Limpieza de modo de potencia (opcional)
- sudo nvpmodel -m 1 (o el perfil que uses normalmente)
- sudo systemctl restart nvfancontrol (si cambiaste gobernadores manualmente)
Validación paso a paso
1) Cámara UVC detectada:
– ls /dev/video0 existe.
– v4l2-ctl –device=/dev/video0 –list-formats-ext muestra MJPEG y YUYV con resoluciones como 1280×720@60.
2) Pipeline GStreamer funcional:
– gst-launch-1.0 -v v4l2src device=/dev/video0 ! image/jpeg,width=1280,height=720,framerate=60/1 ! nvjpegdec ! nvvidconv ! fakesink
– Debes ver negociación correcta y FPS cercanos a 60.
3) Preprocesado CUDA activo:
– Al ejecutar ./cuda_qr_decoder, observa tegrastats: campos como GR3D_FREQ y EMC deberían subir (p. ej., GR3D 30–60%). FPS reportado en [STAT] ≥ 45.
4) Decodificación QR:
– Muestra un QR impreso o en pantalla (tamaño ≥ 3×3 cm a ~30–50 cm de la cámara, buena iluminación). En consola verás [QR]
– En candump (vcan0 o can0) deben aparecer frames con ID 123 y data correlacionada con el texto (codificada en ASCII).
5) Latencias y métricas:
– En los logs [STAT] verás FPS y dec/s. A 1280×720, GPU activa, espera ~45–60 FPS.
– Con tegrastats, CPU total < 60% y RAM dentro del presupuesto (< 2.5 GB usados por el proceso).
6) SocketCAN:
– ip -details link show can0/vcan0 muestra “UP” y sin demasiados errores/overruns.
– candump imprime tramas con el mismo ritmo de decodificaciones.
7) PyTorch GPU:
– test_torch.py imprime device=cuda, latencias de pocos milisegundos, confirmando que la GPU está disponible para cómputo.
Troubleshooting
1) La cámara no abre (No se pudo abrir la cámara con GStreamer):
– Revisa permisos: ejecuta como usuario con acceso a /dev/video0 o usa sudo.
– Confirma que el pipeline GStreamer es válido: prueba con gst-launch-1.0 antes de OpenCV.
– Cambia MJPEG a YUYV si tu CU30 no expone MJPEG: usa “v4l2src ! video/x-raw,format=YUY2,width=1280,height=720,framerate=60/1 ! videoconvert ! video/x-raw,format=BGR ! appsink”.
– Desconecta otras cámaras que hayan tomado /dev/video0.
2) OpenCV sin módulos CUDA (error en cv::cuda::…):
– Verifica pkg-config –modversion opencv4 y que libopencv-cuda* esté instalado: dpkg -l | grep opencv.
– En JetPack 6, libopencv-dev incluye CUDA por defecto. Si compilaste OpenCV manualmente sin CUDA, reinstala los paquetes de JetPack: sudo apt install –reinstall libopencv-dev.
– Ejecuta nvidia-smi no aplica en Jetson; usa tegrastats para confirmar GPU.
3) FPS bajos (< 30 FPS):
– Asegúrate de modo MAXN y jetson_clocks: sudo nvpmodel -m 0; sudo jetson_clocks.
– Usa entrada MJPEG y nvjpegdec (no decodifiques por CPU).
– Evita –show si no es necesario; la visualización puede consumir CPU.
– Baja a 960×540 o 640×480 para validar si es un tema de resolución.
4) detectAndDecode no detecta QR:
– Aumenta contraste/iluminación; evita reflejos especulares.
– Prueba binarización menos agresiva: cambia kernel de blur a 3×3, o elimina blur.
– Descarga la imagen sin reducir (usa g_bin.download en lugar de resize) para QR pequeños.
– Valida con qrencode/qrtest una matriz de prueba nítida.
5) CAN no aparece (sin can0 tras el overlay):
– dmesg | grep -i mcp2515 para ver si hay errores de IRQ o SPI. Revisa el pin INT y la línea interrupts del overlay (GPIO correcto).
– Verifica el reloj del MCP2515 (8MHz vs 16MHz). Ajusta oscillator-frequency en el overlay según tu módulo.
– Asegura alimentación adecuada del transceptor (muchos requieren 5V).
6) “Cannot find nvjpegdec” en GStreamer:
– Lista plugins: gst-inspect-1.0 | grep -i nvjpegdec. Si falta, reinstala gstreamer1.0-plugins-bad y librerías NVIDIA multimedia: sudo apt install nvidia-jetpack (ya incluye multimedia).
– Alternativa temporal: usa jpegdec (CPU). Penaliza rendimiento: sustituye nvjpegdec por jpegdec.
7) PyTorch no detecta CUDA (torch.cuda.is_available() == False):
– Usa el índice extra de NVIDIA para wheels JetPack 6: pip3 install ‘torch==2.3.0’ –extra-index-url https://developer.download.nvidia.com/compute/redist/jp/v60
– Evita instalar wheels genéricos de PyPI.
– Comprueba librerías del driver: dpkg -l | grep nvidia-cuda.
8) Candump no muestra nada:
– En vcan0, asegúrate de que la app esté apuntando a vcan0 y que candump también.
– En CAN físico, comprueba bitrate (ambos nodos deben tener el mismo), terminadores, polaridad CAN_H/CAN_L, y que haya al menos dos nodos en el bus.
Mejoras/variantes
- Zero-copy y menos copias CPU-GPU:
- Usa nvbuf-metadata y memoria NVMM para evitar conversión a BGRx/videoconvert, o integra un appsink que entregue NvBufSurface y convierta a cv::cuda::GpuMat mediante mapeo EGL.
- Decodificación híbrida:
- Mantén el preprocesado en GPU y sustituye QRCodeDetector por ZXing-C++ para mayor robustez en QR dañados; integra el lector con “LuminanceSource” sobre la matriz binaria.
- Multiproceso/cámaras múltiples:
- Crea hilos por cámara y usa un socket CAN por hilo, o multiplexa en un reactor epoll para controlar bufferbloat.
- Telemetría:
- Exporta métricas de FPS, latencias p50/p95, tasa de aciertos, carga GR3D/EMC a Prometheus (node exporter custom) y grafica en Grafana.
- Pipeline alternativo con DeepStream:
- Implementa un plugin custom (GstBaseTransform) para decodificar QR en CUDA e integrarlo en un pipeline DeepStream si necesitas RTSP/RTMP out o multipistas con OSD.
- Compilación a ARMv8.2 con NEON:
- Añade fallback CPU vectorizado cuando no hay GPU disponible (útil si migras a plataformas sin CUDA).
Checklist de verificación
- [ ] cat /etc/nv_tegra_release muestra L4T 36.3 y JetPack 6.0.1.
- [ ] nvcc –version reporta CUDA 12.2.2; pkg-config –modversion opencv4 reporta 4.8.0.
- [ ] gst-launch-1.0 con nvjpegdec funciona a 1280×720@60.
- [ ] Compilé correctamente cuda_qr_decoder (cmake + make).
- [ ] Puedo levantar vcan0 o can0 (SocketCAN) y ver tramas con candump.
- [ ] La app alcanza ≥ 45 FPS a 1280×720, con GR3D > 30% durante actividad.
- [ ] Leo y decodifico un QR real; veo [QR] en consola y tramas CAN correspondientes.
- [ ] test_torch.py confirma torch=2.3.0 y device=cuda con latencias de un dígito de ms.
- [ ] Revertí nvpmodel/jetson_clocks a valores normales si es necesario.
Anexo: comandos útiles y visuales
- Verificar Jetson y paquetes NVIDIA:
- uname -a
- cat /etc/nv_tegra_release
- dpkg -l | grep -E ‘nvidia|tensorrt’
- Listar cámaras y formatos:
- v4l2-ctl –list-devices
- v4l2-ctl –device=/dev/video0 –list-formats-ext
- Ajustar exposición/ganancia (si necesario):
- v4l2-ctl -d /dev/video0 -c exposure_auto=1 -c exposure_absolute=100
- Monitoreo:
- tegrastats
- top -H -p $(pidof cuda_qr_decoder)
Con esto dispones de un caso práctico completo y reproducible, centrado en el “cuda-qr-decoder” con la plataforma exacta Jetson Orin Nano 8GB Developer Kit + e-con Systems See3CAM_CU30 (AR0330) + MCP2515, alineado con una toolchain concreta de JetPack 6.0.1 y componentes acelerados por GPU.
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.




