11、摄像头与视觉系统:车载摄像头架构、Camera HAL 适配、环视系统原理

车载摄像头,是智能座舱和自动驾驶的“眼睛”。

我最早接触车载摄像头时,觉得它跟手机摄像头差不多。后来踩了不少坑才发现——完全不是一回事。手机摄像头拍糊了可以重拍,车载摄像头如果延迟一帧,可能就撞上了。

这一章,我们聊聊Android Automotive里的摄像头架构、Camera HAL怎么适配,以及环视系统到底是怎么拼出来的。

11.1 车载摄像头架构概览

先看整体架构。车载摄像头系统,从硬件到应用,大致分四层:

  • 物理摄像头:包括鱼眼、广角、长焦、红外等。每颗摄像头都有自己的ISP(图像信号处理器)。
  • Camera HAL:硬件抽象层,负责把不同厂商的摄像头驱动统一成Android Camera API。
  • Camera Service:Android系统的相机服务,管理摄像头生命周期、数据流。
  • 应用层:环视、DMS、行车记录仪等。

我画了一张架构图,帮你理清关系:

应用层 环视系统 | DMS | 行车记录仪 | 车道保持 Camera Service 生命周期管理 | 数据流调度 | 权限控制 Camera HAL HAL3 接口 | 多摄像头同步 | 硬件抽象 物理摄像头层 鱼眼摄像头 | 广角摄像头 | 红外摄像头 | ISP

嗯,这张图你保存好。后面讲HAL适配和环视原理时,都会用到这个分层。

11.2 Camera HAL 适配

Android Automotive的Camera HAL,核心是HAL3接口。它跟手机上的Camera HAL3基本一致,但有几个关键差异点。

11.2.1 多摄像头同步

车载场景下,经常需要同时打开多颗摄像头。比如环视系统,要同时采集前后左右四路视频。

手机上的Camera HAL,同时打开两路摄像头已经很吃力了。车载上要同时开4路、6路甚至8路。这对HAL的buffer管理、ISP带宽都是巨大考验。

我建议你在HAL层实现一个同步帧回调机制。具体做法:

// 伪代码:多摄像头同步帧回调
class MultiCameraSyncCallback {
    std::map<int, CameraMetadata> frameMap;
    int expectedCount = 4;  // 四路摄像头

    void onFrameReceived(int cameraId, CameraMetadata metadata) {
        frameMap[cameraId] = metadata;
        if (frameMap.size() == expectedCount) {
            // 所有摄像头帧都到齐了,统一回调
            notifyAllFramesReady(frameMap);
            frameMap.clear();
        }
    }
}

我在项目中遇到过一个问题:某款车的前置摄像头帧率是30fps,后置是25fps。同步时总是丢帧。后来发现是ISP的时钟源不一致。解决办法是让所有摄像头共用同一个PLL时钟源。

11.2.2 物理摄像头ID映射

Android系统通过cameraId来识别摄像头。但车载上,摄像头的位置是固定的——前视、后视、左视、右视。

我建议在HAL层建立一个物理位置到cameraId的映射表

物理位置 cameraId FOV 分辨率 帧率
前视 0 120° 1920x1080 30fps
后视 1 140° 1920x1080 30fps
左视 2 190°(鱼眼) 1280x720 25fps
右视 3 190°(鱼眼) 1280x720 25fps

这样做的好处是,上层应用不需要关心硬件细节。环视系统只需要说“我要左视的画面”,HAL自动映射到cameraId 2。

⚠️ 注意: 不同车型的摄像头布局可能不同。HAL层一定要支持动态配置映射表,不要写死。

11.2.3 性能优化

车载摄像头对延迟非常敏感。从摄像头采集到画面显示,延迟不能超过100ms,否则驾驶员会感觉画面“卡顿”。

我常用的优化手段:

  • 零拷贝buffer:使用dma-buf在HAL和显示层之间直接传递,避免内存拷贝。
  • 降低分辨率:环视系统不需要4K,720p足够。分辨率越低,处理越快。
  • 硬件加速:把图像拼接、畸变校正放到GPU或DSP上做。
