简单看看3A算法1(基础概念)

该文章已生成可运行项目,

1 综述

相机3A算法其实就是AE自动曝光、AF自动对焦、AWB自动白平衡,是成像系统最核心的三大基础算法,

3A一般运行在ISP(图像信号处理器)或主控 SoC。

典型链路:Sensor 输出原始 RAW → ISP 预处理 → 3A 统计模块采集画面数据 → 3A 算法计算 → 反馈控制。

所有3A都属于闭环负反馈控制系统。控制的内容包括:
控制Sensor:曝光时间、模拟增益、数字增益
控制镜头:马达驱动对焦、光圈
控制ISP:色温矩阵、色彩增益

2 AE 自动曝光(Auto Exposure)

2.1 AE原理

自动对焦的作用就是让整张画面亮度适中,不过曝、不欠曝,兼顾主体、场景、动态范围。

原理是首先对图像进行亮度统计
ISP把画面划分为多个测光分区(常见 8×8、16×16、32×32 网格),逐区计算平均亮度 (Y 亮度通道),输出测光直方图。


测光模式,算法根据模式加权计算目标亮度值。有以下几种
1 矩阵测光(评价测光):全域加权,日常默认
2 中央重点测光:中心区域权重更高
3 点测光:仅小范围区域计算亮度(拍人像、逆光)

根据计算到的亮度,进行曝光收敛计算(核心闭环)
曝光三要素联动调节:

画面亮度∝(正比)曝光时间(Shutter)*模拟增益(AG)*数字增益(DG)*光圈(F)

之后算法对比当前亮度与目标亮度,计算误差:

如果画面偏暗:拉长快门、提高增益、开大光圈。
如果画面偏亮:缩短快门、降低增益、缩小光圈。

此外,也有一些高级AE策略。比如:

逆光/背光补偿:压低背景、提亮主体
夜景 AE:拉长快门 + 限制增益,兼顾亮度与噪点
动态场景:加快收敛速度,避免画面忽亮忽暗

2.2 AE的实际实现

AE 属于ISP 的标准功能模块。基本上全部由ISP完成,首先ISP硬件统计画面亮度(直方图、均值),然后ISP 固件 / 算法计算:要调多少快门、增益、光圈。最后ISP 直接下发设置给 Sensor / 镜头驱动。

测光 → 计算 → 调参,全链路在 ISP 闭环。

软件一般能控制一些,但是不多。首先是直接通过接口设置,基本上就是开关,或者写死一些数值。

v4l2-ctl --set-ctrl=exposure_auto=1          # 手动模式
v4l2-ctl --set-ctrl=exposure_absolute=1000    # 固定快门
v4l2-ctl --set-ctrl=gain=64                     # 固定增益

然后是更底层一点。

ISP硬件负责统计画面亮度、直方图、均值,这部分硬实时、快、耗资源。通过接口读取这些数据,自己计算快门/增益。最后将需要的效果写到Sensor/ISP 寄存器。

最后是在ISP SDK里替换原厂 AE,自己写固件级AE(C 语言),跑在ISP内部小核上。

2.3 AE的软件实现

代码:

import cv2
import numpy as np

class ImageAE:
    def __init__(self):
        self.target_y = 128.0       # 目标平均亮度 0~255
        # 快门配置
        self.shutter = 1000.0
        self.shut_min = 100.0
        self.shut_max = 20000.0
        # 模拟增益
        self.again = 1.0
        self.gain_min = 1.0
        self.gain_max = 16.0
        self.step_k = 0.07          # 调节系数

    def calc_avg_luma(self, img_bgr):
        """BGR图片求全局平均亮度Y"""
        b,g,r = cv2.split(img_bgr)
        y = r*0.299 + g*0.587 + b*0.114
        return float(np.mean(y))

    def ae_adjust(self, img_path):
        # 读图
        img = cv2.imread(img_path)
        if img is None:
            print("图片读取失败")
            return

        curr_y = self.calc_avg_luma(img)
        err = self.target_y - curr_y

        print(f"当前画面平均亮度:{curr_y:.2f} | 目标:{self.target_y} | 亮度误差:{err:.2f}")

        if err > 1e-3:
            # 偏暗:先加快门
            if self.shutter < self.shut_max:
                self.shutter *= (1 + self.step_k)
            else:
                # 快门满了提增益
                if self.again < self.gain_max:
                    self.again *= (1 + self.step_k)
        elif err < -1e-3:
            # 偏亮:先减快门
            if self.shutter > self.shut_min:
                self.shutter *= (1 - self.step_k)
            else:
                if self.again > self.gain_min:
                    self.again *= (1 - self.step_k)

        # 限幅
        self.shutter = np.clip(self.shutter, self.shut_min, self.shut_max)
        self.again = np.clip(self.again, self.gain_min, self.gain_max)

        print(f"调节后 → 快门:{self.shutter:.1f}us  模拟增益:{self.again:.2f}×\n")
        return self.shutter, self.again

