7,903
社区成员
发帖
与我相关
我的任务
分享项目面向钢结构表面腐蚀巡检,后续希望利用双目相机同时提供高清 RGB 图像与空间深度信息,再将腐蚀分割结果定位到钢结构的具体位置。硬件采用阿加犀犀牛派 X1 开发板与 Stereolabs ZED 2i 双目相机,Ubuntu 主机承担程序开发、对照测试及算法研究。
刚拿到硬件时,一个看似简单的目标——“把相机接上开发板,拍下两张清楚的图像”——就涉及若干不同层次的验证:USB 是否正确枚举?Android 是否能调用该摄像头?Linux 内核是否暴露 UVC 视频节点?视频格式能否设置?能否取得完整帧?取得一张高清图后,能否以目标 FPS 持续采集?
我们最终选择 ADB + Android NDK + Linux V4L2,先解决原始视频采集,再做连续取帧与主机对照实验。这样能够把系统接口、采集代码和性能问题逐层拆开,而不是一开始就把采集、深度估计、腐蚀识别都混在一起。

图1|本项目实际使用的犀牛派 X1 开发板与 ZED 2i 双目相机。

图2|Ubuntu 主机、X1 开发板及 ZED 2i 的实验连接关系。
| 项目 | 当前实验配置 |
|---|---|
| 边缘计算设备 | 阿加犀犀牛派 X1 |
| 开发板系统 | Android 13,API 33,ARM64 / aarch64 |
| 内核 | Linux 5.15.178(测试时) |
| 视频相机 | Stereolabs ZED 2i 双目相机 |
| 开发及对照主机 | Ubuntu 22.04,x86_64 |
| 设备管理 | ADB |
| 交叉编译 | Android NDK r30,aarch64-linux-android33-clang |
| 视频采集接口 | Linux UVC / V4L2 |
| 相机输出格式 | YUYV(双目左右水平拼接) |
| 保存格式 | 转换为 RGB 后写入 PPM 文件 |
| USB 枚举速度 | X1 和 Ubuntu 侧均记录约 5000M(USB 3 链路枚举信息) |
本次使用的是同一台 ZED 2i 和同一条连接线。在 X1 与 Ubuntu 两端,用对应平台编译的 V4L2 基准程序执行相同采集模式的对照测试。两端 USB Host 控制器、内核驱动与调度环境并不相同,因此结果可用于比较平台表现,但不能直接当作底层原因的定论。
(1)USB 识别成功:lsusb 看到了摄像头;(2)视频采集成功:可以从 /dev/videoX 获取完整视频帧;(3)连续采集达到设定 FPS:有效帧数量与实际耗时满足目标;(4)端到端视觉算法稳定运行:采集、图像处理、模型推理和输出的整体时延满足业务需求。
本项目目前完成的是前面三个层次中的识别、单帧采集和对连续采集的性能测试。没有把 Ubuntu 主机上的腐蚀分割、SGBM 深度计算成果表述成已经在 X1 完成实时部署。
在 Ubuntu 终端运行:
adb devices
状态显示为 device 后,再查询 X1 本机的 USB 枚举:
adb shell lsusb
本次记录中,ZED 2i 对应的 USB 标识为 2b03:f880、2b03:f881。其中 2b03:f880 对应主要视频设备,另一个为相机相关接口。设备能被枚举,证明 USB 层已经看见外设;但是 Android Camera Framework 并不一定会把它注册成普通相机。

图3|开发板 ADB 状态与 ZED 2i USB 枚举结果。
不能默认 USB 摄像头就是 /dev/video0。X1 自身也有系统相机管理节点,因此使用下面的命令按名称识别:
adb shell 'for f in /sys/class/video4linux/video*/name; do echo -n "$f : "; cat "$f"; done'
实验中显示:video0 为 cam-req-mgr,video1 为 cam_sync,video2 与 video3 均为 ZED 2i: ZED 2i。经测试,主要可用的采集节点为 **/dev/video2**。其他节点不能简单按名字相同就认为采集功能也相同。

