ARTICLE DETAIL

资讯详情

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

树莓派上跑YOLO模型:从ONNX转换到实时检测的完整部署指南

树莓派上跑YOLO模型:从ONNX转换到实时检测的完整部署指南 1. 为什么非要用树莓派跑YOLO场景与选型思考如果你和我一样在电脑上用GPU训好了YOLO模型然后想让它在树莓派上跑起来这件事听起来简单真正动手会发现全是细节。先说清楚一个很多人不愿意面对的事实树莓派不是做边缘推理性能最强的板子但它可能是综合折腾成本最低、资料最全、社区生态最成熟的板子。我见过有人用树莓派4B做车间设备状态识别有人拿它做宿舍门口的猫脸门禁还有人直接拿来当毕设的硬件载体。这些场景都有一个共同点不需要每秒处理30帧不需要4K实时流但需要低功耗、离线运行、稳定不宕机。树莓派4B的CPU是BCM2711四核Cortex-A72主频默认1.5GHz可以超到1.8GHz。没有独立NPU没有GPU加速那个VideoCore VI活在很底层的地方别指望它跑YOLO。所以你的整个推理负载全部压在CPU上。这意味着什么意味着模型选型和优化比训练时更重要YOLOv8x这种大模型想都别想老老实实回到轻量级模型这条路上。很多人纠结一个问题既然树莓派算力这么有限为什么不用Jetson Nano或者干脆买个RK3588的板子我的看法是算力高当然好但价格、功耗、开发资料、上手难度是四座大山。Jetson Nano的Maxwell GPU跑TensorRT确实猛但那个镜像、SDK Manager、CUDA版本之间的相互折磨不是每个人都能用周末时间搞定的。树莓派上你只需要一个apt update和一个pip install就能开始干活这种“开箱即用”的体验决定了它更适合做验证原型和教学平台也适合预算有限的个人项目。另一个让人意外的好处是生态兼容性。你训练YOLO时用的PyTorch、Ultralytics这些库在树莓派上都有ARM版本的wheel包。ONNX Runtime官方提供aarch64的安装包TFLite也有对应的轮子。这种“训练环境到推理环境无缝迁移”的顺畅感恰恰是其他边缘硬件最稀缺的东西。所以我的结论很明确如果你的项目要求的是“在一个可控的环境里、以足够用的速度、让模型稳定跑起来”树莓派是一个非常合适的选择。接下来我按自己的实际部署流程把从模型转换到摄像头运行的完整链路拆开讲。2. 从训练权重到边端格式模型转换这条路2.1 为什么不能直接在树莓派上跑PyTorch权重你的训练产物是一个.pt文件比如best.pt。这在树莓派上能用吗能但很不推荐。先算一笔账PyTorch的CPU推理在x86机器上已经比GPU慢一个数量级了在树莓派这种Cortex-A72上只会更惨。而且PyTorch本体的体积和依赖树非常重装完torch和torchvisionSD卡空间去掉好几个G内存占用也会被顶到很高。树莓派4B的4GB内存版本光加载PyTorch环境就已经吃掉一个G了留给图像数据的空间所剩无几。更重要的是PyTorch的推理路径在ARM上并没有做深度优化。你会发现同一个模型用ONNX Runtime加载跑比用PyTorch原生跑快20%到40%。原因很简单ONNX Runtime针对ARM架构做了线程调度和算子融合优化而PyTorch的CPU后端在移动端和嵌入式端的优化一直不是重点。所以部署的第一步永远是格式转换。这一步在电脑上完成树莓派只吃转换后的产物。2.2 部署格式怎么选ONNX、TFLite还是NCNN如果你在CSDN和GitHub上翻过树莓派部署YOLO的帖子会发现三种格式最常见ONNX、TFLite、NCNN。它们成都差不多但各有偏向。格式适合场景树莓派上的感受量化支持ONNX通用性强、PyTorch到部署的桥头堡ONNX Runtime在ARM上有官方优化速度适中支持INT8/FP16量化但ARM CPU上的FP16优势不明显TFLite嵌入式设备、移动端支持Delegate加速但树莓派上没有GPU Delegate优势主要在量化和核数优化INT8量化成熟转换工具链顺滑NCNN国产方案、手机/IoT需要交叉编译或在板上编译门槛稍高也支持INT8但配置过程费时如果你只是想把训练好的YOLO在树莓派上稳定跑起来我最推荐的是ONNX。原因很简单它离你训练的PyTorch生态最近中间环节少转换过程最不容易出幺蛾子。等你后续想玩量化或者跑更极致的优化再切到TFLite或NCNN也不迟。如果你的模型是在Ultralytics的YOLO框架比如YOLOv8、YOLOv11下训练出来的转换只需要一行命令yolo export modelbest.pt formatonnx opset12 simplifyTrue注意opset参数。ONNX的算子版本太高ONNX Runtime的ARM版本可能不认。实测在树莓派上opset 12到14之间最稳别一上来用opset 17那不是给自己找麻烦吗。转换完之后用净输入检查一下是不是真的转换成功了。别急着关电脑先在本机跑一遍ONNX Runtime的推理确认输出和PyTorch原版一致再拷贝到树莓派上。2.3 更细一步尝试INT8量化在树莓派的CPU上模型量化带来的收益极其明显。同一个YOLOv8n模型FP32的ONNX在树莓派4B上跑一帧大概要500到600毫秒INT8量化之后能压缩到300毫秒左右。这就是质的区别了。TFLite生态里做量化最顺用Ultralytics直接导出TFLite模型也行yolo export modelbest.pt formattflite int8True不过这里有个坑TFLite的INT8量化需要标定数据集。Ultralytics集成的转换流程默认用一部分训练数据做标定但这会显著拖慢转换时间而且如果你的训练数据和实际应用场景差异很大量化后精度掉得很厉害。我自己的实测结果在交通标志识别任务上FP32的mAP是0.89INT8量化之后掉到0.85左右。识别一些对比度不明显的目标时漏检率明显上升。所以量化前一定要拿一批真实场景图片做验证集最好通不过再用回FP32。另一个选项是用ONNX Runtime的动态量化把FP32权重量化到INT8代码很简单from onnxruntime.quantization import quantize_dynamic, QuantType quantize_dynamic(best.onnx, best_int8.onnx, weight_typeQuantType.QUInt8)动态量化不需要标定数据集实现成本低速度提升大概25%到35%。但我的体感是动态量化的数值稳定性不如TFLite的静态量化某些层的输出浮动会稍微大一点。对于毕设、课程设计、以及在比较稳定的光照条件下运行的视觉任务来说完全够用。2.4 验证转换结果这一步不能省转换完模型后我强烈建议你在电脑上写一个最小验证脚本。用一张训练集之外、最好是从实际场景里拍的照片分别喂给PyTorch模型和转换后的模型对比检测框的坐标和置信度。import onnxruntime as ort import torch import numpy as np from ultralytics import YOLO # PyTorch原版推理 model YOLO(best.pt) results model(test.jpg, verboseFalse) boxes_pt results[0].boxes.xyxy.cpu().numpy() scores_pt results[0].boxes.conf.cpu().numpy() # ONNX Runtime推理 session ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) input_name session.get_inputs()[0].name img preprocess(test.jpg) # 1x3xHxW的float32数组 outputs session.run(None, {input_name: img}) # 后处理拿到boxes和scores # ...对比两组检测框允许有微小误差坐标差几个像素、置信度差零点零几但如果出现漏检或者大量错检说明转换过程出了问题。常见的原因包括输入尺寸与训练时不匹配、归一化方式不一致应该除以255还是减均值除方差、模型本身包含了非标准算子导致转换后行为漂移。这个验证脚本建议直接使用venv的Python环境配好onnxruntime之后测试确认无误后再把模型拷贝到树莓派上。3. 树莓派环境搭建别急着pip install3.1 系统选型与基础配置树莓派部署YOLO系统层面我最推荐Raspberry Pi OS Bookworm的64位版本。原因有三个第一64位系统能充分利用4GB甚至8GB内存跑YOLO推理时不会因为地址空间不足而莫名崩溃。第二Bookworm系统本身自带的Python版本是3.11对ONNX Runtime、OpenCV这些库的兼容性很好。第三libcamera和Picamera2这些摄像头库在Bookworm上是默认集成的直接可以用不用像老系统那样折腾raspistill那一套。拿到系统并烧录到SD卡之后不要急着装环境先把以下几件事做完执行sudo raspi-config在Performance Options里把GPU Memory设成16MB或者32MB。YOLO推理用不到GPU内存留太多反而挤压系统可用内存。开启SSH方便你从电脑上远程操作。我个人习惯是全程SSH连接到树莓派推理结果显示在电脑上这样不用额外接显示器和键鼠。更换软件源到国内镜像这一步在部署大包时能省下一大把时间。还有一个小坑必须提一下SD卡的空间。一个干净的Bookworm系统大概占用4GB到5GB安装OpenCV、ONNX Runtime以及后续的模型文件很容易突破15GB。建议直接用32GB以上的SD卡否则装到一半空间满所有工作白费。3.2 swap分区与内存之道树莓派4B的4GB内存版本在跑YOLO推理时情况是这样的模型加载到内存大概占用500MB到800MB输入图像缓存几百MBOpenCV和系统本身再占几百MB整体压力不小。如果你用的是2GB版本内存吃紧的情况更明显。我的做法是先把swap从默认的100MB调高到1024MB。方法很简单sudo nano /etc/dphys-swapfile # 找到 CONF_SWAPSIZE100改成 CONF_SWAPSIZE1024 sudo systemctl restart dphys-swapfile但注意swap只是兜底方案它能防止程序因内存不足被杀掉但swap的读写速度比内存慢得多如果你发现推理进程经常卡住不动大概率是内存不够导致跑到swap上去了。这时候更好的办法是换更小的输入尺寸或者更轻量的模型而不是加大swap死扛。3.3 安装OpenCV与ONNX Runtime树莓派上装OpenCV我强烈建议直接pip安装不要自己编译。OpenCV从源码编译需要数小时而且内存小的板子容易在编译过程中OOM。pip上已经有编译好的ARM版本sudo apt update sudo apt install -y python3-pip python3-venv python3 -m venv yoloenv source yoloenv/bin/activate pip install opencv-python-headless这里选择opencv-python-headless而不是opencv-python因为我们在树莓派上跑的是纯Python推理脚本不需要GUI显示窗口。Headless版本体积更小、依赖更少也更不容易跟系统的摄像头库发生冲突。接着安装ONNX Runtimepip install onnxruntime在树莓派的aarch64架构上官方PyPI源会直接分发对应的wheel包不需要自己编译。装好后可以验证一下能加载的providerimport onnxruntime as ort print(ort.get_available_providers())正常情况下会输出[CPUExecutionProvider]。不要指望这里出现CUDA或TensorRT树莓派上没有这些东西。还有一个容易被忽略的包numpy。ONNX Runtime的wheel有一定版本的numpy依赖如果pip自动给你装了最新版导致冲突手动把numpy降到指定版本即可。我的经验是pip install numpy2比较稳ONNX Runtime 1.16以上的版本对新numpy的兼容性有坑。3.4 把模型和代码同步到树莓派模型文件比如best.onnx大小一般在10MB到50MB之间最省事的方式是在电脑上直接用scp传过去scp best.onnx pi树莓派IP:~/yolo/如果你的电脑和树莓派不在同一局域网也可以用U盘拷贝反正模型文件不大传输成本忽略不计。代码文件可以直接写一个Python脚本传过去或者直接在树莓派上用nano编辑。我习惯在电脑上写好测试OK再传因为在树莓派上编辑代码实在不是一种享受。4. 推理代码的完整拆解从读图到画框的过程4.1 预处理letterbox为什么不能省YOLO训练时会对输入图像做letterbox操作也就是把原始图像等比缩放到目标尺寸比如640×640剩余部分用灰色填充。这样做的目的是保持图像的长宽比避免目标被拉伸变形。推理时的预处理必须和训练时保持一致否则检测精度会明显劣化。大部分教程给出的预处理是把图像直接resize到640×640这种写法是错的。直接resize会让原本圆形的物体变成椭圆YOLO虽然有一定鲁棒性但小目标的检测框会明显偏移。我用一个真实的对比数据在安全帽检测任务中直接resize的mAP比letterbox低了4个点这个差距在部署中是不能接受的。正确的letterbox实现def letterbox(img, new_shape(640, 640), color(114, 114, 114)): shape img.shape[:2] # 原始H,W r min(new_shape[0] / shape[0], new_shape[1] / shape[1]) new_unpad (int(round(shape[1] * r)), int(round(shape[0] * r))) dw (new_shape[1] - new_unpad[0]) / 2 # 宽方向的padding dh (new_shape[0] - new_unpad[1]) / 2 # 高方向的padding dw, dh int(dw), int(dh) img cv2.resize(img, new_unpad, interpolationcv2.INTER_LINEAR) top, bottom dh, new_shape[0] - new_unpad[1] - dh left, right dw, new_shape[1] - new_unpad[0] - dw img cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, valuecolor) return img, r, dw, dh注意函数返回的r、dw、dh这三个值在后处理的坐标还原时会用到很多人在这里丢掉了还原参数导致检测框位置完全错掉。这是新手最容易踩的坑。4.2 前向推理ONNX Runtime的会话管理加载ONNX模型并执行推理代码非常简洁session ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) input_name session.get_inputs()[0].name input_shape session.get_inputs()[0].shape # 假设输入是 [1, 3, 640, 640]这里有一个性能的关键点会话Session应该只初始化一次然后在循环里复用。很多人的做法是在每一帧里重新创建InferenceSession这样模型每次都要重新加载和解析速度会慢五到十倍。正确的方式是在程序启动时加载模型之后主循环只做前向计算。还有一个小细节输入张量的数据布局。ONNX Runtime默认输入是NCHW格式也就是[batch, channel, height, width]。从OpenCV读出来的图像是HWC格式所以需要先做通道变换和归一化img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img img.astype(np.float32) / 255.0 img img.transpose(2, 0, 1) # HWC - CHW img np.expand_dims(img, axis0) # 加batch维 outputs session.run(None, {input_name: img})如果你跳过/255.0这一步模型的输出置信度会全部错乱。YOLO训练时的输入分布通常是[0,1]区间推理时的预处理必须保持一致。4.3 后处理从原始输出到可见的检测框ONNX Runtime输出的原始张量长什么样取决于模型的导出方式。以YOLOv8为例典型的输出形状是[1, 84, 8400]其中84代表4个框坐标加上80个类别置信度8400代表所有尺度的候选框数量。很多人拿到这个输出直接被吓住了其实后处理逻辑并不复杂核心分三步第一把输出的形状从[1, 84, 8400]转成[8400, 84]方便逐行处理。第二筛选出置信度超过阈值的行。第三对这些候选框做NMS非极大值抑制合并重叠框。这里我直接给出一个精简但可用的实现def postprocess(output, conf_thres0.5, iou_thres0.45): # 压缩batch维度 [1,84,8400] - [84,8400] - [8400,84] preds output[0].squeeze(0).transpose(1, 0) # 需要根据实际输出维度调整 boxes preds[:, :4] # cx,cy,w,h class_scores preds[:, 4:] class_ids np.argmax(class_scores, axis1) confs class_scores[np.arange(len(class_scores)), class_ids] mask confs conf_thres boxes boxes[mask] class_ids class_ids[mask] confs confs[mask] # 把cx,cy,w,h转成x1,y1,x2,y2 xyxy np.zeros_like(boxes) xyxy[:, 0] boxes[:, 0] - boxes[:, 2] / 2 xyxy[:, 1] boxes[:, 1] - boxes[:, 3] / 2 xyxy[:, 2] boxes[:, 0] boxes[:, 2] / 2 xyxy[:, 3] boxes[:, 1] boxes[:, 3] / 2 # NMS indices cv2.dnn.NMSBoxes(xyxy.tolist(), confs.tolist(), conf_thres, iou_thres) if len(indices) 0: indices np.array(indices).flatten() return xyxy[indices], confs[indices], class_ids[indices] return [], [], []这套后处理逻辑不依赖任何YOLO特有的库纯numpy和OpenCV实现在树莓派上跑完全没问题。把它封装成函数之后主程序的逻辑就变得非常清晰了。4.4 坐标还原letterbox参数的最后一次使用获得检测框之后需要把框坐标映射回原始图像尺寸。这一步必须用到预处理时保存的r缩放比、dw宽pad和dh高pad。公式很简单restored_x1 (xyxy[:, 0] - dw) / r restored_y1 (xyxy[:, 1] - dh) / r restored_x2 (xyxy[:, 2] - dw) / r restored_y2 (xyxy[:, 3] - dh) / r注意这里的框坐标是相对于预处理后640×640的图像而言的减去pad并除以缩放比之后就回到了原始分辨率坐标系。画框时候直接用cv2.rectangle在原图上画即可。到这里一个完整的静态图片检测脚本就全部跑通了。接下来是这个项目真正体现价值的地方——让模型对摄像头画面做实时检测。5. 摄像头实时检测迈向可用的边缘设备5.1 摄像头选型CSI还是USB树莓派的摄像头方案有两种主流选择一个是CSI接口的摄像头模块比如树莓派官方的OV5647或者更高像素的IMX219另一个是普通USB摄像头。从延迟角度讲CSI摄像头有明显优势。CSI通过专用接口直连SoC数据不走USB带宽延迟低CPU占用也小。USB摄像头插上就能用通用性强但图像通过USB总线传输时会有额外的复制和调度开销在低帧率场景下体感不明显但在追求稳定性的实时检测中我会选CSI摄像头。从软件栈来说Bookworm系统上必须使用libcamera和Picamera2库。老教程里那些raspistill、picamera库已经过时了直接用会报错。用Picamera2读取CSI摄像头的流程如下from picamera2 import Picamera2 import cv2 import numpy as np picam2 Picamera2() config picam2.create_preview_configuration( main{size: (640, 480), format: RGB888} ) picam2.configure(config) picam2.start() frame picam2.capture_array() # 返回numpy数组格式为RGB注意Picamera2返回的是RGB格式和OpenCV的BGR格式不一致。如果你习惯用OpenCV的函数做后续处理记得加一行cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)或者干脆保持RGB处理只在用cv2.imwrite或cv2.imshow时转换。5.2 实时检测主循环把前面所有代码整合起来一个完整可用的实时检测脚本是这个样子的import cv2 import numpy as np import onnxruntime as ort from picamera2 import Picamera2 # 初始化模型会话一次 session ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) input_name session.get_inputs()[0].name # 初始化摄像头 picam2 Picamera2() config picam2.create_preview_configuration(main{size: (640, 480), format: RGB888}) picam2.configure(config) picam2.start() def detect_frame(frame): # 预处理 letterboxed, r, dw, dh letterbox(frame, new_shape(640, 640)) blob letterboxed.astype(np.float32) / 255.0 blob blob.transpose(2, 0, 1)[np.newaxis, ...] # 推理 outputs session.run(None, {input_name: blob}) # 后处理 boxes, confs, cls_ids postprocess(outputs[0]) # 还原坐标 if len(boxes) 0: boxes[:, [0, 2]] (boxes[:, [0, 2]] - dw) / r boxes[:, [1, 3]] (boxes[:, [1, 3]] - dh) / r return boxes, confs, cls_ids while True: frame picam2.capture_array() boxes, confs, cls_ids detect_frame(frame) for box, conf, cls_id in zip(boxes, confs, cls_ids): x1, y1, x2, y2 box.astype(int) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, f{names[cls_id]} {conf:.2f}, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) # 只在电脑端往前传一帧用于显示或者保存到本地 # cv2.imshow(result, frame) # if cv2.waitKey(1) 0xFF ord(q): # break在实际的树莓派部署中我通常不会用cv2.imshow在树莓派本机显示画面因为树莓派可能没有接显示器。替代方案有两种一是把检测结果用MQTT或HTTP发给电脑端二是直接把叠加了检测框的帧保存到SD卡或网络磁盘。这两种方式都需要额外的网络代码这里就不展开了。我用过的最简单方案是在树莓派上开启一个HTTP服务用WebSocket往网页端推流延迟在200毫秒以内效果还可以。5.3 实时运行时的性能瓶颈分析跑通实时检测之后你应该做一个瓶颈分析而不是直接认为“工作完成了”。我自己实测的典型数据在树莓派4B上环节耗时摄像头取一帧640×48030~50msletterbox 预处理15~20msONNX推理FP32640×640输入500~600ms后处理NMS等10~20ms推理占了绝对大头。这就是为什么我强调模型格式转换和量化因为不优化推理时间其他环节再怎么折腾都没意义。如果推理时间降不下来退而求其次的做法是降低输入分辨率。YOLOv8n在416×416输入下比640×640大约快25%到30%精度下降对小目标不太友好但如果你检测的目标本来就比较大降低分辨率完全可行。另一个方案是开启ONNX Runtime的多线程session ort.InferenceSession( best.onnx, providers[CPUExecutionProvider], sess_optionsort.SessionOptions() ) session.set_intra_op_num_threads(4) session.set_inter_op_num_threads(1)树莓派4B是四核CPU把intra-op线程数设成4能让推理过程中的矩阵运算利用多核。实测下来线程数从1调成4推理速度大约提升1.5倍。但注意线程数不是越大越好超过4反而会因为线程切换开销导致性能下降。5.4 长时间运行时的内存与温度问题实时检测意味着程序可能长时间运行。这里有两个隐藏问题一个是内存泄漏一个是过热降频。先处理内存泄漏的排查方法。在你的主循环里加一段打印内存占用的代码比如用psutilimport psutil mem psutil.virtual_memory() print(fused: {mem.used / 1024 / 1024:.1f}MB, percent: {mem.percent})如果发现内存占用随时间线性增长先检查是不是每一帧都在创建新的numpy数组而没有释放旧引用。特别注意np.expand_dims和transpose会产生新的数组这些数组如果没有及时赋值为局部变量并在循环结束后释放内存会越堆越高。最稳妥的办法是把预处理后的blob变量在每一轮循环末尾主动置为None让Python的引用计数立刻归零。温度问题同样重要。树莓派4B的CPU在达到85℃时会主动降频从1.8GHz一路掉到600MHz推理速度会断崖式下跌。所以如果你的设备要在机箱里密闭运行必须加强散热。我的实测数据裸板带一个小散热片持续跑YOLO推理时CPU温度稳定在65℃左右加了风冷散热器能压到50℃以下如果什么都不加运行半小时温度突破85℃然后推理时间从500ms涨到800ms体验非常拉胯。6. 速度优化与散热玄学实测数据说话6.1 让CPU火力全开树莓派默认的CPU调频策略是ondemand或者schedutil这意味着CPU频率会根据负载动态调整。对于实时推理这种突发负载CPU频率切换会有延迟导致单帧耗时波动。把CPU governor固定为performance模式可以消除这种波动sudo sh -c echo performance /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor这条命令在重启后会失效如果不想每次都手动执行可以把它写进/etc/rc.local。另一个容易被忽略的设置是raspi-config里的超频选项。树莓派4B可以安全地把CPU超到1.8GHz在官方散热器加持下稳定性没有问题。超频对YOLO推理的收益很明显实测下来推理时间能再降10%左右。6.2 不同模型和格式在树莓派4B上的实测对比为了让你对目标帧率有个直观预期我把自己在树莓派4B 4GB版本上的测试数据整理成表格。模型统一用COCO预训练或自己微调的变体输入640×640CPU四线程模型格式单帧推理耗时约等帧率YOLOv5nONNX FP32420ms2.4 FPSYOLOv8nONNX FP32560ms1.8 FPSYOLOv8nONNX INT8动态量化360ms2.8 FPSYOLOv8nTFLite INT8310ms3.2 FPSYOLOv8nTFLite INT8 输入416230ms4.3 FPSYOLOv11nONNX FP32520ms1.9 FPS这里有一个重要信息YOLOv8n在纯CPU推理下比YOLOv5n慢原因在于v8的anchor-free结构在解码阶段计算量更大。如果你的部署任务对帧率要求高优先考虑YOLOv5n而不是盲目用最新版本。老模型在边缘推理里更吃香这是很多人不会告诉你的事。6.3 减少输出层冗余模型裁剪很多YOLO模型在导出ONNX时输出张量包含了训练时用到的多余分支。比如YOLOv8的export默认会带上一个1×84×8400的输出但如果你只做检测不需要输出类别概率矩阵中的细粒度信息可以尝试用模型重参数化或者导出时去掉不必要的头。不过这个操作对代码能力要求较高如果你只是想快速部署可以不折腾。6.4 如果还是不够快怎么办在树莓派上把YOLO跑到4 FPS到5 FPS基本到了CPU方案的极限。如果这个速度还不能满足需求接下来的道路就分叉了换更轻量的模型。YOLOv8n已经是最small的版本但你可以换更大的输入尺寸不是换更小的输入尺寸或者换成专门为边缘设备设计的轻量模型比如NanoDet、PP-PicoDet。这些模型在CPU上的推理速度比YOLO快不少代价是精度略低。加一块神经计算棒。英特尔的Movidius NCS2或者Google Coral TPU都是插在USB上的推理加速设备Coral TPU跑INT8模型的延迟能压到20ms左右。但注意Coral TPU支持的是TFLite模型而且对算子的支持有限你的YOLO模型需要先用工具链转换和校准这一步可能会让你怀疑人生。换一块更强的板子。这是个很实在的建议。如果你的项目已经跑通了逻辑只是性能不够直接考虑瑞芯微RK3588或者英伟达Jetson Orin Nano。这两类板子都有NPU或GPU加速跑YOLO的体验和树莓派完全不是一个量级。树莓派的价值在于快速验证和算法可行性研究到了要交付性能指标的阶段该换平台就换平台不要恋战。我自己的体会是树莓派部署YOLO这个事真正的价值不在于最终能跑到多少帧率而在于它让你把整个部署链路完整走了一遍从模型转换、环境搭建、推理优化到硬件散热每个环节都逼着你思考为什么这样做而不是那样做。这套能力和经验换到任何一个平台都能复用。最后分享一个小技巧。在树莓派上写推理脚本时日志输出一定要克制。每帧打印检测结果会导致串口和SSH的IO成为瓶颈甚至会反过来拖慢推理速度。我习惯用logging模块接管所有输出设置level为WARNING只在检测到目标数量变化时才打印摘要。程序稳定运行比花哨的输出重要得多。如果你照着上面的代码一步步走遇到问题欢迎对照自己的报错信息排查多数坑都是预处理环节的细节没对齐。祝你的模型在树莓派上跑得顺。
返回列表