if __name__ == "__main__":
    ae = ImageAE()
    # 换成自己的图片路径
    pic_path = "test.jpg"
    ae.ae_adjust(pic_path)

随便找了一张图片进去,结果如下:

python3 ae.py
当前画面平均亮度:136.35 | 目标:128.0 | 亮度误差:-8.35
调节后 → 快门:930.0us  模拟增益:1.00×

根据这个代码,就很容易看到AE的关键。首先怎么计算亮度,其次是如何进行调整。

首先是亮度计算,这里是y = r*0.299 + g*0.587 + b*0.114。我也不知道这个怎么来的,总之这个也是一个计算方法。

另外是调整亮度,在这里的代码是先是快门,快门满了调增益。但实际是怎么调的,我估计复杂的多,还有一点就是这里没有光圈,原因很简单。在一般的手机camera中,都是没有光圈这种高档货的。就算是快门,也是电子快门。

3 AF 自动对焦(Auto Focus)

3.1 技术原理

自动对焦目前有两个主流实现。

路线1:反差对焦 CAF(传统手机 / 监控 / 低端相机,纯图像算法)
原理:画面对比度越高 = 对焦越清晰。
ROI 选区:选定对焦区域(中心、触摸对焦、人脸区域)。
对比度计算:对ROI 做边缘检测 / 高频分量统计,得到对焦评价值(Focus Value, FV)。
爬山搜索算法(核心)
驱动镜头马达小幅移动,连续采集 FV:
FV 上升 → 继续同方向移动
FV 下降 → 反向移动
FV 达到峰值 → 判定合焦,停止马达
状态控制
单次 AF:合焦后锁定
连续 AF:持续跟踪主体,动态微调
失焦重搜:画面大幅变化时重新启动搜索
缺点:暗光、低反差场景(白墙、蓝天)容易拉风箱。


路线2:相位对焦 PDAF(现在手机 / 微单主流,硬件 + 算法结合)
原理:Sensor 内置相位检测像素,模拟人眼双眼视差,直接计算对焦偏移量。
硬件基础
部分像素被遮挡,分成左右两组相位像素,输出两组图像。
相位差计算
比对两组图像偏移量,算出离焦方向 + 离焦距离。
开环粗对焦 + 闭环精对焦
粗调:根据相位差一步到位移动镜头(速度极快)
精调:切换反差 AF 做微小修正,保证精度
算法增强
多窗 PDAF:全画面多点对焦
人脸 / 人眼追踪 AF:优先锁定人脸、瞳孔 ROI
运动预测 AF:预判物体移动轨迹,提前追焦

3.2 软件模拟

from PIL import Image
import numpy as np

class AutoFocus:
    def __init__(self):
        # 马达位置区间 0~100
        self.pos = 50.0
        self.pos_min = 0.0
        self.pos_max = 100.0
        self.step = 3.0
        self.dir = 1.0
        self.last_contrast = 0.0

    def calc_contrast(self, img):
        arr = np.array(img.convert("L"), dtype=np.float32)
        h, w = arr.shape
        # 中心对焦ROI
        x1, y1 = w//4, h//4
        x2, y2 = w*3//4, h*3//4
        roi = arr[y1:y2, x1:x2]
        # 分开计算横竖梯度,各自取平均再相加
        gx = np.abs(np.diff(roi, axis=1))
        gy = np.abs(np.diff(roi, axis=0))
        mean_gx = np.mean(gx)
        mean_gy = np.mean(gy)
        return float(mean_gx + mean_gy)

    def af_step(self, img_path):
        img = Image.open(img_path)
        curr_con = self.calc_contrast(img)
        print(f"位置:{self.pos:.1f} | 当前对比度:{curr_con:.2f} | 上次:{self.last_contrast:.2f}")

        if curr_con > self.last_contrast:
            self.last_contrast = curr_con
            self.pos += self.dir * self.step
        else:
            # 过峰,换向+缩小步长
            self.dir *= -1.0
            self.pos += self.dir * self.step
            self.step *= 0.5

        self.pos = np.clip(self.pos, self.pos_min, self.pos_max)
        print(f"新马达位置: {self.pos:.1f}\n")
        return self.pos

