22、Camera系统深度调优:Camera HAL3架构、ISP流水线调试、多摄像头同步、图像质量调优

各位同学,今天我们来啃一块硬骨头——Camera系统的深度调优。说实话,在i.MX Android Automotive的移植工作中,Camera这块是最容易出幺蛾子的。我最早做车载项目时,就因为在Camera HAL层踩了个坑,导致图像延迟整整高了30ms,被测试追着跑了三天。嗯,从那以后,我对Camera系统就多了一份敬畏。

这一章,我会把Camera HAL3的架构、ISP流水线、多摄像头同步以及图像质量调优这几个核心点串起来讲。你想想看,车载上有环视、有DMS、有行车记录,摄像头越来越多,怎么让它们协同工作不出乱子?这就是我们要解决的问题。

22.1 Camera HAL3架构解析

Android从5.0开始引入了Camera HAL3,取代了老旧的HAL1。说白了,HAL3的核心思想就是让应用层能更精细地控制摄像头。它把拍照流程拆成了一个个独立的请求(Capture Request),每个请求可以指定不同的参数——曝光、增益、对焦、白平衡,你都可以单独控制。

我个人习惯把HAL3架构分成三层来看:

  • 应用层:通过Camera2 API下发请求
  • 框架层:CameraService管理会话和流
  • HAL层:实现ICameraDevice、ICameraDeviceSession等接口

在i.MX平台上,我们通常需要实现一个自定义的Camera HAL模块。这里有个关键点——Pipeline Depth。它决定了HAL能同时处理多少个未完成的请求。我建议至少设为4,否则在高帧率场景下容易丢帧。

核心要点:HAL3的流配置(Stream Configuration)必须在openSession之前完成。一旦session建立,流配置就不能改了。我在项目中遇到过有人试图在运行中动态添加输出流,结果直接crash。

来看一段典型的HAL3初始化代码片段:

// 配置输出流
camera3_stream_t output_stream;
output_stream.stream_type = CAMERA3_STREAM_OUTPUT;
output_stream.width = 1920;
output_stream.height = 1080;
output_stream.format = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
output_stream.usage = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_RENDER;
output_stream.max_buffers = 4;

// 配置输入流(用于ZSL或 reprocess)
camera3_stream_t input_stream;
input_stream.stream_type = CAMERA3_STREAM_INPUT;
// ... 类似配置

// 配置流集合
camera3_stream_configuration_t stream_config;
stream_config.num_streams = 2;
stream_config.streams = &streams[0];
stream_config.operation_mode = CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE;

注意那个max_buffers,它决定了HAL内部buffer pool的大小。设小了容易阻塞,设大了浪费内存。我一般根据帧率和处理延迟来算:max_buffers = 帧率 × 处理延迟(秒) + 2

22.2 ISP流水线调试

ISP(图像信号处理器)是Camera系统的灵魂。i.MX8系列集成了ISP,但说实话,默认的ISP参数往往不是最优的。你需要根据具体的sensor模组和光照环境来调。

ISP流水线大致是这样的:

RAW输入 BLC AWB Demosaic YUV输出 调试点1 调试点2 调试点3 ISP流水线调试节点 输入/输出节点 处理节点 格式转换

每个调试点我们都可以插入hook函数,抓取中间数据。我常用的方法是:在BLC(黑电平校正)之后、AWB(自动白平衡)之后分别dump一帧RAW数据出来,对比看看算法有没有跑偏。

调试技巧:在i.MX平台上,可以通过sysfs节点动态调整ISP参数。比如:echo "100 200 300" > /sys/class/isp/isp0/gain。这样不用重新编译,调参效率高很多。

有一次,我发现图像偏绿严重。查了半天,原来是AWB的色温表没校准。sensor的RGB响应曲线和ISP默认的色温表不匹配。解决办法很简单——重新标定色温表,或者干脆在HAL层把AWB的增益范围放宽一点。

22.3 多摄像头同步

车载场景下,多摄像头同步是个大难题。环视系统需要4个摄像头画面无缝拼接,如果帧不同步,拼接处就会出现撕裂。

多摄像头同步有两种主流方案:

方案 原理 优点 缺点
硬件同步 共用VSYNC信号 精度高(微秒级) 需要硬件支持,布线复杂
软件同步 基于时间戳对齐 实现简单,无需额外硬件 精度低(毫秒级),有漂移

我个人更倾向于硬件同步。在i.MX平台上,我们可以把主摄像头的VSYNC信号通过GPIO输出,分发给其他从摄像头。这样所有sensor的曝光起始时间就对齐了。

但硬件同步也有坑。我曾经遇到一个问题:主摄像头帧率是30fps,从摄像头也是30fps,但画面就是不同步。后来用示波器一量,发现从摄像头的VSYNC有大约200μs的抖动。原因是PCB走线太长,信号质量下降了。解决办法是在从摄像头端加一个施密特触发器整形。

注意:软件同步方案虽然简单,但在车载这种对实时性要求高的场景下,我不推荐。Android的getTimestamp()精度有限,而且不同sensor的clock domain可能不同,时间戳漂移是必然的。

如果你非要用软件同步,我建议在HAL层实现一个syncFrame机制:主摄像头每产生一帧,就通过回调通知从摄像头,从摄像头立即触发一次capture。这样虽然做不到真正的帧同步,但至少能保证帧对齐误差在一个frame interval以内。

22.4 图像质量调优

图像质量调优,说白了就是让画面看起来舒服。这里涉及的东西很多——噪声、锐度、色彩、动态范围……我挑几个重点讲。

22.4.1 噪声控制

车载摄像头经常在低光照下工作,噪声是头号敌人。ISP里有3DNR(三维降噪)模块,但参数调不好容易把细节也抹掉。

我的经验是:先调空域降噪(2DNR),再调时域降噪(3DNR)。空域降噪的强度不要超过3(假设范围0-10),否则边缘会糊。时域降噪的帧数设为3-5帧比较合适,再多就会有拖影。

22.4.2 动态范围

行车记录仪经常遇到逆光场景——天空过曝,地面死黑。这时候需要开启HDR(高动态范围)模式。i.MX的ISP支持多帧HDR和DOL-HDR两种方式。

多帧HDR:连续拍三帧(短曝光、中曝光、长曝光),然后合成。优点是画质好,缺点是有运动伪影。

DOL-HDR:一次曝光读出多个不同增益的信号。优点是速度快,缺点是噪声大。

我建议在白天用多帧HDR,晚上用DOL-HDR。这个切换逻辑可以在HAL层根据环境亮度自动判断。

22.4.3 色彩校准

色彩校准需要用到色卡(比如X-Rite ColorChecker)。流程是:拍一张色卡照片,提取24个色块的RGB值,和标准值对比,算出色彩校正矩阵(CCM)。

这个矩阵通常在ISP的tuning tool里配置。但如果你像我一样喜欢手动调,可以在HAL层加一个色彩校正的post-processing:

// 色彩校正矩阵(示例)
float ccm[3][3] = {
    {1.2, -0.1, -0.1},
    {-0.1, 1.3, -0.2},
    {0.0, -0.1, 1.1}
};

// 对每个像素应用CCM
for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
        float r = src[y][x].r;
        float g = src[y][x].g;
        float b = src[y][x].b;
        dst[y][x].r = ccm[0][0]*r + ccm[0][1]*g + ccm[0][2]*b;
        dst[y][x].g = ccm[1][0]*r + ccm[1][1]*g + ccm[1][2]*b;
        dst[y][x].b = ccm[2][0]*r + ccm[2][1]*g + ccm[2][2]*b;
    }
}

重要提醒:色彩校准必须在固定的光源下进行。D65(6500K)是标准日光光源,但车载场景下建议再校准一个D50(5000K)的矩阵,用于黄昏和隧道场景。

好了,Camera系统调优的内容就讲到这里。这一章信息量不小,从HAL3架构到ISP流水线,再到多摄像头同步和图像质量调优,每个点都值得你在实际项目中反复打磨。记住,调Camera没有捷径,就是不断试错、不断看画面、不断调参数。我当年为了调一个环视系统的拼接效果,在车里坐了整整两天,眼睛都快看花了。但当你看到最终画面完美呈现的那一刻,一切都值了。

公众号:蓝海资料掘金营,微信deep3321