树莓派5进车间,听起来就是把一个小盒子往产线边上一放,实际动手才发现,从系统到摄像头再到模型部署,没有一步是顺的。这篇总结记录我把自训练的YOLOv5模型部署到树莓派5上的完整过程,围绕六个最卡人的环节展开,希望能给准备把边缘设备放进产线环境的你省下几个星期的试错时间。如果你有一点Linux基础,想做视觉检测类的小项目,或者正在纠结要不要用树莓派当边缘计算节点,这篇应该能帮上忙。内容是我自己踩出来的真实经验,不是对着官方文档复读,后面每个环节都会说清楚为什么这么做,以及还有哪些备选方案。
1. 六件事是怎么来的,以及整体方案选型
1.1 为什么是树莓派5?车间场景的需求拆解
车间里做视觉检测,常见的需求无非是产品外观缺陷识别、安全区域人员闯入检测、设备状态灯和仪表读数识别。这类场景不需要训练时那种大算力,真正重要的是把模型部署到摄像头旁边,做到低延迟、数据不出厂、成本可控。
树莓派5相比前几代提升最明显的就是CPU算力和IO能力。BCM2712处理器带四个Cortex-A76核心,ARM64架构,主频可以跑到2.4GHz左右,再配合PCIe接口,可以接NVMe固态硬盘做存储加速,对于跑一个轻量级YOLOv5模型来说,算力是够用的。更重要的是,树莓派5能完整支持Ubuntu Server,这就解决了驱动和软件生态的问题,PyTorch、ONNX Runtime这些机器学习栈在ARM64 Ubuntu上基本都能装上。
我一开始也考虑过用Jetson Nano或者二手工控机,但最后还是选了树莓派5。原因很现实:Jetson Nano官方支持老一点,量产功耗高;工控机体积大、接口适合固定安装,但摄像头接入和GPIO控制反而不如树莓派灵活。树莓派5在性价比和可玩性上比较均衡,而且社区资料多,遇到问题不至于卡死。
1.2 六件事是怎么定下来的
项目推进过程中,真实卡住我的就是六个环节,不是解决办法不够多,而是每个环节都需要在“能用”和“好用”之间做取舍:
- 系统安装与启动:树莓派5上装Ubuntu不是烧录完就能跑,第一次启动的引导配置就很折腾;
- 散热与供电:车间没有空调,树莓派5满载发热量不小,供电不足还会触发降频;
- 摄像头接入与图像采集:CSI摄像头的驱动和视频流管道配置,跟USB摄像头完全两套逻辑;
- YOLOv5环境搭建与模型转换:ARM平台安装PyTorch依赖,以及把训练好的模型转成适合推理的格式;
- 自训练模型部署:从PC训练到树莓派上跑通,中间涉及图像预处理、后处理、坐标映射一堆细节;
- 性能调优与长期运行:单帧推理速度只有几百毫秒时,怎么优化到可用水平,以及如何让程序开机自启、崩溃自愈。
这六件事每件单独看都不难,难的是串行推进时每一步都要磨合。下面的内容就按这六件事展开,每部分我都会先讲方案选择,再讲实现细节,最后讲踩过的坑。
2. 第一件事:Ubuntu安装和系统启动
2.1 烧录系统与初次启动的坑
树莓派5改进了启动机制,不像树莓派4那样随便找个TF卡就能跑稳。我踩到的第一个坑就是TF卡兼容性。普通杂牌卡在高负载下容易丢数据,甚至开机后文件系统就损坏。建议选择A2级别的TF卡,容量至少32GB,或者干脆用官方NVMe HAT接一块SSD。
系统镜像我选的是Ubuntu Server 24.04 LTS,这个版本对树莓派5支持比较好,而且LTS维护周期长,适合车间长期运行。烧录用的是Raspberry Pi Imager,选择定制版Ubuntu镜像写入TF卡。Imager在写入时会提供两个关键选项:一个是WiFi网络配置,另一个是启用SSH。
初次启动最容易出问题的是用户账户。Ubuntu Server首次启动会走cloud-init初始化,默认用户是ubuntu,密码是ubuntu,第一次登录会强制要求改密码。如果你在Imager里预置了SSH密钥,可以跳过密码,但如果没有预置,要提前在显示器上登录一次。我图省事,直接用Imager把SSH公钥放进去,这样开机就能远程连。
还有一个很隐蔽的坑:树莓派5的TF卡插槽旁边有个通过螺丝固定的PCIe接口,如果同时用了NVMe HAT,并且系统装在NVMe上,TF卡里就不能留旧系统文件,否则启动时会去读TF卡导致不一致。我一开始TF卡和NVMe各装了一套系统,结果反复开不进NVMe,后面直接把TF卡分区清空才稳定。
2.2 启动后的网络和基础配置
系统能正常启动后,第一件事就是固定IP。车间网络环境跟办公室不同,往往没有DHCP预留,树莓派重启后IP变了,SSH直接断掉。我的做法是在netplan里配置静态IP。
Ubuntu Server的Netplan配置文件在/etc/netplan/下,默认文件名可能是50-cloud-init.yaml。编辑前先看一下现有内容,确认网卡名称是eth0还是end0。我的配置大致如下:
network: version: 2 ethernets: eth0: dhcp4: no addresses: - 192.168.10.210/24 routes: - to: default via: 192.168.10.1 nameservers: addresses: - 192.168.10.1改完以后执行sudo netplan apply验证。如果远程操作,建议先用ip a确认当前IP,再把配置文件里的IP改成目标IP,避免改完连不回去。
顺便把基础的运维工具装了:htop、git、vim、tmux、samba(便于从PC传模型文件)。传文件的时候,小文件我直接跑scp,大模型文件会走samba共享,省得反复输密码。TFTP和NFS我试过,不是不好用,是配置起来需要固定的NFS服务端,对于单节点部署没必要。
3. 第二件事:散热和供电,车间环境的第一道坎
3.1 车间环境的温度与降频问题
树莓派5性能提升的代价就是发热量比前代大很多。我刚开始只装了一个被动散热片,放在车间里跑YOLOv5模型后,CPU温度很快冲到82℃,然后系统开始自动降频,推理速度从300多毫秒掉到600毫秒,体感非常明显。
车间环境比家里更严峻,灰尘大、夏天没有空调的时候温度高。被动散热完全扛不住,主动风扇是必须的。我选的是一个带铜管和涡轮风扇的散热套件,装机后温度压在55℃到62℃之间。
具体压测方式:跑一个持续推理脚本,同时用vcgencmd measure_temp看温度。树莓派5上vcgencmd依然能用,只是要确认系统里有libraspberrypi-bin这个包。看到温度稳定在60℃左右,不涨上去,说明散热够了。如果还在缓慢爬升,检查风扇方向是不是装反了,别用那种没有测速线的静音风扇,因为无法判断它有没有转。
3.2 供电方案与电流限制
供电是树莓派5项目的隐形杀手。树莓派5官方要求5V/5A,但实际电流取决于外设:摄像头、NVMe SSD、风扇、USB转串口模块都参与分电。我试过用一个5V/3A的旧手机充电器供电,系统能开机,但负载一高就出现欠压提示,在SSH终端里能看到内核日志报bcm2835-power相关警告。
另外树莓派5的USB-C口支持PD协议,它会把CC引脚拉低来请求5V5A,如果你用的充电器不支持协商,就只会给5V3A,甚至更少。最靠谱的方案是买官方27W电源,或者确认是支持5V5A的PD充电器。我用的是支持PD的氮化镓充电器,插上以后通过vcgencmd get_config和vcgencmd measure_volts确认核心电压没有异常掉电,再跑满载推理脚本验证稳定性。
如果供电不足,树莓派会自动降低CPU和USB外设性能,典型表现是TF卡读写变慢、SSH操作卡顿。我强烈建议在车间场景里加一个测量USB电压电流的小工具,能看实时电流,省得排查半天找不到原因。
4. 第三件事:摄像头接入和视频采集
4.1 选择CSI摄像头还是USB摄像头
车间部署的摄像头选择直接决定了视频流采集成败。我最初用的是USB摄像头,插上以后在Ubuntu里识别为video0,OpenCV里cv2.VideoCapture(0)也能打开,但很快发现两个问题:
- USB摄像头默认输出MJPG或YUYV格式,通过USB总线传输会持续占用CPU进行格式转换和帧搬运;
- 多分辨率切换不稳定,有时候设置
CAP_PROP_FRAME_WIDTH为1280会失败,实际输出还是640x480; - 摄像头固定角度时,USB线的长度和布线在车间走线里是个麻烦事。
后来换成树莓派官方摄像头模块V3,在树莓派5上走CSI接口,直接用libcamera栈,视频流通过片上ISP处理,几乎不占CPU资源,帧率也更稳定。V3摄像头支持最高1080P/50fps,低光表现不错,适合车间现场。
不过要注意,树莓派5的CSI接口是15pin,跟树莓派4的排线通用,但Ubuntu系统默认不一定开启了所有摄像头模式。我装完Ubuntu后,先执行sudo libcamera-hello --list-cameras,如果返回摄像头信息,就说明驱动正常。如果看不到摄像头,去检查/boot/firmware/config.txt里的camera_auto_detect=1,有些定制镜像会把它注释掉。
4.2 用libcamera和GStreamer采集视频流
用libcamera直接输出视频流很容易,但要让YOLOv5推理脚本接收,需要把帧送进Python代码。我这里用的是GStreamer管道方式。
先安装依赖:
sudo apt install libgstreamer1.0-0 gstreamer1.0-tools gstreamer1.0-libcamera python3-gi python3-gi-cairo然后在Python里用OpenCV打开GStreamer管道:
import cv2 pipeline = "libcamerasrc ! video/x-raw,width=640,height=640,framerate=30/1 ! videoconvert ! video/x-raw,format=RGB ! appsink drop=1" cap = cv2.VideoCapture(pipeline, cv2.CAP_GSTREAMER)第一版我直接用cap.read()循环,发现延迟很高,原因是缓冲区排队导致帧陈旧。后来在管道末尾加上drop=1,即追加appsink drop=1,强制丢帧,让推理脚本只处理最新一帧。延迟从原来的两百多毫秒降到几十毫秒,对实时检测非常重要。
如果用USB摄像头,可以通过cv2.VideoCapture(0)打开,但建议设置:
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M','J','P','G')) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 640) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)不然默认的YUYV格式会让CPU使用率直接拉满。
5. 第四件事:YOLOv5环境搭建和模型转换
5.1 在树莓派上安装PyTorch和OpenCV
部署YOLOv5之前,要先在树莓派上搭好Python环境。Ubuntu Server默认自带Python 3.12,PyTorch和OpenCV都有ARM64 wheel包,直接用pip装没问题。
我建议创建一个独立的虚拟环境,不要用系统全局的pip,否则升级依赖时很容易把系统包搞坏:
python3 -m venv ~/yolo-env source ~/yolo-env/bin/activate pip install --upgrade pip pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install opencv-python-headless numpy onnxruntime onnx这里有个关键选择:opencv-python-headless而非opencv-python。树莓派Server版没有GUI,如果装了完整OpenCV,它会去拉Qt和GTK依赖,重量大而且很多版本在ARM上编译有问题。headless版不依赖显示环境,完全够用。
pip安装过程中可能遇到内存不足的问题,尤其是树莓派5 4GB版本。PyTorch的轮子包比较大,安装时解压临时文件对内存和交换分区都有压力。如果pip被kill掉,可以临时扩大swap:
sudo fallocate -l 2G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile装完以后验证一下:
python -c "import torch; print(torch.__version__)" python -c "import cv2; print(cv2.__version__)"5.2 把自训练的YOLOv5模型转到ONNX
直接拿着PyTorch的best.pt在树莓派上推理,是我最初的做法,结果CPU占用很高,单帧要跑到1.2秒,根本没法用。后来我把模型转成ONNX,再配合ONNX Runtime推理,速度提升非常明显。
在PC端(有GPU或CPU都行)的YOLOv5项目里执行导出:
python export.py --weights best.pt --include onnx --opset 12 --imgsz 640导出后得到best.onnx。这个导出流程会顺带在模型里加入NMS节点,但我觉得树莓派端自己处理NMS更可控,所以导出时用的官方默认版本,然后在推理脚本里手工解析输出。
ONNX模型对输入尺寸比较严格,训练时如果用640x640,推理也尽量保持640x640,否则坐标缩放很容易出错。如果你要更快的推理速度,可以在导出时把--imgsz 320,后面检测精度会打折,但帧率能翻倍,这个取舍看具体检测目标。
把best.onnx传到树莓派上时,要注意校验文件完整性。我最初用scp传了三次,每次推理都报维度不对,最后checksum才发现文件传断了。大文件建议先md5sum校验,再放到目标目录。
6. 第五件事:部署自训YOLOv5模型的完整实现
6.1 从PC训练到树莓派推理的路径梳理
其实整个部署链路是这样的:数据标注 → PC训练YOLOv5 → 导出ONNX → 传到树莓派 → 在树莓派上用ONNX Runtime加载模型 → 摄像头采集 → 预处理 → 推理 → 后处理 → 绘制结果。
每一步都要核对尺寸和坐标系。我在树莓派上写推理脚本时,最容易出错的地方是图像预处理。YOLOv5官方的推理脚本里有一个letterbox函数,它会保持长宽比地缩放图像,然后填充灰色边缘到640x640,推理完后需要把检测框坐标还原回原始图像尺寸。
如果不做letterbox,直接resize到640x640,检测框在边缘位置会偏移,而且小目标漏检严重。
6.2 推理脚本核心代码和后处理细节
下面这个脚本是我最终稳定的版本,简化了类名加载和日志,完整代码可以按这个骨架扩展:
import cv2 import numpy as np import onnxruntime as ort CLASSES = ['defect', 'normal'] # 换成你自己的类别 CONF_THRESH = 0.4 IOU_THRESH = 0.45 INPUT_SIZE = 640 def letterbox(img, new_shape=(INPUT_SIZE, INPUT_SIZE)): shape = img.shape[:2] 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, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] dw, dh = dw // 2, dh // 2 resized = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR) top, bottom = dh, dh left, right = dw, dw canvas = cv2.resize(img, (INPUT_SIZE, INPUT_SIZE)) # 简化分支,实际用resize再填充 return canvas, r, (dw, dh) session = ort.InferenceSession('/home/ubuntu/yolo/best.onnx', providers=['CPUExecutionProvider']) input_name = session.get_inputs()[0].name cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() if not ret: break img, ratio, pad = letterbox(frame) img = img[:, :, ::-1].transpose(2, 0, 1) img = np.ascontiguousarray(img, dtype=np.float32) img /= 255.0 img = img[None, ...] outputs = session.run(None, {input_name: img})[0] # (1, 25200, 5+n) outputs = outputs[0] boxes = [] scores = [] class_ids = [] for i, det in enumerate(outputs): score = float(det[4]) if score < CONF_THRESH: continue # 省略坐标解码和缩放 # 后续接NMS和画框需要留意ONNX输出维度。YOLOv5在640x640输入下,输出是[1, 25200, 85](假设一个类别就是[1,25200,6],实际根据类别数变化)。25200来自三个特征层:80x80、40x40、20x20,加起来是6400+1600+400=8400?不对,应该是25200,因为YOLOv5的锚框每个格子有3个anchor,所以80x80x3=19200,40x40x3=4800,20x20x3=1200,合计25200。推理脚本里要固定这个数值,如果动态输入导致维数变了,后处理循环会直接报错。
后处理我直接用了OpenCV的cv2.dnn.NMSBoxes来做非极大值抑制,比自己写排序方便,但要注意不同OpenCV版本返回值格式有差异,新版本返回一个元组,不要按旧版本索引去解。
7. 第六件事:性能调优和长期运行
7.1 帧率优化:从3帧到16帧的实操记录
初始版本在树莓派5 CPU上跑640x640输入,onnxruntime单线程推理,单帧耗时大概330毫秒,加上图像预处理和视频解码,实际只能到3FPS。这个速度看静态画面还行,但产线上产品一流动起来,根本追不上。
我按下面的顺序逐项优化:
- 把输入尺寸从640降到416,单帧推理耗时直接降到180毫秒左右;
- 设置onnxruntime的线程数:
session_options.intra_op_num_threads = 4,推理耗时降到120毫秒; - 把摄像头分辨率降到640x480,通过GStreamer的videoscale在采集阶段就缩放,省得在CPU上大幅缩放;
- 在推理循环里用OpenCV的
cap.grab()和cap.retrieve()分离抓帧和解码,让摄像头buffer保持在最新帧; - 用torch的多线程? ONNX Runtime底层已经做了指令集加速,NEON优化默认开启,不需要额外配置。
最终稳定在14到16FPS。如果进一步把输入调到320,能到22FPS,但小目标检测精度下降太多。
7.2 用systemd做服务化和看门狗
车间设备不可能每次开机都手动跑脚本,必须要做成服务。把推理脚本写到/etc/systemd/system/yolo-inference.service:
[Unit] Description=YOLOv5 Inference Service After=network.target [Service] Type=simple User=ubuntu WorkingDirectory=/home/ubuntu/yolo ExecStart=/home/ubuntu/yolo-env/bin/python /home/ubuntu/yolo/infer.py Restart=always RestartSec=5 [Install] WantedBy=multi-user.target然后:
sudo systemctl enable yolo-inference sudo systemctl start yolo-inference sudo systemctl status yolo-inference设置Restart=always之后,即使进程崩溃,systemd也会在5秒后拉起。我还加了一个简单的健康检查脚本,每隔1分钟检查/proc/$PID状态,如果连续三次没有响应,就重启服务,防止模型推理线程假死。
车间断电后恢复供电,树莓派会重新开机,systemd服务自动启动,算是做到免维护。
8. 常见问题与避坑速查
8.1 六件事之外的额外坑
项目过程中还有几个不在计划内的坑,非常影响进度,也列出来:
- 首次安装依赖时,PyTorch和OpenCV的临时文件占满了根分区。树莓派默认分区只有TF卡的总容量,建议在烧录时自定义分区大小,或者至少装完后检查
df -h。 - 摄像头用GStreamer管道时,如果
appsink的buffer太大会导致延迟,太小会丢帧严重。我用drop=1解决,但如果在检测结果中看到偶发的空帧,可以适当增大max-buffers。 - 用
systemd跑Python脚本时,环境变量和PATH与手动终端完全不同。直接写ExecStart=/home/ubuntu/yolo-env/bin/python是最可靠的,别用python这种简写,否则会指向系统的python3,虚拟环境失效。 - 车间里开机次数多,如果用普通TF卡,建议开只读挂载或定期备份。我就遇到过一次断电导致文件系统损坏,最后重刷系统才恢复。
8.2 问题速查表
| 现象 | 原因 | 解决方案 |
|---|---|---|
| SSH能连,但推理服务启动失败 | systemd里没有用虚拟环境python | ExecStart写全路径到venv/bin/python |
| 模型推理结果框偏移 | 没有做letterbox或坐标还原错误 | 统一用letterbox并记录pad和ratio |
| CPU温度高、主频上不去 | 被动散热不够、供电不足 | 换主动风扇,确认电源支持5V5A |
| OpenCV打不开摄像头 | Ubuntu驱动或GStreamer依赖缺失 | 先跑libcamera-hello确认驱动,再检查GStreamer |
| 摄像头延迟高 | 视频缓冲队列堆积 | 在appsink加drop=1 |
| pip安装被kill | 内存不足 | 临时扩大swap 2GB |
| 断电后文件系统损坏 | 非正常关机 | 启用只读挂载或定期备份 |
我个人在实际项目里的体会是,树莓派5作为边缘推理节点是可行的,前提是严格按“系统 → 硬件外设 → 环境 → 模型 → 服务化”这个顺序走,不要跳步。每一步的坑都集中在环境兼容性和资源限制上,提前做好规划和验证,就能把大部分问题拦在还没进车间的时候。如果你也要把自训练的YOLOv5模型往树莓派上搬,我建议先在小负载下把整个链路跑通,再去碰性能上限。最后提醒一点:所有参数调优都要以实际场景为准,别人能跑通的配置,不一定适合你的车间环境,多测几组数据再固定下来。