3. 算法仿真与原型验证:用数据说话

算法写出来是一回事,跑起来是另一回事。我个人习惯,在动手写任何一行C代码之前,先要在Python或Matlab里把算法原型搭起来。这一步叫仿真验证,说白了就是用公开数据集先跑一遍,看看你的3A算法到底行不行。

你想想看,如果连在PC上都调不好,直接扔到嵌入式平台上去debug,那简直是自找麻烦。我在项目里见过太多人,上来就写硬件代码,结果发现算法本身就有bug,改起来痛苦得要命。

3.1 仿真环境搭建

环境搭建其实不复杂。我一般用Python,因为生态好,库多。Matlab也行,但授权麻烦。

推荐工具链:

  • Python 3.8+ + OpenCV + NumPy + Scikit-image
  • Matlab R2020+ + Image Processing Toolbox
  • 数据集:MIT-5K(曝光)、RAISE(噪声/色彩)

安装命令很简单:

pip install opencv-python numpy scikit-image matplotlib

嗯,这里要注意。OpenCV的版本别太新,有些旧接口在新版里被改了。我建议用4.5.x系列,稳定。

3.2 公开数据集的选择与加载

数据集是算法的试金石。我常用的两个:

数据集 用途 特点 规模
MIT-5K 自动曝光(AE)验证 5000张RAW,含专家调色结果 5K张
RAISE 自动白平衡(AWB)验证 高分辨率,含真实光照标注 8156张
自定义对焦序列 自动对焦(AF)验证 自己拍一组离焦到合焦的序列 视情况

加载代码示例:

import cv2
import numpy as np

def load_mit5k(path):
    # 读取MIT-5K数据集中的一张图
    img = cv2.imread(path, cv2.IMREAD_COLOR)
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    return img_rgb

def load_raise(path):
    # RAISE数据集是TIFF格式
    img = cv2.imread(path, cv2.IMREAD_UNCHANGED)
    return img

我的经验:MIT-5K的图片很大,动辄4000x6000。仿真时先缩放到1080p,能省大量时间。我曾经傻乎乎地跑原图,一个算法跑半小时,后来发现缩放后效果几乎一样。

3.3 核心评估指标

算法好不好,不能靠感觉。得用指标说话。我常用的四个指标:

3.3.1 PSNR(峰值信噪比)

PSNR衡量图像重建质量。值越高,失真越小。一般超过30dB就算不错。

def calculate_psnr(img1, img2):
    mse = np.mean((img1 - img2) ** 2)
    if mse == 0:
        return float('inf')
    max_pixel = 255.0
    psnr = 20 * np.log10(max_pixel / np.sqrt(mse))
    return psnr

3.3.2 SSIM(结构相似性)

SSIM更贴近人眼感知。范围0到1,越接近1越好。0.95以上说明结构保留得很好。

from skimage.metrics import structural_similarity as ssim

def calculate_ssim(img1, img2):
    # 注意:ssim函数要求输入是灰度图或单通道
    score = ssim(img1, img2, channel_axis=-1)
    return score

3.3.3 DeltaE(色差)

这个指标对AWB算法特别重要。DeltaE越小,颜色越准。一般DeltaE < 3人眼就分辨不出来了。

def calculate_deltae(img1, img2):
    # 转LAB空间
    lab1 = cv2.cvtColor(img1, cv2.COLOR_RGB2LAB)
    lab2 = cv2.cvtColor(img2, cv2.COLOR_RGB2LAB)
    # 计算欧氏距离
    delta = np.sqrt(np.sum((lab1.astype(float) - lab2.astype(float)) ** 2, axis=-1))
    return np.mean(delta)

3.3.4 对焦速度

这个指标比较特殊。它不是算一张图,而是算一个序列。我一般用“爬山法”模拟对焦过程,统计从离焦到合焦的步数。

def focus_speed(sharpness_curve):
    # sharpness_curve: 每一帧的清晰度值
    # 找到峰值位置
    peak_idx = np.argmax(sharpness_curve)
    # 从起始到峰值需要的步数
    steps = peak_idx
    return steps

避坑指南:我曾经在计算DeltaE时,直接用RGB空间算欧氏距离,结果发现数值完全不对。后来才意识到,DeltaE必须在LAB空间算,因为RGB空间不是感知均匀的。这个坑我踩过,你别再踩了。

3.4 原型验证流程

整个验证流程,我一般分四步走:

  1. 数据准备:从数据集中挑出100-200张代表性图片
  2. 算法运行:用Python实现你的3A算法,跑一遍
  3. 指标计算:算PSNR、SSIM、DeltaE、对焦速度
  4. 结果分析:看指标是否达标,不达标就调参

下面这张图,是我自己总结的验证流程:

3A算法仿真验证流程图 3A算法仿真验证流程 公开数据集 图像预处理 3A算法核心处理 AE / AWB / AF 评估指标计算 结果分析与调参 不达标则调参 输入:MIT-5K / RAISE 输出:PSNR / SSIM / DeltaE 缩放、去噪、色彩空间转换 统一输入格式

3.5 实战:跑一个完整的AE验证

我拿MIT-5K数据集举个例子。目标是验证你的自动曝光算法能不能把过曝或欠曝的图拉回正常亮度。

import cv2
import numpy as np
from skimage.metrics import peak_signal_noise_ratio

# 加载一张欠曝图
img_underexposed = load_mit5k('mit5k/underexposed.jpg')
# 加载对应的专家调色结果(ground truth)
img_gt = load_mit5k('mit5k/expert_c.jpg')

# 你的AE算法
def my_ae_algorithm(img):
    # 这里简化处理:用直方图均衡化模拟
    img_yuv = cv2.cvtColor(img, cv2.COLOR_RGB2YUV)
    img_yuv[:,:,0] = cv2.equalizeHist(img_yuv[:,:,0])
    img_result = cv2.cvtColor(img_yuv, cv2.COLOR_YUV2RGB)
    return img_result

# 运行算法
img_corrected = my_ae_algorithm(img_underexposed)

# 计算PSNR
psnr_value = peak_signal_noise_ratio(img_gt, img_corrected)
print(f'PSNR: {psnr_value:.2f} dB')

# 计算SSIM
ssim_value = ssim(img_gt, img_corrected, channel_axis=-1)
print(f'SSIM: {ssim_value:.4f}')

关键结论:如果PSNR > 30dB且SSIM > 0.9,说明你的AE算法基本可用。如果DeltaE < 5,说明AWB也靠谱。对焦速度嘛,一般10步以内算快,20步以上就得优化了。

3.6 常见问题与避坑

  • 数据集不匹配:MIT-5K是曝光数据集,别拿它测AWB。我见过有人拿错数据集,折腾了两天。
  • 指标滥用:PSNR高不代表人眼看舒服。有时候SSIM更重要。
  • 忽略对焦速度:很多人在仿真时只测图像质量,忘了测速度。结果移植到硬件上发现对焦慢得像蜗牛。
  • 测试集太小:别只测三五张图就下结论。至少测100张,取平均值。

我的习惯:每次跑完仿真,我会把结果图、指标值、算法参数一起存成一个日志文件。这样回头调参时,能清楚看到改了哪里、效果如何。这个习惯救过我很多次。

好了,仿真环境搭好了,指标也会算了。下一步就是把算法从Python搬到C++,开始真正的移植适配。不过那是后面的事,先把仿真这关过了再说。