【Quectel Pi M2 实战】Day3 USB 摄像头图像采集:拍照、录像与踩坑记录(V4L2 + FFmpeg)

0 阅读18分钟

【Quectel Pi M2 实战】Day3 USB 摄像头图像采集:拍照、录像与踩坑记录(V4L2 + FFmpeg)

目录

【Quectel Pi M2 实战】Day3 USB 摄像头图像采集:拍照、录像与踩坑记录(V4L2 + FFmpeg)

前言

一、开发环境自检:先让第一个程序跑起来

1.1 安装编译工具链(gcc + make)

1.2 C 语言版 Hello World

1.3 Python 版 Hello World(缩进是关键)

二、接入 USB 摄像头并完成设备识别

2.1 硬件接线

2.2 设备识别命令(重点:和 CSI 摄像头不一样)

三、精准定位 USB 摄像头对应的 video 节点

四、查看摄像头支持的分辨率与编码格式

4.1 两种编码对比

4.2 推荐选型

五、抓拍一张图片,验证采集正常

六、把图片拉到电脑查看(adb pull)

6.1 踩坑:目标路径带空格导致拉取失败

6.2 踩坑:pi 用户拍照,文件属主却是 root

七、进阶:V4L2 拍照与录像(FFmpeg 封装)

7.1 桌面预览(xrdp RDP 桌面内预览,可选)

7.2 V4L2 拍照(采集一帧,FFmpeg 转 jpg)

7.3 V4L2 录像,输出 mp4(本机实测版)

7.4 实时无限录像(按 Ctrl+C 停止)

八、USB 摄像头 vs CSI 摄像头差异

九、本章坑点汇总(常见问题 FAQ)

十、总结


前言

        系列说明:Day1 完成开箱上电、Day2 打通 ADB / 串口 / SSH 多路调试并搭好 Debian 环境。本篇进入外设实战第一站:在没有 IMX219 MIPI 模组的条件下,改用 USB 免驱摄像头(斯泰克 2K),从零验证开发板的 V4L2 视频子系统—— 完成设备识别、参数探测、抓拍、FFmpeg 录像封装与 ADB 文件回传,并记录全程真实踩坑。

:warning: 全文为实操验证记录,走无头 ADB 调试路线,命令均已实测可跑通。

一、开发环境自检:先让第一个程序跑起来

正式接摄像头之前,先确认开发板的编译与运行环境正常。这部分就是 Day2 装好的 gcc/make/Python 的 “体检”。

1.1 安装编译工具链(gcc + make)

进入 Quectel Pi M2 开发板系统终端后,执行:

sudo apt update && sudo apt install gcc make
  • 首次安装会提示有若干软件包可升级(本篇环境提示 49 个),可直接回车 Y 继续。
  • 安装摘要:升级 0 个、安装 21 个,下载约 34.9 MB,需要空间 148 MB。

安装执行过程图如下所示。(apt 拉取依赖包列表)

安装完成、Setting up 各依赖包图如下所示。

1.2 C 语言版 Hello World

1、创建源文件

打开系统终端,在终端输入命令创建 hello.c 文件。

touch hello.c 

ls # 查看文件是否创建成功

创建后目录列表里出现 hello.c(红框标注即为新建文件)。

2、用 vim 打开编辑

输入下面命令使用 vim 编辑器打开 hello.c 文件。

sudo vim hello.c

3、写入代码并保存退出

将下列代码段复制到 hello.c 文件内,按下"ESC + Shift + :(英文状态下:)",然后再输入 "wq " 保存代码并退出vim终端 。

(按下 ESC → 输入 :wq → 回车保存退出。)

#include <stdio.h>
int main(void)
{
    printf("hello world\r\n");
    return 0;
}

4、编译

执行以下命令,编译代码:

gcc hello.c -o hello

5、运行

执行以下命令,运行hello程序:

./hello

6、输出结果

程序运行结果打印:

./hello     # 运行

# 输出结果:
hello world

1.3 Python 版 Hello World(缩进是关键)

系统已预装 Python环境,无需额外安装python(环境)解释器,直接写脚本即可。

1、创建文件

打开系统终端,在终端输入命令创建 hello.py 文件。

touch hello.py
ls

adb shell 里 touch hello.py + ls 图,如下所示。

2、vim 编辑

输入下面命令使用 vim 编辑器打开 hello.py 文件。

sudo vim hello.py

3、写入代码(注意:必须正确缩进!)

将下列代码段复制到 hello.py 文件内,按下"ESC + Shift + :",然后再输入 "wq " 保存代码并退出 vim 终端。

def main():
    print("Hello, World!")

if __name__ == "__main__":
    main()

坑点(专栏重点):Python 用缩进表示代码块。若 print 与 main() 前面不加缩进,运行时会报缩进错误 / 无输出。文档示例里未缩进的版本运行失败,正确版本需如上加上 4 个空格缩进。保存退出同样用 :wq。

vim 内未缩进的错误版本图,如下所示。

上述图片最终运行结果错误,代码一定要有缩进。

若不注意缩进,运行后没有结果。

vim 内正确缩进版本图,如下所示。

4、运行(Python 是解释型语言,无需编译)

Python是解释型语言,无需编译,直接执行即可:

python3 hello.py

5、输出

程序运行结果打印:

python3 hello.py  # 运行程序

# 输出结果:
Hello, World!

python3 hello.py 输出 Hello, World! 图,如下所示。

二、接入 USB 摄像头并完成设备识别

前置说明:本机没有 IMX219 MIPI 模组,改用 USB 免驱摄像头(斯泰克 2K USB Camera)完成图像采集,借此验证开发板的 V4L2 视频子系统。

2.1 硬件接线

  1. 将 USB 摄像头插入开发板的 USB Host 口;
  2. 上电,先确认系统能识别到该设备。

接线说明:

1、开发板上方 USB‑A 口:USB 线缆连接 PC 电脑;

2、开发板下方 USB‑A 口:USB 线缆接入斯泰克 2K 自动对焦 USB 摄像头,用于 V4L2 图像采集;

3、白色 USB‑C 线缆:开发板供电,同时提供 ADB 调试通路。

2.2 设备识别命令(重点:和 CSI 摄像头不一样)

# 查看 USB 设备,确认摄像头被识别
lsusb

# 列出所有 video 节点
ls /dev/video*
  • lsusb 输出中能看到 0bda:5857 Realtek Semiconductor Corp. Web Camera,说明硬件枚举成功,开发板认到了这个 USB 相机。
  • ls /dev/video* 会列出大量 video 节点。

提示:USB 摄像头一般是 /dev/video0、/dev/video1 这类节点;而 CSI 摄像头固定是 /dev/video33、/dev/video51,两者编号规则完全不同。

adb shell 内执行 lsusb + ls /dev/video* 图(含 Realtek Web Camera 识别结果),如下所示。

三、精准定位 USB 摄像头对应的 video 节点

设备节点太多,直接筛选出刚插入的 USB 相机:

v4l2-ctl --list-devices
  • 该命令会区分板载 MIPI 摄像头和刚插上的 Realtek USB Web Camera;
  • 输出会写明 USB 相机绑定到哪些 /dev/videoX(一般 USB 相机占 2 个节点:一个视频流、一个元数据)。

本机输出参考:

Web Camera: Web Camera (usb-xhci-hcd.0.auto-1.4):
    /dev/video73
    /dev/video74
    /dev/media9

结论:/dev/video73 是视频流节点(采集用这个);/dev/video74 一般是元数据 / 控制节点。

--list-devices 输出 MIPI 摄像头节点(rkisp /rkcif/rkvpss 部分)图,如下所示。

--list-devices 输出 Web Camera 部分(/dev/video73、video74)图,如下所示。

