☰
香橙派5 RK3588实战:YOLOv5s接入摄像头抓帧推理教程
2026/10/1 1:49:27 网站建设 项目流程

说到香橙派5和RK3588,很多朋友都是冲着它的NPU来的,6 TOPS算力在单板圈里算是很能打的存在。前几期教程我把YOLOv5s的RKNN示例从头到尾跑通,评论区有不少人卡在同一步:跑测试图一切正常,换成真实场景就不知道怎么接。这一期我就来处理这个问题——给YOLOv5示例接上摄像头,抓一帧画面,推理出目标框,保存结果,整个过程走一遍完整链路。

先说清楚这期要解决什么事:不是做实时视频流,而是把模型从“读图片文件”改成“读摄像头抓帧”。这一步是很多项目的起点,把单帧链路打通之后,后面做实时检测、RTSP取流、推流显示都只是循环的事。过程中会涉及摄像头设备节点、OpenCV读取方式、图像预处理、坐标映射这些细节,我也会把平时踩过的坑一并讲掉。无论你是刚拿到香橙派5的初学者,还是已经跑通示例想往应用靠的进阶玩家,这篇都值得收藏。

1. 动手前的硬件与系统准备

1.1 摄像头选型:入门阶段优先选USB

去网上搜“香橙派5 摄像头”,能搜出一堆OV5647、IMX219这些MIPI接口模块,价格也不贵。但我个人强烈建议,在第9期这个阶段先老老实实用USB摄像头。原因很简单:省事。

对比项USB摄像头(UVC协议)MIPI摄像头(OV5647等)
接口USB 2.0/3.0MIPI CSI排线
驱动免驱,内核自带UVC需要设备树配置,部分型号要自己适配
Python调用直接cv2.VideoCapture(0)即可先确认驱动是否注册成功,再走/dev/video节点
调试成本低,插上就能用高,容易卡在花屏、无图像、帧格式不支持
画面质量上限中等较高,但那是后话

MIPI摄像头确实香,尤其做低延迟高性能应用时,走CSI接口比USB更稳。但那是你把整个软件链路跑通之后再去折腾的方向。现阶段目标是“让YOLOv5s看到真实世界的一帧画面”,USB摄像头是风险最低的选择,它通过UVC协议即插即用,OpenCV底层直接封装好了,不需要额外装驱动。

你手头如果有旧手机不用的USB网络摄像头、USB免驱摄像头,直接拿来用。分辨率别太抠,720p的就行,人民币几十块的摄像头完全够用,因为最终喂给YOLOv5s的图也会缩放成640x640,摄像头分辨率再高,预处理阶段也会降下来。

1.2 系统自检:确认NPU和示例状态都正常

摄像头连接前,先把系统状态确认一遍。香橙派5我装的是官方Debian/Ubuntu系统,YOLOv5s示例已经能跑通测试图。检查清单如下:

# 查看系统版本 cat /etc/os-release # 查看Python版本,RKNN的Python绑定在Python3环境 python3 --version # 确认NPU设备节点存在 ls /dev/rknpu* # 确认摄像头节点存在(接好摄像头后执行) ls /dev/video*

NPU驱动是重中之重,检查/dev/rknpu节点是否存在。如果这个节点不存在,后面rknn.init_runtime()一定会报错,跑不到推理那一步。我之前遇到过一次系统里有节点但版本不匹配的情况,加载yolov5s.rknn直接提示Runtime版本不对,最后是把rknn-toolkit2的runtime部分重新装了一遍才解决。顺手看一眼ls -l /dev/rknpu*的权限,确保当前用户有访问权限,否则后面各种权限报错会搞得你很崩溃。

示例目录里应该有yolov5s.rknn这个模型文件,确认一下它的路径。等下写脚本时要用绝对路径或者确保运行目录找得到它。另外,原有的后处理函数(负责从模型输出里解析出目标框坐标和类别的那部分代码)直接沿用就行,不用重新写。

2. 摄像头在Linux下是怎么工作的

2.1 V4L2框架和 /dev/video 节点的关系

Linux下所有摄像头设备最终都会注册成/dev/videoX节点。你插入USB摄像头后,系统内核的V4L2框架会识别它,分配一个video节点。V4L2是Linux处理视频设备的标准接口框架,应用层通过open()打开节点、ioctl()设置采集格式和分辨率、mmap()映射缓冲区或者用read()直接读帧。OpenCV的VideoCapture底层全部封装好了,你不需要直接操作V4L2,但必须理解节点的概念。

