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三通道均值,然后计算各通道增益:
,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 增强抗色偏,抑制灯光串色
参数平滑:所有调节都加滤波 / 平滑算法,保证视频画面流畅,无闪屏、无跳色。
| 模块 | 硬件依赖 | 算法特点 | 典型应用 |
|---|---|---|---|
| AE | Sensor、光圈、快门 | 亮度闭环、分区测光、防闪烁 | 全场景通用 |
| AF (反差) | 镜头马达 | 爬山搜索、对比度评价 | 监控、IPC、老款相机 |
| AF(PDAF) | PDAF 像素、马达 | 相位测距 + 爬山,高速追焦 | 手机、微单、运动相机 |
| AWB | ISP 色彩处理单元 | 灰度世界、白点检测、色温矩阵 | 所有成像设备 |
6 ISP和3A

ISP(图像信号处理器)是整套图像流水线。
向图像流水线的专用硬件加速器阵列,属于专用 ASIC 类 IP / 芯片:
以固定功能硬件模块为主,整条链路按图像数据流串行 / 并行排布(RAW 处理、去马赛克、降噪、色彩、缩放等);
可编程能力弱:仅少量配置寄存器、简单微码,不支持自由编写复杂通用算法;
设计目标:高吞吐、低延迟、低功耗,专门适配像素流(逐行 / 逐帧处理);
天生适配 Sensor 输出的连续图像数据流,流水线深度固定。
算法实现上面每个部分都说过,这里再小结一下。
AF(自动对焦):算法主体不完全在 ISP。
PDAF原始数据由ISP解析,但对焦搜索、马达驱动、追焦逻辑大多在外部主控/马达驱动IC。
AE/AWB:统计在 ISP,算法可在 ISP 固件或主控。

&spm=1001.2101.3001.5002&articleId=161597862&d=1&t=3&u=39edadd35a6947da8b3af9efa38a5165)
41

被折叠的 条评论
为什么被折叠?