四、查看摄像头支持的分辨率与编码格式

v4l2-ctl -d /dev/video73 --list-formats-ext

能看到摄像头支持 MJPG 和 YUYV 两种编码,以及各分辨率下的帧率。

4.1 两种编码对比

编码说明特性
MJPG(Motion-JPEG)硬件压缩帧最高支持 2592×1944(2K)@ 30fps,带宽占用低,AI 推理优先用
YUYV(原始无压缩)原始图像分辨率拉高后帧率极低,1080P 只有 5fps,适合调试、不适合实时检测

4.2 推荐选型

  • 测试抓拍:1920×1080 MJPG 30fps
  • 边缘 AI 推理(降低算力压力):1280×720 MJPG 30fps

--list-formats-ext 输出 MJPG 及 2592×1944 ~ 1920×1080 各分辨率帧率图,如下所示。

输出 1600×1200 ~ 800×600 帧率图,如下所示。

输出 640×360 ~ 640×480 帧率 + [1]: 'YUYV' 标记截图,如下所示。

YUYV 格式各分辨率(1080P 仅 5fps)帧率截图,如下所示。

五、抓拍一张图片,验证采集正常

在 adb shell 里执行(抓一张 2K 图验证):

v4l2-ctl -d /dev/video73 --set-fmt-video=width=1920,height=1080,pixelformat=MJPG --stream-mmap --stream-count=1 --stream-to=/tmp/test.jpg

进入 adb shell 检查文件是否生成:

adb shell

ls -l /tmp/test.jpg

如果提示 No such file or directory,说明上次抓拍没成功,重新执行上面的抓拍命令即可。

抓拍命令输入图,如下所示。

ls -l /tmp/ 结果(test.jpg/test1.jpg 已生成,红框标注)图,如下所示。

六、把图片拉到电脑查看(adb pull)

重点:adb pull 要在电脑 CMD 里执行,不是 adb shell 里!

抓完之后先退出 adb shell:

exit

再执行拉取:

adb pull /tmp/test.jpg

输出类似:

/tmp/test.jpg: 1 file pulled. 16.6 MB/s (114346 bytes in 0.007s)

文件会传到当前所在的 platform-tools 文件夹(此处也可自定义)里,去该目录找 test.jpg 即可。

CMD 中 adb pull /tmp/test.jpg 成功图,如下所示。

platform-tools 文件夹中 test.jpg(红框)图,如下所示。

6.1 踩坑:目标路径带空格导致拉取失败

直接指定带空格的目标路径会报错:

adb pull /tmp/test.jpg D:\0-Quectel_Project\Quectel Pi M2\

# 报错:adb: error: failed to access 'M2\': No such file or directory

原因:路径里 Quectel Pi M2 有空格,Windows CMD 带空格的文件夹路径必须加双引号。

正确写法(注意用 / 分隔):

adb pull /tmp/test.jpg "D:/0-Quectel_Project/Quectel Pi M2/"

adb devices 识别 + adb shell + 无引号路径 adb pull 报错图,如下所示。

加双引号后三次 adb pull 全部成功图,如下所示。

Quectel Pi M2 文件夹里 test.jpg/test1.jpg 缩略图预览图,如下所示。

6.2 踩坑:pi 用户拍照,文件属主却是 root

现象:执行 su - pi 切到 pi 用户后拍照,生成文件却显示属主是 root。

-rw-rw-r- 1 root root 110122 Oct 28 11:51 /tmp/test.jpg

说明:摄像头设备节点权限 + 内核 V4L2 框架写文件的特性,在 Quectel Pi M2 这个系统里,由 pi 用户执行 v4l2-ctl 采集时,最终写入文件的属主会变成 root。这不是操作的问题。

  • 图片内容本身正常,adb pull 不受权限阻碍,不影响开发;
  • 如果后续 Python 程序用 pi 用户读写该文件,会出现权限报错,需要 chown 修正属主。

