☰
香橙派5跑YOLOv5摄像头推理:V4L2/OpenCV实战避坑指南
2026/10/1 1:21:38 网站建设 项目流程

我手上这块香橙派5(RK3588芯片)吃灰大半年,前两天翻出来准备跑YOLOv5s,结果卡在最后一步——模型能加载,图像能推理,但摄像头流死活接不进来。折腾到凌晨两点半,终于把这根“最后一米”打通了。今天就把完整过程从头到尾捋一遍,包括那些官方文档不会写的小坑,给同样在香橙派5上折腾YOLOv5摄像头推理的朋友做个参考。

先说清楚这文章适合谁:已经能在香橙派5上跑通YOLOv5示例代码,但想让模型“睁眼看现实”、把USB摄像头或CSI摄像头的画面实时抓取并推理的人。如果你是零基础刚装完系统,步骤里涉及环境安装部分我也会捎带提一句,但建议先对照香橙派官方wiki把基础环境配好再来读这篇。

1. 需求拆解:YOLOv5其实是“喂图”的,摄像头是“找图”的

1.1 为什么示例代码能跑通图片,却接不上摄像头

很多人在香橙派5上第一次跑YOLOv5示例,直接用的是官方仓库里的detect.py,命令一般是:

python detect.py --weights yolov5s.pt --source data/images/bus.jpg

这个流程本质上是把一张静态图片喂给模型,模型输出检测框、图像标注,然后保存结果。整个过程不涉及视频流,也没有摄像头设备的打开、取流、帧同步这些环节。一旦把--source换成摄像头设备号,比如0或/dev/video0,代码就会走另一条分支:调用OpenCV的VideoCapture去读摄像头帧,再丢进推理逻辑。

问题就出在“调用OpenCV读摄像头”这一步。香橙派5跑的是Ubuntu/Debian桌面或服务器系统,OpenCV的预编译包不一定带了你那块摄像头的V4L2后端支持,或者摄像头驱动本身没加载好,导致VideoCapture.open()返回False,代码直接报错退出。

说白了,示例代码只是“能推理图片”,不等于“能推理摄像头画面”。摄像头接入涉及设备节点、驱动、像素格式、帧率、分辨率,这几层任何一个环节出问题,YOLOv5都只能干瞪眼。

1.2 香橙派5上摄像头接入的两条技术路线

香橙派5(RK3588)同时支持USB摄像头和MIPI CSI摄像头,两者原理和调试方式完全不同。

USB摄像头:通过USB总线枚举,设备节点是/dev/video0、/dev/video1这类。插上就能被系统识别,驱动通常是uvcvideo内核模块。优点是即插即用,兼容性好,市面上常见的罗技C270、免驱USB摄像头都能用。缺点是图像质量上限一般,延迟略高,但跑YOLOv5s这种轻量模型完全够用。

MIPI CSI摄像头:通过摄像头排线接到开发板上的CSI接口,驱动通常是厂商自己写的,比如香橙派官方配套的OV5647摄像头模块。这种摄像头帧率高、延迟低,但装驱动比USB摄像头麻烦得多,经常需要修改设备树、重新编译内核或加载独立驱动模块。香橙派5的官方镜像一般内置了OV5647驱动,但不同版本的固件不一定默认开启,需要确认/dev/video0是否存在。

本文主要基于USB摄像头讲解,因为通用性最强。如果你用的是CSI摄像头,操作逻辑相同,只是需要额外确认驱动和v4l2节点是否正常。

2. 环境准备:先把摄像头和推理链路各自调通

2.1 确认系统与Python环境

我的板子刷的是香橙派官方Ubuntu 22.04桌面版,内核版本5.10,Python 3.10。YOLOv5官方代码要求Python ≥ 3.7,依赖库包括torch、torchvision、numpy、opencv-python、matplotlib等。

如果你还没装YOLOv5,按官方仓库的方式拉下来即可:

git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt

这里有个经验之谈:在香橙派5上,不要强求用CPU跑torch,因为RK3588有6 TOPS算力的NPU,但YOLOv5官方仓库默认只走CPU/CUDA,并不会利用NPU。所以即使你有NPU硬件,跑官方示例用的还是CPU推理。不过RK3588的CPU是4核A76+4核A55,跑YOLOv5s在640x640输入下,CPU推理速度大概在300~500ms一帧,做演示和验证完全足够。想上NPU加速是另一个大坑,等以后单独写一篇。

