资讯动态

基于FPGA的图像透雾实现:暗通道先验与ISP硬件加速

发布时间:2026/9/5 14:01:09 来源:尧图企业网站定制
1. 项目缘起与整体设计思路1.1 为什么是FPGA来做图像透雾前几天整理项目素材时翻到了去年调透雾算子的记录觉得整个过程还挺有代表性的——从算法选型到RTL落地再到板级调试几乎踩遍了硬件图像处理该踩的坑。今天就把这个基于FPGA的图像电子透雾(ISP Dehaze)项目完整拆一遍给正在做或者准备做类似工作的朋友一个参考。先回答一个很多人问过的问题透雾算法用软件跑不就行了为什么非得用FPGA确实OpenCV里几行代码就能实现暗通道透雾在PC上跑一张图也就是毫秒级。但问题在于场景——如果是车载摄像头的实时视频流或者监控相机的全天候视频你需要的是每一帧都在几十毫秒内完成处理而不是隔几秒处理一张图。CPU的瓶颈在于串行执行GPU的瓶颈在于功耗和成本而FPGA恰好站在了实时性和能效比的交叉点上。更关键的是透雾处理如果放在ISP管线里可以直接在RAW域或者RGB域操作避免了后端做了一遍又一遍的色彩空间转换这是架构层面的优势。另一个容易被忽视的原因是算法迭代的灵活性。透雾算法本身还在快速演进——从最经典的暗通道先验到后来的颜色衰减先验、深度学习透雾。FPGA相比ASIC的优势就在于算法大版本升级时你重新综合一遍工程就行不用重新流片。我见过不少团队用FPGA做ISP原型验证等算法彻底收敛了再固化到ASIC里这个路径本身就很成熟。1.2 项目核心需求拆解说回项目本身。标题是基于FPGA的图像电子透雾(ISP Dehaze)拆开看有三个关键词FPGA、ISP、Dehaze。FPGA部分需要实现完整的硬件图像处理管线从视频输入到透雾处理再到显示输出。ISP部分意味着透雾不是孤立的一个算法而是嵌在整套ISP pipeline里的一个模块——具体来说输入可能是RAW域数据经过Bayer插值变RGB然后进透雾模块出来再做白平衡、色彩校正、Gamma最后输出YUV给显示或者编码。Dehaze部分则是整个项目的核心算法需要把雾天降质的图像恢复成清晰图像。之所以强调ISP Dehaze而不是单纯的FPGA Dehaze是因为透雾算法在ISP管线中的位置很讲究。放在Bayer域做放在RGB域做各有各的优缺点后面我会详细展开。但提前说结论大多数实际工程里透雾放在RGB域、Bayer插值之后做这是性能和效果的平衡点。从需求场景来看这个项目最典型的应用就是安防监控的雨天雾天增强和车载相机的雾天视觉增强。我当时的测试素材也来自这两类场景——一组是高速公路雾天的监控视频一组是城市道路的雨天视频。这两类场景对实时性的要求都很硬帧率要求至少30fps延时要求在100ms以内所以用FPGA做硬件加速是正确选择。项目的最终交付物是一套可在FPGA开发板上运行的透雾演示系统包含完整的RTL代码、UART配置接口、HDMI显示通路以及用MATLAB/Python脚本做效果对比的半自动化验证流程。1.3 技术选型的前期调研正式开始写RTL之前我花了不少时间调研透雾算法在硬件上的可实现性。其实这个环节非常关键因为很多算法在MATLAB里跑得飞起一落到FPGA上就各种水土不服——要么是运算量太大导致资源爆炸要么是复杂度过高导致时序收敛困难。当时的主要候选方案有三种。第一种是暗通道先验(Dark Channel Prior)算法它由何恺明团队提出核心思想是绝大多数户外无雾图像的局部区域里至少有一个颜色通道的像素值非常低趋近于0。基于这个先验可以估算出大气光和透射率再反解出清晰图像。这个算法的优点是物理意义明确、无雾恢复效果好而且可以做成行流水的硬件架构缺点是全局大气光估算需要统计全图信息会引入一定的帧间延时。第二种是基于**颜色衰减先验(Color Attenuation Prior)**的方法核心思想是雾的浓度与像素亮度和饱和度的差值成正比。这个算法计算量更小硬件实现更简单但效果对场景比较敏感天空区域容易偏色。第三种是深度学习透雾效果最好但硬件成本极高——至少需要DSP阵列或者AI加速器辅助纯逻辑资源很难干下来。综合对比后我选了暗通道先验作为主算法理由是它效果好、原理清晰、适合流水线架构而且在FPGA上的资源消耗可控。这就是用最成熟的方案优先解决工程问题不是所有项目都要上最新最潮的技术。2. 透雾算法原理与硬件化适配2.1 暗通道先验的数学原理暗通道先验的公式是学术界的老朋友了但还是花点篇幅讲讲它的物理含义毕竟后面所有硬件模块都是围绕它设计的。在无雾的图像中对于任意的像素点定义它的暗通道为$$J^{dark}(x) \min_{y \in \Omega(x)} \left( \min_{c \in {R,G,B}} J^c(y) \right)$$其中$c$表示RGB三个颜色通道$\Omega(x)$是以像素$x$为中心的局部区域通常取15x15或更小。这个式子的意思是在图像的任意一个小局部区域里至少有一个通道的亮度值很低。如果这个值趋近于0就是暗通道先验成立的区域。那雾天图像的成像模型咱们用一个经典的大气散射模型来描述$$I(x) J(x)t(x) A(1 - t(x))$$其中$I(x)$是有雾图像也就是传感器直接拍到的$J(x)$是清晰无雾的图像我们希望恢复的$t(x)$是透射率表示光线穿过大气到达相机的比例$A$是全局大气光。这个公式的物理含义其实很直观相机接收到的光由两部分组成一部分是景物反射的光经过雾气衰减后到达相机的$J(x)t(x)$另一部分是大气光$A$被雾气散射进相机的$A(1-t(x))$。雾越浓$t(x)$越小大气光贡献越大图像看起来就是白蒙蒙的。要恢复$J(x)$就得知道$A$和$t(x)$。暗通道先验在这里的价值是它告诉我们在无雾图像的暗通道里$J^{dark}(x)$趋近于0。把大气散射模型代入暗通道的计算式再结合这个先验就能解出透射率$$t(x) 1 - \omega \cdot \frac{I^{dark}(x)}{A}$$这里的$\omega$是一个[0,1]之间的常数通常取0.95用于保留一点雾气感避免恢复过头让图像看起来更自然。$I^{dark}(x)$是输入有雾图像的暗通道。大气光$A$通常取暗通道中亮度最高的前0.1%像素对应位置的原图像素值。恢复公式则是$$J(x) \frac{I(x) - A}{\max(t(x), t_0)} A$$其中$t_0$是一个下限阈值通常取0.1防止透射率太小导致除以0和噪点放大。这些公式转成硬件实现时我的处理思路是把问题拆成四步一算暗通道二估大气光三求透射率四做恢复映射。四个步骤分别对应独立的硬件模块模块之间用FIFO或者行缓存衔接。2.2 算法到硬件的数据流重构上面的数学推导听起来挺顺畅但把算法落到FPGA上的时候你会发现软件思维和硬件思维之间有道坎。软件实现里暗通道计算是在整帧图像上先做一次滑动窗口统计存下整帧的暗通道图然后再找全局大气光再逐像素运算。这种先全图统计、后逐像素处理的思路在FPGA上行不通因为FPGA的内存资源有限不可能把整帧图像缓存下来随便访问。硬件上必须改成行流水架构让数据像水一样流过每个处理阶段。具体来说暗通道计算需要$N\times N$的窗口比如5x5则需要$N$行的行缓存Line Buffer。每个时钟周期进来一个像素同时输出这个像素周围窗口内的最小值。这一步是纯组合逻辑加移位寄存器非常容易流水化。大气光估计这是暗通道先验里最不适合硬件实现的部分因为需要全图的暗通道统计结果。我的做法是双帧处理的思路当前帧计算暗通道并做透射率估计同时把暗通道的统计结果用来估算大气光下一帧的透射率恢复使用这一帧得到的大气光。对于视频流来说相邻帧的大气光基本不变所以这个近似完全够用却能把非行流水的全局统计变成可流水的帧级处理。透射率估计有了暗通道值和大气光透射率就是一两个减法、一次除法、一次乘法的事。除法在FPGA里比较奢侈我用了查表法——把大气光A的倒数预先算好存在BRAM里透射率就用乘法替代除法。恢复映射也是按像素级的公式直接算配合一个透射率下限钳位。除了算法本身还要考虑数据的位宽和精度。我用的是8-bit RGB输入中间暗通道用8-bit透射率用12-bit定点数8-bit整数部分4-bit小数部分来保证精度。处理完成后还要做饱和度调整和亮度调整这两个模块是透雾效果能不能看着舒服的关键后面会细说。2.3 透雾模块在ISP管线中的位置前面说过透雾模块不是独立存在的它嵌在ISP pipeline里。我当时用的是自己设计的一条简化ISP链路顺序是Sensor RAW输入 - Bayer插值(RGB) - 黑电平校正 - 透雾Dehaze - 白平衡 - 色彩校正 - Gamma - YUV输出。为什么把透雾放在白平衡前面这里有个工程上的权衡。一种方案是把透雾放在RAW域、Bayer插值之前做。优点是RAW域数据还没有经过色彩处理数据量小单通道处理带宽低资源省。缺点是Bayer域的透雾算法需要处理的是单通道的暗通道信息颜色信息没有完全解出来恢复效果打折。另一种方案是把透雾放在RGB域。缺点是数据量大三倍三通道BRAM资源更紧张但好处是按真实颜色计算暗通道效果明显更好。我最终选了RGB域处理。原因很简单效果优先而且现代FPGA的BRAM容量早就不是瓶颈了。Xilinx 7系列/Kintex系列动辄几百个BRAM块做行缓存绰绰有余。还有几个细节值得注意。透雾处理会导致整体亮度偏暗需要加一个增益补偿。我是在透雾模块内部做了一个可配置增益范围1.0~1.5通过寄存器配置。另外透雾模块对天空区域容易过度增强产生光晕效应我用的是引导滤波的近似简化版——做一个边缘保护的平滑处理降低光晕感。这个模块说复杂也不复杂本质上是一个小尺寸的低通滤波器配合自适应阈值判定代价很小但对画面观感提升明显。3. FPGA硬件架构与模块设计3.1 系统整体架构整个系统的架构按照标准视频处理流程来搭顶层结构可以分成五块视频输入模块、ISP预处理模块、Dehaze核心模块、ISP后处理模块和视频输出模块。我用的是Xilinx Artix-7系列的开发板具体型号XC7A200T板载HDMI输入输出接口、DDR3存储、UART接口。FPGA内部通过AXI4总线互联DDR3主要用于帧缓存——不过这里有个设计取舍透雾模块本身是行流水处理不需要整帧缓存DDR3主要是给视频输入输出做帧率匹配和图像缩放用的。输入端的做法是HDMI信号进来先过silicon image的接收芯片解出并行RGB数据然后在FPGA内部转成行场同步信号经过一个FIFO隔离时钟域进入ISP预处理。输出端则是反向的流程ISP后处理完的数据经过FIFO跨时钟域后送到HDMI发送芯片编码输出。值得说明的是这个系统跑的是1080p30fps。1080p每帧1920x1080每像素3字节RGB888一帧原始数据约6MB帧率30fps数据率约180MB/s。这个带宽在Artix-7上没什么压力关键是时序上要保证每个像素在300MHz的像素时钟内完成从输入到输出的全链路处理。3.2 行缓存与窗口生成器设计行缓存是整个透雾模块的基础设施它负责把逐行输入的像素流变成窗口形式的像素块供暗通道计算使用。我的窗口大小选的是5x5。这个尺寸是权衡后的结果——窗口太小暗通道估计不准透射率图噪声大窗口太大需要更多行缓存资源翻倍。5x5是个经验值测试下来在透雾效果和资源消耗之间取得了不错的平衡。行缓存的实现方式有两种选择。一种是用FPGA的BRAM搭建另一种是用移位寄存器SRL16E搭建。BRAM方式的优点是容量大缺点是只能顺序读写移位寄存器方式的优点是灵活适合窗口滑动但深度有限。我最终用的是BRAM构成的多行缓存结构用两个BRAM构建5行缓存每行深度1920宽度8-bit单通道暗通道计算只需要一个通道的最大值/最小值实际上我缓存三个通道深度1920宽度24-bit这样一次读就能取出窗口内三通道的数据。窗口生成器每个时钟周期从行缓存里读出第0行、第1行、第2行、第3行、第4行的当前像素组成一个5x5窗口。实现上的一个经验是行缓存的写地址和读地址要错开写地址超前读地址这样可以避免读到未更新的数据。另外一个容易被坑的地方是窗口数据和当前像素的时序对齐——窗口中心像素要和其他位置的像素对齐到同一个时钟周期否则后面的计算全都错位。我的做法是在窗口生成器输出端加几级寄存器对齐保证每个窗口数据的中心像素绝对同步。3.3 暗通道计算模块的RTL实现暗通道计算模块要做的事情非常纯粹对5x5窗口内的25个像素分别取R、G、B三个通道的最小值再把三个最小值再取一次最小值得到该像素的暗通道值。用伪代码描述就是for (i 0; i 5; i) { for (j 0; j 5; j) { min_r min(min_r, window[i][j].r); min_g min(min_g, window[i][j].g); min_b min(min_b, window[i][j].b); } } dark_channel min(min_r, min_g, min_b);RTL实现的时候我分了三级流水第一级做25个像素的R/G/B通道各自的最小值第二级做9个分组最小值第三级做最终最小值。这样每一级的组合逻辑深度都控制在2~3个比较器以内时序收敛很容易。资源消耗方面这个模块几乎不用DSP比较器用LUT实现纯逻辑资源大概占用800个LUT左右非常轻量。运行频率在200MHz以上没有压力。这里有一个关键细节暗通道计算要有一个使能信号控制边框像素。图像的边缘像素凑不齐5x5窗口硬件上通常的做法是边缘保持原值不做透雾或者对边缘窗口做镜像填充。我选择的是保持原值即边缘一圈不做透雾处理视觉上完全看不出差别实现却简单很多。3.4 大气光与透射率硬件实现大气光估计在硬件上是个微妙的部分。假如原样照搬论文做法——对全图暗通道做统计、取前0.1%最亮像素在硬件里几乎没有直接实现方式因为每来一个像素就更新统计全图处理完再计算这天然就不是行流水的路子。我的妥协方案是分块统计把整帧图像在垂直方向分成16个块每个块独立统计暗通道的最大值和对应的原图RGB值。帧结束时把16个块的统计结果汇总选择最大的那个作为全局大气光A。这个做法在效果上非常接近全局统计因为大气光通常是天空或者远处的雾气无论怎么分块最亮的块一定能捕获到它。透射率的计算过程相对简单。暗通道值$I^{dark}$出来了大气光A也定了那么透射率$$t(x) 1 - \omega \cdot \frac{I^{dark}(x)}{A}$$硬件上实现除法不划算我预先用软件算出$1/A$存成16-bit定点数8-bit整数部分8-bit小数部分放进寄存器。然后透射率就是一次乘法、一次减法的事。$\omega$取0.95透射率下限$t_0$取0.1用比较器做钳位。透射率图做出来后还需要做一次最小值滤波平滑。这一步在软件里是导向滤波硬件上我用的是3x3的最小值滤波做替代。效果上虽然不如导向滤波那么精细但光晕抑制的效果已经很不错而且资源消耗极其有限。如果对画质有更高要求可以考虑用分离的横向纵向一维滤波近似二维滤波能省不少BRAM。3.5 透雾映射与后处理管线透射率求出来了大气光求出来了最后的恢复公式$$J(x) \frac{I(x) - A}{\max(t(x), t_0)} A$$硬件实现同样要规避除法。我预先算好一张透射率映射表——透射率的值域是0.1到1.0量化成256级每一级对应一个16-bit的增益系数$1/\max(t, t_0)$存在BRAM里。实际处理时直接用透射率查表得到增益然后做乘法和加减法运算。这里还有一个工程细节恢复出来的$J(x)$可能出现超过8-bit范围的值需要对输出做饱和截断而不是简单的位截断。我用的是两个比较器实现饱和处理大于255输出255小于0输出0。这个细节很多初学者会忽略导致图像出现条纹。恢复完的RGB图像还要经过两级后处理。第一级是亮度/饱和度调整。透雾后的图像通常会显得有点灰因为整体被拉伸了我加了一个可配置的饱和度增益寄存器范围0.7~1.3R、G、B三个通道做了基于亮度系数的饱和度扩展。第二级是一个简单的边缘增强用的是3x3拉普拉斯算子的近似版把边缘信息叠加回原图提升清晰度。这个边缘增强模块是可以旁路的通过寄存器配置。4. 系统调试与效果调优实录4.1 仿真验证与常见bug开发过程中验证占了大半时间。FPGA开发有个铁律仿真阶段没查出来的bug上板调试的成本要高一个数量级。我的验证策略是三层。第一层是模块级仿真用ModelSim或者Vivado Simulator跑透雾核心模块输入用MATLAB生成的有雾测试图转成Hex文件读入仿真输出存成Hex文件再转回图片和MATLAB的参考结果做对比。这个环节最容易抓到的bug是位宽越界和时序错位。位宽越界体现在仿真波形里就是出现X态或者毛刺查起来反而不难时序错位则诡异得多输出图像的边缘会出现错位条纹看起来像鬼影重叠实际上就是行缓存的读写时序没有对齐。第二个高发bug是饱和截断被遗漏。有一次我在仿真里发现恢复出来的图像的天空区域出现了许多黑色斑点查了很久最后发现是$J(x)$超出了255之后没有饱和截断直接截断低位导致负数被映射到了0。这类问题如果仿真时只用干净的无雾图像做测试几乎发现不了必须用天空占比较大的雾天图像测试。第三类bug是跨时钟域问题。HDMI的像素时钟和FPGA内部处理时钟不同源输入输出FIFO如果深度不够或者标志位逻辑错误就会出现画面撕裂。我的解决方法是只让FIFO跨越时钟域不在跨时钟域路径上做任何组合逻辑。4.2 上板调试的避坑经验仿真跑通了不等于板子能跑上板调试才是真正考验工程能力的时候。我遇到的第一块硬骨头是大气光模块的帧同步问题。前面说了大气光用的双帧估算当前帧的结果要给下一帧用。但板子上电的初始几帧大气光寄存器的值是全零会导致透射率计算异常——第一帧恢复出来是全黑的。解决方法是加了一个帧计数复位逻辑在检测到前几帧的VBLANK帧消隐时用默认值填充大气光寄存器等统计结果有效后再切换到估算值。第二个问题是行缓存初始态。每次VSYNC信号到来时行缓存里还残留着上一帧的数据。如果不做清空新帧的头部像素会和残留数据混在一起产生画面顶部的一条斜纹。解决办法是在VSYNC到来时用复位信号清空行缓存的写地址并且把有效数据标志位关掉几个周期。第三个是DDR3带宽瓶颈。虽然透雾模块本身不需要DDR3但整条系统链路上HDMI输入写入DDR3、从DDR3读出做缩放和叠加、再输出HDMI三个通路共享DDR3带宽。在1080p60fps下DDR3带宽利用率接近90%偶尔会出现刷新冲突导致的画面闪烁。我把刷新优先级调低并把输出通路的AXI突发长度改成16总算把问题压下去了。4.3 定量调优从PSNR到主观观感说到调优必须提一个观点算法的客观指标和主观观感有时候并不一致而工程上需要优先照顾主观观感。我用MATLAB脚本对透雾前后图像的PSNR峰值信噪比做了对比。对于标准的薄雾图像PSNR可以提升8~12dB对于浓雾图像提升只有3~5dB——浓雾场景下暗通道信息几乎丢失算法能恢复的信息有限。而对比SSIM结构相似性指数提升通常在0.1~0.2之间。但更重要的是调出一组让眼睛舒服的参数。这里有三个关键寄存器值要重点调透射率下限$t_0$调太大会导致恢复不彻底图像还是有点蒙调太小天空区域会出现伪影。我用的是0.1到0.2之间微调最终稳定在0.15。$\omega$参数控制保留雾气的程度。0.95是论文原值但实际调试发现0.85~0.9在监控场景下观感更好因为完全去掉雾气反而显得图像发干不自然。饱和度和亮度增益这两个参数直接决定最终画面观感。我调的默认值是饱和度1.15倍、亮度增益1.2倍测试视频里无论白天还是夜晚的效果都能接受。4.4 资源占用与性能报告整个系统在Artix-7 XC7A200T上的资源占用如下资源类型使用量总容量占用率LUT4120013480030.5%Flip-Flop2860026920010.6%BRAM (36Kb)8636523.6%DSP48E1427405.7%时钟频率200MHz--透雾核心模块本身的资源占比大约是整个系统的40%其余被视频输入输出、缩放、叠加等逻辑消耗。时序方面关键路径在透射率查表和恢复映射模块之间编译后WNS最差负时序裕量约0.8ns余量充足。性能方面实测1080p30fps下从HDMI输入到HDMI输出的端到端延迟约为3帧时间约100ms。其中真正由透雾算法引入的延迟只有不到2行的时间剩下的延迟基本来自帧缓存同步和显示刷新的固有等待。如果不需要叠加OSD和多帧缓存透雾模块的纯行流水延时就几十个时钟周期这个量级在工业场景下完全可以接受。5. 效果对比与常见问题速查5.1 不同场景的透雾效果对比我用了三类典型场景做了系统化的测试分别是薄雾场景、浓雾场景、雨天场景。因为github或者社区上找不到统一评测集样本主要来自实际拍摄和公开测试图库测试框是1080p视频流。薄雾场景下能见度200m左右恢复后的图像对比度明显提升远处山体和建筑的轮廓清晰度改善显著。主观观感上天空区域颜色略微偏冷因为大气光估计偏蓝但完全在可接受范围内。浓雾场景能见度50m以内恢复效果有限暗处能看到细节但整体还是白茫茫的。这里有个物理学规律无法绕过雾太浓时传感器根本没有捕捉到足够的反射光任何算法都不可能无中生有。工程上对浓雾场景的常规处理不是完全恢复而是尽量增强对比度有没有用另说。雨天场景比较特殊。雨滴会造成局部强反射亮斑暗通道先验容易把雨滴反射误判为雾气进而过度处理。我的应对方案是在Bayer插值后、透雾之前加一个简单的雨滴检测模块——通过高亮和位置特征标记雨滴区域在暗通道计算时把标记区域的像素排除在最小值统计之外。这样一来雨滴的影响被明显抑制透雾效果也能保留。5.2 透雾参数调节建议如果你在自己的系统上复现透雾模块我建议按照下面的顺序调参先定窗口大小。5x5是通用值如果你的场景里雾比较均匀比如海面用3x3也行动态范围和细节保留会更好如果雾分布很不均匀比如城市街道7x7窗口更稳。再调透射率下限$t_0$。从0.1起步观察天空区域。如果出现色块或伪影上调到0.15如果恢复不彻底、天空发白下调到0.08。然后调**$\omega$**。从0.9起步观察远处细节。太闷就降低太干就升高。最后调饱和度和亮度增益。这两个参数的调节没有通用公式只能靠人眼积累感觉。一个容易被忽视的经验是参数要按场景分组保存因为单组参数很难覆盖所有天气条件。我在系统里做了4组预设——薄雾/中雾/浓雾/雨天动了寄存器切换的自动模式。接入光传感器的话还可以全自动切换。5.3 常见问题与排查速查表这里列一份我调试过程中碰到的典型问题和排查方法基本都是踩过坑之后的总结。问题现象可能原因排查方法输出图像全黑大气光寄存器初始为0透射率计算异常检查帧计数复位逻辑是否生效确保前几帧用默认大气光填充画面边缘有条纹错位行缓存读写时序未对齐检查窗口生成器的对齐寄存器确认中心像素和窗口数据同步天空区域出现紫色斑块透射率$t_0$太小恢复时放大噪声上调透射率下限到0.15左右检查是否有饱和截断图像整体偏暗透雾后未做增益补偿检查后处理模块的亮度增益寄存器是否配置正确画面顶部一段斜纹VSYNC复位时行缓存未清空在VSYNC后清空行缓存地址关掉几个周期的数据有效信号动态场景出现光晕暗通道窗口太大或透射率未平滑减小窗口尺寸或增加3x3最小值滤波做透射率平滑雨滴区域过度增强雨滴反射被误判为雾加入雨滴检测/排除模块或调整暗通道计算的掩膜帧率上不去DDR3带宽饱和或时序路径太长检查AXI突发长度设置优化关键路径的流水级数5.4 透雾效果的主观评估方法搞硬件的人往往习惯用数据说话但透雾效果这件事硬件指标不能完全替代主观观察。我强烈建议在调试阶段建立一套简单的主观评分流程固定几个测试视频片段包含薄雾、浓雾、雨天、逆光等场景每调整一次参数就用HDMI实时输出到显示器或者接采集卡录制然后对比透雾前和透雾后的画面。评分维度可以包括细节恢复程度、天空区域自然度、边缘过度增强程度、色彩还原度、动态场景稳定性。每个维度打1到5分最后求加权总分。这样做的好处是当你改动一个参数导致PSNR提升了0.5dB但主观观感明显变差时不会被数字骗过去——这种指标上升、观感下降的情况我在调饱和度增益和边缘增强强度时碰到过不止一次。6. 工程优化空间与扩展方向6.1 算法层面从暗通道到深度学习如果要把这个项目继续往前推算法层面有几个明确的方向。第一个方向是用深度学习模型替换暗通道先验。现在轻量级的透雾网络比如AOD-Net的硬件友好变体已经可以做到在FPGA上跑实时不过资源消耗比暗通道大很多。如果你用的是Zynq UltraScale这类带AI引擎的FPGA可以考虑把透雾网络部署到AI引擎上其他ISP处理留在可编程逻辑里。第二个方向是把单帧透雾扩展为视频透雾。单帧的暗通道先验算法对视频流的时序一致性其实是不够的帧与帧之间的亮度突变和透射率抖动会让人眼感到闪烁。改进方法是加入时域滤波器——对透射率图做时间上的低通滤波或者引入光流信息做运动补偿。不过时域滤波会显著增加硬件复杂度属于效果天花板比较低、工程复杂度比较高的方向。第三个方向是透雾与HDR高动态范围的融合。雾天场景往往天空很亮、地面很暗动态范围极大。把透雾和HDR放在一条管线里做比如对暗通道估计做自适应分区可以实现更自然的动态范围压缩。这个方向目前学术界有论文支持工程上还比较新鲜感兴趣的可以关注。6.2 架构层面多路摄像头并行透雾在实际的安防或者车载场景里一个FPGA芯片上往往要跑多路视频输入——比如车载的环视系统是四路鱼眼摄像头同时工作。对于多路透雾有两种架构选择。第一种是时分复用多路视频源通过仲裁器轮流进入透雾模块由于每路视频的像素时钟是独立的需要先做像素缓冲和时钟域转换再复用一套透雾IP。这种方案资源最省但时延会变大而且四路输入的分辨率不能太高否则中间缓冲的FIFO深度会撑不住。第二种是模块复制把透雾模块例化四份每一路独享一套硬件。资源消耗翻四倍但每一路的延迟保持最低适合对实时性要求极高的场景。我建议先按第二种方案做原型验证——因为在Artix-7上整个透雾模块的资源占用量并不大四路复制后总占用率仍然能控制在可接受范围内。等算法收敛、场景明确了再优化成时分复用把资源省下来。6.3 供应链与长期维护视角作为一个嵌入式方向的FPGA工程师选型时还得多想一步长期可维护性。芯片选型上我用的Artix-7在工业级温度范围、供货稳定性、工具链成熟度方面都有保障。如果你做的是车规级项目可以关注一下Zynq UltraScale或者国产的紫光同创、安路等FPGA方案。在国产FPGA上移植这个项目的成本主要在于工具链的适配RTL代码本身是跨平台的。尤其是提到Xilinx Vivado工具链的话Xilinx的Vivado对Artix-7的支持已经非常成熟IP核可以直接用免费的Vivado ML Standard版生成如果切到国产FPGA比如紫光同创的PDS或者高云的IDE大部分RTL代码可以无缝移植但需要重新评估IP核的兼容性像DDR控制器、HDMI收发这些通常要换成厂商自己的IP或第三方兼容方案。另外提醒一个非常实际的点IP核的授权和工具链版本要提前锁定。有些厂商的IP核授权是绑定工具版本的如果你中途升级Vivado或者PDS之前生成的IP核可能全部要重新生成非常影响项目进度。我自己吃过这个亏现在做项目会先把工具链版本写死在配置管理文档里。6.4 扩展应用嵌入式透雾不止于ISP最后说一个让我自己都觉得意外的发现透雾算法并不仅仅用于ISP。它有相当多的扩展场景而且每个场景都能复用这套硬件架构。医疗影像内窥镜图像中组织表面覆盖的体液就像雾气透雾算法的暗通道先验可以直接套用帮助医生看清组织细节。水下成像水下图像的散射模型和大气散射模型极其相似本质区别只是介质从空气变成了水。暗通道先验在水下图像增强里有大量论文验证FPGA实现的架构几乎原封不动。工业检测恶劣环境下的视觉检测比如粉尘车间里的设备监控、蒸汽环境里的管道检测透雾都能做实时增强。遥感图像卫星或者无人机航拍图像受大气散射影响严重透雾在预处理阶段做增强能提升后续目标检测的准确率。这些方向的应用需求核心算法都能回归到成像模型和透射率估计上而我已经调通的FPGA行流水架构可以快速复用——只需要调整参数、接口和分辨率硬件主体不需要大改。这也算是我做这个项目最大的收获一套架构多种应用投入产出比很高。我自己在项目后期的体会是不要被FPGA透雾这个具体的名字框住思路。这个项目真正交付的是一套基于暗通道先验的实时图像增强系统的硬件实现方案透雾只是它的第一个实例。掌握了这套方案就等于掌握了在FPGA上做实时图像增强类算法的基本方法论——行缓存怎么搭、全局统计怎么近似成行流水的局部处理、除法怎么用查表替代、算法效果和硬件资源怎么权衡。这些方法论比透雾本身的值钱多了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价