尧图精选

FPGA图像电子透雾算法详解:从暗通道先验到ISP流水线落地

🕒 发布时间:2026/9/9 0:24:08 📁 来源:尧图网络
这年头搞视频图像处理的只要不是纯做算法仿真基本都绕不开一个词电子透雾。安防监控、车载摄像、无人机航拍一到雾天、霾天、回南天画面灰白一片细节全丢后端算法再强也白搭。物理透雾加滤光片确实有效果但成本高、安装受限、还怕脏污维护起来一堆麻烦。所以现在主流方案都是在ISP管线里做Dehaze也就是图像电子透雾。我这两年用FPGA做了几个相关的项目从最初的算法验证到最终的硬件落地踩了不少坑也积累了一些实打实的经验。这篇就把整个设计思路和实现细节拆开讲讲。这篇文章适合正在做FPGA图像处理、ISP相关开发或者想在Zynq、Artix-7这类平台上落地透雾算法的朋友。我会从算法原理讲起再深入到FPGA流水线架构、模块划分、资源优化、时序收敛最后附上调试经验和问题排查手册。就算你是FPGA新手刚接触ISP pipeline这个概念这篇文章也能给你一条非常具体的入门路径。1. 方案选型为什么电子透雾必须上FPGA1.1 从物理透雾到电子透雾先说透雾的原理。雾气对成像的影响本质上是光在传播过程中被悬浮颗粒散射导致到达传感器的光中包含大量非成像方向的杂散光。物理透雾的思路很直接在镜头前加装偏振片或特定波段的滤光片把散射光滤掉一部分入射光干净了画面自然通透。但物理透雾的缺点是致命的一片好的偏振滤光片成本不低而且只对特定角度的散射光有效雾不浓时效果还行浓雾基本无解。更要命的是这种方式没法自适应场景变化太阳角度一变、雾浓度一波动效果就大打折扣。电子透雾则完全靠算法。它直接对传感器采集到的原始图像做处理通过估计场景深度、大气光、透射率等参数把雾造成的对比度损失和颜色偏移补偿回来。整个过程不需要额外硬件一颗SoC或者FPGA就能搞定。对于嵌入式平台来说这是目前性价比最高、部署最灵活的透雾方案。1.2 为什么用FPGA而不是DSP或CPU先算一笔账。以1080p30fps为例每秒要处理的像素数是1920108030约6200万像素。如果每个像素要做几十次乘加运算和多次非线性变换需要几百GOPS的算力。对于纯软件方案即便是高端ARM Cortex-A72跑NEON优化过的算法也很难在功耗可控的情况下做到实时。DSP的优势是乘加密集任务但Dehaze算法里面有大量的分支判断、取最大值最小值、数据搬移这些恰恰是DSP的弱项。FPGA方案的逻辑则完全不同。它是空间并行架构可以把整条ISP pipeline分成多级流水线每一级只处理自己那一段逻辑数据像流水一样前进。720p的一个图像行在DSP上可能需要循环几千次在FPGA上只需要一个时钟周期就能全部并行处理完。实测下来在Xilinx Artix-7 200T上1080p30fps的完整Dehaze流水线LUT占用率控制在60%以内功耗不到5W延迟只有几毫秒。这个指标DSP或者CPU方案很难做到。还有一个关键优势是可定制性。FPGA的开发本质上是设计硬件你可以把算法中的某一个算子直接映射成硬件逻辑。比如暗通道先验里的最小值滤波在CPU上是用循环遍历窗口在FPGA上就是一个滑动窗口的移位寄存器组加比较器树性能完全不在一个量级。2. 算法原理与硬件映射从暗通道先验到流水线算子2.1 暗通道先验算法的核心公式目前工业界用得最多的电子透雾算法依然是何恺明提出的暗通道先验Dark Channel Prior, DCP。这个算法的核心观察是在绝大多数无雾图像的局部区域里至少有一个颜色通道的亮度值非常低趋近于零。而雾的干扰会让这个暗通道值升高雾越浓暗通道值越接近大气光。基于这个先验可以反推出透射率和大气光从而恢复出无雾图像。算法流程可以拆成四个关键步骤第一步求暗通道图。对RGB三通道逐像素取最小值得到单通道图J_dark_initial再对该图做局部窗口最小值滤波。窗口大小通常取15x15但FPGA上做这么大的窗口太吃资源后面我会讲工程上的近似处理。第二步估计全局大气光A。取暗通道图中亮度最高的前0.1%像素将这些像素对应到原图中的最大亮度值作为A。第三步估计透射率t。按公式 t 1 - ω * min_c(I_c / A_c) 计算其中ω是保留少量雾感的调节因子一般取0.85到0.95。第四步恢复无雾图像。J (I - A) / max(t, t0) A其中t0是透射率下限防止除零和噪声放大一般取0.1。2.2 算法到硬件的逐级映射上面这四个步骤看着不复杂但要落到FPGA上每一步都得重新思考数据流。暗通道的最小值操作比较好办。RGB三通道逐像素取最小值这个在FPGA上就是一个多路比较器一拍就能完成。最麻烦的是窗口最小值滤波。15x15的窗口意味着需要15行行缓存Line Buffer同时每个输出像素要比较225个输入值。对于720p分辨率一行是1280个像素15行就是19200个像素的存储全用BRAM大概要几十KB资源压力不小。工程上常见的做法是把窗口缩小到5x5或者7x7然后级联多级同样的滤波器来近似大窗口效果。比如做两次5x5的串行最小值滤波等效于一个较大的窗口资源消耗却能降低一半。实际测试下来这种方法对暗通道估计的精度影响很小特别是后续还有透射率细化步骤可以补偿这部分的误差。大气光估计在FPGA上通常用“局部最大值统计时间滤波”实现。全局统计需要两遍扫描第一遍找暗通道最大值第二遍在原图对应位置找亮度最大值。实时视频流里没法做两遍扫描所以硬件实现时都改成逐帧统计用帧同步信号做复位和锁存。具体做法是暗通道和原图数据流入统计模块时不断更新最大值等到一帧结束锁存这一帧的A值再用一个小的平滑系数对多帧的A值做IIR滤波避免A值跳变引起画面闪烁。透射率估计的除法在FPGA上比较麻烦。A_c是常数I_c是变量t 1 - ω * I_c / A_c。除法器的实现可以调用Xilinx的Divider Generator IP核但延迟较大且资源消耗高。工程上更常用的方法是把除法转换成乘法和移位。因为A_c是全局估计出来的常量可以预先计算1/A_c然后转换成定点乘法。如果A_c的值需要动态更新就用查找表实现除法近似的倒数查找把除法变成一次查表加一次乘法精度完全够用。最后的图像恢复J (I - A) / max(t, t0) A同样面临除法问题。但注意这里的t是逐像素变化的不能预计算倒数。实操中我一般把1/t近似成t的反函数查找表输入t的高8位作为查找表地址输出对应的1/t定点值。t的精度损失一段从8位到8位对最终图像的观感影响微乎其微硬件实现却简单得多。2.3 透射率细化引导滤波的替代方案直接用上述流程得到的透射率图会有比较明显的块效应画面看起来一块一块的很不自然。原始DCP论文里用的是软抠图Soft Matting效果很好但计算量极大在FPGA上基本行不通。后来何恺明又提出了引导滤波Guided Filter效果接近软抠图且效率高很多。但引导滤波涉及均值滤波、方差计算、线性回归在FPGA上实现依然不轻。我实测比较过几种方案最终推荐用“快速双边滤波”或者干脆用“多级均值滤波近似”。具体做法是对透射率图做3x3的均值滤波对结果再做5x5的均值滤波然后做一个边缘保护处理用原图亮度作为引导图对透射率图做一次联合双边滤波。联合双边滤波的权重计算需要访问原图对应位置的像素这个在FPGA上需要同时缓存原图和透射率图的行数据BRAM占用会比单纯透射率滤波多一倍。但效果提升很明显边缘区域的细节被保留平滑区域又很自然不会出现光晕伪影。如果你的BRAM资源实在紧张至少要把前两级均值滤波做了效果能改善不少。3. FPGA系统架构设计与核心模块实现3.1 整体系统框图一套完整的基于FPGA的ISP Dehaze系统核心架构包含几个大模块视频输入接口MIPI RX或者并行RGB输入、Dehaze算法流水线、后端ISP模块Gamma校正、色彩空间转换、自动曝光统计等、视频输出接口MIPI TX或者并行RGB输出以及一个控制管理模块通常挂在I2C或AXI-Lite总线上负责参数配置和寄存器读写。Dehaze算法流水线内部分成五个子模块按数据流顺序串联RGB最小值模块求暗通道初值多级滑窗最小值滤波模块生成暗通道图大气光估计模块逐帧统计时间滤波透射率计算模块定点乘加查找表图像恢复模块除法近似像素融合整条流水线设计成两级流水结构。第一级是“流式处理”数据进来后按行依次经过最小值滤波、透射率计算输出的透射率延迟若干行第二级是“帧级反馈”需要用前一帧估计的大气光A来处理当前帧的透射率和恢复所以数据在进入恢复模块前需要做行对齐和帧对齐缓冲。3.2 行缓存与滑动窗口的实现细节滑动窗口是整个FPGA图像处理的基础也是新手最容易写崩的地方。以5x5最小值滤波为例核心是用4条行缓存加5组移位寄存器。行缓存的深度是图像行宽宽度是像素位宽。每一行数据进来先写入当前行的移位寄存器组然后依次向下一行推进形成5x5的窗口数据矩阵。在Verilog里实现时有个细节一定要注意行缓存的写入和读出时序必须严格配合行同步信号的有效窗口。我见过很多开发者在仿真时一切正常上板后边缘几列像素错位、颜色花掉都是因为行缓存的读写控制没有处理好行首和行尾的无效像素HBlank区域。正确做法是在计数器里把有效像素的起始和结束位置都拉出来写指针和读指针严格限定在有效像素范围内。窗口数据就绪后取25个像素的最小值可以用比较器树两级比较器就够了。先分成5组每组5个像素互相比较取最小再把这5个中间结果比较一次一共用到30个比较器。对FPGA这种并行资源丰富的平台来说30个LUT级别的比较器几乎不占什么资源。如果窗口增大到7x7那就需要49个比较器占用会明显上升。这也就是为什么我建议在多级小窗口方案上花功夫而不是直接上一味追求大窗口。3.3 大气光估计的流水线实现大气光估计在FPGA上不如前面几个模块那么“流式”因为它需要跨帧处理。我的实现思路是暗通道数据流进入统计单元设一个寄存器存储当前帧最大的暗通道值每来一个新像素就比较一次大于就更新。与此同时原图数据流进入另一个统计单元但只统计那些暗通道值大于阈值的对应像素。这里需要把原图延迟等待暗通道结果做像素级对齐。帧同步信号到来时一帧结束把统计得到的最大值锁存并更新给后续模块同时复位统计寄存器开始新一帧的统计。为了稳定性锁存后的A值不要直接使用而是和上一帧的A做一次加权平均A_cur alpha * A_frame (1-alpha) * A_prev。alpha一般取0.05到0.1在FPGA上实现就是一次乘加。这种做法虽然牺牲了“全局精确性”——严格来说A应该取暗通道前0.1%亮度的对应位置但工程上“全帧最大”已经足够。雾天的暗通道最大值天然高无雾区域暗通道值很低所以全局最大值基本都落在浓雾区域误差很小。3.4 透射率恢复模块的定点量化策略透射率和图像恢复这两个模块在整个流水线中涉及的定点运算最复杂也最容易出问题。我调试时吃过亏的地方就是位宽截断运算中间结果的位宽明明够截断后低几位丢得太狠结果图像偏色或者出现色阶断层。这里给出我验证过的一套量化参数以8比特输入输出为例RGB通道8位无符号范围0到255。暗通道初值3个通道取最小值依然是8位。暗通道滤波结果8位。A值估计12位从8位原图扩展而来方便做除法精度保留。透射率计算中间值用32位定点其中整数位4位小数位28位。计算t 1 - ω * I_c / A_c时先把ω和1/A_c合并成系数I_c乘这个系数后右移28位得到定点小数。这个精度在FPGA上已经远高于人眼分辨力了。图像恢复时1/t查找表的输入用t的高8位输出为32位定点小数计算J时一次乘法加一次加法搞定。这个量化方案在MATLAB定点仿真和FPGA实际输出之间做过对比PSNR能到42dB以上肉眼完全看不出差距。3.5 时序约束与跨时钟域处理Dehaze流水线通常工作在视频像素时钟域。MIPI接收端出来的是什么时钟频率整个Dehaze模块就跟着跑什么频率。很多通用视频平台是24MHz、74.25MHz、148.5MHz这些速率的像素时钟Dehaze流水线要在这个频率下保证一个像素在一个时钟周期内处理完。如果时序收敛有问题第一件事不是优化布局布线而是检查关键路径。Dehaze模块最长的组合逻辑链通常在透射率计算和图像恢复的定点乘法上。在Vivado里查看时序报告后如果发现关键路径绕了很远可以在乘法器前加一级流水寄存器把组合逻辑拆分成两级。跨时钟域主要集中在参数配置上。视频流是像素时钟域CPU配置寄存器是AXI-Lite时钟域这两个域之间必须做同步处理。我的习惯是所有配置寄存器用AXI-Lite域写入写完置一个更新标志位在视频域中检测到该标志时用两级同步寄存器把数据打拍到视频域。这样虽然会引入几个时钟周期的延迟但完全避免了亚稳态风险。4. 实操过程中遇到的问题与排查经验4.1 输出图像整体偏色青色过重这个问题我在第一版硬件上遇到过。现象是处理完的雾图天空区域明显发青绿色植物的颜色也不正。排查思路分两步第一步检查暗通道滤波的窗口大小。如果窗口太小暗通道估计偏小透过率计算偏大导致恢复时蓝色通道的提升过猛。我的解决方法是把最小值滤波的窗口从5x5加大到7x7并且连续做两级串行滤波等效窗口约13x13接近桌面算法常用的15x15。第二步检查透射率下限t0的取值。t0设得太小透射率趋近零的区域浓雾区会过度增强蓝色通道失真更明显。把t0从0.05调整到0.15后偏色问题基本解决。4.2 画面边缘出现纵向条纹或伪影这个问题的根源通常不在Dehaze算法本身而在行缓存和边界填充的匹配上。滑动窗口处理到图像的左边缘和右边缘时窗口会跑到有效像素范围外。这些位置的数据如果没有正确处理在图上就表现为边缘的纵向条纹。解决办法是给行缓存加镜像填充逻辑当横向计数小于窗口半宽时用同行的镜像像素填充当横向计数超过图像宽度减窗口半宽时用反向镜像填充。镜像填充比补零填充的效果好很多尤其是边缘区域的纹理细节不会被破坏。4.3 透雾后的图像出现闪烁亮度跳变这类问题十有八九出在A值的稳定性上。我刚提过A值如果不加时间滤波直接逐帧更新前后帧的A值会有波动导致整体亮度一跳一跳的。解决方法是增加一个帧级的IIR低通滤波器对A值进一步平滑。alpha系数的选择需要现场调试我最后固定在0.08画面亮度的变化已经非常平滑而且没有拖影。还有一个细节容易被忽略直接从传感器出来的RAW图经过Debayer之后RGB通道的增益其实是不同的。在求暗通道、计算透射率、恢复图像时如果不对白平衡增益做归一化处理透雾图像很容易出现整体色偏。我的做法是在Dehaze模块前面加一个简单的增益补偿在计算暗通道时先把三个通道除以各自的白平衡增益恢复图像时再乘回来。4.4 资源不足与时序收敛问题BRAM的规划是Dehaze模块能否放下去的关键。以1080p为例每行需要1920个像素存储如果每个像素是24位RGB888一行就要约4.6KB。5级级联的滤波需要4条行缓存做暗通道、4条行缓存做透射率滤波再加上原图延迟线总共差不多要40到50KB的BRAM。这个量对于Artix-7 200T芯片来说占总BRAM的20%左右完全不紧张。但如果用资源更小的芯片比如Zynq-7010BRAM总量只有270KBDehaze加其他ISP模块会非常紧张。这时候可以考虑把行缓存的位宽从24位压到16位牺牲一点暗通道精度换取容量实测效果仍可接受。时序方面如果乘法器使用LUT实现路径延迟明显。我通常会用Xilinx的DSP48E1硬核来实现定点乘法这一个改动能把关键路径缩短至少一个级别。还有一个常见的优化点把透射率计算模块里的除法查找表提前一拍从寄存器里读出做到下一拍直接用避免查表和后续运算串行在这种组合逻辑里。Vivado里的UltraFast设计方法学对此有详细说明照着调即可。4.5 验证与对比方法算法在FPGA上实现后必须和软件仿真结果对比否则没法确认硬件逻辑是否正确。我的做法是用MATLAB读同一张雾图执行和FPGA完全一致的定点算法注意不是浮点算法而是和我最终量化方案一致的定点版本得到一张参考输出图。然后把FPGA处理同一个输入的输出图像通过串口、USB或者SD卡导出来。在MATLAB里对两张图做逐像素对比计算PSNR和差值图。PSNR低于40dB或者差值图存在明显的区域块就说明硬件逻辑某处有偏差。这个对比方法帮我抓到了好几个逻辑bug尤其是定点截断位置选错的问题。5. 性能实测结果与扩展方向5.1 实测数据我在Xilinx Zynq UltraScale MPSoC平台和一片低成本的Artix-7 200T上都完整跑过这套Dehaze流水线结果如下分辨率帧率核心逻辑LUT触发器BRAMDSP48E1功耗1080p30fps42K28K48块123.8W720p60fps40K26K36块123.2W480p60fps31K21K22块82.5W处理延迟方面从第一个像素进入到第一个像素输出暗通道滤波是21行延迟透射率细化是11行延迟加上其他的对齐缓冲总延迟控制在三个图像帧以内。对于实时视频链路来说这个延迟对用户体验几乎无感。主观效果上对一个浓雾场景的测试视频透雾后的图像在对比度、细节纹理和颜色还原上都有明显提升。处理前后图像的局部对比度平均提升约2.4倍雾区细节的可见度从几乎无法辨认到能清晰分辨出车辆轮廓。虽然和桌面级浮点算法相比还有一点点差距但在嵌入式实时处理的框架下这个效果完全满足工程要求。5.2 可能的扩展方向Dehaze模块做完整后自然可以往几个方向扩展。第一个方向是带回片内亮度统计的自适应参数控制让ω和t0根据场景动态调整比如在雾天和正常天气之间平滑切换而不是所有场景都用一组参数。第二个方向是集成视频增强功能比如在透雾之后再接一个局部直方图均衡模块进一步增强低照度下的对比度。第三个方向是支持多路视频的同时透雾多路MIPI输入进来Dehaze模块做成时分复用一个模块轮流处理多路视频流资源利用率更高。第四个方向是结合神经网络做雾浓度估计给透雾强度提供一个更智能的参考。这套架构不只是在雾天有用。雨天、烟尘、薄雾、逆光等低能见度场景下的图像增强算法流程其实高度相似改一改参数就能复用。6. 调试工具与方法仿真之外的实战技巧6.1 用好仿真一定要用带测试台的模块级测试写Dehaze模块不能一上来就做全系统联调。一定要做充分的模块级仿真。我写每个子模块都会配一个独立的testbench喂入简单的递增图像或固定色块验证模块的输出是否符合预期。比如暗通道滤波模块我会生成一个5x5的测试图手动算好中间像素的滤波结果仿真时做比较。如果这一步都没法确认后面整链路调不通就很难定位是哪一级出了问题。6.2 ILA抓信号状态机的强制约束FPGA调试和软件调试不同你不能随便打断点。Xilinx提供的ILA调试核是嵌入式调测最重要的帮手。但ILA抓信号有条件只能抓有限的信号深度深度受BRAM容量限制。我常把ILA挂在Dehaze模块的输入、透射率计算输出、恢复模块输出这三处每个位置抓256个深度就够了。拉出时序波形后看同步信号和像素数据的对应关系一眼就能判断数据流是否对齐。我第一次上板调试时正是靠ILA抓到了行计数错位的bug——输出图像里有一条垂直的移位带波形一看就明白是行缓存读指针动得晚了一拍。6.3 参考硬件平台用什么板子少走弯路FPGA开发板的选择直接影响效率。做图像处理建议选择带MIPI接口、DDR3/DDR4内存颗粒、HDMI输出的板子。我首推Xilinx的Zynq系列开发板比如Zynq-7020或者Zynq UltraScale MPSoC。Zynq的优势在于ARM和FPGA在同一个芯片里调试、传数据、做控制逻辑都很方便。如果纯做FPGA验证Artix-7系列配合FMC接口的摄像头模组也完全能跑。黑金、正点原子或者米尔科技的板卡都有视频处理的参考例程能省不少前期开发时间。看重性价比的话高云或者安路的FPGA也有不错的表现只是生态和IP库相对薄弱工具链需要熟悉一下。最后一个小技巧做完这套Dehaze之后我还有几点体会。首先是算法验证阶段就别偷懒尽量用MATLAB定点仿真去对齐硬件等硬件做出来再返工查算法代价会大得多。其次是参数化的思想很重要ω、t0、滤波窗口大小、A值平滑系数全都做成寄存器可配这样在现场调效果时不用改代码重新编译直接在软件端改参数就行。最后如果做视频处理一定要想清楚你的视频流里有没有OSD叠加。OSD字符如果在Dehaze之前叠加透雾会把字符一起处理掉导致文字变淡变形所以一般要放在Dehaze之后做OSD。这个顺序问题看似小影响却很大我就是吃了这个亏才记住的。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →