🍓 第十章 相机模块与视频串流
📄 一句话概括

USB 摄像头走的是 UVC 标准驱动,插上就出现 /dev/video0——但它和 CSI 相机是两套互不相通的软件栈:rpicam 命令与 picamera2 都看不见它,拍照用 fswebcam、Python 用 OpenCV。

📝 本节和前面几节的关系

10-1 到 10-5 全部围绕 CSI 栈(libcamera → rpicam 命令 → picamera2),本节换轨到 V4L2 栈(内核 UVC 驱动 → fswebcam/OpenCV)——前面所有相机命令在这里全部失效,这是本章最后一个、也是最容易栽的大坑。7-2 的【几代库并存】思路在这里再演一次:同一台机器上两类相机各用各的库。

一、两套栈对照:本章的总复习兼分水岭

CSI 官方模块(10-1~10-5) USB 摄像头(本节)
接口 CSI 排线直连 SoC USB 口(UVC 标准)
设备节点 libcamera 管理(无 /dev/video0) /dev/video0
命令行 rpicam-hello / rpicam-still… fswebcam / ffmpeg
Python picamera2 OpenCV(VideoCapture)
画质参数 曝光、白平衡、对焦全可控 摄像头给什么用什么,控制少

一句话记死:rpicam 和 picamera2 只认 CSI 相机;USB 摄像头在它们眼里不存在。反过来,fswebcam/OpenCV 找 USB 摄像头很顺,但摸不到 CSI 模块的 ISP 管线。网上教程混着写时,先看它用的是哪套栈、再看你插的是哪种相机。

二、即插即用四步

# ① USB 口上认到设备了吗
lsusb
# (输出里出现摄像头厂商字样,如 Logitech、Chicony 等)

# ② 设备节点出现了吗
ls -l /dev/video*
# (典型输出:/dev/video0,可能还有配套的 video1 等附属节点)

# ③ 这颗摄像头支持哪些分辨率与格式
v4l2-ctl --list-formats-ext -d /dev/video0

# ④ 拍一张试试
sudo apt install -y fswebcam
fswebcam -r 1280x720 --no-banner photo.jpg

看到什么算成功:lsusb 有设备、/dev/video0 存在、第 ③ 步列出 YUYV/MJPG 等格式清单、photo.jpg 能打开(--no-banner 去掉 fswebcam 自带的文字横幅)。

读结果:第 ③ 步的清单最重要——OpenCV 里设的分辨率必须是清单里有的,随手写 1920x1080 而摄像头不支持,读到的就是默认值还看不出错。

三、Python 走 OpenCV:三行取流

import cv2

cam = cv2.VideoCapture(0)          # 0 = /dev/video0(第一个 USB 摄像头)
cam.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*"MJPG"))   # 指定 MJPG 格式(见第四节)
cam.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cam.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)

ret, frame = cam.read()            # 取一帧:ret 是成功与否,frame 是 NumPy 数组
print("一帧尺寸:", frame.shape)     # (720, 1280, 3) —— 高在前,同 10-4

cam.release()                      # 用完释放

与 10-4 的 capture_array() 对照:同样是 NumPy 数组,所以学过的逐帧处理思路原样通用——区别只在【取流的来源】(picamera2 走 CSI、OpenCV 走 V4L2)。进阶提示:若默认后端行为怪异,可显式写 cv2.VideoCapture(0, cv2.CAP_V4L2)。

四、为什么必须 MJPG:USB 2.0 的带宽账

未压缩格式 YUYV 每像素 2 字节。算一笔账(1080p、30 帧):

所以摄像头在 1080p 下只肯给你 MJPG(帧内压缩)格式;要跑未压缩格式就得降分辨率或降帧率。这解释了 v4l2-ctl 清单里【高分辨率只有 MJPG】那一栏——不是摄像头吝啬,是总线物理上运不动。

五、什么时候选 USB 摄像头

代价前面说过:画质参数控制少、走 CPU 的处理多、好摄像头不便宜。固定装机、追画质选 CSI;临时扩展、图省事选 USB。

六、动手 Demo:从认设备到取流一条龙

# ① 插上 USB 摄像头后:
lsusb
ls /dev/video*
v4l2-ctl --list-formats-ext -d /dev/video0 | head -15