图4|X1 系统识别到的 V4L2 节点。/dev/video2 为本次经验证的主视频采集节点。
本次环境下,相机虽然能通过 UVC 驱动形成 Linux 视频节点,但不能按照常规 Android 相机应用的使用方式直接取得 ZED 2i 图像。因此我们绕开普通 Camera Framework,直接对 /dev/video2 使用 V4L2 的 open、ioctl、mmap 和队列操作。这是针对当前固件与设备组合的方案,并不意味着所有 Android 设备都无法使用 USB 摄像头。
板端 Android 环境没有预装完整的 v4l2-ctl、FFmpeg、GStreamer 等调试工具,普通 x86_64 Linux 可执行文件也不能直接在 ARM64 Android 上运行。我们于是编写 C 语言采集程序,在 Ubuntu 上通过 NDK r30 交叉编译,随后用 ADB 上传到 X1。

图5|USB 枚举、V4L2 缓冲区采集到双目图像保存的程序流程。
程序调用的核心 V4L2 操作包括:VIDIOC_QUERYCAP(能力查询)、VIDIOC_S_FMT(设置视频格式)、VIDIOC_REQBUFS(申请缓冲区)、VIDIOC_QBUF(缓冲区排队)、VIDIOC_STREAMON(启动)、VIDIOC_DQBUF(取出视频帧)与 VIDIOC_STREAMOFF(关闭)。
如果已将环境变量 ANDROID_NDK_HOME 指向安装好的 Android NDK 根目录,可以使用下列编译形式(具体工具链路径需要与本机一致):
"$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android33-clang" \
-O2 -Wall -Wextra zed_hd2k_capture.c -o zed_hd2k_capture
上传可执行文件并赋予权限:
adb push zed_hd2k_capture /data/local/tmp/
adb shell chmod +x /data/local/tmp/zed_hd2k_capture
提示: zed_hd2k_capture 是本项目编写的 C 语言程序,不是 Android 默认命令,也不是 ZED SDK。素材包中附有本次采集程序源码,便于理解采集流程;不同系统上复现仍需要检查 NDK 和设备权限。
在当前采集接口下,每个视频帧同时包含左右两个摄像头的画面,以水平拼接形式输出。因此,不能把 4416×1242 看作单眼分辨率。
| 视频帧(双目拼接) | 每路图像尺寸 | 每帧 YUYV 理论大小 |
|---|---|---|
1344×376 | 672×376 | 1,010,688 B |
2560×720 | 1280×720 | 3,686,400 B |
3840×1080 | 1920×1080 | 8,294,400 B |
4416×1242 | 2208×1242 | 10,969,344 B |

图6|不同采集模式的左右目拼接关系。
YUYV 是一种 YUV 4:2:2 打包格式,理论平均每个像素约占 2 字节。所以一帧原始数据量为:
FrameBytes = Width × Height × 2
例如 HD2K 拼接帧:4416 × 1242 × 2 = 10,969,344 bytes。日志里 bytesperline = 8832,与 4416 × 2 对应。
在确认视频节点、程序可执行后运行:
adb shell /data/local/tmp/zed_hd2k_capture /dev/video2
本次设置为 4416×1242 YUYV、目标 15 FPS,程序会等待 20 个完整有效帧作为预热,再将最后取得的双目图像转换为 RGB 并分成两路图像。采集程序并非通过 ZED SDK 直接获取彩色深度数据。

图7|实际运行 HD2K 采集程序的终端日志;取得完整有效帧,左右目 PPM 保存成功。
终端最后显示:
Stable HD2K frame acquired: 10969344 bytes
Saved:
Left : /data/local/tmp/zed_left_hd2k.ppm
Right: /data/local/tmp/zed_right_hd2k.ppm
Capture and HD2K stereo split successful.
将文件传回 Ubuntu 查看:
mkdir -p ~/下载/zed_v4l2_test
adb pull /data/local/tmp/zed_left_hd2k.ppm ~/下载/zed_v4l2_test/
adb pull /data/local/tmp/zed_right_hd2k.ppm ~/下载/zed_v4l2_test/