if __name__ == "__main__":
    af = AutoFocus()
    # 循环迭代对焦
    for _ in range(12):
        af.af_step("test.jpg")

这里的核心原理就是怎么计算对焦。

  • 模糊:相邻像素亮度变化平缓 → 像素差值小;
  • 清晰:物体边缘多,相邻像素明暗跳变剧烈 → 像素差值大。

此时相邻像素做减法,用来抓取边缘变化(高频信息),也就是 AF清晰度评分。得分越大越清晰。

之后的整个过程有点类似AI的梯度下降,焦距不断调整,找到一个最大的值。之后传给马达寄存器。

具体实现则是清晰度的统计由ISP硬件完成,生成一个score,之后可能是SOC根据这个来调整摄像头的马达,也可能是ISP直接控制。

4 AWB 自动白平衡(Auto White Balance)

4.1 AWB原理

白平衡的核心问题很简单。假设在一个黄光的环境下,此时一张白纸因为黄光会看起来泛黄。本来这个也没问题,但是此时人脑自动修正色温,最后眼睛依旧看成白色。

从生存的原理来说,同一个物体如果在不同光线下差异很大,甚至无法分辨,就会严重影响对物体的试别,影响到生存。所以人脑会把所有看到的物体,都还原成白光下的颜色,也就是白平衡。

所以自动白平衡核心目标就是,在工业camera中,也实现消除环境色温影响,让白色物体呈现真实白色,纠正偏色(黄、蓝、绿)。色温单位:K(开尔文)
暖光(室内、夕阳):低色温 2000K~4000K → 画面偏黄
冷光(阴天、户外):高色温 6000K~10000K → 画面偏蓝


经典实现算法(工业界主流)

(1)灰度世界法(最基础、通用)

如果拍摄一个自然场景,整张画面平均下来,R=G=B,整体就是是中性灰 / 白色
那么首先统计整图R/G/B三通道均值,然后计算各通道增益:

Gain_R = \frac{G_{avg}}{R_{avg}},\quad Gain_B = \frac{G_{avg}}{B_{avg}},GainG​=1.0


之后ISP用R、B增益修正像素,拉平三通道,实现R=G=B,从而实现白平衡。
优点:简单、算力低;缺点:大面积单色(蓝天、草地)会失效。

(2)白点检测法(现在手机主力方案)

寻找画面里最接近纯白的像素点作为参考白点,也就是亮度足够高、颜色最接近R=G=B的低色彩饱和度区域的像素(候选白点)。
统计候选点的色温分布,计算全局最优色温。
将找到的色温映射为R/G/B增益矩阵/色温转换矩阵,送入ISP对画面进行调色。


(3)色域投影 + 色温曲线校准
将 RGB 转到 YCbCr/CIE xy 色彩空间,分离亮度与色度。
结合出厂标定的色温曲线(不同镜头、Sensor 固有偏色)做补偿。
分区 AWB:画面不同区域色温不一致时,分区校正(复杂灯光场景)。

4.2 白平衡模拟实现

这里是用的灰度世界方法,也就是上面第一种方法。代码如下:

from PIL import Image
import numpy as np
import rawpy