问题来了:插了一个摄像头,系统里可能出现/dev/video0、/dev/video1等多个节点。USB摄像头通常对应一个video节点,但如果有MIPI摄像头也同时接入,节点编号就不是固定的了。怎么区分哪个节点才是你要用的摄像头?

# 查看设备列表,会显示每个节点对应的设备名称 v4l2-ctl --list-devices

输出能看到类似“UVC Camera (046d:...) /dev/video0”这样的信息。如果系统里v4l2-ctl命令没装,先执行sudo apt install v4l-utils。另外要注意,现代系统里有些video节点是metadata节点,属于某个摄像头但并不能用于取流,具体哪个能用,直接在Python里cv2.VideoCapture(0)试一下就知道了。

2.2 为什么先做“抓一帧”而不是直接实时检测

很多朋友一看“接摄像头”就想上一秒24帧的实时检测。我的建议是先把“抓一帧推理”跑通,再谈实时性。抓一帧的好处非常明显:

第一,问题定位简单。实时视频流里出了问题,你很难判断是取流问题、推理问题还是显示问题。抓一帧保存成图片后,推理结果烂不烂、画框歪不歪、颜色对不对,一目了然。

第二,资源占用低,方便观察到整个流程的时间分布。跑单帧的时候,你能单独测出摄像头抓帧耗时、预处理耗时、NPU推理耗时,后面做优化才有依据。一上来就循环抓帧,数据被平均掉,反而看不出瓶颈在哪。

第三,实时视频流本质就是“循环抓帧 + 推理 + 绘制显示”。单帧链路跑通后,外面套一层while True就是实时检测。所以第9期没必要直接上实时,把基础打扎实比什么都强。

3. 实操:把摄像头帧送进YOLOv5s

3.1 第一步:验证摄像头能不能正常出图

动手改YOLOv5代码前,先把摄像头单独验证一下。写个小脚本,测试OpenCV能否抓帧成功。

import cv2 cap = cv2.VideoCapture(0) # 0 对应 /dev/video0,打不开就试试1、2 if not cap.isOpened(): print("无法打开摄像头") exit(1) # 可选:强制设置分辨率,有些USB摄像头默认是低分辨率 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) ret, frame = cap.read() if ret: cv2.imwrite("test_cam.jpg", frame) print(f"抓帧成功,图尺寸:{frame.shape}") else: print("摄像头打开了,但读取帧失败") cap.release()

运行这段代码,如果当前目录出现了test_cam.jpg,说明摄像头整条通路是好的。注意cap.release()一定要执行,不然摄像头一直被占用,后面正式脚本再打开时会报“Resource busy”。

很常见的情况:VideoCapture(0)打不开,但换成VideoCapture(1)就好了。这通常是因为系统里存在两个video节点,0号其实是MIPI摄像头或者某个metadata节点。用v4l2-ctl --list-devices看清楚,或者干脆写个循环把所有节点都试一遍。

3.2 第二步:把示例代码里的读图改成读摄像头

之前那份YOLOv5s RKNN示例的核心代码逻辑大概是这样的:

img = cv2.imread("bus.jpg") img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = cv2.resize(img, (640, 640)) outputs = rknn.inference(inputs=[img])

现在要把“从文件读图”改成“从摄像头抓帧”,改动其实只有几行。但就这么几行,藏了三个最常见的坑:

坑一:颜色通道顺序。OpenCV读出来的图像是BGR顺序,而YOLOv5训练时用的是RGB。示例里用cvtColor做了转换,改成摄像头后这个转换一步都不能省。如果你忘了转,图片颜色会偏蓝偏红,YOLOv5的检测精度会肉眼可见地变差,甚至完全检不出目标。

坑二:分辨率必须统一。摄像头输出的分辨率是1280x720也好、640x480也好,YOLOv5s模型的输入固定是640x640。所以必须cv2.resize到640x640。这一步的缩放比例会直接影响画框坐标,后面要记住这个比例。

