
这次我们继续树莓派5玩转AI系列第三集的任务很明确在树莓派5上部署YOLOv8实现人员检测。不过这一集不走Ultralytics完整推理链路而是用OpenCV的DNN模块加载ONNX模型也就是标题里强调的“dnn版”。为什么这么选后面会详细展开。简单说树莓派5是ARM架构CPU没有NVIDIA GPU直接跑PyTorch推理一方面依赖太重另一方面速度也不占优势。用OpenCV DNN加载YOLOv8导出的ONNX模型依赖只有opencv-python和numpy两个库模型文件只有十几MB整个链路非常适合嵌入式设备。本文会带你完成以下内容准备树莓派5的Python环境、在PC上把YOLOv8导出为ONNX模型、编写OpenCV DNN推理代码、用图片验证人员检测效果、再用摄像头做实时检测最后给出一套性能观察和常见问题排查清单。无论你是刚开始玩树莓派5还是想把手里的YOLOv8模型部署到边缘设备这篇文章都值得收藏。1. 核心能力速览先把这一集的技术方案和硬件门槛摆在前面方便你快速判断要不要继续往下看。能力项说明项目定位树莓派5上的YOLOv8人员检测推理框架OpenCV DNNcv2.dnn模型格式ONNX推荐硬件树莓派54GB / 8GB 内存均可建议使用64位系统是否需要GPU不需要纯CPU推理显存需求无主要功能图片、视频、摄像头中的人员检测是否内置API无内置API可自行封装HTTP服务批量任务没有现成队列可对图片目录循环处理依赖复杂度低只需 opencv-python numpy主要优势依赖少、模型体积小、部署简单、适合边缘设备合适场景嵌入式AI入门、人员计数、区域安防原型、机器人视觉需要说明的是“API”和“批量任务”不是这个DNN版内置的能力但通过Python脚本可以很轻松地扩展。后面会给出示例代码。2. 适用场景与使用边界YOLOv8人员检测在树莓派5上最常见的落地场景包括实验室或办公区人员存在检测用于设备空闲时自动休眠。门店客流统计统计画面中出现的人数。课堂出勤辅助通过摄像头判断教室是否有人。安防监控原型检测划定区域内是否出现人员。机器人避障与跟随把人员检测结果作为视觉输入之一。这一集只解决“检测到人”这一步。它不等于人脸识别YOLOv8输出的是一个包含人体的矩形框不能直接判断这个人的身份。这一点在选型时要先想清楚如果你的需求是“识别出具体是谁”需要在这个基础上再接人脸识别或ReID模型而不是单纯依靠YOLO。使用边界方面要特别注意人员检测涉及人体画面属于敏感隐私数据。测试时建议使用公开数据集图片、自拍素材或实验室内部实景不要未经许可拍摄路人。任何人形检测系统在部署到公共区域前都要经过合规评估。不要用这类技术做偷拍、未经授权的跟踪或大规模监控。树莓派5 CPU推理能力有限不要对检测精度和实时性抱有太高期望。它是入门和原型验证的好工具但不适合直接承担高危安防任务。3. 为什么是DNN而不是直接跑Ultralytics很多新手拿到树莓派5第一反应是pip install ultralytics然后像在PC上一样直接跑model.predict()。这条路在树莓派5上不能说完全不能走但体验会很差。原因主要有三点。第一Ultralytics依赖PyTorch。PyTorch的ARM版本虽然存在但体积大、依赖多在树莓派5上安装耗时很长而且推理时会占用大量内存。树莓派5的4GB/8GB内存看起来不小但PyTorch加载YOLOv8模型后留给图像处理和系统的余量会明显减少。第二树莓派5的CPU是Cortex-A76性能比树莓派4强不少但没有CUDA、没有TensorRTPyTorch在CPU上的算子优化不一定能发挥到最好。OpenCV DNN针对CPU推理做了很多优化在嵌入式平台上反而可能比裸跑PyTorch更快。第三从工程化角度DNN版的部署更干净。OpenCV DNN只需要一个ONNX文件不依赖模型训练时的完整Python环境。模型导出一次后面不管换到树莓派、Jetson还是Windows环境只要OpenCV能跑同一份代码就能复用。所以这一集的选择很明确在PC上用Ultralytics导出ONNX在树莓派上用OpenCV DNN做推理。这也是很多边缘部署项目的通用套路。4. 环境准备与前置条件先说明一下我建议的环境配置。树莓派5建议使用64位的Raspberry Pi OS Bookworm因为OpenCV Python包的arm64 wheel可以直接通过pip安装省去源码编译的麻烦。如果你还在用32位系统opencv-python新版本对armhf32位ARM的支持很不友好可能会被迫使用旧版OpenCVDNN模块的ONNX算子支持也会差一些。开始之前先更新系统并安装Python虚拟环境sudo apt update sudo apt upgrade -y sudo apt install -y python3-pip python3-venv python3-dev然后创建并激活虚拟环境。这里建议建一个独立环境避免和系统Python打架。mkdir -p ~/yolov8-dnn cd ~/yolov8-dnn python3 -m venv venv source venv/bin/activate激活后安装OpenCV和NumPypip install --upgrade pip pip install opencv-python numpy国内网络环境下如果下载慢可以加清华镜像源pip install opencv-python numpy -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后验证OpenCV版本。这一步很重要DNN模块对ONNX的支持在不同版本间差异较大。python3 -c import cv2; print(cv2.__version__)如果能看到类似4.10.0的输出说明OpenCV安装成功。建议OpenCV版本不低于4.8这样才能获得较好的ONNX算子支持。到这里树莓派5这边的软件环境就准备好了。本集不需要额外安装PyTorch、Ultralytics或CUDA相关组件。5. 模型准备从YOLOv8导出ONNXYOLOv8官方仓库和Ultralytics包都支持导出ONNX。这里推荐在PC上完成导出因为PC通常配备NVIDIA显卡导出速度快环境也稳定。导出的ONNX文件是纯模型文件不依赖PyTorch直接拷贝到树莓派5就能用。在PC上安装Ultralyticspip install ultralytics然后导出YOLOv8n模型。n是nano版本模型最小、速度最快最适合树莓派5这种CPU推理场景。命令行方式yolo export modelyolov8n.pt formatonnx opset12 simplifyTrue也可以写Python脚本from ultralytics import YOLO model YOLO(yolov8n.pt) model.export(formatonnx, opset12, simplifyTrue)这里有两个关键参数需要注意。opset12是为了兼容OpenCV DNN。OpenCV对高版本ONNX算子支持有时候会滞后设置opset12能避开不少算子兼容问题。simplifyTrue会调用onnx-simplifier对计算图做简化去掉一些OpenCV不支持的冗余算子。对于YOLOv8来说这两项设置基本可以保证cv2.dnn.readNetFromONNX一次加载成功。导出完成后目录下会生成yolov8n.onnx。这个文件体积很小通常在十几MB级别。把它拷贝到树莓派5上scp yolov8n.onnx piraspberrypi.local:/home/pi/yolov8-dnn/如果你的树莓派5已经联网也可以直接在树莓派上下载别人导出的公共ONNX模型。但更稳妥的方式还是自己导出一方面可以保证模型结构符合预期另一方面后续如果要换yolov8s、yolov8m训练自定义数据集流程是统一的。这里再补充一点关于模型大小的选择。YOLOv8官方提供n、s、m、l、x五个规格复杂度递增。树莓派5的CPU更适合yolov8n和yolov8s不要一上来就选yolov8x否则会非常慢。6. 编写DNN推理代码并运行模型文件准备好后接下来写核心推理代码。先跑图片检测把整套后处理逻辑跑通再上摄像头。创建detect_person.pyimport cv2 import numpy as np import time # 配置参数 MODEL_PATH yolov8n.onnx IMAGE_PATH test.jpg CONF_THRESHOLD 0.5 IOU_THRESHOLD 0.4 INPUT_SIZE 640 TARGET_CLASS_ID 0 # COCO中person是第0类 # 加载ONNX模型 net cv2.dnn.readNetFromONNX(MODEL_PATH) # 读取图片 img cv2.imread(IMAGE_PATH) if img is None: raise FileNotFoundError(f无法读取图片: {IMAGE_PATH}) H, W img.shape[:2] # 构造blob blob cv2.dnn.blobFromImage( img, 1.0 / 255.0, (INPUT_SIZE, INPUT_SIZE), swapRBTrue, cropFalse ) net.setInput(blob) # 推理并统计耗时 start time.time() outputs net.forward() infer_time_ms (time.time() - start) * 1000 print(f模型推理耗时: {infer_time_ms:.1f} ms) # 后处理 # YOLOv8导出ONNX后输出形状通常是(1, 84, 8400)或(1, 8400, 84) # 其中84 4个坐标 80个COCO类别置信度 if outputs.shape[1] 84: detections outputs[0].T # (8400, 84) else: detections outputs[0] # 已经是(8400, 84) boxes [] scores [] for det in detections: cx, cy, w, h det[:4] class_scores det[4:] class_id int(np.argmax(class_scores)) confidence float(class_scores[class_id]) # 只检测person类别 if class_id ! TARGET_CLASS_ID: continue if confidence CONF_THRESHOLD: continue # 将640x640坐标映射回原图坐标 x1 int((cx - w / 2.0) * W / INPUT_SIZE) y1 int((cy - h / 2.0) * H / INPUT_SIZE) x2 int((cx w / 2.0) * W / INPUT_SIZE) y2 int((cy h / 2.0) * H / INPUT_SIZE) # 边界裁剪 x1 max(0, x1) y1 max(0, y1) x2 min(W - 1, x2) y2 min(H - 1, y2) if x2 x1 or y2 y1: continue boxes.append([x1, y1, x2 - x1, y2 - y1]) scores.append(confidence) # NMS去掉重叠框 indices cv2.dnn.NMSBoxes(boxes, scores, CONF_THRESHOLD, IOU_THRESHOLD) if len(indices) 0: for i in indices.flatten(): x, y, w, h boxes[i] cv2.rectangle(img, (x, y), (x w, y h), (0, 0, 255), 2) label fperson {scores[i]:.2f} cv2.putText(img, label, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) print(f检测到 {len(indices)} 个人) else: print(未检测到人员) cv2.imwrite(result.jpg, img) print(结果已保存到 result.jpg)在树莓派5上运行时把test.jpg换成你自己的测试图片python3 detect_person.py预期输出是一行推理耗时的统计以及检测人数的提示。如果图片中存在人体result.jpg中会出现红色框和person 0.9x的标签。这里补充一下代码里的关键点。blobFromImage的swapRBTrue是因为OpenCV读入图像默认是BGR格式而YOLO训练时使用的是RGB格式。1.0 / 255.0负责归一化。输入尺寸固定为640x640这是YOLOv8的标准推理尺寸。输出维度需要理解一下。8400来自三个检测尺度80x80、40x40、20x20加起来正好是8400个锚框。84中的前4个值是cx、cy、w、h后80个值是COCO数据集80个类别的置信度。COCO类别中person是第0类所以代码里用TARGET_CLASS_ID 0做过滤。如果你不想只检测人可以去掉class_id ! TARGET_CLASS_ID这段过滤再写一个coco.names文件把80个类别名称加载进来就能输出所有检测类别。7. 摄像头实时检测图片检测跑通后摄像头实时检测就没多少新东西了。核心是把图片检测的逻辑封装成一个函数在摄像头循环里逐帧调用。创建detect_camera.pyimport cv2 import numpy as np import time MODEL_PATH yolov8n.onnx CONF_THRESHOLD 0.5 IOU_THRESHOLD 0.4 INPUT_SIZE 320 # 实时检测建议用320提升帧率 TARGET_CLASS_ID 0 net cv2.dnn.readNetFromONNX(MODEL_PATH) def detect_person(frame): H, W frame.shape[:2] blob cv2.dnn.blobFromImage( frame, 1.0 / 255.0, (INPUT_SIZE, INPUT_SIZE), swapRBTrue, cropFalse ) net.setInput(blob) outputs net.forward() if outputs.shape[1] 84: detections outputs[0].T else: detections outputs[0] boxes [] scores [] for det in detections: cx, cy, w, h det[:4] class_scores det[4:] class_id int(np.argmax(class_scores)) confidence float(class_scores[class_id]) if class_id ! TARGET_CLASS_ID or confidence CONF_THRESHOLD: continue x1 int((cx - w / 2.0) * W / INPUT_SIZE) y1 int((cy - h / 2.0) * H / INPUT_SIZE) x2 int((cx w / 2.0) * W / INPUT_SIZE) y2 int((cy h / 2.0) * H / INPUT_SIZE) x1 max(0, x1) y1 max(0, y1) x2 min(W - 1, x2) y2 min(H - 1, y2) if x2 x1 or y2 y1: continue boxes.append([x1, y1, x2 - x1, y2 - y1]) scores.append(confidence) indices cv2.dnn.NMSBoxes(boxes, scores, CONF_THRESHOLD, IOU_THRESHOLD) result frame.copy() count 0 if len(indices) 0: for i in indices.flatten(): x, y, w, h boxes[i] cv2.rectangle(result, (x, y), (x w, y h), (0, 0, 255), 2) cv2.putText(result, fperson {scores[i]:.2f}, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) count 1 return result, count # 打开摄像头 cap cv2.VideoCapture(0) if not cap.isOpened(): print(无法打开摄像头请检查USB摄像头或权限) exit(1) print(摄像头已打开按 q 退出) while True: ret, frame cap.read() if not ret: break start time.time() result, count detect_person(frame) fps 1.0 / (time.time() - start) cv2.putText(result, fFPS: {fps:.1f} person_count: {count}, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow(Raspberry Pi 5 YOLOv8 DNN, result) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()运行python3 detect_camera.py这里有一个重要提醒摄像头推荐优先用USB摄像头也就是/dev/video0设备。树莓派官方CSI接口的摄像头在Bookworm系统上默认走libcamera框架OpenCV直接VideoCapture(0)不一定能打开需要额外配置v4l2兼容层。新手第一次调试直接上一个免驱USB摄像头最省事。另外实时检测时我把INPUT_SIZE降到320。原因很简单输入尺寸越大树莓派5 CPU推理越慢。640是标准精度但实时性明显吃力320能换取更高的帧率人员检测这种相对宽松的场景320已经完全够用。8. 性能观察与调优建议树莓派5跑YOLOv8最关心的就是性能。这里不写死具体帧率因为性能受很多因素影响比如模型规格、输入尺寸、图片分辨率、散热状态、后台任务负载。但可以明确一点树莓派5没有NVIDIA GPUCPU推理的性能上限摆在那里不要指望和PC显卡相比。你可以在代码中加上耗时统计通过实际数据来判断优化方向。上一节代码里已经包含了time.time()计时它会输出每帧的检测耗时和FPS。影响性能的主要因素模型规格yolov8n比yolov8s快帧率差距明显。树莓派5建议固定用n。输入尺寸640输入比320输入慢约3到4倍。人员检测可以用320如果检测目标较小再考虑提升到480。后处理逻辑代码里为了可读性用了Python循环遍历8400个检测框这一部分也会占用一些时间。如果要做高帧率优化可以用NumPy向量化操作或Cython。后台任务树莓派5同时在跑桌面环境、摄像头的内存拷贝等任务都会占用CPU。最小化桌面服务、用命令行模式运行能省出一些资源。给几个可落地的调优建议先固定使用yolov8n不要轻易换大模型。实时检测用INPUT_SIZE320起步先跑通流程再根据效果调整。如果只是做人员存在检测不需要每帧都跑推理。可以改成每隔3帧或5帧检测一次中间帧直接沿用上一帧的检测结果。摄像头分辨率不要拉满。树莓派5处理720p绰绰有余1080p反而增加缩放负担。想进一步提速可以研究setNumThreads设置线程数。树莓派5有四核OpenCV默认有时反而会开过多线程手动设置为4有时能提升稳定性。后续想要大幅提升帧率可以考虑外接树莓派AI KitHailo-8L加速器把模型部署到NPU上。这一集先不展开等下一篇可以专门聊。9. 常见问题与排查实际部署中最容易踩坑的主要是模型加载、检测结果不理想和摄像头打不开。整理成下表方便查阅。问题现象可能原因排查方式解决方案readNetFromONNX报错OpenCV版本过低或ONNX算子不兼容查看报错中提到的算子名称升级OpenCV导出时设置opset12 simplifyTrue模型加载成功但检测不到人置信度阈值过高模型识别能力不足测试图太模糊临时把CONF_THRESHOLD降到0.25清晰图片换一张包含完整人体的测试图检测出很多重叠框NMS阈值设置不合适观察IOU_THRESHOLD取值把IOU_THRESHOLD降到0.3或0.35检测框和人物不匹配坐标映射出错检查原图宽高和INPUT_SIZE的映射代码确认x1、y1是按原图尺寸计算的摄像头打不开设备不是USB摄像头v4l2驱动未加载ls /dev/video*确认设备存在换USB摄像头加载bcm2835-v4l2模块画面显示正常但推理很慢输入尺寸太大模型规格太大打印每帧推理耗时降到320换yolov8nimport cv2报错或版本不对虚拟环境和系统包冲突which python3、pip list检查严格在venv里安装依赖代码启动后内存持续升高摄像头帧被缓存后处理数组频繁分配使用htop观察内存避免每帧创建大数组检测帧间隔拉长这里单独说一下OpenCV算子兼容问题。cv2.dnn.readNetFromONNX加载失败时报错一般会指向某个具体算子。如果遇到这种情况优先考虑两个手段升级OpenCV或者重新导出模型时强制opset12加simplifyTrue。只要模型能加载成功后面就基本畅通了。10. 最佳实践与使用建议把这一套东西从“能跑”变成“好用”有几个工程化建议值得提前布局。第一文件目录要清晰。树莓派5上的工作目录建议按模型、脚本、素材、结果分开。~/yolov8-dnn/ ├── models/ # ONNX模型文件 ├── scripts/ # Python推理脚本 ├── data/ # 测试图片、视频 ├── output/ # 检测结果 └── venv/ # Python虚拟环境第二批量检测很容易实现只要把图片检测的代码包成一个函数然后用os.listdir遍历目录即可。比如对data/目录下所有jpg文件循环执行检测结果统一保存到output/。如果图片数量多建议在脚本里加日志每处理一张输出一条记录方便定位中途失败的文件。第三如果想把模型封装成HTTP接口可以在Linux下用Flask或FastAPI包一层把detect_person的逻辑暴露成POST接口。但要注意树莓派5性能有限接口并发能力不强适合内网调试不适合直接做高并发生产服务。第四合规红线不要碰。人员检测涉及人体画面测试素材要自己拍或者用公开数据集。不要把摄像头对准街对面或办公室隔壁区域。部署到任何公共场合前先确认当地隐私法规和使用授权。如果检测结果要长期存储必须对视频数据进行脱敏和加密管理。第五系统散热会影响性能。树莓派5在高负载推理时发热明显最好配一个主动散热风扇。CPU过热降频后推理速度会明显下降排查性能问题时先看温度。11. 总结与下一步这一集最值得动手验证的点就是在树莓派5上用最短的依赖链把YOLOv8人员检测跑通。整个过程不装PyTorch、不装Ultralytics只靠OpenCV和一个十几MB的ONNX文件依赖干净模型可复用是非常典型的边缘AI部署思路。建议你第一次运行时先找一张包含多个人的清晰全身照作为测试图片把整套流程跑通。接着再加摄像头实时检测观察不同输入尺寸对帧率的影响。最容易踩的坑是OpenCV版本与ONNX算子兼容问题以及非USB摄像头无法被OpenCV直接捕获这两点提前准备好就不会被卡太久。后续可以继续扩展的方向很多在PC上训练自定义人员数据集再导出ONNX、用Hailo-8L NPU加速、把检测结果接入MJPEG推流服务、或者结合MQTT实现区域人员告警。只要掌握了“Ultralytics导出ONNX OpenCV DNN部署”这条链路换模型、换任务都只是参数层面的调整。建议收藏备用后面几集会在这套基础上继续叠加更多树莓派5的AI玩法。