💡 小技巧: 我曾经在调试时发现,某款芯片的ISP在输出YUV420格式时,比输出NV12快了15%。所以,不要盲目用默认格式,多试试不同格式的实测性能。

11.3 环视系统原理

环视系统,也叫360°全景影像。它把车身四周的摄像头画面拼接成一个鸟瞰图,让驾驶员在车内就能看到车周围的情况。

11.3.1 核心流程

环视系统的处理流程,我总结为四步:

  1. 图像采集:从4颗(或更多)鱼眼摄像头获取原始画面。
  2. 畸变校正:鱼眼镜头有严重的桶形畸变,需要校正成平面图像。
  3. 透视变换:把校正后的图像投影到鸟瞰视角。
  4. 图像拼接:把四幅鸟瞰图拼成一幅完整的360°画面。

我画了一个流程图:

图像采集 畸变校正 透视变换 图像拼接 四路鱼眼 → 校正 → 鸟瞰 → 拼接 关键参数: • 每路摄像头FOV ≥ 190°,确保重叠区域足够 • 拼接重叠区域通常为30-50像素,用于特征匹配 • 输出分辨率一般为1920x1080或1280x720

11.3.2 畸变校正原理

鱼眼摄像头的畸变校正,说白了就是“把弯曲的直线拉直”。

数学上,它需要一个畸变映射表。这个表记录了原始图像每个像素点在校正后图像中的位置。

// 畸变校正映射表生成(简化版)
for (int y = 0; y < dstHeight; y++) {
    for (int x = 0; x < dstWidth; x++) {
        // 将目标像素映射到归一化坐标
        float nx = (x - cx) / fx;
        float ny = (y - cy) / fy;
        
        // 应用鱼眼畸变模型
        float r = sqrt(nx*nx + ny*ny);
        float theta = atan(r);
        float theta_d = theta * (1 + k1*theta*theta + k2*theta*theta*theta*theta);
        
        // 计算原始图像中的坐标
        float srcX = cx + fx * (theta_d / r) * nx;
        float srcY = cy + fy * (theta_d / r) * ny;
        
        mapX[y][x] = srcX;
        mapY[y][x] = srcY;
    }
}

这段代码看起来有点复杂,但核心思想很简单:每个目标像素,都能在原始图像中找到对应的位置。如果找不到,就插值。

🔑 关键点: 畸变映射表只需要在标定时计算一次,之后运行时直接查表。查表操作可以用GPU并行加速,效率极高。

11.3.3 图像拼接与融合

四幅鸟瞰图拼在一起,接缝处会有明显的亮度差异和几何错位。这就需要图像融合

我常用的融合方法有两种:

  • 线性融合:在重叠区域,按距离加权平均。简单,但效果一般。
  • 多频段融合:把图像分解成低频和高频,分别融合。效果好,但计算量大。

实际项目中,我建议用线性融合 + 亮度均衡。先对每路图像做全局亮度调整,让四幅图的亮度一致,再用线性融合处理接缝。这样计算量小,效果也够用。

⚠️ 注意: 拼接时一定要考虑车辆自身的遮挡。车头、车尾、后视镜都会遮挡一部分视野。这些区域需要用虚拟的“车身模型”覆盖掉。

11.4 避坑指南

最后,分享几个我踩过的坑:

  • 摄像头标定:每颗摄像头的内参和外参必须精确标定。我曾经因为标定板没贴平,导致拼接画面错位了5个像素。后来重新标定才解决。
  • 帧同步:四路摄像头如果帧不同步,拼接画面会出现“撕裂”。我建议用硬件触发信号(如PWM)来同步所有摄像头的曝光时刻。
  • 热管理:车载摄像头在夏天暴晒后,温度可能达到85°C以上。ISP的性能会下降,甚至丢帧。一定要做降频保护。

嗯,这一章的内容就到这里。摄像头和视觉系统是智能座舱的核心,也是坑最多的地方。希望我的经验能帮你少走弯路。


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