图8|同一次采集的左右目图像,每路原始像素尺寸均为 2208×1242。
左右目画面均能呈现同一场景,存在合理的视角差异。这里证明的是图像拆分成功,不是深度测量精度已经得到验证。
应分开检查 USB 枚举、视频节点、Camera HAL 与 V4L2 采集能力。USB 设备 ID 已出现,不代表 Android Camera Framework 已完成注册;节点存在,也不代表该节点一定能稳定输出需要的格式。
早期采集中出现过颜色异常,以及 bytesused=0 的情况。后来程序增加了有效字节数检查、完整帧长度检查和预热阶段;这样能够降低错误帧被当成最终图片保存的风险。
需要注意: 20 帧是本次测试采用的预热数量,不是所有相机的通用标准;简单丢弃前20帧也不能解释或修复所有曝光、白平衡与 USB 传输问题。
上一节的日志显示 Driver frame rate: 15.000 FPS,但这是驱动报告的设定/协商帧率,不是连续取帧过程中独立计时得到的有效 FPS。这一点直接引出了下一轮测试。
基准程序设置指定的分辨率与帧率,在视频流启动后连续取帧,用实测时长与成功取得的有效帧数计算:
Measured FPS = 完成取帧的有效帧数 / 经过时间(秒)
这种 FPS 与 VIDIOC_G_PARM 等 API 报告的配置值不是一个概念。我们还会检查 sequence 的连续性,避免只看计数就忽略中间帧是否跳跃。
下表为先前保留的项目实验记录;暂未为每组模式附上完整的多轮重复测试方差,因此属于阶段性单组测试结果汇总,不是产品规格书或所有场景可保证的性能。
| 双目拼接模式 | 请求 FPS | X1 有效采集 FPS | 达标比例 |
|---|---|---|---|
1344×376 | 100 | 97.67 | 97.7% |
2560×720 | 30 | 27.98 | 93.3% |
2560×720 | 60 | 49.09 | 81.8% |
3840×1080 | 15 | 10.92 | 72.8% |
3840×1080 | 30 | 22.80 | 76.0% |
4416×1242 | 15 | 8.20 | 54.7% |

图9|X1 在六种 YUYV 采集模式下的请求帧率与实际有效帧率。
可以看出,低分辨率下更容易接近目标值:例如 1344×376@100 实测约 97.67 FPS;而 HD2K 4416×1242@15 连续采集约 8.20 FPS。这里的“较低 FPS”不能直接理解为该相机在任何 X1 软件环境下都只有这一性能。
为了排除“相机本身无法产生目标帧率”这一简单解释,使用同一台相机和同一条线缆,在 Ubuntu 主机上进行了三种相同采集模式的测试。
| 拼接图像模式 | 目标 FPS | X1 实测 FPS | Ubuntu 实测 FPS |
|---|---|---|---|
2560×720@60 | 60 | 49.09 | 60.00 |
3840×1080@30 | 30 | 22.80 | 29.73 |
4416×1242@15 | 15 | 8.20 | 15.00 |

图10|同一 ZED 2i、同一线缆,在 X1 与 Ubuntu 主机上运行对应模式的实测帧率。
Ubuntu 在这三项测试里均接近目标帧率,而 X1 在当前软件/硬件链路下出现不同程度下降。因此,排查重点应转向两个平台的采集链路差异:USB Host 控制器、驱动、缓存机制、用户态程序运行环境及系统负载等。
但是: 对照实验只能提高相关怀疑的合理性,不能直接证明某个模块“出了故障”;因为控制器、内核和调度条件尚未完全统一。
在图7的高清采集日志里,程序记录的20个完整有效预热帧 sequence 依次为:
0, 1, 2, 7, 8, 9, 10, 15, 18, 19,
20, 23, 26, 27, 32, 43, 50, 53, 54, 63

图11|由实际采集日志绘制的 sequence 记录,虚线是假定每次取帧都连续递增的参考线。
如果按从0到63的编号范围观察,共有64个序列号位置,而我们这次只记录了20个完整有效帧,另外44个编号没有出现在这个有效帧列表中。应谨慎理解:这不等于已经证明 USB 总线上丢了44个帧或44个数据包。应用端过滤不完整帧、内核缓冲队列溢出或调度导致取帧不及时等,都可能造成类似现象。
排查时至少要把几个量分开:设备实际产生了多少帧、内核驱动接收到多少帧、DQBUF 返回多少帧、应用主动忽略了多少帧,以及最终落盘多少帧。仅凭一张截图,还不能定位到具体一层。
同理,序列跳跃的截图用于展示“存在异常”,不能代替独立连续采集的 FPS 统计。本文 HD2K 的 8.20 FPS 来自此前的连续采集基准结果,而不是从这20个帧编号直接换算。
YUYV 原始吞吐量可以按拼接帧宽高、每像素2字节、设定 FPS 计算:
理论数据率(MiB/s)= Width × Height × 2 × RequestedFPS / 1,048,576
| 视频模式 | 设定 FPS | 理论原始数据率 |
|---|---|---|
2560×720 | 60 | 210.94 MiB/s |
3840×1080 | 30 | 237.30 MiB/s |
4416×1242 | 15 | 156.92 MiB/s |