2.2 摄像头识别三连查:设备节点、格式、图像

接好USB摄像头后,第一步是确认系统认识它。打开终端,输入:

lsusb

正常情况下会看到类似Webcam或Chicony Electronics等字样。然后查看视频设备节点:

ls -l /dev/video*

如果看到/dev/video0,说明至少有一个摄像头设备节点。接着用v4l2-ctl这个工具查看摄像头支持的格式和分辨率:

sudo apt install v4l-utils v4l2-ctl --list-formats-ext -d /dev/video0

输出会列出摄像头所有支持的像素格式,比如YUYV、MJPG,以及每个格式下的分辨率列表。这一步极其重要,因为YOLOv5调用OpenCV读摄像头时,如果默认请求的像素格式或分辨率不被摄像头支持,就会打开失败或只拿到黑屏/花屏。

实操经验:很多廉价USB摄像头在YUYV格式下最大只支持到640x480@30fps,而在MJPG格式下支持到1920x1080@30fps。OpenCV默认会尝试用YUYV格式打开,如果分辨率设置成1280x720,摄像头可能无法输出该尺寸的YUYV帧,导致打开失败。解决方法是显式优先使用MJPG格式,并把分辨率设成摄像头支持的值。

验证摄像头能不能出图,最简单的方式是用Python的OpenCV读一帧并保存:

import cv2 cap = cv2.VideoCapture(0, cv2.CAP_V4L2) 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, 480) if not cap.isOpened(): print("摄像头打开失败") else: ret, frame = cap.read() if ret: cv2.imwrite("test_camera.jpg", frame) print("保存测试帧成功") cap.release()

注意第二行cv2.CAP_V4L2,这是强制OpenCV走V4L2后端。如果不加,OpenCV在Linux上有时会走V4L或其它后端,行为不可控。跑完这个脚本后,看看同目录下的test_camera.jpg是否正常。如果这张图清晰、色彩正常,说明摄像头链路OK,下一步就是把它接上YOLOv5。

注意:如果上面脚本打开摄像头失败,先检查是否被占用。比如Chrome浏览器开了摄像头、GStreamer管道占用等,都会导致VideoCapture失败。用sudo fuser -v /dev/video0查占用,或者干脆先重启板子再试。

2.3 确认YOLOv5推理链路正常

在接摄像头前,先确认YOLOv5本身在板子上能跑通。用一张测试图片跑一次:

python detect.py --weights yolov5s.pt --source data/images/bus.jpg --project /tmp/yolo-test

跑完后在/tmp/yolo-test/exp/bus.jpg下能看到带检测框的图。如果这一步报错,多半是依赖有问题,优先检查torch和opencv版本是否兼容。

在我的环境里,torch用的是1.13.1,opencv-python是4.6.0.66,YOLOv5官方仓库用的是commitec601a1,跑得很稳。如果你用最新代码遇到问题,可以回退到这个版本:

git checkout ec601a1

3. 把摄像头帧“喂”给YOLOv5:不止是改--source 0

3.1 官方示例支持摄像头,但有个特点必须知道

YOLOv5的detect.py其实支持视频流输入,理论上输入:

python detect.py --weights yolov5s.pt --source 0

它就会尝试打开第0个摄像头,并实时显示推理画面。但这个实现有几个问题:

  • 默认使用cv2.VideoCapture(0),没有显式指定后端和像素格式,碰上兼容性差的摄像头直接打不开。
  • 默认输入分辨率受模型imgsz影响,且内部会做letterbox填充,如果摄像头帧率低,画面会感觉卡顿。
  • 输出窗口用OpenCV的imshow显示,如果在无桌面环境的Ubuntu Server上,会报cannot open display错误。

所以我更推荐的方式是:写一个独立的Python脚本,自己控制摄像头取帧,然后逐帧调用YOLOv5的模型对象执行推理。这样灵活度最高,后续想接推流、保存视频、联动硬件都方便。

3.2 用YOLOv5的Python API写一个“摄像头推理脚本”

YOLOv5的模型可以直接通过torch.hub加载,也能直接调用仓库里的models.experimental来加载权重。最省事的方式是直接调用detect.py里封装好的DetectionModel。

下面是我调试通过的完整脚本,可以直接复制改名使用:

import cv2 import torch # 加载YOLOv5s模型 model = torch.hub.load('./', 'yolov5s', source='local', force_reload=False) model.conf = 0.5 # 置信度阈值 model.iou = 0.45 # NMS IOU阈值 model.classes = None # 检测所有类别 model.max_det = 100 # 最多检测目标数 # 打开摄像头(优先使用MJPG,640x480) cap = cv2.VideoCapture(0, cv2.CAP_V4L2) 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, 480) if not cap.isOpened(): print("摄像头打开失败") exit(1) print("摄像头开帧成功,开始推理。按q退出。") while True: ret, frame = cap.read() if not ret: print("读取摄像头帧失败") break # 推理:results.xyxy[0]为每行[x1,y1,x2,y2,conf,cls] results = model(frame) # 绘制检测框和标签 rendered = results.render()[0] # numpy数组 # 显示画面 cv2.imshow("YOLOv5s Camera", rendered) if cv2.waitKey(1) & 0xFF == ord('q'): break # 打印每帧检测信息到终端 if len(results.xyxy[0]) > 0: for *xyxy, conf, cls in reversed(results.xyxy[0]): label = f"{model.names[int(cls)]} {conf:.2f}" print(label) cap.release() cv2.destroyAllWindows()

注意第5行torch.hub.load('./', ...)里,.代表当前目录,前提是你已经在yolov5仓库目录下运行。如果你的脚本放在别处,需要把路径改成实际仓库目录,比如torch.hub.load('/home/orangepi/yolov5', 'yolov5s', source='local')。

运行这个脚本后,电脑或显示器上会弹出一个窗口,左上角是实时的检测结果。如果用的是无头模式(没有显示器),注释掉cv2.imshow和cv2.waitKey即可,只打印检测信息。

3.3 为什么一定要cv2.CAP_V4L2和MJPG组合

很多教程只写cv2.VideoCapture(0),在PC上没问题,但在香橙派这类ARM Linux板子上面很容易踩坑。原因在于OpenCV在Linux上的VideoCapture后端是动态探测的,默认可能选择GStreamer后端,也可能选择V4L2后端,不同后端的打开行为和帧格式协商逻辑差异很大。

显式指定cv2.CAP_V4L2后,OpenCV会直接调用V4L2驱动,走/dev/video0的ioctl通道去设置格式、申请缓冲、队列取流,链路短且稳定。MJPG模式是指摄像头直接输出JPEG压缩帧,每帧数据量远小于YUYV裸流,USB带宽占用低,不容易出现帧率掉到个位数的情况。

如果你试了MJPG还是打不开,可以试试把编码格式改成YUYV,同时分辨率降到640x480。有些老摄像头固件对MJPG支持不完整,只支持YUYV,反向切换反而正常。

3.4 推理时数据类型需要注意的一个细节

YOLOv5的model(frame)接收的是BGR顺序的numpy数组(即OpenCV默认读帧格式),内部会做归一化和通道转换。千万不要先cv2.cvtColor转成RGB再传给模型,那样会倒过来,识别率大降。我一开始就是被这个细节坑过,把frame转成RGB再喂进去,结果检测框全乱飘。

4. 摄像头帧率、分辨率、实时性调优心得

4.1 RK3588上YOLOv5s的实际性能数据

拿我这块香橙派5实测,跑YOLOv5s,输入640x480,关闭所有显示、只推理,CPU模式下单帧推理时间大约在300~400ms之间,也就是大概3fps左右。如果开显示窗口,加上绘制和imshow,整体会掉到2fps上下。

这个速度做静态验证、网页展示、慢速巡检是够的。但如果你想要流畅实时视频流,至少要跑10fps以上,就得重新评估方案:

  • 换用更轻的模型:YOLOv5n,精度略低但速度快一倍左右,能到5~6fps。
  • 降输入分辨率:把imgsz从640降到320,推理时间可以缩短到100ms级别,代价是检测小目标能力下降。
  • 用RKNN工具把模型转成NPU格式,部署到RK3588的NPU上,推理时间能压到30~50ms,这才是正经的端侧实时方案。

这里我强烈建议:不要对CPU跑YOLOv5s抱太高实时性期望。香橙派5的定位是边缘计算板,我们要做的是先把流程打通,后续再考虑NPU加速。

4.2 抓帧环节的帧率陷阱

如果你按我的脚本跑,发现终端打印一帧要等很久,先看一眼摄像头本身的输出帧率。有些USB摄像头标称30fps,但在V4L2默认配置下实际只输出10fps。查询实际帧率的命令:

v4l2-ctl --get-fmt-video -d /dev/video0 v4l2-ctl --get-parm -d /dev/video0

--get-parm会显示当前的Frames per second。如果默认是30,但推理显示很慢,问题在推理耗时;如果默认显示10,那先把帧率设置上去:

v4l2-ctl --set-parm=30 -d /dev/video0

再跑脚本试试。另外,在OpenCV里也可以用cap.set(cv2.CAP_PROP_FPS, 30)尝试设置,但注意不是所有摄像头都支持通过V4L2控制帧率,设置无效时不要纠结,忽略即可。

4.3 避免“读一帧卡半秒”的缓冲延迟问题

OpenCV的VideoCapture.read()是阻塞式读取,如果摄像头内部缓冲区为空,它会一直等待下一帧到来。在低帧率摄像头上,这会导致整体显示卡顿感明显。

一个常见技巧是加大缓冲队列,或者用grab()+retrieve()分离,减少帧间时间戳不准的影响。不过对于YOLOv5这种同步推理,最简单的策略是保持摄像头30fps输出,而推理脚本只在摄像头有新的帧时才去读并处理。如果推理比摄像头慢,可以加一个cap.grab()丢弃中间帧,只取最新帧:

# 丢弃积压的旧帧 for _ in range(5): cap.grab() ret, frame = cap.retrieve()

这五帧丢弃操作会让推理画面始终接近“当前时刻”,而不是滞后半秒的旧画面。代价是摄像头数据会丢帧,但对目标检测来说,丢帧比延迟更有价值——宁可检测最新的画面,也不要看着半秒前的图像。

5. CI摄像头(树莓派OV5647模块)接入的特殊处理

如果你用的是香橙派配套的OV5647 CSI摄像头,调试逻辑与USB摄像头有较大区别。插上排线后,先看设备节点:

ls /dev/video*

如果没有任何video节点,说明驱动没加载或设备树没使能。香橙派的Ubuntu镜像一般预装了video_rk3568或rkcif相关驱动,但可能需要手动modprobe:

sudo modprobe rkisp sudo modprobe rkisp-vir

不同固件模块名略有区别,建议先sudo dmesg | grep -i camera和sudo dmesg | grep -i rkisp看内核日志,判断驱动加载到哪一步。

CSI摄像头在OpenCV里的打开方式与USB摄像头一致,但像素格式通常是NV12或YUV420,分辨率也是固定的一组(比如1920x1080、1280x720、640x480)。如果直接用cv2.VideoCapture(0)打不开,试试指定CAP_PROP_FOURCC为YV12:

cap = cv2.VideoCapture(0, cv2.CAP_V4L2) cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('Y', 'V', '1', '2')) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

还有种情况是CSI摄像头和USB摄像头同时存在,设备节点顺序会变。比如USB是video0,CSI是video1。可以通过名字确认节点:

ls -l /dev/video? v4l2-ctl --list-devices

v4l2-ctl --list-devices会按设备名分组显示节点,一目了然。

6. 常见问题与排查技巧实录

6.1 摄像头能打开,但推理画面全黑

黑屏通常意味着摄像头确实在出帧,但像素格式或分辨率协商不对,导致OpenCV拿到的是错的帧。处理方法:先用v4l2-ctl --list-formats-ext查看支持的格式,再在OpenCV里显式设置FOURCC和分辨率。另外,有的摄像头需要几帧预热时间,黑屏时先连续读10帧看看。

6.2 画面有但特别暗或特别花

这是摄像头自动曝光/白平衡没初始化好的典型表现。在OpenCV里可以尝试设置:

cap.set(cv2.CAP_PROP_BRIGHTNESS, 0.5) cap.set(cv2.CAP_PROP_CONTRAST, 0.5)

不过这些参数对V4L2后端的支持程度不统一,如果无效,用v4l2-ctl直接调:

v4l2-ctl -d /dev/video0 --set-ctrl=auto_exposure=1 v4l2-ctl -d /dev/video0 --list-ctrls

先看有哪些控制项,再逐个调整。

6.3 运行时提示Cannot open camera by index

大概率是设备节点号不对。比如你插了多个摄像头,/dev/video0被另一个设备占用。用v4l2-ctl --list-devices确认,再把脚本里的索引改成可用的设备号。

另一个可能性是权限问题。当前用户不在video组,导致无法打开设备。解决:

sudo usermod -a -G video $USER

