Jetson Nano 上实现 YOLOv5n 实时检测:NVMM、CUDA、TensorRT 与 EGL 零拷贝复现 Jetson Nano 上实现 YOLOv5n 实时检测NVMM、CUDA、TensorRT 与 EGL 零拷贝复现本文给出一套可复现的 Jetson Nano 实时目标检测方案。摄像头通过 Argus/GStreamer 输出 NVMM 帧CUDA 直接完成预处理和检测框绘制TensorRT 执行 YOLOv5n 推理最后由NvEglRenderer显示。实测摄像头约 59.5 FPS推理和显示稳定在 26 FPS 左右。github有时间会补上去或者放在评论区求⭐。1. 最终效果与测试结果1.1 测试环境项目配置开发板NVIDIA Jetson NanoJetPack4.5.1L4T32.5.2CUDA10.2TensorRT7.1.3GStreamer1.14.5摄像头MIPI CSI通过 Argus 采集摄像头模式1280×720、60 FPS、sensor_mode4模型YOLOv5nCOCO 80 类TensorRT 输入1×3×640×640 FLOAT32TensorRT 输出tensorrtx 自定义YoloLayer_TRT输出工程语言C17CUDA 模块使用 C14本文针对 JetPack 4.x 的旧版 Multimedia API。JetPack 5/6 的NvBufSurface、GStreamer 和 CUDA EGL 接口有所不同不能直接照搬所有底层映射代码。1.2 约两分钟实测指标结果摄像头采集59.50 FPSTensorRT 推理26.23 FPSEGL 实际显示26.23 FPSGPU 预处理0.83 msTensorRT execute35.27 msEGL 渲染2.62 ms端到端平均延迟45.99 ms端到端 P5045.94 ms端到端 P9553.93 ms统计期间共采集 7044 帧、推理 3106 帧、显示 3105 帧。摄像头快于推理时系统主动覆盖过期帧因此不会形成越来越长的处理队列。2. 为什么要重写实时运行链路直接使用 YOLOv5 的 Pythondetect.py在 Jetson Nano 上通常会遇到以下问题MIPI CSI 摄像头需要nvarguscamerasrc不是普通 USB/V4L2 摄像头。CPU resize、颜色转换和 NCHW 排列占用 CPU并增加输入拷贝。cv::imshow和 X11 软件显示会产生明显阻塞。如果摄像头帧进入普通队列推理速度低于采集速度时延迟会持续累积。NVMM、EGLImage、CUDA resource 和借用 FD 的生命周期必须明确管理。本项目采用独立 C runtime把采集、映射、预处理、推理、后处理和显示拆成职责清晰的模块。3. 最终架构MIPI CSI 摄像头Argus NV12 / NVMMnvvidconv RGBA / NVMMappsink opaque NVMM sample提取借用 FDEGLImageCUDA EGL 映射RGBA device viewCUDA resize/letterbox/RGB/CHWTensorRT YOLOv5nCPU 解码与 NMSCUDA 绘制框和逐框标签NvEglRenderer运行时使用两个单槽位CameraSource → LatestFrameSlot只保留最新摄像头帧 → inference worker → LatestResultSlot只保留最新检测结果及其对应帧 → main thread / NvEglRenderer这是一种实时系统常用的“保新不保全”策略。采集到的每一帧不一定都推理但送到屏幕上的结果不会因为队列积压而越来越旧。4. 工程结构假设下载后的项目根目录为PROJECT_ROOT/ ├── tensorrtx/yolov5/ # YOLOv5 TensorRT engine 构建器及插件 ├── yumi/ │ ├── CMakeLists.txt │ ├── config/ │ │ ├── camera.yaml │ │ ├── model.yaml │ │ └── coco.names │ ├── include/yumi/ │ ├── src/ │ └── tools/ └── yolov5n.pt # 需要自行准备主要模块职责模块职责CameraSource创建 Argus/GStreamer 管线并发布帧LatestFrameSlot只保存最新帧覆盖旧帧NvmmCudaMapperNVMM handle → FD → EGLImage → CUDA viewGpuPreprocessorCUDA 融合 resize、letterbox、RGBA→RGB、HWC→CHW 和归一化TrtEngine管理 TensorRT engine、binding、context 和 CUDA streamPostprocessor解码、置信度过滤、NMS 和原图坐标还原GpuDisplayOverlay在 NVMM surface 上绘制检测框和逐框标签LatestResultSlot保存检测结果以及产生该结果的同一帧HardwareDisplay使用NvEglRenderer显示借用 FDMetrics统计各阶段耗时、吞吐、P50/P95 和丢帧5. 准备系统环境5.1 确认 JetPack 组件先确认关键文件存在nvcc--versiondpkg-l|grep-Etensorrt|nvinfer|gstreamer|opencvls/usr/src/jetson_multimedia_api/include/nvbuf_utils.hls/usr/src/jetson_multimedia_api/samples/common/classes/NvEglRenderer.cpp本项目依赖 JetPack 提供的 CUDA、TensorRT、Argus、nvvidconv、libnvbuf_utils和 Jetson Multimedia API 示例源码。如果 Multimedia API 目录不存在应先安装与当前 L4T/JetPack 完全匹配的 Multimedia API而不是从其他 JetPack 版本复制头文件。5.2 安装通用开发依赖sudoaptupdatesudoaptinstall-y\build-essential\cmake\pkg-config\libopencv-dev\libgstreamer1.0-dev\libgstreamer-plugins-base1.0-dev\libx11-devEGL、GLES、CUDA 和libnvbuf_utils应使用 JetPack/L4T 自带版本。不要为了补头文件而混装其他 JetPack 版本或用 Mesa 库替换 NVIDIA 驱动库。检查 GStreamer 的 Jetson 插件gst-inspect-1.0 nvarguscamerasrc gst-inspect-1.0 nvvidconv两条命令都应能显示插件信息。6. 生成 YOLOv5n TensorRT engine6.1 为什么必须在目标 Nano 上生成TensorRT engine 包含与 GPU、TensorRT 版本和 tactic 选择相关的信息。不要直接复制其他 Jetson、PC 或其他 JetPack 版本生成的 engine。推荐在实际运行的 Nano 上重新序列化。本项目使用 tensorrtx 的自定义YoloLayer_TRT输出主程序也按该输出格式解码。不要用一份输出契约不同的 ONNX engine 替换它除非同时修改后处理逻辑。6.2 将.pt转成.wtsgen_wts.py需要在能够加载 YOLOv5 6.0 模型的 Python/PyTorch 环境中执行。可以在 Nano 上执行也可以在兼容的 PC 环境中只生成.wts之后把.wts复制到 Nano。在 YOLOv5 6.0 Python 环境中执行cdPROJECT_ROOTpython3 tensorrtx/yolov5/gen_wts.py\-wyolov5n.pt\-otensorrtx/yolov5/yolov5n.wts如果出现模型结构或anchor_grid不兼容通常是.pt、YOLOv5 源码和gen_wts.py不属于同一代版本。本文使用 YOLOv5 6.0 风格权重和源码。6.3 检查 tensorrtx 编译配置确认tensorrtx/yolov5/src/config.h中使用 FP16 和 80 类#defineUSE_FP16constexprstaticintkNumClass80;tensorrtx/yolov5/CMakeLists.txt中若存在其他机器的 TensorRT 8 路径例如/home/nvidia/TensorRT-8.2.5.1/include /home/nvidia/TensorRT-8.2.5.1/lib应删除或改为当前 JetPack 的系统路径/usr/include/aarch64-linux-gnu /usr/lib/aarch64-linux-gnuJetPack 4.5.1 自带 TensorRT 7.1.3不应混用 TensorRT 8 的头文件或库。6.4 编译 builder 并生成 enginecdPROJECT_ROOT/tensorrtx/yolov5 cmake-S.-Bbuild-native-DYOLO_INPUT_SIZE640cmake--buildbuild-native--targetyolov5_det--parallel2./build-native/yolov5_det\-syolov5n.wts yolov5n.nano.fp16.engine n其中最后一个参数n表示 YOLOv5n 的 depth/width 系数。成功后应得到PROJECT_ROOT/tensorrtx/yolov5/yolov5n.nano.fp16.engine输入尺寸是编译期契约。若改成 512 或 416必须用同一个尺寸重新编译 builder、插件和 engine不能只修改运行时 resize。7. 配置摄像头与模型7.1 摄像头配置编辑yumi/config/camera.yamlsensor_id:0sensor_mode:4width:1280height:720fps:60不同 CSI 摄像头的 mode 列表可能不同。先用以下命令观察 Argus 输出的模式gst-launch-1.0 nvarguscamerasrc sensor-id0num-buffers1\!video/x-raw(memory:NVMM)\!fakesink运行时日志会列出可用 Sensor modes。本文使用的摄像头中sensor_mode4对应 1280×72060 FPS。只写分辨率和帧率不一定能让 Argus 选择预期模式因此建议显式配置sensor_mode。7.2 模型配置yumi/config/model.yaml当前使用绝对路径。公开部署时必须改成读者机器上的真实路径engine:PROJECT_ROOT/tensorrtx/yolov5/yolov5n.nano.fp16.engineplugin:PROJECT_ROOT/yumi/build/libmyplugins.solabels:PROJECT_ROOT/yumi/config/coco.namesclasses:80confidence:0.45iou:0.35max_detections:30请把PROJECT_ROOT替换为绝对路径不能原样保留尖括号占位符。engine 和运行时插件来自同一份yololayer.cu输入尺寸和类别数也必须一致。8. 编译实时程序CUDA 10.2 对 CUDA C17 支持有限因此本工程让普通 C 模块使用 C17CUDA 预处理和显示模块使用 C14。cdPROJECT_ROOT/yumi cmake-S.-Bbuild-DCMAKE_BUILD_TYPERelease cmake--buildbuild--parallel2只重新构建主程序cmake--buildbuild--targetyumi_realtime--parallel2成功后会生成yumi/build/yumi_realtime yumi/build/libmyplugins.so如果看到以下警告cudaEGL.h: warning: extra ; [-Wpedantic]它来自 CUDA 10.2 系统头文件不影响构建结果。9. 先做分层验证不要一开始就运行完整显示链路。按“模型 → 摄像头 → NVMM 映射 → 主程序”的顺序排查定位更快。9.1 检查 enginecdPROJECT_ROOT/yumiLD_PRELOAD./build/libmyplugins.so\./build/engine_probe\PROJECT_ROOT/tensorrtx/yolov5/yolov5n.nano.fp16.engine应确认输入约为3×640×640 FLOAT32输出共 38001 个float。该输出对应1 1000 × sizeof(tensorrtx Detection) / sizeof(float)9.2 检查摄像头./build/camera_probe01280720601204camera_probe的参数依次是sensor_id width height fps frames sensor_mode它不读取camera.yaml所以这里显式传入 mode 4。如果需要直接验证 NVMM 到 CUDA 的映射./build/nvmm_probe01280720601204参数依次为sensor_id width height fps frame_count sensor_mode9.3 无显示运行./build/yumi_realtime--frames120先确认 TensorRT 推理、后处理和资源释放正常再启用显示。10. 运行硬件显示在 Jetson 桌面会话中执行cdPROJECT_ROOT/yumiDISPLAY:0\XAUTHORITY/run/user/1000/gdm/Xauthority\./build/yumi_realtime--display有限帧回归DISPLAY:0\XAUTHORITY/run/user/1000/gdm/Xauthority\./build/yumi_realtime--display--frames120当前硬件窗口使用终端CtrlC退出。如果桌面用户不是 UID 1000XAUTHORITY路径可能不同。可先检查DISPLAY:0\XAUTHORITY/run/user/1000/gdm/Xauthority\xset q10.1 CPU/OpenCV 回退路径用于排查 NVMM/EGL 问题DISPLAY:0\XAUTHORITY/run/user/1000/gdm/Xauthority\./build/yumi_realtime --host-input--display该路径会执行 CPU BGR 转换并使用cv::imshow性能低于默认硬件显示不建议作为正式运行方式。11. 如何阅读性能日志程序周期性输出[metrics] capture_fps... infer_fps... render_fps... preprocess_ms... gpu_preprocess_ms... execute_ms... e2e_ms... render_ms...退出时输出总计[summary] capture_frames... capture_fps... infer_frames... infer_fps... render_frames... e2e_p50_ms... e2e_p95_ms...主要字段字段含义capture_fps摄像头实际采集速度infer_fps完成推理和后处理的速度render_fps实际显示速度gpu_preprocess_msCUDA 融合预处理耗时execute_msTensorRT 网络执行耗时e2e_ms从采集到检测完成的端到端延迟render_msEGL 或 OpenCV 显示耗时camera_drop最新帧槽主动覆盖的过期摄像头帧result_drop显示跟不上时被覆盖的旧结果在本文实测中camera_drop持续增加是预期行为摄像头约 60 FPS而推理约 26 FPS。系统主动丢弃过期帧以保证延迟不累积。12. 零拷贝实现中的关键坑12.1 appsink 内存不是标准 dmabufJetPack 4.5.1 上appsink 得到的 allocator 名称为nvfilter。它是 opaque NVMM memory不是标准GstDmaBufMemory。实际映射链路为GstSample → GstBuffer → map opaque NVMM handle → ExtractFdFromNvBuffer → NvEGLImageFromFd → cuGraphicsEGLRegisterImage → cuGraphicsResourceGetMappedEglFrame不能假设可以直接调用通用 dmabuf API 取 FD。12.2 ExtractFdFromNvBuffer 返回借用 FD该 FD 属于上游 GStreamer/NVMM 缓冲池。当前模块只能借用禁止NvReleaseFd(fd);close(fd);错误释放后可能前几帧正常随后破坏nvvidconv缓冲池。12.3 sample 必须活到渲染完成以下资源存在严格依赖GstSample └─ opaque NVMM buffer └─ borrowed FD └─ EGLImage └─ CUDA graphics resource只有最后一个 CUDA/EGL 消费者完成后才能按逆序释放。项目使用 move-only RAII 对象保存这些资源并让检测结果持有产生该结果的同一 NVMM 帧。12.4 同一帧上绘制同一结果不能把 Frame N 的检测框画到最新的 Frame N2 上。当前顺序是Frame N → 预处理 → TensorRT → 后处理 → 在 Frame N 的 NVMM surface 上绘制 → render(Frame N fd) → 释放 Frame N这样检测框、类别、置信度和底图始终对应。12.5 类别标签不能使用 renderer 全局状态文字NvEglRenderer::setOverlayText()是固定坐标的全局文字不适合逐检测框标签。当前实现先在 CPU 栅格化很小的文字蒙版再由 CUDA 将类别和置信度合成到对应框左上角。它不需要下载整帧也不会退回 OpenCV 软件显示。13. 常见故障排查13.1cannot open config或cannot open engine检查model.yaml中是否仍然是作者机器的绝对路径。把engine、plugin和labels全部改为当前机器真实路径。13.2cannot open display/ EGL 窗口创建失败确认echo$DISPLAYls-l/run/user/1000/gdm/XauthorityDISPLAY:0XAUTHORITY/run/user/1000/gdm/Xauthority xset qSSH 环境下还需要确保 Jetson 本机已有有效桌面会话。13.3 Argus 找不到摄像头或 mode 不匹配先停止其他占用 CSI 摄像头的程序然后测试gst-launch-1.0 nvarguscamerasrc sensor-id0num-buffers30\!video/x-raw(memory:NVMM),width1280,height720,framerate60/1\!nvvidconv\!fakesink根据日志选择当前摄像头真实支持的sensor_mode。13.4 engine 反序列化失败常见原因engine 不是在当前 Nano/JetPack 上生成生成 engine 和运行程序使用了不同 TensorRT 版本自定义插件未加载libmyplugins.so与生成 engine 时的yololayer.cu不一致。删除旧 engine在当前设备重新构建和序列化。13.5 类别错误或框数量异常同时检查以下四处.pt/.wts模型实际类别数tensorrtx/yolov5/src/config.h的kNumClassmodel.yaml的classes标签文件行数。自定义类别模型修改后需要重新编译插件、重新生成 engine并重新构建yumi_realtime。13.6 编译时找不到NvEglRenderer.cpp确认ls/usr/src/jetson_multimedia_api/samples/common/classes/项目会直接编译该目录下的NvEglRenderer.cpp、NvElement.cpp和NvElementProfiler.cpp。不同 JetPack 的示例目录结构可能变化需要相应调整yumi/CMakeLists.txt。14. 优化过程与收益本项目不是一次性改成零拷贝而是按可验证步骤推进阶段改动结果基线CPU BGR、OpenCV 预处理和显示显示约 17 FPSGPU 预处理CUDA 融合 resize/letterbox/RGB/CHW降低 CPU 占用和预处理耗时NVMM→CUDA去除输入全帧 host staging 和 H2DGPU 预处理低于 1 ms混合显示CUDA RGBA→BGR 后全帧 D2Himshow仍是瓶颈CUDA overlay EGL原位画框FD 直接交给 renderer显示约 26 FPSrender 约 2.6 ms逐框标签小型字形蒙版 CUDA 合成保留硬件显示性能与正确标签位置最终主要瓶颈为TensorRT execute ≈ 35 ms继续优化低于 1 ms 的预处理不会让整体帧率翻倍。如果需要明显提高 FPS应优先测试 512/416 输入尺寸并使用验证集评估小目标召回率而不是盲目增加 CUDA stream 或承诺 INT8 一定更快。Jetson Nano 没有 Xavier/Orin 同级的 INT8 Tensor Core因此 INT8 是否优于 FP16必须实测。15. 复现验收清单建议按以下清单验收nvarguscamerasrc和nvvidconv插件可用当前摄像头 mode 能稳定输出 1280×72060 FPSengine 在目标 Nano 上生成engine、插件、类别数和输入尺寸一致engine_probe输出 binding 符合预期nvmm_probe连续 120 帧无映射错误无显示模式可完成 120 帧并正常退出EGL 显示模式下框、类别和置信度对应正确render_ms保持在数毫秒而不是几十毫秒端到端延迟不会随运行时间持续增长连续运行 10 分钟以上没有明显内存增长CtrlC后 Argus、CUDA、EGL 和 GStreamer 正常释放。16. 总结Jetson Nano 实时检测的关键不是只把模型转换成 TensorRT而是建立完整、可控的数据链路Argus/NVMM 采集 → CUDA EGL 零拷贝映射 → CUDA 融合预处理 → TensorRT FP16 推理 → CPU 解码与 NMS → CUDA 原位绘制 → NvEglRenderer 硬件显示这套实现把显示从约 17 FPS 提升到约 26 FPS并将 EGL 渲染控制在约 2.6 ms。当前性能上限主要由 640×640 YOLOv5n 的 TensorRT 网络执行时间决定而不是摄像头采集、预处理或显示。更重要的是系统使用最新帧槽位控制延迟并用 RAII 管理 NVMM、FD、EGLImage、CUDA resource 和GstSample生命周期。对实时视频系统而言这些数据流和所有权设计与模型推理速度同样重要。建议 CSDN 标签Jetson Nano、YOLOv5、TensorRT、CUDA、GStreamer、NVMM、嵌入式视觉