1. 从“调亮度”说起:为什么ISP里需要一个数字增益模块?
大家好,我是老李,在图像处理和FPGA这块儿摸爬滚打了十几年。今天想和大家聊聊一个听起来有点专业,但实际非常“接地气”的话题:ISP里的数字增益模块,也就是DGain。说白了,它的核心任务就是给图像“调亮度”。
想象一下你用手机拍照,光线太暗了,画面一片漆黑,你手指一划拉,把亮度条往上推,画面瞬间就亮了起来。这个“划拉”的动作,在图像信号处理器(ISP)内部,很大程度上就是由数字增益模块来完成的。它通过一个乘法运算,把每个像素的数值放大,从而实现整体画面亮度的提升。
那么问题来了,这个“调亮度”的功能,为什么非要在FPGA上用硬件来实现,而不是用软件或者CPU算一下呢?这就要说到实时图像处理的核心矛盾了。我们处理的往往是摄像头实时采集的视频流,比如1280x720@60fps,这意味着每秒钟要处理超过5500万个像素。如果用软件逐像素做乘法,对CPU的负担极大,功耗高,延迟也难以控制。而FPGA的并行处理能力,可以让它同时对一整行、甚至多行像素进行运算,轻松满足实时性的苛刻要求。
但是,在FPGA上实现这个“乘法”和“加法”,可不是写个 out = in * gain 那么简单。原始文章里给出了一个基础的Verilog实现,它完成了核心功能,但就像一辆能跑的“原型车”,直接上路可能会遇到油耗高、加速慢(资源占用大、时序紧张)的问题。今天,我就结合自己踩过的坑,和大家深入聊聊,如何把这辆“原型车”优化成一辆性能与效率兼备的“赛车”。我们会围绕如何节省宝贵的DSP资源、如何确保运算精度不丢失、如何设计高效的流水线来降低时序压力这几个核心点展开,并附上我优化后的完整源码。
2. 理解核心:模拟增益、Sensor数字增益与ISP数字增益
在动手优化代码之前,我们必须先搞清楚我们要实现的到底是什么,以及为什么要这么做。这能帮助我们在做设计决策时,不至于迷失在代码细节里。
2.1 模拟增益 vs. 数字增益:一场关于噪声的博弈
原始文章里提到了一个关键概念:模拟增益(Analog Gain) 和 数字增益(Digital Gain)。我打个比方你就明白了。
假设你有一个非常微弱的声音信号(好比CMOS Sensor感光产生的微弱电信号)。你想把它录下来。
- 模拟增益:相当于在话筒(传感器)后面、录音机(ADC模数转换器)前面,加了一个物理的“话放”(话筒放大器)。它先把微弱的模拟电信号放大,再送给录音机转换成数字信号。这样做的好处是,被放大的主要是“真实声音信号”,录音机本身固有的电路底噪并没有被同步放大。所以最终录音的信噪比(SNR)比较好。
- 数字增益:相当于声音信号已经经过录音机,变成了数字音频文件。这时候你觉得音量太小,用软件把每个采样点的数值都乘以一个系数。坏处是,录音时混进去的底噪也被同等倍数放大了,听起来“嘶嘶”声会更明显。
所以,在图像Sensor里,模拟增益能获得更干净的图像,因为它是在引入大量电路噪声之前放大信号。但模拟增益的调节范围有限,而且调太高了信号可能会失真。当光线极暗,模拟增益开到最大还不够时,就不得不用数字增益来“补亮度”。
2.2 为什么选择在ISP端做数字增益?
这里又引出一个关键点:数字增益放在哪里做?原始文章指出了一个重要实践:通常关闭Sensor自带的数字增益,选择在ISP流水线中后期添加自己的数字增益模块。
我刚开始做项目时也不理解,觉得在Sensor端直接乘一下多方便,何必在FPGA里再算一次?后来在调试中吃了大亏。Sensor输出的RAW图(原始拜耳阵列数据)包含了很多噪声:热噪声、固定模式噪声(FPN)、坏点等等。如果直接在Sensor端施加数字增益,等于把这些噪声也一并放大,给后续的降噪模块带来了巨大的压力,甚至可能产生无法消除的瑕疵。
而一个典型的ISP流水线是这样的:Sensor RAW -> 坏点校正(DPC) -> 黑电平校正(BLC) -> 拜耳域降噪(BNR) -> 数字增益(DGain) -> ... 我们把DGain模块放在降噪之后,意味着我们先用了各种算法把图像“清洗”干净,然后再对干净的图像数据进行亮度提升。这样得到的画面质量,远比在脏数据上直接放大要好得多。这个模块的放置位置,本身就是一种重要的系统级优化。
理解了这些背景,我们就能明白,FPGA上的DGain模块,处理的是已经过初步净化的图像数据,我们的优化目标就是在保证图像质量(精度)的前提下,尽可能地快、尽可能地省资源。
3. 基础实现回顾与潜在的性能瓶颈
我们先快速回顾一下原始文章给出的Verilog实现,并分析一下它在实际工程中可能遇到的挑战。
// 摘自原始文章的核心计算部分
always @ (posedge pclk) begin
data_0 <= in_raw * gain; // 乘法
end
always @ (posedge pclk) begin
data_1 <= data_0 + {offset, {4‘d0}}; // 加法
end
always @ (posedge pclk) begin
// 对结果进行钳位(饱和处理)
data_2 <= data_1[BITS-1+9:4] > {BITS{1‘b1}} ? {BITS{1’b1}} : data_1[BITS-1+4:4];
end
这个实现非常清晰,采用了三级寄存器(打三拍)的流水线结构:
- 第一拍:完成像素数据
in_raw与增益系数gain的乘法。 - 第二拍:将乘法结果加上一个偏移量
offset。 - 第三拍:对加法结果进行定点数调整和饱和处理(防止溢出变亮为暗)。
同时,同步信号(href, vsync)也延迟了三拍,以对齐数据路径。
那么,它的瓶颈可能在哪里呢?
- DSP资源消耗:
in_raw * gain这一句,综合工具(如Vivado、Quartus)默认会使用FPGA内置的专用DSP Slice来实现。一个8位乘以8位的乘法,就会消耗一个DSP。对于高清视频流,这个模块在每个时钟周期都在工作,虽然只消耗一个DSP,但在资源极其紧张的设计中,或者当我们需要同时处理多个颜色通道(R、Gr、Gb、B)时,这个消耗会被放大。 - 组合逻辑路径长:尽管是流水线设计,但乘法器本身可能产生较长的组合逻辑延迟(如果不用DSP而用LUT搭建)。在高速时钟下(比如150MHz以上),这可能会成为时序收敛的难点。
- 精度与位宽管理:代码中使用
gain是4.4格式(4位整数,4位小数)的定点数。乘法后位宽扩展,加法后再扩展,最后取整截断。这个过程中,如何平衡精度和资源?截断时是直接舍弃
625


&spm=1001.2101.3001.11974&articleId=150760411&d=1&t=3&u=e214e0d1c0934695852d2ec8581c9707)

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