注销重新登录,或者干脆sudo跑脚本。

6.4 推理显示窗口报错OpenCV(4.6.0) Error: Unspecified error (The function is not implemented)

这表示你的OpenCV编译时没有GTK或Qt支持,无法使用imshow。常见于用pip安装的opencv-python,在服务器版系统上不含GUI依赖。

解决方式有三种:

  • 安装libgtk2.0-dev和相关库后重新编译OpenCV(费时);
  • 换成opencv-python-headless,但那就不能用imshow;
  • 放弃本地显示,改成输出检测结果或保存图片。这也是我推荐的做法:把每帧检测到的目标打印到终端,或者定时保存带框图片,从远程查看。

我这里直接贴一段“无头模式”的代码片段:

results = model(frame) if len(results.xyxy[0]) > 0: # 保存一张检测结果图 results.save(save_dir='/tmp/detections')

预测结果会按帧保存到指定目录,方便远程查看。

6.5 推理结果只检测到少量目标或漏检

先检查置信度阈值,model.conf默认是0.25,如果你设了0.8,自然漏检多。其次检查输入分辨率,如果摄像头分辨率太高,比如1920x1080,YOLOv5内部会做letterbox缩放,文字、车牌的细节可能被压缩丢,降低小目标召回率。建议把摄像头输出分辨率设为1280x720以内,或直接640x480,减少缩放损失。

6.6 摄像头热插拔后设备节点消失

我在实际调试中遇到过好几次:运行中拔掉USB摄像头,再插回去,/dev/video0可能变成/dev/video2,脚本就一直报错。排查的方法还是先运行ls /dev/video*或v4l2-ctl --list-devices确认当前节点,然后修改脚本里的设备索引。

如果你要做一个长期运行的部署,建议在循环里加一个摄像头重连机制。思路很简单:每次cap.read()失败时,释放摄像头,休眠1秒,重新VideoCapture(0)并重新设置参数,最多重试5次。这在长时间无人值守场景里非常实用。

7. 让摄像头推理结果落地:保存图片、统计目标、加时间戳

7.1 把每帧检测结果保存成图片

调试时除了打印终端信息,我更常做的是把带检测框的帧保存成图片,方便事后复盘。YOLOv5的results对象自带save方法,也可以直接用OpenCV画框保存。个人建议用results.save,它会自动把label信息画在图上,省事:

from pathlib import Path save_dir = Path('/tmp/yolo_cam_results') results.save(save_dir) # 每帧会保存为 image0.jpg, image1.jpg...

注意save_dir目录需要存在,如果不存在,YOLOv5会自动创建。

7.2 实时统计每帧目标数量

如果你要做的是生产线计数或车辆统计,需要拿到每帧的检测框数量、类别。代码里已经能拿到len(results.xyxy[0]),如果只是统计数量,不需要绘制和显示,推理耗时可以省下绘制那部分:

counts = results.pandas().xyxy[0]['name'].value_counts().to_dict() print(counts)

results.pandas()会把检测结果转成DataFrame,按类别统计数量非常方便。

7.3 给检测帧打上时间戳

防止复盘时搞不清时间,我在保存图片时会加一个时间戳:

import time timestamp = time.strftime("%Y%m%d_%H%M%S") cv2.imwrite(f"/tmp/yolo_cam/{timestamp}.jpg", rendered)

注意别在循环里频繁创建字符串和写文件,最好限制每3秒保存一张,避免磁盘IO拖慢推理。

8. 关于香橙派5上部署摄像头的几个经验总结

经过这轮折腾,我的体会是:香橙派5本身不是问题,问题全在“摄像头生态”的复杂性上。USB摄像头设备节点不确定、像素格式支持五花八门、V4L2后端行为不一致,这些坑跟PC上开发完全是两码事。但只要养成“先v4l2-ctl看设备信息,再小脚本单测摄像头,最后接YOLOv5”的调试顺序,大多数问题都能在一两分钟内定位到具体环节。

最后再分享一个小经验:如果你确实要用香橙派5跑YOLOv5做长期项目,别在CPU推理这条路上死磕,趁早研究RKNN NPU部署。把YOLOv5s转成RKNN后跑在NPU上,一帧推理能做到40ms左右,而且CPU可以空出来做业务逻辑,这才是RK3588这颗芯片的正确打开方式。摄像头取流这层解决之后,NPU转换和推理就是另一个战场了。

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

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

立即咨询