七、进阶:V4L2 拍照与录像(FFmpeg 封装)

7.1 桌面预览(xrdp RDP 桌面内预览,可选)

需先设置环境变量(wayland 桌面权限问题),再通过 gstreamer 预览:

export GST_V4L2_FORCE_LIBV4L2=0
export WAYLAND_DISPLAY=wayland-0
export XDG_RUNTIME_DIR=/run/user/1000
gst-launch-1.0 v4l2src device=/dev/video0 ! videoconvert ! autovideosink

博主在这里直接跳过该步骤,进行下一步测试。

7.2 V4L2 拍照(采集一帧,FFmpeg 转 jpg)

先按需修改分辨率,确认你的 USB 摄像头支持什么分辨率,再采集原始 YUYV 帧并用 FFmpeg 转 jpg:

v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=YUYV

v4l2-ctl -d /dev/video0 --stream-mmap=2 --stream-skip=5 --stream-count=1 --stream-to=/tmp/usb_cam.yuv

ffmpeg -f rawvideo -pix_fmt yuyv422 -s 1920x1080 -i /tmp/usb_cam.yuv -frames:v 1 /tmp/usb_photo.jpg

# 查看是否录制成功
ls -l /tmp/

7.3 V4L2 录像,输出 mp4(本机实测版)

官方文档是 MIPI CSI 摄像头(/dev/video33)输出 NV12 原始 YUV,必须转码;而本机 USB 摄像头 /dev/video73 输出 MJPG,可以直接封装 MP4,不用走 YUV 转码,效率更高。

进入 pi 用户终端:

su - pi

# 采集 60 帧 MJPG 流,保存为裸流
v4l2-ctl -d /dev/video73 --set-fmt-video=width=1920,height=1080,pixelformat=MJPG \
  --stream-mmap --stream-count=60 --stream-to=/tmp/usb_cam.stream

# ffmpeg 封装成 mp4,30fps,60 帧 ≈ 2 秒视频
ffmpeg -f mjpeg -framerate 30 -i /tmp/usb_cam.stream -c:v copy /tmp/usb_video.mp4

参数说明:

  • --stream-count=60:采集帧数;30fps 时 60 帧 ≈ 2s、300 帧 ≈ 10s;
  • -c:v copy:直接拷贝码流不重新编码,速度快。

录完回到电脑 CMD 拉取:

exit
adb pull /tmp/usb_video.mp4

# 输出:/tmp/usb_video.mp4: 1 file pulled. 37.0 MB/s (10687198 bytes in 0.275s)

su - pi + v4l2-ctl 采集 60 帧 + ffmpeg 封装 mp4 完整输出图,如下所示。

exit 后 adb pull /tmp/usb_video.mp4 成功截图,如下所示。

platform-tools 文件夹里 usb_video.mp4(红框)图,如下所示。

7.4 实时无限录像(按 Ctrl+C 停止)

说明:这条命令会持续采集 USB 摄像头 MJPG 码流并封装成 MP4,不会自动停止;

按下 Ctrl + C 正常终止录制,文件可以正常播放。

ffmpeg -f v4l2 -input_format mjpeg -video_size 1920x1080 -i /dev/video73 -c:v copy /tmp/loop_record.mp4

# 按 Ctrl+C 停止视频录制

ls -l /tmp/           # 查看是否录制成功

pi 用户下 ffmpeg 执行、打印流信息、Ctrl+C退出提示图,如下所示。

录制结束后,在 pi 用户终端查看文件是否生成:

ls -l /tmp/
# 查看是否录制成功

ls -l /tmp/ 图如下所示,红框标注 loop_record.mp4,可以看到文件属主是 pi、文件大小持续增长。

录制完成之后,先执行 exit 退出 pi 用户、再退出 adb shell,回到电脑 CMD,使用 adb pull 把视频拉到本地电脑:

adb pull /tmp/loop_record.mp4