class AWB_GrayWorld:
    def __init__(self):
        self.gain_r = 1.0
        self.gain_g = 1.0
        self.gain_b = 1.0
        self.min_gain = 0.2
        self.max_gain = 3.0

    def load_dng_no_camwb(self, dng_path):
        # 读取DNG,禁用相机自带白平衡、自动白平衡,得到原始RGB
        with rawpy.imread(dng_path) as raw:
            rgb = raw.postprocess(
                use_camera_wb=False,
                use_auto_wb=False,
                no_auto_bright=True,
                output_bps=8
            )
        return rgb.astype(np.float32)

    def calc_rgb_mean(self, rgb_arr):
        mean_r = np.mean(rgb_arr[:, :, 0])
        mean_g = np.mean(rgb_arr[:, :, 1])
        mean_b = np.mean(rgb_arr[:, :, 2])
        return mean_r, mean_g, mean_b

    def awb_calc(self, dng_path, out_jpg="test.jpg"):
        # 载入无原生白平衡RGB
        arr = self.load_dng_no_camwb(dng_path)
        mr, mg, mb = self.calc_rgb_mean(arr)
        print(f"原图均值 R:{mr:.2f} G:{mg:.2f} B:{mb:.2f}")

        # 灰度世界算增益
        self.gain_r = mg / mr
        self.gain_b = mg / mb
        self.gain_r = np.clip(self.gain_r, self.min_gain, self.max_gain)
        self.gain_b = np.clip(self.gain_b, self.min_gain, self.max_gain)
        print(f"AWB输出增益: R_gain={self.gain_r:.3f}, G_gain={self.gain_g:.3f}, B_gain={self.gain_b:.3f}")

        # 通道乘增益做白平衡
        arr[:, :, 0] *= self.gain_r
        arr[:, :, 2] *= self.gain_b
        # 限制0~255防止溢出
        arr = np.clip(arr, 0, 255).astype(np.uint8)

        # 保存结果jpg
        img_out = Image.fromarray(arr)
        img_out.save(out_jpg, quality=95)
        print(f"已保存白平衡结果: {out_jpg}")
        print("=====\n")
        return self.gain_r, self.gain_g, self.gain_b

if __name__ == "__main__":
    awb = AWB_GrayWorld()
    awb.awb_calc("test.dng", "test.jpg")

处理前后区别如下:

好像直观感觉就是处理后画面就柔和那么一些。。。

至于AWB的实现。调色和统计动作是ISP,算法决策是 CPU/NPU。

ISP 硬件:逐像素实时统计 R/G/B 均值、白点数量(硬件累加器)+ 像素乘 RGB 增益校正画面(硬件运算,纳秒级)。
主控 CPU/NPU:拿着 ISP 统计出来的数据跑算法(灰度世界 / 白点 / AI 色温),算出 GainR/GainB,下发寄存器给 ISP。

其实AWB也不用闭环反馈控制,据说只是问了让画面更平缓,不至于让人眼看着那么跳跃。而且对于图片来说,AWB完全可以后期软件做,如果是视频,目前来看就只能ISP了。

5 3A调度器

3A 不是独立运行,存在互相牵制,由3A 调度器统一控制。
AE 优先:亮度是基础,曝光剧烈变化时,AF、AWB 暂时放慢收敛,避免抖动。
AF 动作触发 AE/AWB 重计算:镜头移动、画面构图大变 → 重新测光、测色温。
低光联动:暗光下:
AE 提高增益 → 噪点上升
AF 切换低光对焦策略(放宽对比度阈值、增强 PDAF)
AWB 增强抗色偏,抑制灯光串色
参数平滑:所有调节都加滤波 / 平滑算法,保证视频画面流畅,无闪屏、无跳色。

模块硬件依赖算法特点典型应用
AESensor、光圈、快门亮度闭环、分区测光、防闪烁全场景通用
AF (反差)镜头马达爬山搜索、对比度评价监控、IPC、老款相机
AF(PDAF)PDAF 像素、马达相位测距 + 爬山,高速追焦手机、微单、运动相机
AWBISP 色彩处理单元灰度世界、白点检测、色温矩阵所有成像设备

6 ISP和3A

ISP(图像信号处理器)是整套图像流水线。

向图像流水线的专用硬件加速器阵列,属于专用 ASIC 类 IP / 芯片:
以固定功能硬件模块为主,整条链路按图像数据流串行 / 并行排布(RAW 处理、去马赛克、降噪、色彩、缩放等);
可编程能力弱:仅少量配置寄存器、简单微码,不支持自由编写复杂通用算法;
设计目标:高吞吐、低延迟、低功耗,专门适配像素流(逐行 / 逐帧处理);
天生适配 Sensor 输出的连续图像数据流,流水线深度固定。

算法实现上面每个部分都说过,这里再小结一下。

 AF(自动对焦):算法主体不完全在 ISP。
PDAF原始数据由ISP解析,但对焦搜索、马达驱动、追焦逻辑大多在外部主控/马达驱动IC。
AE/AWB:统计在 ISP,算法可在 ISP 固件或主控。

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值