ARTICLE DETAIL

资讯详情

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

香橙派5 RK3588实战:YOLOv5s接入摄像头抓帧推理教程

香橙派5 RK3588实战:YOLOv5s接入摄像头抓帧推理教程 说到香橙派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. 实操把摄像头帧送进YOLOv5s3.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, dtypenp.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, scaleLettterbox带来的问题是后处理得到的框坐标是在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, dtypenp.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 fcls_{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占用各是多少评论区来对答案。
返回列表