
车间里的产线质检一直想上视觉检测之前用PC NVIDIA显卡做推理主机成本高、占地大后来试着把方案往边缘端压一压树莓派 5 就成了第一批试水的硬件。八核 64 位 A768GB 内存比 4B 那会儿强了不止一档看着参数确实够“进车间”的。但真把它摆在工位上、接上工业相机、跑起自己训练的YOLOv5模型问题一个接一个往外冒。前后折腾了两周说大不大说小不小最后卡在六件事上。这篇就把我这六件事掰开揉碎讲讲全是实操记录准备入坑的朋友可以少走不少弯路。1. 第一件事系统底座别想当然1.1 怎么选系统Ubuntu 还是官方 Raspberry Pi OS拿到树莓派 5 之后第一个选择就是系统。很多人直接刷 Raspberry Pi OS图省事。但车间场景要跑自训练的YOLOv5模型还得装一堆机器学习的依赖官方系统其实没那么省心。Debian 系底子老Python 版本跟着系统走PyTorch 在 ARM 平台的支持版本又偏旧装个 torcheval 都能给依赖折腾到吐。我后来选了 Ubuntu 24.04 LTS针对树莓派的官方镜像内核 6.8系统自带 Python 3.12PyTorch 的 ARM 预编译轮子直接装省了很多编译时间。而且 Ubuntu Server 版本尽可能精简了桌面环境开机内存占用不到 400MB给推理进程留足了空间。这里有个前提要先说清楚树莓派 5 是 ARM64 架构和常规的 x86_64 服务器不一样很多 pip 包只有 x86 的预编译轮子ARM 上要么找 aarch64 的 wheel要么就本地编译。所以我建议装系统时直接选 64 位镜像这是所有后续工作的地基。Flash 工具用的是官方 Raspberry Pi Imager烧录 Ubuntu Server 时注意选对设备别把 USB 存储当 SD 卡写了。提示树莓派 5 的供电比 4B 更讲究。官方推荐 5V/5A USB-C 电源实际测下来如果接了 USB 硬盘或大功率摄像头低于 4.5A 就容易出现低频降频。车间环境里电压不稳的建议加个有电容补偿的工业电源模块别拿手机充电头糊弄。1.2 开机的三个坑bootloader、网络和散热新板子开机第一件事是检查 bootloader。树莓派 5 的 EEPROM 出厂版本如果不是最新的有概率碰到 NVMe SSD 引导不识别、USB 启动失败这类问题。我直接在 Raspberry Pi OS 的 recovery 模式下刷了最新 bootloader后来从 NVMe 盘启动就很稳定。这一步看似无关紧要但工业现场讲究确定性能规避的启动兼容问题提前规避掉。然后是网络。Ubuntu Server 默认用 netplan树莓派有线网口默认 DHCP这个没啥说的。问题在无线车间里没那么多网线接口有些工位只能靠 Wi-Fi。树莓派 5 的 WiFi 模块对 5GHz 信道支持还可以但 Ubuntu 下需要自己写 netplan 配置注意 Wi-Fi 国家码别留成默认的 US有些国家码限制会让 5GHz 频段搜不到。我吃了这个亏后来手动配置了 regulatory domain 才稳定连上。散热这块我得多说两句。树莓派 5 的 BCM2712 性能上去后发热比 4B 猛不少跑推理时外壳温度能到 85°C一过热就强制降频推理速度直接从 600ms 掉到 900ms。所以机箱必须选带主动散热的最好带风扇调速功能我从淘宝买了个铝制散热壳小涡轮风扇开机半小时后温度压在 60°C 左右这才算压住。2. 第二件事训练模型这件事交给 PC2.1 为什么不在树莓派上训练有这个想法的人不少毕竟树莓派 5 看着配置还行。但真要自己训练YOLOv5哪怕是 s 规模的网络树莓派上跑一个 epoch 都得按小时算一个 batch 8 张 640x640 的图CPU 训练慢到让人崩溃。真要训练应该老老实实把活放在有 NVIDIA GPU 的机器上做。我的流程是这样的用一台 GTX 3060 以上的 PC 做训练数据整理好之后在 PC 上跑完训练再把模型导出、压缩、转换最后推到树莓派上做推理。训练阶段把 batch size、输入分辨率这些参数放在 PC 上调树莓派只负责跑已经训练好的模型这也是边缘部署的正常流程。训练之前要先把数据集搞好。我这边要检测车间传送带上的产品外观缺陷标注用的是 LabelImg框的类别有三类划痕、缺角、异色。数据大概拍了一千张做了翻转、旋转、亮度变换这些数据增强扩到三千张。训练前一定要做数据划分train 占 80%val 10%test 10%划分的时候要用随机种子固定保证每次跑出来的结果可比。2.2 训练参数和输出物我用的训练命令走 YOLOv5 官方仓库的标准流程git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt python train.py --data dataset.yaml --weights yolov5s.pt --img 640 --batch 32 --epochs 150 --device 0dataset.yaml 里除了路径最重要是 class 名称必须和标注时一致不然训练出来的类别名和实际标签对不上。训练结束后真正要用的是 runs/train/exp/weights/best.pt这个是根据 val 集 mAP 选出来的最优权重。到这里树莓派那边还什么都没干。不要急着在树莓派上装 ultralytics/yolov5 全套仓库那个仓库装完依赖一堆光 torch 就占好几个 GSD 卡根本不够用。我们需要的是一个轻量的部署环境ONNX Runtime。3. 第三件事导出和转换是真正的分水岭3.1 把一个 PyTorch 模型变成树莓派能跑的格式自己训练的YOLOv5模型原生格式是 PyTorch 的 .pt 文件。想在树莓派上直接用 torch.load cv2 做前向推理理论可以但效率极低PyTorch 在 ARM CPU 上的优化很一般而且内存占用非常难看。我最后用了 ONNX 中间格式 ONNX Runtime 推理这一步是整个部署流程里最值得耐心的环节。导出 ONNX 直接用官方脚本python export.py --weights best.pt --include onnx --opset 12 --simplify这里有两个参数特别关键。一个是 opset 版本树莓派上装的 ONNX Runtime 不一定支持最新 opset我测试下来 op set 12 最稳向后兼容性也最好。第二个是 --simplify它会用 onnx-simplifier 把计算图简化一遍去掉很多冗余节点模型体积能小 10% 上下推理也能快一点。不简化的话有些 Resize 和 Concat 节点在 CPU 上跑起来很笨重。另外导出的 ONNX 输入尺寸是固定 640x640。如果之后想用动态尺寸可以加参数指定动态轴但树莓派 CPU 上动态尺寸适配不好我建议就固定尺寸省心也快。3.2 树莓派上的推理环境ONNX Runtime 是主角树莓派上安装pip install onnxruntime默认源装的是最新版本支持 aarch64实测没问题。如果需要更极端的 CPU 优化可以试试把 onnxruntime 的线程数显式设置成树莓派 5 的物理核心数 4默认情况下它可能会跑到 8 线程反而因为超线程导致缓存争抢变慢。我用 ONNX Runtime 跑推理的核心代码是这样import onnxruntime as ort import cv2 import numpy as np session ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) input_name session.get_inputs()[0].name output_names [o.name for o in session.get_outputs()] img cv2.imread(test.jpg) img cv2.resize(img, (640, 640)) img img[:, :, ::-1].transpose(2, 0, 1) img np.ascontiguousarray(img, dtypenp.float32) img / 255.0 img np.expand_dims(img, axis0) results session.run(output_names, {input_name: img})这段代码看起来很简短但有几个隐藏问题cv2 读出来是 BGR转 RGB 一定要做不然模型输出的类别置信度会乱。输入要归一化到 0-1YOLOv5 训练时用的是 /255.输出结果的解析不能直接用 YOLOv5 后处理函数因为导出 ONNX 后输出是三个 head 的原始张量需要自己做 NMS。这个后处理我后面单独说。3.3 模型压缩从 FP32 到 INT8 的实际收益树莓派 5 的 CPU 没有 GPU 加速FP32 模型推理一次大约 700ms对生产线上秒级节拍来说勉强能接受但如果要做连续视频流检测就太慢了。我尝试做动态量化把权重从 FP32 压到 INT8实测推理时间降到 250-350ms提升非常明显。动态量化的做法from onnxruntime.quantization import quantize_dynamic, QuantType quantize_dynamic(best.onnx, best_int8.onnx, weight_typeQuantType.QInt8)量化后的模型精度损失要看具体任务。我这边三类缺陷都是形态特征明显的视觉问题mAP 掉得不多大概从 0.92 降到 0.89车间里完全够用。但如果你的检测目标是小目标或者纹理不明显的量化前一定要在测试集上评估一遍别盲目上。提示量化后模型的输出数值分布和 FP32 有细微差别NMS 的置信度阈值建议从 0.25 适当调到 0.3过滤掉量化抖动带来的误检。4. 第四件事摄像头接入是车间里的第一道坎4.1 CSI 还是 USB 摄像头这里是我踩坑最深的地方。树莓派 5 的 CSI 接口用的是新的 MIPI CSI-2 标准和树莓派 4B 时代的摄像头排线定义不一样想直接把旧 CSI 摄像头插上去方向对不上。而且树莓派 5 上 CSI 摄像头默认走 libcamera 框架OpenCV 的 cv2.VideoCapture 直接传 0 是打不开 CSI 摄像头的。这是很多人拿到树莓派 5 之后最懵的一步。我最终选了 USB 工业摄像头支持 UVC 协议理由很简单OpenCV 直接 VideoCapture 就能读UVC 免驱接上就能用省掉 libcamera 那套应用层适配。并且 USB3.0 接口带宽足够传 1080P 30fps 的视频流对 YOLOv5 检测来说这个输入分辨率足够了。4.2 OpenCV 读视频流的参数调优用 OpenCV 读 USB 摄像头时必须手动设置分辨率、帧率和缓冲区大小否则默认 VGA 画质或者缓冲延迟拉满cap cv2.VideoCapture(0, cv2.CAP_V4L2) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) cap.set(cv2.CAP_PROP_FPS, 30) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)BUFFERSIZE 这个参数特别重要。不设置时 V4L2 默认缓冲队列比较深摄像头画面已经更新了几帧你读到的还是旧帧检测结果就和实际对不上在运动产线上就是错位。设成 1 之后每帧都是最新一帧。还有曝光和白平衡的问题。车间灯光通常是频闪的荧光灯或 LED 灯自动曝光会导致亮度忽明忽暗检测置信度也跟着跳。我是在初始化摄像头的第一帧之后锁死曝光参数直接关闭自动曝光手动固定曝光值。不同环境灯光下这个值要自己标定我的场景大概在 120 左右。4.3 光照影响比想象中大车间现场的光照条件真的是检测精度的隐形杀手。同一套模型我在办公室光下测试 mAP 有 0.89搬到车间日光灯下直接掉到 0.8因为现场的亮度和色温完全不一样。我建议在摄像头前加一个窄带滤光片或者用红外补光灯减少环境光变化对画面的干扰。我最后选了 850nm 红外补光灯加普通摄像头去掉 IR-CUT画面反而更稳定因为车间自然光里的红外分量相对稳定模型在近红外光下的检测效果还挺让人意外。5. 第五件事性能优化是慢慢榨出来的5.1 推理速度实测与瓶颈我记录了几组真实的性能数据用的模型是 YOLOv5s、输入 640x640、树莓派 5 8GB 版本、ONNX Runtime CPU 推理模型格式单张推理耗时内存占用备注FP32 ONNX680ms约 1.2GB默认线程设置FP32 ONNX 4线程520ms约 1.4GB手动设置线程数INT8 ONNX300ms约 800MB动态量化后INT8 ONNX 4线程230ms约 900MB当前生产配置这里的推理耗时只算模型前向NMS 还在外面另算。线程数必须显式配置不配置的话 ONNX Runtime 默认可能开满逻辑核心树莓派 5 是 4 核 8 线程实际测试中发现开满后线程切换开销很大反而不如 4 个物理线程稳定。用下面这段设置so ort.SessionOptions() so.intra_op_num_threads 4 so.graph_optimization_level ort.GraphOptimizationLevel.ORT_ENABLE_ALL session ort.InferenceSession(best_int8.onnx, so, providers[CPUExecutionProvider])5.2 后处理 NMS 也要优化很多人部署 YOLO 到了推理这步就只顾着模型前向耗时忽略后处理。YOLOv5 的三个输出头总共有 25200 个 anchor 候选框如果直接在 Python 里 for 循环做筛选NMS 算下来随随便便 200ms 起步。我第一个版本就这么干气得不行。正确做法是向量化处理用 numpy 做框筛选和 IoU 计算。我自己写了一个精简的 NMSdef nms(pred, conf_thres0.3, iou_thres0.5): boxes pred[pred[:, 4] conf_thres] if len(boxes) 0: return [] boxes boxes[np.argsort(-boxes[:, 4])] keep [] while boxes.size: best boxes[0] keep.append(best) ious compute_iou(best[:4], boxes[1:, :4]) boxes boxes[1:][ious iou_thres] return keep这样在 Python numpy 环境里也能压到 30ms 以内。如果再激进一点可以用 pybind11 把 NMS 写进 C 动态库但那个开发成本偏高车间的软硬件环境也不复杂没必要。除非之后检测帧率要求上到 20FPS 以上那时再来考虑 C 化。5.3 视频流检测的整体节奏单张图片推理快不代表视频流检测就流畅。摄像头 30FPS 进来树莓派根本不可能每帧都跑一次模型。我用的是丢帧策略视频流线程只管取最新帧放进环形队列推理线程检测到队列里有图就拉一张去算处理完结果通过 MQTT 或 HTTP 打到车间看板。实际上大约 3-4 帧里处理一帧显示端视觉上还是“连续检测”的效果生产节拍也完全覆盖得住。6. 第六件事稳定运行比性能更重要6.1 供电和看门狗不能省车间环境不比家里电闪雷鸣一个电涌就可能重启跑半个月的模型服务直接前功尽弃。我给树莓派 5 配了一个工业级 DC-DC 电源输出 5V/5A 带过压保护USB SSD 和摄像头都从它供电避免多路电源电压差导致的问题。另外 SD 卡在频繁读写下寿命扛不住工业场景一定要用 NVMe SSD 或者至少 A2 级的高耐久 SD 卡。我直接换成 NVMe 转 USB3.2 的硬盘盒系统跑在 NVMe 上内存占用和启动速度都舒服很多。看门狗我用的是树莓派内部硬件看门狗加载 bcm2835_wdt 内核模块之后通过 /dev/watchdog 喂狗。检测程序死锁超过 10 秒系统自动重启。这样即使程序崩溃也不会耽误太久产线上只断几秒钟工人基本无感。6.2 程序守护与日志树莓派跑推理程序的守护我用了 systemd 服务配置很简单[Unit] DescriptionYOLOv5 Detection Service Afternetwork-online.target [Service] ExecStart/usr/bin/python3 /opt/detection/main.py Restartalways RestartSec5 WatchdogSec10 [Install] WantedBymulti-user.target除了 systemd 自带的 Restart我在程序里还加了心跳线程每 5 秒向一个文本文件写时间戳。外部脚本检查时间戳超过 20 秒没有心跳就强制 kill 进程交给 systemd 拉起来。这算是双保险。日志方面别随便 print我直接写到 systemd journal 按天轮转的本地文件里方便哪天出问题翻日志。车间工人不需要看代码所以我在看板上只显示三个指标当前检测状态、单次处理耗时、今日检测总数。这比任何调试输出都管用。6.3 模型更新不用拆机模型隔一段时间就要更新总不能每次拿读卡器拆机刷。我在服务端做了一个简单的更新接口树莓派启动时和每天凌晨 3 点去拉一次远程目录比较模型文件的 MD5如果有新模型就下载、校验、备份然后重新加载推理会话。整个过程不需要停机Python 里切换模型前先等当前一帧处理完再替换 session非常顺滑。踩过的坑是模型文件名不能带空格和中文ONNX Runtime 对路径的编码兼容性一般不管在哪个环境里还是老老实实只用字母、数字和下划线。注意车间网络的防火墙策略通常比较严格树莓派尽量别主动去连外网更新服务器设在局域网内即可。安全巡检的时候也别把推理服务暴露出公网工业现场的设备安全底线不能含糊。7. 写在后面这六件事往下还能怎么走这六件事做完树莓派 5 在车间里已经能顶一个小工位用了固定采集图像、跑目标检测、把结果传到看板全程无人工干预。我自己跑下来的体会是树莓派 5 确实把边缘检测门槛压到了几千元内但它不是“装个系统就能跑模型”的小白设备坑基本集中在系统适配、模型转换和工业现场的稳定性上。最后再分享一个小技巧不确定模型在 ARM CPU 上跑多快时先拿导出的 ONNX 在同一块树莓派上跑一遍推理再用 perf 命令看瓶颈在哪一层。大多数时候瓶颈不在模型深度而在 Resize 和 NMS 这类辅助环节优先优化这些效果立竿见影。这个项目之后如果要升级我会往多模型流水线和 TensorFlow Lite 方向试试到时候再写一篇实战记录。