CMD 中 adb pull 拉取成功图,如下所示。

最终,可以在本地电脑文件夹中查看到被拉取的视频文件,如下图所示。

关键点说明:

  1. -c:v copy 直接复用摄像头输出的 MJPG 码流,不重新编码,CPU 占用很低,适合长时间录制;
  2. 必须在 pi 用户下执行(su - pi 切换),本次实测生成的 loop_record.mp4 文件归属为 pi,权限正常;
  3. 注意 /tmp 是临时文件目录,开发板重启后视频文件会直接丢失,录制完务必立刻 adb pull 取回电脑;
  4. 终止录制一定要用 Ctrl + C,ffmpeg 会完成 MP4 文件尾封装,视频才能正常播放;如果直接强制断开连接,MP4 文件会损坏无法打开。

八、USB 摄像头 vs CSI 摄像头差异

维度CSI 摄像头(MIPI)USB 摄像头(本机)
video 节点固定 /dev/video33 / /dev/video51/dev/video73(视频流)、/dev/video74(元数据)
输出格式ISP 输出 NV12 裸 YUV,必须 FFmpeg 编码硬件输出 MJPG 压缩帧,可直接封装 MP4
编码开销高(需转码)低(直接 copy)
适用官方示例、固定安装免驱即插即用、AI 采集数据

九、本章坑点汇总(常见问题 FAQ)

Q1:Python Hello World 运行没有任何输出?

A:print、main() 代码块必须保证 Python 缩进规范,缩进错误会直接导致程序运行失败、无打印输出。

Q2:adb pull 本地保存路径带空格,执行直接报错?

A:类似 Quectel Pi M2 这种包含空格的路径,路径两端需要添加双引号,路径分隔符使用 /;示例命令:adb pull /tmp/test.jpg "D:/0-Quectel_Project/Quectel Pi M2/"。

Q3:pi 用户执行拍照,生成文件属主变成 root?

A:这是 V4L2 驱动写入文件的机制造成,图片内容正常,不影响 adb pull 拉取;后续 Python 程序要读写该文件,需要先用chown修改文件归属权限。

Q4:CSI 摄像头和 USB 摄像头的命令能不能直接互相照搬?

A:设备节点、图像格式并不通用;CSI (MIPI) 常用 NV12 格式,USB 摄像头大多是 YUYV/MJPG,不要直接复制官方 CSI 的示例命令给 USB 摄像头使用。

Q5:RDP 远程桌面里面 gst 预览画面打不开?

A:Wayland 桌面存在权限隔离,gst 预览需要提前配置XDG_RUNTIME_DIR等环境变量,否则预览程序启动失败。

Q6:USB 摄像头高分辨率下帧率很低?

A:是 USB 摄像头带宽上限导致;做 AI 视觉推理项目,推荐使用1280×720分辨率 + MJPG 格式,平衡画质与帧率。

Q7:RDP 远程桌面中 gst 预览画面持续卡顿?

A:属于远程桌面转发画面的正常现象,摄像头采集调试优先使用 ADB/SSH 命令行方式,稳定性更好。

Q8:ffmpeg 实时录像按 Ctrl+C 中断后,mp4 视频打不开、文件损坏?

A:ffmpeg 需要收到正常退出信号才能封装 MP4 文件尾;录制结束务必用Ctrl + C正常停止录制,不要直接断开 adb 连接、强制关闭终端;录制的视频存放在/tmp临时目录,板子重启文件可能会丢失,录制完成立刻 adb pull 取回电脑。

十、总结

本篇完成了开发板环境自检(C / Python Hello World),并基于 USB 免驱摄像头 走通了 V4L2 视频子系统的完整链路:设备识别 → 节点定位 → 参数探测 → 抓拍 → FFmpeg 录像 → ADB 文件回传,同时理清了 USB 与 CSI 摄像头的本质差异,为下一步图像数据处理打好基础。

#移远产品体验官 #QuectelPi