图12|根据 YUYV 原始数据尺寸与目标帧率计算的理论数据率,不代表 USB 总线实测吞吐量。
有一个重要但容易忽略的事实:HD2K@15 的理论原始数据率,比 1080p@30 更低。但是在 X1 上,相对设定 FPS 的下降却更明显。
这表明不能简单用“分辨率越高 → 带宽越大 → 肯定掉帧”来解释。我们需要同时考虑:单帧有效载荷与缓冲区管理、USB 等时传输调度、URB 的队列深度、UVC 数据重组、DMA/内存搬运,以及处理线程是否及时执行 DQBUF 等可能因素。
此前调试中出现过与 xhci_hcd、URB 取消等相关的信息,但这些日志有的发生在关闭视频流时。当前证据只能把它们列为排查线索,不能将其直接认定为根因。 USB 协商显示约 5000M,也只能说明连接枚举速度,不等于无额外开销的有效应用数据带宽。
下一轮应把“观察到的异常”和“可能的解释”分开,减少重复盲测。

图13|针对本项目的分层排障路线(建议方案,不是已经完成的诊断结果)。
建议保留固定的测试记录字段:采集模式、UVC 格式、缓冲区个数、运行时长、成功 DQBUF 次数、过滤帧数、每帧 bytesused 与 sequence、系统负载、采集程序是否执行 RGB 转换或文件写入等。对每个模式至少重复多次,记录均值与波动区间。
在程序侧,先让基准测试只负责取帧和计时,再逐步打开颜色转换、左右目分离、保存文件,才能判断应用处理开销占了多少;在内核侧,进一步分析 UVC 队列和 xHCI / URB 相关统计,但需要有权限且避免把正常关闭视频流时的日志误判为故障。
特别说明:本文附带的是HD2K 单帧采集程序的源码。用于连续帧率统计的多模式基准程序不是这份源码本身,读者不能直接执行该单帧程序并据此复现上表所有连续 FPS 数字。
对于钢结构腐蚀巡检,低分辨率实时观察与高清静态记录的需求不同。综合当前测试,可以考虑采用两个采集阶段:巡检时采用 2560×720@30 等相对稳定的模式,在 X1 上实际采集约 27.98 FPS;发现疑似腐蚀后切换 HD2K 获取静态图像,每路保留 2208×1242 的细节。

图14|分阶段采集与腐蚀识别的工程架构设想,尚未完成 X1 端端到端部署及性能验证。
这种“移动巡检 + 高清取证”的方案有利于把采集压力与图像细节需求进行折中,但只是后续系统设计方向。当前腐蚀模型训练、数据集测试和双目深度算法主要在 Ubuntu 主机上进行,不能据此宣称 X1 已完成全流程实时腐蚀识别与三维定位。
复查设备状态可依次执行:
adb devices
adb shell lsusb
adb shell 'for f in /sys/class/video4linux/video*/name; do echo -n "$f : "; cat "$f"; done'
在确认 /dev/video2 是当前实际可用节点、且板端二进制程序存在后,再执行:
adb shell 'ls -lh /data/local/tmp/zed_hd2k_capture'
adb shell /data/local/tmp/zed_hd2k_capture /dev/video2
重要: 程序会重新写入相同名称的 PPM 文件,复测前先备份已有图片;设备节点编号也可能在重连后变化。不要直接照搬 15.000 FPS 的日志输出作为连续实时性能,必须独立计时。
至此,本项目已经完成一条可复核的低层采集路径:ADB 识别设备 → UVC/V4L2 访问 → NDK 编译 → YUYV 双目取帧 → 预热与完整性检查 → RGB 拆分 → PPM 导出;同时通过连续采集和 Ubuntu 对照,发现了当前 X1 组合的部分高清模式存在掉帧与序列编号跳跃。