ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Jetson Docker部署YOLOv5实战:从基础镜像到TensorRT加速

Jetson Docker部署YOLOv5实战:从基础镜像到TensorRT加速 简介面向需要在NVIDIA Jetson平台如Jetson Nano上运行YOLOv5目标检测的开发者这份资源是一套Docker容器化部署工程帮助跳过手工编译依赖、直接构建可用的推理镜像尤其适合刚接触容器化部署的入门用户。压缩包体积仅4KB但功能完整共9个文件Dockerfile负责定义镜像构建基线两个sh脚本分别封装了镜像构建与运行调用的常用命令patch文件用于环境适配md与txt则提供说明和资源清单让整个工程可以直接复制使用。目前已有52人浏览学习。用户执行build.sh即可构建名为yolov5的镜像通过run.sh连接/dev/video0摄像头实时检测目标若有自定义模型只需按说明准备权重文件并用docker run挂载即可无缝替换默认检测配置。这套方案覆盖了构建、运行、换模三个核心环节为在Jetson上落地YOLOv5提供了清晰、轻量的自动化参考。1. 在 NVIDIA Jetson 上跑 YOLOv5 推理Docker 既是最好的锁也是最大的坑在 NVIDIA Jetson 上部署 YOLOv5 推理我见过最快的交付方式是把整套环境装进 Docker宿主机只负责驱动和系统推理环境用镜像锁死换板子、换项目、换同事接手都不用在现场重排一遍依赖。但这里有个让不少人撞墙的细节——Jetson 的 Docker 镜像跟服务器不通用。你在 x86 机器上跑得好好的 YOLOv5 推理 Dockerfile原样搬到 Jetson 上通常会在第一条docker run报 exec format error哪怕强制拉 arm64 镜像后面还要跟 L4T、JetPack、CUDA 的版本继续较劲。这篇笔记从怎么选基础镜像、怎么把 PyTorch 和 YOLOv5 塞进镜像、怎么在板端跑通一次真实推理一层层拆开讲最后把那些属于板子的坑一个个替你踩平。适合正在做 Jetson 部署、被镜像搞到头大的人读也适合刚拿到板子想少走弯路的人按步骤复现。2. 为什么服务器镜像拉不动Jetson 的容器环境与 x86 的三处偏差2.1 L4T、JetPack 和 arm64板子的 CUDA 不在 Docker Hub 里很多第一次在 Jetson 上写 Dockerfile 的人习惯先去 Docker Hub 找nvidia/cuda官方镜像再往上叠加 YOLOv5 依赖。这个思路在 x86 服务器上完全成立放到 Jetson 上就彻底跑偏。Jetson 的底层系统叫 L4TLinux for TegraNVIDIA 把板子的驱动、CUDA 库、cuDNN、TensorRT 打包成 JetPack SDK 分发。整个生态的硬件平台是 arm64不是 x86_64而且 CUDA 的安装方式也和服务器不同服务器上 CUDA 是独立安装包Jetson 上是跟内核模块、板级驱动绑定在一起发布的。你从 Docker Hub 拉的nvidia/cuda:11.8本质是 x86 或通用 arm64 的 CUDA 运行库它没有 L4T 里的板级补丁、没有 Tegra 专用的库路径拉回来也对接不上板子上的硬件。真正的 Jetson 镜像来源是 NVIDIA 自己的 NGC 仓库比如nvcr.io/nvidia/l4t-base和nvcr.io/nvidia/l4t-ml。我在项目里一般直接拉l4t-ml因为它把 PyTorch、TensorFlow 这些常用框架预装好了省去在板子上一个个 pip 安装的等待时间。缺点是镜像体积大JetPack 5 的 ML 镜像解压后十多个 GB 很正常对小存储的 Jetson Nano 不友好反过来用l4t-base自己搭镜像能压到 4GB 内但要求你对版本对应关系有把握。判断板子当前 L4T 版本最简单的方法是执行dpkg-query -W nvidia-l4t-core输出里的版本号就是 L4T 版本。注意 JetPack 和 L4T 的对应不是完全一一对应我这边的经验是写 Dockerfile 时宁可锁 L4T 的大版本号也不要只写一个“latest”否则镜像换台板子就出现莫名其妙的 CUDA 运行时报错。2.2 三个错误基础镜像选型拉不进来、跑不起来、转出不来我见过不少同事在基础镜像选型上翻车归纳起来是三种典型模式。第一种直接拿ultralytics/yolov5官方镜像跑 Jetson。这个镜像在 x86 上开箱即用但它是给通用平台做的没有为 L4T 定制过。搬到 Jetson 上常见结果是 pull 下来后发现平台不对或用 docker manifest 手动指定 arm64 拉取后运行时又因为缺少 Tegra 相关库直接崩。官方镜像的定位是通用演示和开发不是板端部署强上板子属于给自己找不痛快。第二种选nvidia/cuda官方镜像。这个镜像在 Docker Hub 上有 arm64 变体但 Jetson 的驱动并不是标准 CUDA 安装路径容器里import torch时大概率报libcudart.so.xx.xx: cannot open shared object file。原因是容器内的 CUDA 库和宿主机 L4T 驱动不匹配即使把宿主机的/usr/local/cuda挂载进去也还要处理库路径、软链接和版本一致性绕一圈回到原点。第三种用基础 Ubuntu 镜像再自己装 CUDA。看起来最自由实际坑最多Jetson 上没有 NVIDIA 官方给 x86 用的那种.run安装包CUDA 是通过 JetPack 的 apt 源装到系统里的你手动指定运行库版本时很容易和 L4T 驱动错位最终镜像构建是成功的但torch.cuda.is_available()永远是 False。正确方向是明确一个事实Jetson 的容器不能自带驱动它必须共享宿主机的内核驱动和板级库。基础镜像只需要提供用户空间的应用环境和相匹配的运行库版本剩下的交给 Docker 运行时去打通。2.3 运行时的开 GPU 姿势nvidia-container-runtime 与设备挂载参数镜像写好了运行时还有一个绕不开的环节让容器访问到 Jetson 的 GPU 和其他设备。x86 服务器上常用的--gpus all在 Jetson 上未必总是有效特别是 JetPack 4.x 时代依赖的是--runtime nvidia这个运行时参数。JetPack 在刷机后一般会预装nvidia-container-toolkit你可以先用docker info看运行时列表里有没有 nvidia 这一项确认后再决定命令怎么写。我的习惯是写一个统一的启动脚本在这个脚本里同时带上--runtime nvidia和--network host这样 GPU 和摄像头设备都能正常访问。容器默认访问不了宿主机的/dev/video0这类设备文件使用 CSI 摄像头时还要考虑 ISP 管线映射。我这边实际部署时优先用 USB 摄像头启动命令里加一行--device /dev/video0省去 CSI 设备映射的额外处理。RTSP 视频流则不需要设备映射只要网络通了就能跑更适合生产环境。3. 给 Jetson 写一份能用的 YOLOv5 推理 Dockerfile从零构建到可跑镜像3.1 选基础镜像l4t-base、l4t-ml 还是裸 Ubuntu动手写 Dockerfile 之前的第一个决定是选哪个基础镜像。我给自己定了一条选择标准镜像体积控制 5GB 以内用 l4t-base 自己装开发调试期图省事直接 l4t-ml裸 Ubuntu 不作为候选因为它意味着一整套 CUDA 环境要自己配时间成本远大于收益。下面这份 Dockerfile 我是按 JetPack 5.xL4T r35写的基础镜像用 l4t-base适合对体积敏感的产品镜像。如果你用的是 l4t-ml可以跳过安装 PyTorch 那一步直接放 YOLOv5 的代码和推理脚本即可。# 用于 NVIDIA JetsonJetPack 5.x / L4T r35的 YOLOv5 推理镜像 # 基础镜像来自 NVIDIA NGC注意 tag 要跟板子的 L4T 大版本对上 FROM nvcr.io/nvidia/l4t-base:r35.2.1 # 关闭交互式安装提示pip 不缓存板子存储本来就紧张 ENV DEBIAN_FRONTENDnoninteractive \ PIP_NO_CACHE_DIR1 \ PYTHONUNBUFFERED1 # 系统层依赖python3、opencv 运行库、git 等 RUN apt-get update apt-get install -y --no-install-recommends \ python3-pip python3-dev \ git wget curl \ libgl1 libglib2.0-0 \ rm -rf /var/lib/apt/lists/*这段apt-get的命令里libgl1和libglib2.0-0是 OpenCV 运行时必需的很多人在容器里跑 YOLOv5 时遇到ImportError: libGL.so.1原因就是少了这两个包。rm -rf /var/lib/apt/lists/*是为了清掉 apt 索引缓存板载 eMMC 存储宝贵这一步能给最终镜像省下几百 MB。如果你对板子存储空间没有强迫症直接换 l4t-ml 的 tag 做基础镜像可以把这段系统依赖压缩很多。l4t-ml 镜像的问题是体积大但它在板子上跑 PyTorch 的兼容性经过 NVIDIA 验证踩坑概率更低。3.2 装 PyTorch用对 ARM64 的 wheel不要硬装 x86 轮子PyTorch 在 Jetson 上的安装是这份 Dockerfile 里最容易出错的一段。直接pip3 install torch会去 PyPI 找通用包那些包是给 x86_64 或通用 ARM 的在 Jetson 上要么装不上要么装上之后 CUDA 不可用。正确做法是用 NVIDIA 提供给 Jetson 的预编译 wheel。常见做法是先把板子当前的 JetPack 版本查清楚再对照官方论坛里的 wheel 列表把对应的 torch 和 torchvision 版本号写进pip install命令。# PyTorch 用 Jetson 专用 wheel 安装索引指向 torch 的 arm64 轮子 # JetPack 5.x 对应 torch 1.13.x版本号不对会在 import 时直接崩 RUN pip3 install --no-cache-dir \ torch1.13.0 \ torchvision0.14.0 \ -f https://download.pytorch.org/whl/torch_stable.html这里-f指定的是额外的查找索引不是唯一来源。如果网络条件不好可以改成两段式先从宿主机下好.whl文件用COPY指令放进镜像再安装。这样构建过程更稳定不依赖构建机的临时网络。版本对应关系我最怕别人拿一个老帖子里的组合硬套新板子。JetPack 6 已经换到 torch 2.x你再按 torch 1.13 装大概率import torch的时候报 CUDA 库版本找不到。我习惯把版本组合写进注释里比如torch1.13.0配 JetPack 5.0等板子系统升级后第一件事就是回来更新 Dockerfile 这一行。3.3 把 YOLOv5 放进镜像COPY 源码、权重和入口脚本镜像的应用层我把它拆成三个部分源码、权重、入口脚本。源码强烈建议 COPY 而不是在镜像里git clone因为外部仓库的更新可能让镜像不可复现今天构建成功明天失败。权重单独 COPY采样一个小模型比如 yolov5s.pt正式部署时再替换成自己训练的数据集产出的权重文件。WORKDIR /opt # COPY 本地源码进镜像避免构建时 git clone 外部仓库保证可复现 COPY yolov5 /opt/yolov5 WORKDIR /opt/yolov5 # YOLOv5 的依赖比较多requirements.txt 里会把 numpy 这些基础包一起处理 RUN pip3 install --no-cache-dir -r requirements.txt # 权重放镜像里运行时不再依赖外网下载 COPY weights/yolov5s.pt /opt/yolov5/weights/ # 入口脚本放在 yolov5 目录下方便 import detect 模块 COPY run_detect.py /opt/yolov5/ ENTRYPOINT [python3, run_detect.py]COPY yolov5 /opt/yolov5这段要求你本地已经有一份完整可用的 YOLOv5 源码目录。我不建议直接把整个仓库拷贝进去可以只拷需要的部分detect.py、models目录、utils目录和requirements.txt能显著缩小镜像体积和构建上下文传输时间。入口脚本承担的事很简单把命令行参数转给 YOLOv5 的run函数同时把imgsz固定成 640打开半精度推理。这比直接CMD [python3, detect.py]更可控因为你可以在脚本里做路径检查、设备检查和预热逻辑。# run_detect.py把命令行参数转给 YOLOv5 推理并检查 CUDA 状态 import sys from pathlib import Path sys.path.insert(0, /opt/yolov5) import torch from detect import run if __name__ __main__: import argparse parser argparse.ArgumentParser() parser.add_argument(--source, default/opt/yolov5/data/images) parser.add_argument(--weights, default/opt/yolov5/weights/yolov5s.pt) parser.add_argument(--conf-thres, default0.25, typefloat) parser.add_argument(--iou-thres, default0.45, typefloat) parser.add_argument(--project, default/output) parser.add_argument(--name, defaultdet) args parser.parse_args() if not torch.cuda.is_available(): raise RuntimeError(CUDA not available: check --runtime nvidia when running container) # halfTrue 在 Jetson 上通常能带来 20% 以上的速度提升 run(weightsargs.weights, sourceargs.source, conf_thresargs.conf_thres, iou_thresargs.iou_thres, projectargs.project, nameargs.name, imgsz(640, 640), halfTrue, exist_okTrue)run()是 YOLOv5 源码里暴露的函数入口参数跟命令行参数一一对应。exist_okTrue很关键没有它时同一project/name下重复运行会报目录已存在。我把halfTrue写在这层而不是指望调用方记得加因为 Jetson 上有 Tensor Core 的话半精度性能提升明显而且 YOLOv5 的 half 推理在 640 输入下精度损失很小。4. 构建与运行先验证镜像里 CUDA 通不通再做真实验收4.1 docker build 时的三个前置检查空间、内存、网络在板子上直接构建镜像最容易遇到的不是 Dockerfile 语法错误而是硬件资源不够。Jetson 的入门级板子内存普遍在 4GB 到 8GBpip 安装 torch 或 opencv 这类大型包时内存占用会瞬间冲高构建进程被系统杀掉时日志末尾通常只有一行Killed。我每次在板子上构建之前的例行检查是三个命令df -h /var/lib/docker free -h ping -c 2 download.pytorch.orgdf看存储free看内存ping确认能不能访问 wheel 索引。存储不够时先docker system prune -a清旧镜像内存不够时给板子加一块 NVMe 并启用 swap 文件8GB 内存的板子配 8GB swap 基本能保证构建不碎。如果宿主机是 x86 开发机想交叉构建 arm64 镜像可以用docker buildx指定--platform linux/arm64但要注意 buildx 默认用 QEMU 模拟构建速度慢而且某些原生库在模拟环境下编译不过。我的做法是宁可把 Dockerfile 和源码同步到板子上直接构建时间上反而更快。# 在板子上构建-f 指定 Dockerfile 文件名最后一个 . 是构建上下文 docker build -f Dockerfile.jetson -t yolov5-jetson:latest .构建成功后再看镜像大小docker images | grep yolov5-jetson。如果镜像超过 6GB检查是不是系统依赖装多了比如 apt 装了一堆没用的调试工具。镜像越大后续在内网传输和部署就越痛苦。4.2 镜像构建完成后的第一件事验证 GPU 可见很多人在这一步跳过验证直接跑推理脚本然后对着torch.cuda.is_available()返回 False 的日志发呆。我建议把 GPU 验证作为构建后的第一个阻塞门禁命令很简单但能排除一半的运行时问题。# 验证容器内能否访问 Jetson GPU docker run --rm --runtime nvidia --entrypoint python3 \ yolov5-jetson:latest \ -c import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))如果输出True NVIDIA Tegra X1或类似设备名说明 GPU 通路没问题可以继续。如果输出 False优先检查--runtime nvidia这个参数有没有生效可以先用docker info看有没有 nvidia runtime。JetPack 5 里如果之前手动装过 Docker可能会把 nvidia-container-toolkit 的配置覆盖这时需要在/etc/docker/daemon.json里重新加上 runtime 配置并重启 Docker 服务。注意--entrypoint python3会覆盖镜像里的默认入口脚本所以后面要跟-c来执行一段内联代码。这段验证不用跑完整推理节省时间而且结论明确。4.3 用图片和摄像头跑一次真实推理GPU 验证通过后就可以跑第一张图片。YOLOv5 仓库自带测试图片我在启动容器时会把输出目录挂载出来方便在宿主机上直接看检测结果。# 用仓库自带图片做第一轮推理 docker run --rm --runtime nvidia \ -v $(pwd)/output:/output \ yolov5-jetson:latest \ --source /opt/yolov5/data/images/bus.jpg推理脚本默认把结果写到/output宿主机当前目录下的 output 文件夹里就能看到标注了检测框的 bus.jpg。第一轮推理通常会比较慢因为模型加载、权重解析、CUDA 上下文创建都挤在第一次调用里这是正常现象不要因此怀疑镜像有问题。摄像头推理比图片多一个设备映射参数以 USB 摄像头为例# 用 USB 摄像头做实时检测--device 把宿主机的 video 设备放给容器 docker run --rm --runtime nvidia \ --device /dev/video0 \ -v $(pwd)/output:/output \ yolov5-jetson:latest \ --source /dev/video0摄像头跑流时注意容器内的 OpenCV 是否带 V4L2 支持。如果报Cannot open camera先确认宿主机上v4l2-ctl --list-devices能看到设备再确认容器里没有缺libv4l这类库。RTSP 流更简单不需要映射设备文件把--source直接改成 RTSP 地址就行生产环境我一般是这么用的。5. Jetson 容器推理避坑记录五个现象级问题的排查与处理5.1 现象构建到一半进程被 Killed构建 torch 或 opencv 这类重依赖时日志最后只有一行Killed没有报错堆栈。这是 Jetson 板内存不足OOM killer 把构建进程干掉了。原因板载内存不够 pip 在安装大型包时的峰值占用尤其 torch 的 arm64 wheel 解压和安装过程需要额外内存。解决给板子加 swap。先在宿主机制作一个 8GB 的 swapfile 并启用再重新构建。我自己的习惯是同时把PIP_NO_CACHE_DIR打开避免 pip 缓存再吃一份磁盘和内存空间。5.2 现象import torch 报 libcudart.so.x.x 找不到容器能启动但进去后import torch直接报libcudart.so.11.0: cannot open shared object file。原因基础镜像里的 CUDA 运行库和宿主机 L4T 驱动版本不对应。Jetson 的 CUDA 库是 JetPack 版本绑定的容器里装的是 CUDA 11.x宿主机的 L4T 驱动却是另一套版本。解决先查宿主机dpkg-query -W nvidia-l4t-core的 L4T 版本再去 NVIDIA 官方论坛的 wheel 列表里找对应的 torch 和 torchvision 组合。镜像 tag 的 L4T 版本、torch 的 wheel 版本、宿主机 JetPack 版本必须形成一条锁定的对应链任何一个不匹配报错都是这样一句没头没尾的缺库。5.3 现象docker run 直接报 exec format error命令一执行就报 exec format error根本没进到 Python 那一步。原因镜像平台是 x86_64板子架构是 arm64。常见于从 Docker Hub 直接拉ultralytics/yolov5或nvidia/cuda的默认镜像。解决放弃用通用镜像改用nvcr.io/nvidia/l4t-base或l4t-ml起手。如果你确实想在本地 x86 机器上构建 arm64 镜像用docker buildx --platform linux/arm64但注意部分依赖在模拟环境下编译失败这种构建产物也要在板子上完整验证一轮才能进生产。5.4 现象torch.cuda.is_available() 返回 False镜像、容器都正常但就是调不起 GPU。原因启动容器时没有加--runtime nvidia。JetPack 4 对--gpus all的兼容性不好很多人被近期服务器的 Docker 习惯带偏在板子上用--gpus all启动然后发现 CUDA 不可用。解决统一用--runtime nvidia启动。启动前先用docker info | grep -i nvidia确认 runtime 配置在如果不在需要重新安装配置 nvidia-container-toolkit。这一条排查成本最低却是我见到出错率最高的一环。5.5 现象第一帧很慢后续帧也不稳定推理能跑速度却远低于预期帧率忽高忽低。原因第一帧慢是正常的因为模型权重要加载到显存CUDA 上下文要初始化。后续帧不稳定就要查是不是 CPU 频率被锁了或者板子处于默认省电模式。解决加一段预热逻辑在正式推理前用一张黑色图片跑一次检测让 CUDA 和 TensorRT 相关上下文初始化完。运行时用jetson_clocks把 CPU/GPU 频率拉满。这里注意如果你是用自定义数据集训练的权重在 Dockerfile 里直接替换成best.pt第一帧预热对它的效果同样成立。6. 让推理再快一步TensorRT 引擎与吞吐验证方法YOLOv5 在 Jetson 上跑 PyTorch 原模型性能只能说“够用”。要榨出板子真正的推理能力尽量把模型转成 TensorRT 引擎。转换这一步可以在容器内完成因为镜像里已经有完整的依赖环境。# 在容器内把模型导出为 TensorRT engine # 引擎文件和板子的 JetPack 版本绑定换板子必须重新导出 docker run --rm --runtime nvidia \ -v $(pwd)/weights:/opt/yolov5/weights \ --entrypoint python3 \ yolov5-jetson:latest \ export.py --weights yolov5s.pt --include engine --half --device 0export.py是 YOLOv5 自带的导出脚本--include engine指定生成 TensorRT 引擎--half用 FP16 精度--device 0指定用 GPU 转换。导出的.engine文件放在 weights 目录下推理时把--weights改成.engine文件即可。TensorRT 引擎有一个被很多人忽略的性质它跟硬件的 GPU 架构和 JetPack 版本强绑定。在这台 Jetson Orin 上导出的 engine 文件拷贝到另一台相同型号、相同 JetPack 版本的板子上能跑但换一个 JetPack 版本就可能直接拒绝加载更不用说在 Xavier 和 Orin 之间互拷。正确的做法是每台板子各自导出一次 engine这事绕不过去。验证推理结果最直接的方法是跑一段固定数量的视频帧或图片统计平均耗时不要拿单帧时间当性能指标。我一般用一百张测试图片跑一轮取总耗时除以数量再和 PyTorch 原模型的均值对比。实测在常见的 Jetson 板子上TensorRT FP16 通常能比 PyTorch FP16 再快一到两倍而精度下降通常在可接受范围用 mAP 对比的话大概在零点几到一两个点之间。验证另一个重要的点是确认推理确实走的是 GPU 而不是 CPU。在容器里跑推理时另开一个终端执行tegrastats观察 GPU 使用率。如果推理过程中 GPU 占用常年低于 30%说明大部分时间耗在了数据加载和预处理上这时优化方向应该看输入管线和图像缩放而不是继续折腾模型。我现在的习惯是每次部署都保留三样东西一份锁版本的 Dockerfile、一份和镜像同标签的测试结果记录、一份导出的 engine 文件。前两样保证环境可复现engine 文件单独放在板子上不放进镜像里因为它的生命周期跟板子绑定跟镜像无关。这套流程从早期踩坑到现在基本没再返工过希望帮到你。本文还有配套的精品资源点击获取
返回列表