结论:车载视觉与惯性输入必须把图像时间、帧同步、曝光、IMU 时间和故障降级当作独立契约,不能只满足「能输出 NV12 / XYZ」。原生 camerad 与 sensord 是两个独立时钟源的生产者:logMonoTime 是发布封装时刻,不等于 IMU 中断时间或图像 SOF;源码中的 EOF 是由内核 SOF 时间加传感器读出时间推算的,不是直接测得的硬件 EOF。固定快照基线为 c8fb906,本文为静态代码阅读,未连接相机模组、I2C / GPIO 实板或路测数据。
原生摄像头:启动到发布
进程选择由 USE_WEBCAM 决定:置位时原生 camerad 不启用;原生程序设置 CPU 亲和性后进入 camerad_thread()。该 C++ 目标在非 Darwin 平台构建,PC / macOS 不能仅因目录存在便声称原生采集可运行。
板级配置里广角、窄角、座舱固定 camera_num 0 / 1 / 2,各有 VisionIPC stream、焦距、发布主题和 PHY;窄 / 广角同步 SOF,座舱期望半周期错相。即使禁用某路图像,源码仍会尝试探测 / 配置传感器,以避免 strobe GPIO 电气副作用。设备初始化打开 Qualcomm req_mgr / cam_sync / ISP / ICP,订阅 SOF 事件;逐路先探测 OS04C10、再探测 OX03C10,探测失败会禁用该路。
| 阶段 | 实现事实 |
|---|---|
| 缓冲 | 为处理模式分配原始缓冲,并为各流创建 18 个 YUV VisionIPC 缓冲;发布时附带 frameId、SOF、EOF。 |
| 逐帧 | poll 读取 req_mgr SOF,按 session_handle 路由,验证请求 / 帧序号,等 IFE / BPS sync,处理帧后送 VisionIPC 与对应 *CameraState,再按亮度更新曝光寄存器。 |
| 状态消息 | 记录帧号、requestId、SOF / EOF、曝光 / 增益及处理耗时;图像主数据走 VisionIPC,FrameData 是另一条状态消息。 |
图像主数据走 VisionIPC,而 FrameData 是另一条状态消息;FD 握手、缓冲复用与通知一致性需与图像数据契约分开验证。
同步与故障边界
validateEvent() 对 requestId=0、帧号 / 请求号跳变重排队;waitForFrameReady() 等 IFE 最多 100 ms、BPS 最多 50 ms,失败清队列并重排,避免使用可能撕裂的帧——该恢复动作不等于图像连续性已保住。
syncFirstFrame() 要求非错相路面相机约 0.2 ms 内、错相座舱相机在 25 ms 偏移的约 0.2 ms 内对齐。若 IFE 帧号超过 40 仍未同步,源码记录超时并将 first_frame_synced 设真,后续可继续发布;因此「有图像」不证明同步质量合格。processFrame() 用内核 SOF 时间加传感器 readout_time_ns 推算 EOF;set_camera_exposure() 根据抽样 Y 平面亮度、目标灰度和前 3 帧曝光状态调整积分时间、模拟增益及高转换增益,测光区域依赖焦距 / 输出尺寸。替换镜头 / 传感器必须重新标定焦距、ROI、曝光延迟和增益表。
webcam:开发替代,不等价于车载模组
Camerad.__init__() 从环境变量选择窄路面、可选广路面和驾驶员摄像头,创建 VisionIPC 缓冲;Camera.read_frames() 经 OpenCV 读取、旋转 180 度并用 PyAV 转 NV12;camera_runner() 以 20 Hz 发布。_send_yuv() 的 SOF / EOF 值用 frame_id * 0.05 s 合成,FrameData 只填帧号与单位变换;没有原生传感器 SOF / EOF、跨相机硬件同步、曝光 / 增益状态。它是开发输入替代,不足以验证车载相机时间契约。
sensord:采样、坐标与时间
I2C bus 1 的 LSM6DS3 加速度、陀螺仪和温度共享设备;先 reset / init,再启动 IRQ 和温度轮询线程。GPIO 边沿时间戳来自内核,用 time.time_ns - time.monotonic_ns 折算到单调时钟;时钟偏移突变超过 10 ms 时跳过当次事件并更新偏移。
| 层 | 实现事实 |
|---|---|
| 加速度 | 读取 data-ready 与三轴寄存器,按 ±2g / 16-bit 换算 m/s²,输出轴顺序为 [y, -x, z];没有 data-ready 时不发布。 |
| 角速度 | 读取 data-ready 与三轴寄存器,按量程换算 rad/s,同样输出 [y, -x, z];与加速度共享 IRQ,但逐传感器检查可读状态。 |
| 温度 | 按 2 Hz 轮询,使用单调时间并按芯片变体选温度比例。 |
| 启动有效性 | 从首次调用有效性检查起,前 0.5 s 不发布有效数据;自测仅在对应环境变量触发。 |
传感器插件如果改轴定义而不更新定位观测模型,会产生系统性估计误差。定位 / 模型消费端应依据观测时间戳和有效性判断新鲜度,而不是依赖发布时刻的 logMonoTime。
验证状态与硬件任务
test_camerad.py 标记 COMMA_HARDWARE_TEST,覆盖曝光收敛、帧连续、路面同步 / 座舱错相及故障注入;test_sensord.py 同样要求硬件,检查频率、时间戳、数值范围和中断停机。本次仅确认测试存在,未运行。自制硬件的最小验收应记录:各路图像 SOF / EOF / 帧号、读出时间、丢帧及重排次数、测光 / 曝光响应;IMU IRQ 时间、轴向 / 量程、启动稳定、掉线和时钟跳变。再以回放、台架和实板测试逐项比较,不以 webcam 或仿真通过替代。
常见问题
「有图像」为什么不能说明相机同步合格?
因为 syncFirstFrame() 在 IFE 帧号超过 40 仍未同步时,源码记录超时并直接把 first_frame_synced 设真,后续可继续发布。允许发布不等于对齐质量达标,需记录实际对齐误差与重排次数。
图像 EOF 是硬件测得的吗?
不是。processFrame() 用内核 SOF 时间加传感器 readout_time_ns 推算 EOF,文中 EOF 并非直接测得的硬件 EOF。新相机插件必须提供时间域、读出时间、请求序列及同步质量,不能把采集完成时刻冒充 SOF。
IMU 的 logMonoTime 就是采样时间吗?
不是。logMonoTime 是发布封装时刻,不等于 IMU 中断时间;GPIO 边沿时间戳来自内核再折算到单调时钟。定位 / 模型消费端应依据观测时间戳和有效性判断新鲜度。
IMU 轴顺序为什么重要?
因为 LSM6DS3_Accel 与 LSM6DS3_Gyro 都输出 [y, -x, z]。传感器插件若改轴定义而不更新定位观测模型,会产生系统性估计误差;新硬件必须显式定义启动稳定时间、量程、轴系和异常状态。
延伸阅读
- IMU 中断与 I2C 采样契约:data-ready 与时间戳 —— 承接本文的 LSM6DS3 中断与轴系说明。
- 相机请求与缓冲生命周期:谁持有那一帧 —— 解释 VisionIPC 缓冲复用与发布一致性。
- 车载传感器曝光与 ISP:亮度如何变成积分时间 —— 承接本文的曝光调整与测光区域。
- 硬件几何变换:坐标系与标定如何落到实现 —— 说明轴系与安装标定为何必须逐项验证。
参考资料
- 项目 02 ——
openpilot-research/CAMERA_IMU_DEEP_DIVE.md(取用了摄像头启动到发布、同步 / 故障边界、webcam 替代路径与 sensord 采样轴系分析)。
说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11