坑三:缩放方式。直接resize会把图像拉伸变形,物体比例不对,小目标容易丢。更稳的做法是letterbox,保持宽高比,在短边填充灰边。YOLOv5官方预处理就是letterbox。这里给一个简版letterbox思路:

def letterbox(img, new_size=(640, 640)): h, w = img.shape[:2] scale = min(new_size[0] / h, new_size[1] / w) nh, nw = int(h * scale), int(w * scale) resized = cv2.resize(img, (nw, nh)) canvas = np.full((new_size[0], new_size[1], 3), 114, dtype=np.uint8) canvas[(new_size[0] - nh) // 2:(new_size[0] - nh) // 2 + nh, (new_size[1] - nw) // 2:(new_size[1] - nw) // 2 + nw] = resized return canvas, scale

Lettterbox带来的问题是:后处理得到的框坐标是在640x640输入图上的坐标,画回到原始分辨率图片时,要先减去灰边偏移,再除以缩放比例。这一步漏掉的话,画框位置会整体偏移,看起来像“框没对准目标”。

3.3 第三步:完整推理脚本,一键跑通

下面是整合后的完整脚本,后处理函数沿用你之前示例里的,不用重写:

import cv2 import numpy as np from rknnlite.api import RKNNLite def letterbox(img, new_size=(640, 640)): h, w = img.shape[:2] scale = min(new_size[0] / h, new_size[1] / w) nh, nw = int(h * scale), int(w * scale) resized = cv2.resize(img, (nw, nh)) canvas = np.full((new_size[0], new_size[1], 3), 114, dtype=np.uint8) pad_h = (new_size[0] - nh) // 2 pad_w = (new_size[1] - nw) // 2 canvas[pad_h:pad_h + nh, pad_w:pad_w + nw] = resized return canvas, scale, pad_h, pad_w # 1. 加载RKNN模型 rknn = RKNNLite() if rknn.load_rknn("yolov5s.rknn") != 0: print("模型加载失败,确认rknn文件路径") exit(1) rknn.init_runtime() # 2. 打开摄像头并抓一帧 cap = cv2.VideoCapture(0) if not cap.isOpened(): print("摄像头打开失败") exit(1) ret, frame = cap.read() cap.release() if not ret: print("抓帧失败") exit(1) # 3. 预处理:letterbox + BGR转RGB,送入NPU input_img, scale, pad_h, pad_w = letterbox(frame) input_img_rgb = input_img[..., ::-1] # BGR转RGB # 4. NPU推理 outputs = rknn.inference(inputs=[input_img_rgb]) # 5. 后处理(沿用示例自带函数,返回框/类别/分数) boxes, classes, scores = post_process(outputs) # 6. 坐标映射回原图并画框 for box, cls, score in zip(boxes, classes, scores): x1, y1, x2, y2 = box # 先去掉letterbox灰边,再缩放回原始分辨率 x1 = int((x1 - pad_w) / scale) y1 = int((y1 - pad_h) / scale) x2 = int((x2 - pad_w) / scale) y2 = int((y2 - pad_h) / scale) # 超出边界的裁剪 x1, y1 = max(0, x1), max(0, y1) x2, y2 = min(frame.shape[1], x2), min(frame.shape[0], y2) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) label = f"cls_{cls} {score:.2f}" cv2.putText(frame, label, (x1, max(0, y1 - 10)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) # 7. 保存结果 cv2.imwrite("result_cam.jpg", frame) print("推理完成,结果已保存为 result_cam.jpg")

这段脚本里,post_process直接用你之前示例里的实现,它的输入是rknn.inference的输出,输出是boxes, classes, scores。如果你的示例里后处理函数已经封装好了,直接照这个流程接就行。RKNNLite是跑在RK3588上的轻量级推理接口,和PC上的RKNN驱动版本要匹配,这部分前几期应该已经解决。

3.4 跑起来之后应该看到什么

运行脚本后,终端会输出“推理完成”,当前目录下出现result_cam.jpg。用任何图片查看器打开,理论上你正对着摄像头的时候,图片里人的位置会被绿色框标出来,框的左上角有类别和置信度。

如果一切正常,恭喜你,香橙派5上的YOLOv5s第一次“看到”了真实世界。这个瞬间很朴素,但它是后面所有视觉应用的地基。如果画面里什么都没框出来,先别急着怀疑代码。检查三件事:镜头前是否有完整的目标物体、灯光是否充足、人体是否太小或太偏。YOLOv5s对中大型目标检测效果好,隔着几米拍一个人本来就不容易框到。

4. 实战中的坑与排查实录

4.1 摄像头打不开:先按这张表逐项排除

现象可能原因处理方式
VideoCapture(0)返回False设备节点编号不对改成VideoCapture(1)循环尝试,用v4l2-ctl确认
能打开但操作被拒绝当前用户无设备权限sudo usermod -aG video $USER后重新登录
打开时提示Resource busy摄像头被其他进程占用检查是否有残留Python进程,kill掉或重启板子
打开成功但read()一直返回False摄像头帧格式或分辨率不支持先cap.set()设置成640x480试,等待2秒再读帧
图像全黑或全花USB带宽不足或线材质量差换一根短线,不要插USB Hub,直连板子USB口

权限问题我要多说一句。在香橙派这类开发板上,很多系统默认把普通用户加进了video组,但你如果用的比较精简的镜像,可能默认不在。执行groups看看自己的用户组里有没有video。没有的话,按表里命令加入后重新登录,不要指望sudo python3 xxx.py能绕过,有时候反而会引出一堆环境变量问题。

4.2 推理结果异常:八成在预处理和后处理

画面有框,但框的位置明显歪、检测不到该有的目标、或者把墙面整个框起来,这类问题几乎都出在两个地方。

第一个是颜色通道。我在调试时遇到过最诡异的一次:检测人没问题,检测红色物体全军覆没。排查半天发现摄像头输出的图像在某个分辨率下,OpenCV读入后通道顺序偶尔会乱。所以无论何种情况,统一在预处理里做img[..., ::-1]转换,不要依赖平台默认行为。

第二个是坐标映射。如果你用的是letterbox预处理,模型推理出的坐标是基于640x640输入图的,直接画在原始1280x720画面上必然偏。不少朋友在论坛上问“为什么我的YOLOv5画框偏了”,十有八九是忘了把坐标还原。还原公式就在上面完整脚本的第6步。一个小习惯:调试时先在原图画一个固定点,比如画面中心,再对比预处理图和输出图,快速确认坐标变换哪一步出问题。

还有一类问题跟模型本身有关:你的模型是自训练的,类别数可能不是COCO的80类。如果示例里的post_process引用的是预训练模型的类别标签列表,输出类别就会对不上。自训练模型要同步修改标签列表。

4.3 性能数据与后续还能怎么玩

我的实测数据:RK3588跑YOLOv5s 640x640输入,NPU推理单帧大约40到80毫秒,换算成帧率大概12到25 FPS。这个速度做实时检测够用了,瓶颈其实在摄像头取流和预处理上,不在NPU。

如果后面做实时视频流,注意几点:

控制取流帧率。不要盲目追求高帧率,YOLOv5s推理来得及就按推理速度来,用cap.set(cv2.CAP_PROP_FPS, 15)限制摄像头输出,减少系统资源浪费。

网络摄像头接法。把VideoCapture(0)换成VideoCapture("rtsp://你的摄像头地址/stream"),其他逻辑完全不变。这样能直接对远程监控画面做检测。

结果推流。检测后的画面加上框,用ffmpeg重新编码推流出去,就能做成一个真正的智能监控应用。瑞芯微平台上有硬件编码器,不过那是后面的教程了。

换YOLOv8s也是好方向。微信群里不少人已经在香橙派5上跑YOLOv8s,转换流程和YOLOv5s类似:先导出ONNX再用rknn-toolkit2转成RKNN。NPU算力足够,就是推理时间会比YOLOv5s稍微多一点点,换来的是精度提升。

这个系列走到第九期,YOLOv5s已经不只是跑测试图的玩具,它能接上真实世界的眼睛了。我在实际测试中最感慨的一点是:摄像头接入是整个视觉应用里“第一公里”的工程,很多时候问题根本不在AI模型本身,而是在取流、格式、颜色、坐标这些小细节上。把这些基础啃下来,后面不管做实时检测、多摄像头联动还是边缘计算盒子,你都会比别人少走很多弯路。最后再给你留个作业:把脚本改成循环抓帧加推理,看看板子跑实时检测时CPU占用和NPU占用各是多少,评论区来对答案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询