ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

基于FPGA的图像透雾算法实现:暗通道先验与ISP流水线设计

基于FPGA的图像透雾算法实现:暗通道先验与ISP流水线设计 1. 项目起底为什么用FPGA做图像透雾而不是直接上GPU或DSP做图像处理的人对“透雾”这个词应该不陌生尤其是安防、车载、航拍这几个方向的从业者几乎每年都会碰到几回“雾天图像发灰、对比度低、细节全埋在霾里”的需求。传统的做法要么是上通用处理器跑软件算法要么是交给专用的ISP芯片去处理。但当我第一次接到“基于FPGA的图像电子透雾”这个项目时我意识到它跟普通的嵌入式图像处理项目不太一样——它的核心矛盾不在算法本身而在“实时性”和“灵活性”的博弈。先说透雾算法。目前工业界用得最多的还是何恺明提出的暗通道先验Dark Channel Prior, DCP配合导向滤波做透射率细化效果确实能打。但DCP的瓶颈在于计算量大——暗通道要做两次最小值滤波透射率细化如果做引导滤波那更是窗口越大越吃资源。这种东西跑在CPU上一帧1080P的图几秒钟出不来跑在GPU上虽然快但功耗和体积在嵌入式监控场景里根本扛不住。FPGA的优势在于流水线处理同一块逻辑可以拆成多级流水像素数据一边进来一边算延迟只有几个时钟周期吞吐量却能跑到几十甚至上百帧每秒。这意味着你可以在Xilinx或者高云的FPGA上直接搭一条ISP Dehaze流水线不用等整帧图像存完再处理而是边采边算延迟低到肉眼无感。再说灵活性。专用ISP芯片的透雾功能大多是固定算法参数调起来要么通过寄存器要么干脆是黑盒。但FPGA不一样你可以把暗通道计算、透射率估计、大气光估计、去雾复原这些模块全部打散按自己的需求重新组合。比如某一路视频源需要更强的透雾强度另一路只做轻度的对比度增强完全可以在逻辑里动态切换。这个自由度是做ISP后端集成和图像质量调试的工程师最看重的。也正是这个原因近两年“FPGA ISP”这个词在行业内开始频繁出现不少方案公司直接把透雾功能做成了FPGA的IP核配合MIPI或LVDS接口接入Sensor输出干净的RGB/YUV给后端的编码芯片。这个项目适合谁参考我理解三类人比较对口一是做摄像头模组和ISP方案开发的工程师需要快速验证透雾算法的硬件可行性二是做FPGA图像处理但没怎么接触过ISP Pipeline的开发者想了解透雾模块怎么嵌入完整链路三是正在做毕业设计或竞赛的学生想拿一个既能讲清楚算法原理又能跑出实际效果的题目。下面我就按我当时做项目的思路从算法原理到硬件设计再到调试踩坑完整梳理一遍重点写方案怎么选、模块怎么搭、代码怎么写、问题怎么查尽量做到拿过去能直接用。2. 核心思路拆解ISP Dehaze的算法选型与硬件化路径2.1 电子透雾算法综述DCP、多尺度Retinex、基于深度学习三足鼎立图像透雾在算法层面大致分三种流派第一种是物理模型法代表就是暗通道先验DCP。它的理论根基是大气散射模型认为我们看到的雾天图像是由“场景反射光被大气衰减”加上“大气光散射叠加”两部分组成公式一般写成I(x) J(x)·t(x) A·(1 - t(x))其中I(x)是观测到的有雾图像J(x)是清晰无雾的场景辐射t(x)是透射率取值范围0到1A是全局大气光。DCP通过统计大量无雾晴天的户外图像发现至少存在一个颜色通道的局部最小值趋近于0即“暗通道”极暗。基于这个先验就能反推出透射率t(x)再结合大气光A反解出J(x)。这个算法效果稳定物理可解释性强是目前工业落地最广的方案。第二种是多尺度Retinex它把图像理解成“光照分量”和“反射分量”的乘积通过多尺度的高斯滤波估计光照并剔除从而增强反射分量。这种方法对提升对比度和色彩有一定效果但它本质是增强而不是物理去雾容易产生光晕和色彩失真且高斯滤波在FPGA上实现时大核卷积的资源开销比较可观。第三种是基于深度学习的方法比如AOD-Net、FFA-Net这类网络直接端到端回归无雾图。效果确实是三种里最好的但模型参数量动辄百万级即便量化到INT8在FPGA上做推理也要消耗大量DSP和BRAM资源。除非你用Zynq这种带ARM核的SoC FPGA然后在PS端跑神经网络、PL端做前处理否则纯PL实现性价比不高。从我实测的经验来看在同等资源预算下DCP的性价比最高——算法结构规整几乎全是窗口比较和乘法累加特别适合FPGA流水线化。2.2 FPGA透雾设计的架构选型纯PL实现与PSPL协同方案对比确定了算法方向之后接下来要决定的是系统架构。我当时评估过两种主流做法第一种是纯PL实现即透雾算法完全用硬件逻辑Verilog/VHDL实现整个系统不依赖ARM处理器或者ARM只做寄存器配置和状态监控。这种方案的优势是数据通路完全在硬件里流转延迟最低时序最容易控制适合视频流直通处理。缺点是开发周期长算法迭代时要重新综合布局布线调试不太灵活。第二种是Zynq软硬协同方案把FPGA逻辑和ARM核结合起来PL端负责像素级的高速数据搬运、MIPI/LVDS接收、色彩插值和透雾预处理PS端负责运行复杂的参数自适应算法比如根据场景自动调节透雾强度或者跑一个轻量级的场景分类模型来判断当天空气质量。这种方案的开发效率高而且算法更新只需要改软件不用动硬件。我最后选了Zynq UltraScale平台理由很直接这个项目要支持多路视频输入其中一路是4K分辨率的Sensor纯PL处理4K DCP要消耗大量BRAM做行缓存再加上透射率细化用的滤波资源基本吃紧这时候PS端能分担一部分复杂控制逻辑PL端集中精力做像素流。而且Zynq的DDR带宽比纯FPGA方案高雾图的透射率图和原图做运算时需要临时存取中间结果有DDR兜底心里踏实。2.3 为什么选暗通道先验作为硬件落地的切入点说实话当初选DCP也纠结过。DCP有个著名的痛点天空区域不满足暗通道极暗的假设处理完后会出现偏色和光晕。但仔细分析后你会发现这个问题可以通过限制透射率的下限来缓解——不要把t(x)推得太低保留一部分薄雾掩盖色彩失真。而在FPGA上处理这个问题的成本极低只需要在原透射率图和后处理之间加一个LUT查表将透射率钳制在0.1到0.9之间就可以。相比之下深度学习方案虽然效果好但遇到特殊场景一样会有不可控的幻觉现象调试起来比DCP更痛苦。另一个选DCP的原因是它的计算路径特别规整。整个算法可以拆成四步计算暗通道两次3x3最小值滤波、估计全局大气光取暗通道前0.1%最亮像素的均值、估算透射率、根据大气散射模型复原。每一步都是规律的窗口操作或逐像素运算映射到FPGA上就是“行缓存 比较器阵列 除法器”的组合。这种规整的算法能最大限度地利用FPGA的并行性而不是像神经网络那样动不动就来一堆卷积核和全连接层。3. 像素流ing透雾算法FPGA化的关键技术细节3.1 行缓存与最小值滤波暗通道计算的硬件实现要点暗通道计算的数学定义是对每个像素先在R、G、B三个通道里取最小值得到一个“灰度图”然后对该灰度图做窗口大小为15x15的最小值滤波。很多新手看到15x15的滤波窗口就慌了觉得要缓存14行数据BRAM消耗巨大。其实不用那么死板一种常见的优化是把二维滤波拆成两个一维滤波先做横向15点滑动最小值再做纵向15点滑动最小值。这样行缓存只需要缓存中间结果一行的位宽是8bit灰度而不是24bitRGBBRAM开销直接降为原来的三分之一。实现横向最小值滤波器可以用“滑窗比较树”结构。假设窗口大小为15每个时钟进来一个新像素同时移出一个旧像素。你可以用两组寄存器数组维护窗口内的数据并实时计算最小值。但单纯的全比较需要14个比较器组合逻辑路径比较长。我在实测中发现一个更高效的近似做法采用“分块比较”策略把15点分成3组每组5点先算出三组的局部最小值再比较三个局部最小值得到全局最小值。这样做比全比较器树少一级逻辑时序更好收敛。纵向滤波则需要环形行缓存。经典的实现是用FIFO或BRAM搭一个M行延迟线每个时钟从每行对应位置读出一个像素M个像素同时进入比较器树。注意这里的同步问题像素是逐行流入的你的行缓存写入和读出的地址必须错开M行确保输出的M个像素正好构成一个垂直窗口列。在透雾流水线里暗通道计算放在Bayer转RGB之后还是之前我强烈建议放在RGB之后因为暗通道需要对三个通道同时做最小值运算。如果直接在Bayer域做CFA插值还没完成颜色的空间关系是错的出来的暗通道会有明显的棋盘格伪影。3.2 透射率粗估计从暗通道到t(x)的算术逻辑单元设计按DCP公式透射率的粗估计是t_estimate(x) 1 - ω · dark_channel(x) / A其中ω是一个常数通常取0.95目的是保留少量雾气增加景深感。A是全局大气光。这一步在FPGA上实现时除法器是个麻烦点。如果用Xilinx的除法器IP延迟在十几到二十几个时钟周期且资源占用不小。一个巧妙的替代方案是求A的倒数查表你可以在PS端或启动初始化阶段计算1/A的值存成一个定点数然后透射率估计全部变成乘法运算。这么做虽然精度上有一点点损失但对于图像透雾这种对数值精度不太敏感的算法视觉上完全看不出区别。具体的定点化策略我建议这样像素值统一用12bit表示很多Sensor输出10bit或12bit Bayer插值后保持12bit大气光A也量化成12bit。透射率t的控制精度不必太高8bit定点就够数值范围从0到255对应0.0到1.0。乘法和减法全部用定点数运算单元完成最终输出的t_map是一张8bit灰度图。大气光A的估计也值得讲讲。理论上A取暗通道中最亮的前0.1%像素对应的原图亮度均值但“前0.1%”意味着要统计直方图并做阈值排序硬件实现太麻烦。工程上用的妥协方案是先用一个阈值比如暗通道最大值的90%选出候选像素再对这些候选像素的原始RGB值求平均。由于暗通道本身已经是经过最小值滤波的局部平滑结果这个近似对最终复原效果的影响非常小。我就偷过这个懒实测对比下来和严格实现差别肉眼不可见。3.3 导向滤波的FPGA替代用均值滤波逼近精细透射率理论DCP要求对透射率图做导向滤波guided filter来保留边缘细节避免去雾后在边缘处产生大量白色光晕。但导向滤波的硬件化真是个噩梦它需要计算均值、方差、协方差还有大窗口的矩阵求逆而且这些操作必须为每个像素滑动窗口重复计算。如果窗口是r60即121x121这种大尺寸FPGA资源直接爆炸。所以我当时果断放弃了精确导向滤波改用“均值滤波近似”具体做法是透射率粗估计之后先做一个3x3的最小值滤波消除天空区域的突变点。然后对滤波结果做两次3x3均值滤波box filter。之所以做两次是因为两次均值滤波近似于一个三角核滤波比单次均值滤波更接近高斯核边缘保持能力更好。最后用原灰度图的边缘信息做一次“边缘保护”如果当前像素的梯度大于阈值则认为这是物体边缘对该点的透射率做加重处理保留更多细节否则维持均值滤波的结果。这个方案在硬件上就是把均值滤波器的行缓存重用一下额外加一个梯度计算模块sobel算子。它不具备导向滤波的数学完备性但效果已经非常接近尤其是在监控视频这种动态范围可控的场景里光晕现象几乎不可见。如果你有充足资源也可以考虑拉普拉斯金字塔融合的方式但对一个实时视频透雾应用来说我觉得性价比不高。均值滤波在FPGA上实现的关键是高效的行缓存复用。我直接用了一个可参数化的行缓存模块输入8bit灰度图输出3行3列的窗口数据。均值计算采用“列和缓存”的滑动求和法先对每一列做垂直方向的3点求和然后把三个列和相加得到窗口和再右移3位除以8不是除以9近似平均。右移3位相当于除以8和真正的除以9略有偏差但透射率图本来就是估值这点误差对最终结果无感知影响。这个技巧让均值滤波的计算频率可以跑满整个流水线的时钟不会成为瓶颈。3.4 大气光估计与图像复原除法复用与LUT优化图像复原公式为J(x) (I(x) - A) / t(x) A这里的t(x)是经过细化的透射率图。除以t(x)意味着每个像素都要做一次除法这是FPGA上最消耗资源的部分。我用了两种策略来规避除法第一透射率已经钳制在0.1到0.9之间因此1/t(x)的取值范围是1.11到10。我可以预设一个16bit的查找表输入是8bit透射率值输出是1/t的定点表示精度可以做到0.001以下。查找表的存储空间为256×16bit只占一个BRAM的一小部分但省掉的除法器IP资源非常可观。第二整个流水线采用定点运算图像数据和大气光都做12bit定点化透射率做8bit定点除法查表的结果也定点化为16bit乘法结果截断回12bit输出。这种全定点的设计保证了流水线里没有浮点单元时序特别干净跑150MHz以上的像素时钟毫无压力。大气光的估计我放在了暗通道计算之后、透射率估计之前因为需要暗通道图来选择候选像素。在FPGA端我不想为“前0.1%最亮像素”的统计专门设计一个直方图模块直接用了一个双帧方案第一帧只统计暗通道直方图确定一个阈值比如99.5%分位点第二帧根据该阈值选出候选像素并累加RGB值再取平均作为大气光。因为视频场景中大气光在短时间内变化不大这个双帧方案很实用。4. 系统级整合完整FPGA透雾IP的软硬件协同实现4.1 整体数据通路设计MIPI接收到YUV输出的四级流水我搭的透雾系统整体链路是这样的摄像头Sensor输出RAW BayerMIPI CSI-2接口→ FPGA的MIPI RX端接收解包 → ISP预处理坏点校正、Bayer插值得到RGB、白平衡、Gamma校正→ 透雾模块暗通道、大气光、透射率、复原→ 后处理色彩调整、对比度拉伸→ YUV444转YUV422 → 输出给HDMI显示或编码芯片。这条链路的时序核心是像素同步。MIPI RX解包后产生像素时钟后续所有模块都用同一个时钟域跑避免跨时钟域带来的亚稳态问题。透雾模块的延迟大约为暗通道约42行延迟因为纵向窗口需要缓存14行加透射率细化约6行延迟加复原约2行延迟总共不到50行对1080P来说大约是0.2毫秒的时延。这个延迟比GPU方案动辄几十毫秒的延迟低几个数量级在实时监控场景里非常重要。这里提醒一个容易踩的坑Bayer插值后的RGB数据位宽最好和MIPI接收保持一致。如果你Sensor输出12bit Bayer插值后你用的是12bit运算那后面的暗通道和透射率计算全程都用12bit。中途不要随便截断到8bit否则在雾天场景的低亮度区域会出现严重的banding条带噪声。我自己就吃过这个亏后来花了半天时间把内部数据位宽全部统一成12bit才解决。4.2 Zynq下的自定义透雾IP封装AXI-Lite从接口设计为了让透雾逻辑变成一个可复用的IP核我用Vivado HLS/Verilog做了一套带AXI-Lite从接口的设计。IP核寄存器包括控制寄存器使能/旁路透雾、大气光值寄存器、透射率下限寄存器、透雾强度寄存器对应DCP公式中的ω、调试寄存器输出暗通道图/透射率图等中间结果。这些寄存器通过AXI-Lite挂在PS端的交叉开关上ARM可以直接读写。PS端的驱动代码负责一个配置流程上电后先给IP发送旁路指令让视频流直通确保基础图像正常然后用两帧时间统计大气光写入寄存器接着配置透雾强度为0.95使能透雾模块最后后台每5秒检测一次场景平均亮度自动微调透射率下限防止过曝。这个流程看起来很简单但实际调试中发现一个关键点大气光寄存器的更新时机必须和帧同步信号对齐。如果你的寄存器更新发生在帧中间那么同一帧里有的行用旧大气光算有的行用新大气光算画面会出现横向亮度断层。解决办法是在驱动里等VSYNC中断到来后在VSYNC之后的前几个blanking周期内写寄存器确保新参数只对下一帧生效。4.3 仿真验证与板上实测MATLAB参考模型与Verilog仿真结果对比工程落地之前我先在MATLAB里搭了一个像素级的DCP参考模型。MATLAB脚本做的事包括读入有雾图像、计算暗通道、估计大气光、估计透射率、导向滤波细化、图像复原、后处理并把每一步的中间结果保存成TXT文件。这个文件就是FPGA仿真的“金标准”。然后我在Vivado里写了一个testbench给透雾模块输入同一张有雾图像运行仿真后把RTL输出的暗通道图和透射率图导出来用Python或MATLAB与参考模型逐像素对比。第一次对比下来暗通道图的误差在±5以内12bit透射率图误差在±2以内8bit复原图像的平均PSNR达到35dB以上基本可以接受。不过有个别边缘像素的误差会到±15是因为RTL里用均值滤波近似导向滤波导致的视觉上不影响。板上实测时我用了一颗OV5640 Sensor输出1080P30的RAW图经过透雾IP后直接接HDMI显示器。在一个真实的雾天场景里效果确实明显远处的楼宇轮廓清晰了天空区域也没有出现严重伪彩色。唯一不满意的地方是近处暗部区域的噪声略有放大这是透雾算法的通病只能在后处理里加降噪缓解。5. 踩坑实录FPGA透雾调试过程中的典型问题速查5.1 问题一第一帧图像出现水平割裂透雾强度突然跳变这是所有用动态参数算法的FPGA应用都会遇到的问题。我调试时发现画面在特定行数处出现一个横向切割上半屏透雾效果明显下半屏几乎无效果。排查过程分了三步第一步查寄存器更新时间确认是VSYNC中断里更新大气光排除了帧内更新。第二步查透射率图输出中间结果后发现切割行下方的透射率图全为1无透雾上方正常。这让我怀疑是某个FIFO或行缓存的复位信号有问题。第三步查复位释放顺序最终定位到是透雾模块的输入行缓存复位不彻底当大气光寄存器更新时行缓存正好处于上一帧的末尾复位后新帧的前几行数据没有被正确写入导致纵向最小值滤波输出异常透射率计算直接失败。解决办法是给复位逻辑加上帧同步信号同步确保行缓存在每帧开始时都自动清空。5.2 问题二暗通道图的块状效应传导到最终图像第一次用15x15窗口做暗通道时最终复原图出现了明显的“马赛克”感尤其是物体的边缘轮廓附近。问题出在窗口尺寸和图像分辨率的匹配上对1080P图像15x15的窗口覆盖的区域比较小对雾天这种大范围均匀的大气散射来说窗口小了暗通道里会保留过多纹理导致透射率估计不够平滑。解决办法是把最小值滤波窗口从15x15调整到45x45。虽然行缓存资源增加了三倍但暗通道图明显平滑透射率图也变得更“干净”。对于4K分辨率建议窗口增到60x60以上具体数值可以通过滑窗参数动态配置。5.3 问题三MIPI输入的RAW图在透雾后出现色偏透雾算法的前提是输入图像的白平衡准确。如果你把透雾模块放在白平衡之前会放大白平衡误差导致画面整体偏蓝或偏红。我当时一开始为了省一个行缓存把透雾放在Bayer插值后、白平衡前结果出来的图就偏得离谱。后来调整了ISP Pipeline的顺序Bayer插值 → 白平衡 → 去马赛克后处理 → 透雾。这里要特别提醒透雾一定要放在白平衡之后否则暗通道先验里关于“RGB通道的局部最小值趋近于0”的假设不成立算法就失效了。5.4 问题四FPGA资源优化——BRAM和DSP消耗超出预算我的4K透雾方案最初的资源评估显示暗通道两个方向的滤波各占12行缓存透射率细化占6行缓存大气光统计需要两个小FIFO外加MIPI和HDMI接口的行缓存BRAM总消耗达到180块。而目标FPGAZynq-7020只有140块BRAM直接爆了。优化办法一是把暗通道最小值滤波的中间灰度结果用8bit而不是12bit存储因为已经做了最小值精度损失可接受二是把透射率细化里两次均值滤波合并成一次通过复用行缓存来省BRAM三是大气光统计用滑动窗口的方式避免整帧存储直方图。最终BRAM消耗压缩到128块时序也收敛到了150MHz。5.5 问题五暗光场景下透雾后噪点被过度放大这个问题归根结底是透射率在低照度区域估计过低导致复原增益变大噪声跟着放大。解决办法有两个方向一是对透射率图做空间一致性约束如果某块区域的透射率与邻域差异过大就向邻域均值靠拢二是在复原模块后加一个亮度相关的降噪模块只在暗区提升降噪强度。前者已经在均值滤波细化中实现了大部分后者则需要额外资源做NR滤波。考虑到安防场景中夜晚无雾才是常态我建议透雾强度在低照度时自动降级这个策略可以在PS端根据图像平均亮度动态调整ω系数来完成。5.6 问题六多路视频并行透雾时的带宽瓶颈项目还做了一个扩展需求四路1080P同时透雾之后拼接全景视频。PL端算力是够的因为每路数据流的处理都是并行流水线不会互相干扰。但瓶颈出现在DDR带宽上——四路图像在做行缓存和透射率细化时由于架构本身没用DDR暂存所以开销不大但四路视频同时做MIPI输入和HDMI输出时DDR的总线带宽被占掉不少出现偶尔的帧丢失。解决思路是把四路视频的分辨率配置成720P而不是1080P或者改用MIPI虚拟通道复用同一组物理线路省掉部分输入接口的逻辑。这个也不是什么高深问题关键是在设计之初就要考虑好视频路数和带宽余量。6. 项目扩展与个人总结做完这个项目我对FPGA图像处理的理解深了一层。首先FPGA做图像处理算法一定要“硬件化思维”DCP这种规整的窗口型算法天然适合FPGA而那些大量随机访问内存的算法比如SLAM、光流就不太合适。其次IP化是FPGA开发的必经之路把调试好的透雾算法封装成带AXI-Lite接口的IP后面接项目时只需要拖IP、配寄存器开发效率能提升好几倍。后续如果继续扩展我考虑两个方向一是加入深度学习透雾模型先在Zynq的PS端用DPU跑一个轻量级去雾网络PL端做前后处理这样可以对比物理模型和CNN模型在不同天气下的效果甚至可以做一个自适应的决策模块。二是在透雾模块里加入局部色调映射因为雾天场景往往伴随低动态范围单纯去雾后高光区域容易过曝加一段动态范围压缩会更实用。团队里同事还在试自动色偏校正模块通过统计无雾区域的灰点来动态校准白平衡这个配合透雾的使用效果也很好后续有机会再展开写。最后给后来者一句提醒透雾不是万能的它只是缓解大气散射的影响不可能完全恢复被雾遮挡的信息。做项目时先想清楚你的应用场景——是固定的远距离监控还是高速运动下的车载场景还是手持设备里的拍摄模式。场景变了透雾的算法选型和参数配置逻辑都会完全不同。FPGA给了你足够的灵活度去适应这些变化但能不能驾驭好靠的还是对算法和硬件的双重理解。
返回列表