![[YOLOv8] 在Jetson Nano上实现实时目标检测:从模型转换到TensorRT加速部署全流程](http://pic.xiahunao.cn/yaotu/[YOLOv8] 在Jetson Nano上实现实时目标检测:从模型转换到TensorRT加速部署全流程)
1. 从零开始Jetson Nano环境搭建与YOLOv8模型准备第一次把玩Jetson Nano时这块信用卡大小的开发板给我的印象就是麻雀虽小五脏俱全。作为边缘计算的神器它搭载的128核Maxwell架构GPU对于实时目标检测任务再合适不过。不过在正式部署YOLOv8之前我们需要先给这个小家伙做好热身运动。硬件方面建议选择4GB内存版本实测下来这个配置跑YOLOv8n模型最经济实惠。我试过用2GB版本经常出现内存不足的情况。TF卡至少要32GB起步因为后续安装的JetPack SDK和各种库会占用大量空间。这里有个小技巧选择U3速度等级的TF卡读写速度直接影响系统响应和模型加载效率。软件环境搭建分三个关键步骤刷写系统镜像时推荐使用JetPack 4.6.1这是目前最稳定的版本。烧录工具balenaEtcher操作简单但要注意在Windows系统下可能需要管理员权限才能正常识别TF卡。基础配置完成后建议立即更换apt源。我常用的清华源速度稳定更新软件时能节省不少时间sudo sed -i shttp://.*archive.ubuntu.comhttps://mirrors.tuna.tsinghua.edu.cng /etc/apt/sources.list sudo sed -i shttp://.*ports.ubuntu.comhttps://mirrors.tuna.tsinghua.edu.cng /etc/apt/sources.list安装必备的深度学习环境时顺序很重要。先装OpenCV建议4.5版本再装TensorRT最后装PyTorch。这样能避免库版本冲突的问题。模型准备阶段我强烈建议从Ultralytics官方仓库获取YOLOv8n模型。这个轻量级版本在Nano上实测FPS能达到15而YOLOv8s就会降到7-8帧。转换ONNX格式时有个坑要注意务必指定动态batchFalse因为TensorRT对动态输入的支持有限。我常用的导出命令是model.export(formatonnx, imgsz320, batch1, dynamicFalse)2. 模型转换的艺术从ONNX到TensorRT引擎把ONNX模型转换成TensorRT引擎看似简单实则暗藏玄机。我在这个环节踩过的坑足够写一本《模型转换避坑指南》。首先明确一点转换过程本质上是针对特定硬件做优化所以必须在Jetson Nano本地进行用PC转换的引擎文件大概率无法使用。转换工具trtexec是TensorRT自带的利器但直接使用基础命令往往得不到最优效果。经过多次测试我总结出几个关键参数--fp16开启半精度推理速度提升30%以上--workspace512显存分配大小4GB版Nano建议不超过1024--minShapes/--optShapes/--maxShapes对于动态输入必须指定完整的转换命令应该是这样的trtexec --onnxyolov8n.onnx \ --saveEngineyolov8n.engine \ --fp16 \ --workspace512 \ --minShapesimages:1x3x320x320 \ --optShapesimages:1x3x320x320 \ --maxShapesimages:1x3x320x320转换过程中可能会遇到各种报错最常见的是Unsupported ONNX opset version。这时需要检查ONNX opset版本YOLOv8默认使用opset12而TensorRT 8.x最高支持到opset11。解决方法是在导出ONNX时指定opsetmodel.export(..., opset11)另一个头疼的问题是某些算子不支持。比如YOLOv8的SiLU激活函数在老版本TensorRT中可能报错。我的解决方案是先用onnx-simplifier简化模型python -m onnxsim yolov8n.onnx yolov8n-sim.onnx3. 高效推理C实现与性能优化用Python跑推理虽然方便但在资源受限的Nano上C才是压榨性能的终极武器。我封装了一个YOLOv8类来处理整个推理流程核心包括模型加载、预处理、推理和后处理四个模块。模型加载环节最容易出现内存泄漏。这里分享一个安全加载引擎文件的技巧std::vectorchar engineData(filesize); engineStream.read(engineData.data(), filesize); auto runtime std::unique_ptrnvinfer1::IRuntime(nvinfer1::createInferRuntime(logger)); auto engine std::unique_ptrnvinfer1::ICudaEngine(runtime-deserializeCudaEngine(engineData.data(), filesize));预处理阶段有三个优化点使用CUDA加速的图像resize比OpenCV快3倍采用归一化与BGR2RGB合并的kernel减少内存访问次数预分配所有内存避免在循环中反复申请释放实测下来最耗时的居然是后处理的NMS部分这是因为YOLOv8的输出格式变化导致传统实现效率低下。我的优化方案是将IOU计算改为向量化实现使用线程池并行处理不同类别的NMS对置信度做提前过滤减少无效计算这里给出优化后的NMS核心代码void parallelNMS(std::vectorBox boxes, float iou_thresh) { std::mapint, std::vectorBox class_map; for (const auto box : boxes) { class_map[box.cls].push_back(box); } std::vectorstd::futurestd::vectorBox futures; for (auto [cls, cls_boxes] : class_map) { futures.emplace_back(std::async(std::launch::async, [](){ return singleClassNMS(cls_boxes, iou_thresh); })); } boxes.clear(); for (auto f : futures) { auto res f.get(); boxes.insert(boxes.end(), res.begin(), res.end()); } }4. 性能调优从能用走向好用当第一个检测框出现在屏幕上时那种成就感无与伦比。但要让模型真正实用还需要解决性能瓶颈。我的测试数据显示在320x320输入下整个pipeline耗时约70ms其中预处理占30ms推理仅10ms后处理又占30ms。内存带宽是Nano的最大瓶颈。通过nvidia-smi监控发现推理时GPU利用率只有60%左右。这意味着不是算力不够而是数据搬运太慢。解决方法有两个使用零拷贝内存由于Nano是统一内存架构可以避免主机与设备间的显式拷贝实现异步pipeline让预处理、推理、后处理三个阶段重叠执行我设计的异步流水线结构如下class AsyncPipeline { public: void start() { preprocess_thread_ std::thread(AsyncPipeline::preprocessLoop, this); inference_thread_ std::thread(AsyncPipeline::inferenceLoop, this); postprocess_thread_ std::thread(AsyncPipeline::postprocessLoop, this); } private: void preprocessLoop() { while (running_) { auto frame camera_.getFrame(); preprocess(frame); { std::lock_guardstd::mutex lock(pre_mutex_); pre_queue_.push(frame); } } } void inferenceLoop() { while (running_) { Frame frame; { std::unique_lockstd::mutex lock(pre_mutex_); if (!pre_queue_.empty()) { frame pre_queue_.front(); pre_queue_.pop(); lock.unlock(); engine_-infer(frame); { std::lock_guardstd::mutex lock(post_mutex_); post_queue_.push(frame); } } } } } };温度控制也是实战中的关键点。长时间运行会导致Nano降频我加装了散热风扇并设置了温度监控watch -n 1 tegrastats最终优化后的性能指标分辨率320x32018 FPS分辨率640x6409 FPS功耗5W左右内存占用2.8GB/4GB