# ② 拍一张
fswebcam -r 1280x720 --no-banner usb_photo.jpg

# ③ 连拍三张看帧率手感
for i in 1 2 3; do fswebcam -r 1280x720 --no-banner usb_$i.jpg; done
# ④ OpenCV 取流(保存为 usb_cam.py)
import cv2

cam = cv2.VideoCapture(0)
cam.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*"MJPG"))
n = 0
while n < 30:
    ret, frame = cam.read()
    if ret:
        n += 1
        print("帧", n, "尺寸", frame.shape)
cam.release()
print("共取到", n, "帧")
python3 usb_cam.py

看到什么算成功:脚本打印 30 次【帧 n 尺寸 (720, 1280, 3)】(或你设的分辨率对应值),结尾报告共取到 30 帧,无报错。

读结果:尺寸是(高, 宽, 3)——10-4 的 shape 规矩在 USB 这边一模一样,因为到了 OpenCV 里大家都是 NumPy 数组,栈不同、终点相同。若 ret 一直是 False:查 ① 的设备节点、查是不是被别的程序占用(同一时刻只能有一个主人,7-2 的老规矩)。

七、30 秒实验:自己算一遍带宽账

你的摄像头在 720p(1280 × 720)、30 帧下跑 YUYV 未压缩格式:

  1. 每秒多少字节、多少 Mbps?
  2. USB 2.0(480 Mbps)装得下吗?装不下怎么办?

参考答案:1. 1280 × 720 × 2 × 30 = 55,296,000 字节/秒 ≈ 55.3 MB/秒,× 8 = 约 442 Mbps。2. 理论 480 勉强、实际(协议开销后约 300 多 Mbps)装不下——所以清单里 720p30 也会只列 MJPG,或要求降到 15 帧;办法就是第四节那条:用 MJPG 或降规格。

八、常见误区

  1. 【用 rpicam-hello 找 USB 摄像头】 —— rpicam 全家只认 CSI;USB 摄像头要用 lsusb//dev/video0/fswebcam/OpenCV 这套 V4L2 栈。
  2. 【OpenCV 里随便设分辨率】 —— 先 v4l2-ctl --list-formats-ext 看支持清单,设清单外的值不报错、只是悄悄用默认值。
  3. 【第二个摄像头一定是 /dev/video1】 —— 节点号按识别顺序分配,拔插顺序变了会漂;多摄像头项目要按设备名或绑定规则定位,别写死数字。
  4. 【USB 口都一样,随便插】 —— 高分辨率高帧率优先插 USB 3.0 口(蓝色,Pi 4/5 有);2.0 口跑 1080p 很勉强。
  5. 【摄像头识别不到先怪摄像头】 —— 先查供电(自动对焦马达一启动电流尖峰,弱电源会掉设备,7-1 的供电纪律同样适用于 USB 外设)。
自测三问(点开对答案) 1. picamera2 能控制 USB 摄像头吗? 2. fswebcam 拍出的照片自带文字横幅,怎么去掉? 3. 1080p30 的 YUYV 是多少 Mbps,说明了什么? **参考答案** 1. **不能**。picamera2 建立在 libcamera 上,只覆盖 CSI 相机;USB 摄像头走 OpenCV 的 VideoCapture。 2. 加 `--no-banner` 参数。 3. 1920 × 1080 × 2 字节 × 30 ≈ 995 Mbps,**远超 USB 2.0 的 480 Mbps**——所以高分辨率只能用 MJPG 压缩格式,这是总线的物理限制,不是设置问题。
📄 第十章读完了,回头看这条线

10-1 认硬件(CSI 家族与排线)→ 10-2 点亮它(自动识别,无需启用)→ 10-3 命令行指挥(rpicam 五件套)→ 10-4 交给程序(picamera2 三步骨架)→ 10-5 送上网(编码-封装-传输三层)→ 10-6 换条轨(USB 的 V4L2 世界)。 树莓派至此有了眼睛:静态照片、延时摄影、网络监控三条实战路线全部打通。 回头看 10-1 埋的那句【两套栈互不相通】:rpicam/picamera2 只认 CSI,fswebcam/OpenCV 只认 USB——选对了栈,一切命令才有意义。 下一章按教材顺序,等波仔报标题。

相关笔记