FPGA加速红外图像坏点修复:中值与均值滤波的硬件实现对比
1. 红外图像坏点修复为什么FPGA是“硬件医生”的首选如果你玩过红外热像仪或者接触过国产红外探测器大概率会遇到一个头疼的问题屏幕上总有一些“死点”或者“亮点”怎么拍都去不掉。这些就是所谓的“坏点”。我刚开始做红外图像处理的时候也深受其扰明明场景温度均匀图像上却总有几个像素点要么特别亮要么特别暗像白纸上的墨点一样扎眼。后来才知道这主要是探测器制造工艺导致的像素单元响应不一致甚至有些单元直接“罢工”了。对于追求成像质量的安防监控、工业检测或者医疗设备来说这些坏点绝对不能忍。想象一下在电力巡检中一个坏点可能被误判为过热点在医疗热成像中一个异常的亮点可能干扰诊断。所以在图像输出给用户看之前必须把这些坏点“修”好。软件修复当然可以比如用OpenCV写个脚本跑一下。但在很多实时性要求高的场合比如视频流每秒60帧软件处理根本来不及延迟会让你抓狂。这时候FPGA现场可编程门阵列的优势就体现出来了。它不像CPU那样一条指令一条指令地执行而是可以设计专门的硬件电路并行处理整幅图像的数据。简单说FPGA能让你为“修复坏点”这个特定任务定制一个专用的、高速的“硬件医生”图像数据流进来修复好的数据流出去几乎不占用主处理器资源延迟极低。那么这个“硬件医生”用什么“手术方案”呢最主流的就是两种中值滤波和均值滤波。听起来很像软件里的概念对吧没错原理相通但用硬件实现起来在资源消耗、修复效果和速度上差别可就大了。这篇文章我就结合自己这几年在FPGA上折腾红外图像处理的实战经验带你深入看看这两种方案到底怎么选怎么实现以及各自会踩哪些坑。2. 核心原理中值滤波 vs. 均值滤波谁才是坏点“克星”在动手写代码之前我们得先搞清楚面对一个坏点我们到底要怎么“治”它。核心思想很简单用周围好像素的值来替换这个坏点的值。就像墙上有个污点我们用周围墙皮的颜色把它补上。关键就在于这个“周围墙皮的颜色”怎么取2.1 均值滤波简单的“平均主义”均值滤波的思路最直接把坏点周围一个窗口比如3x3、5x5里所有像素的值加起来然后求个平均值用这个平均值替换坏点。计算上就是加法器和除法器或者右移的事。听起来很合理对吧但这里有个大坑也是我早期项目里踩过的坏点经常扎堆出现。国产红外探测器由于工艺原因坏点分布可能不是完全随机的有时会成小簇出现。如果你用一个坏点周围3x3的窗口取平均而这个窗口里恰好还包含另一个坏点那么你用来做平均的样本本身就是“脏数据”算出来的平均值也就不准了修复效果会大打折扣甚至可能引入新的伪影。2.2 中值滤波可靠的“中间派”中值滤波的思路更聪明一些它不取平均值而是取这个窗口里所有像素值的中位数。什么是中位数就是把窗口里9个像素值从小到大排序排在中间第5位的那个值。这个方法妙在哪里它对极端值也就是坏点不敏感。即使窗口里有一两个其他坏点只要好像素占多数排序后中间的那个值大概率还是一个正常像素的值。这就好比投票少数极端票坏点很难影响最终的结果中值。因此在坏点可能聚集的场景下中值滤波的鲁棒性稳定性通常比均值滤波好得多。为了让你有个直观感受我做了个简单的对比表格特性均值滤波中值滤波核心思想取邻域像素算术平均值取邻域像素排序后的中值对孤立坏点效果好好对聚集坏点效果差易受污染好抗干扰性强硬件实现复杂度低主要是加法树和除法高需要排序网络资源消耗较少逻辑、DSP较多比较器、逻辑输出平滑度高可能模糊细节中等能较好保持边缘所以从修复效果上看中值滤波往往是更优的选择。但天下没有免费的午餐它的硬件实现要比均值滤波复杂这也是我们后面要重点讨论的。在实际项目中选择哪种方案就是在效果、资源和速度之间做权衡。3. FPGA实现基石如何搭建高效的图像处理流水线不管用哪种滤波算法在FPGA里处理图像流第一步都是搭建一个高效的滑动窗口Sliding Window也叫开窗。这是所有局部图像处理滤波、边缘检测等的硬件基础。你可以把它想象成一个在图像上移动的“取景框”每次框住一小块区域比如3x3进行处理。3.1 构建3x3滑动窗口移位寄存器的艺术FPGA处理视频流是“流式”的数据一个像素接一个像素地进来。我们怎么同时拿到一个3x3窗口里的9个像素呢秘诀就是行缓存Line Buffer。以一幅宽度为W、高度为H的图像为例。当像素数据流进来时我们用两个FIFOFirst In First Out或者专用的移位寄存器IP核Shift Register把当前行的数据缓存下来。第一个FIFO缓存第N行第二个FIFO缓存第N-1行。同时当前正在输入的像素是第N1行的。这样在任何一个时刻我们手头就同时有了三行图像的数据正在输入的当前行、FIFO1输出的一行、FIFO2输出的一行。有了三行并行数据接下来就是获取每一行上的三个连续像素。这通过三个寄存器级联就能轻松实现。每个像素时钟数据从上一个寄存器移动到下一个。那么在流水线的某个固定阶段我们就能同时捕获到第一行三个连续的像素 P0, P1, P2第二行三个连续的像素 P3, P4, P5第三行三个连续的像素 P6, P7, P8这9个寄存器里的值恰好就构成了一个以P4为中心的3x3滑动窗口。整个结构就像一条精密的流水线数据源源不断地流过窗口也随之平滑地滑过整幅图像。这里有个工程细节需要注意也是我调试时遇到过的问题行长度可能超出IP核限制。比如你的图像一行有800个像素含消隐期但FPGA提供的移位寄存器IP最大深度可能只有512。这时候怎么办很简单用两个IP核串联起来就能缓存更长的行了。原始文章里提到的“每一行用了2个移位寄存器ip”就是这个原因。3.2 坏点标记的“随流携带”策略我们怎么知道窗口中心的P4是不是坏点呢这就需要一份“坏点地图”。通常我们会用红外相机拍摄标准黑体通过分析不同温度下各像素的响应特性提前标定出所有坏点的坐标生成一个二值化的坏点标记图1表示坏点0表示好点。在FPGA处理时我们不能等整幅图都存下来再查表那样延迟太高。高效的做法是将坏点标记信息“打包”到像素数据流里一起传输。具体怎么操作假设我们的像素数据是16位的。我们可以分配额外的几位比如3位作为“标签位”。例如标签位[18:16] 分别用来携带帧有效信号、行有效信号、坏点标记信号。标签位[15:0] 是原始的16位像素数据。这样{帧有效 行有效 坏点标记 像素数据}就构成了一个19位的“增强型”数据流。这个数据流一起进入我们上面搭建的滑动窗口流水线。当窗口建立好后我们只需要检查中心像素对应的那个寄存器中的“坏点标记”位是否为1就能瞬间判断当前窗口是否需要启动修复算法。这个设计非常巧妙将控制信息与数据流紧密结合省去了复杂的外部寻址和同步逻辑。4. 硬核实现对比中值滤波与均值滤波的FPGA架构拆解理论讲清楚了流水线也搭好了现在该给这个流水线装上“处理核心”了。中值和均值这两个核心的硬件架构截然不同。4.1 均值滤波的硬件实现加法树的狂欢均值滤波的硬件实现相对直白核心就是一个多操作数加法器最后跟一个除法或右移。对于一个3x3窗口我们需要计算9个数的和。直接两两相加需要8个加法器但我们可以设计成加法树的结构来优化时序。比如先每3个数一组相加得到3个中间和再把这三个中间和相加得到总和。在FPGA里我们可以利用专用的DSP数字信号处理切片来高效实现这些加法它们速度快、功耗低。得到总和后除以9对于3x3窗口。在硬件里除法是昂贵的操作。幸运的是如果窗口大小是2的幂次方比如4x4除以16我们可以直接用右移运算来实现几乎不消耗逻辑资源。但3x3窗口的除以9就没这么幸运了。通常有两种做法近似处理除以8右移3位这样会引入系统误差但速度极快。精确除法使用FPGA的除法器IP核或者查找表这会消耗较多的DSP或逻辑资源并且会引入额外的延迟。在我的均值滤波模块代码里见下方简化的关键部分我采用了第一种近似方法用右移3位除以8来代替除以9在实时性和资源消耗上取得了很好的平衡。同时通过一个多路选择器根据中心像素的坏点标记位决定是输出原始值还是修复后的均值。// 均值滤波核心计算部分简化示意 wire [18:0] window_sum; // 假设位宽已扩展以防溢出 wire [15:0] mean_value; // 加法树计算3x3窗口内9个像素的和 assign window_sum p0 p1 p2 p3 p4 p5 p6 p7 p8; // 近似除以9这里演示右移3位除以8实际可根据精度要求调整 assign mean_value window_sum[18:3]; // 取高位相当于除以8 // 输出选择如果是坏点则输出均值否则输出原值 assign fixed_pixel (bad_pixel_flag 1b1) ? mean_value : original_center_pixel;4.2 中值滤波的硬件实现排序网络的挑战中值滤波的硬件实现才是真正的挑战所在其核心是一个排序网络。我们需要对9个输入的数字进行排序然后取出中间的那个值。最“笨”的办法是冒泡排序但那是软件思维在硬件里效率太低。硬件里常用的是基于比较器的排序网络例如双调排序网络。对于9个输入一个高效的排序网络需要大量的比较-交换单元。原始文章里给出的中值滤波代码采用了一种更直观的“比较-选择”方法来实现9取中值。它没有一次性对9个数全排序而是巧妙地分了两步第一步行内排序。分别对三行每行3个像素进行排序每行得到其最大值、中值、最小值。第二步行列中值计算。将三行的三个最大值进行比较取其中的最小值即“最大中的最小”将三行的三个中值进行比较取其中值即“中值中的中值”将三行的三个最小值进行比较取其中的最大值即“最小中的最大”。最后再对得到的这三个值最小最大、中值中值、最大最小进行一次排序取其中值这个值就是整个3x3窗口的中值。这个方法减少了比较的复杂度但依然需要大量的比较器逻辑。下面的代码片段展示了这种分层比较的思路// 中值滤波核心计算部分分层比较法示意 // 第一步对三行数据每行三个数排序得到每行的 max, mid, min // 以第一行为例 always (posedge clk) begin // 比较a,b,c三个数找出max, mid, min if (a b a c) max1 a; else if (b a b c) max1 b; else max1 c; // ... 类似逻辑找出mid1和min1 end // 第二步对三行的max组、mid组、min组分别操作 wire [15:0] min_of_maxs; // 三个max中的最小值 wire [15:0] mid_of_mids; // 三个mid中的中值 wire [15:0] max_of_mins; // 三个min中的最大值 // 找出 min_of_maxs always (posedge clk) begin if (max1 max2 max1 max3) min_of_maxs max1; else if (max2 max1 max2 max3) min_of_maxs max2; else min_of_maxs max3; end // ... 类似逻辑找出 mid_of_mids 和 max_of_mins // 第三步对最终三个值排序取中值 wire [15:0] median_value; always (posedge clk) begin // 对 min_of_maxs, mid_of_mids, max_of_mins 进行排序取中间值 if ( (mid_of_mids min_of_maxs min_of_maxs max_of_mins) || (max_of_mins min_of_maxs min_of_maxs mid_of_mids) ) median_value min_of_maxs; else if ( ... ) // 其他两种情况 median_value mid_of_mids; else median_value max_of_mins; end // 输出选择 assign fixed_pixel (bad_pixel_flag 1b1) ? median_value : original_center_pixel;这种实现方式在逻辑资源的使用上会比均值滤波多很多因为每一步的比较操作都需要实例化大量的比较器电路。但它带来的好处是修复质量更高尤其是在面对现实世界中不完美的红外探测器时。5. 工程选型指南资源、速度与效果的终极权衡纸上谈兵终觉浅到底选哪个方案还得拉出来在真实的FPGA芯片上跑跑看。我基于Xilinx的Zynq-7000系列芯片具体型号XC7Z020分别实现了上述的3x3中值滤波和均值滤波坏点修复模块并在同样的640x51260Hz红外视频流下进行测试得到了一些非常直观的数据。5.1 资源占用Resource Utilization对比这是最硬核的指标直接关系到你的设计能不能在目标芯片上放得下以及成本高低。我们主要看两种关键资源查找表LUTs 用来实现组合逻辑和触发器FFs 用来存储状态。资源类型均值滤波实现中值滤波实现说明查找表 (LUTs)~450~1200中值滤波约是均值的2.7倍触发器 (FFs)~600~950中值滤波约是均值的1.6倍块RAM (BRAM)2 (用于行缓存)2 (用于行缓存)两者相同都用于存储两行图像DSP切片2-4 (用于加法树)0均值滤波使用了DSP加速加法可以看到中值滤波在逻辑资源上的消耗远超均值滤波这主要就耗费在实现那个复杂的排序网络上。而均值滤波虽然逻辑简单但为了高速求和可能会调用一些DSP单元。如果你的FPGA芯片DSP资源丰富而逻辑资源紧张均值滤波可能更有优势。5.2 时序性能Timing Performance与实时性实时性是红外处理的生命线。我们主要关心两个指标最大时钟频率Fmax和流水线延迟Latency。最大时钟频率在同样的时序约束下均值滤波模块由于逻辑路径更简单主要是加法树通常能达到更高的Fmax比如轻松跑到150MHz以上。而中值滤波模块因为比较器网络的关键路径更长Fmax可能会低一些比如在120MHz左右。这意味着均值滤波能处理更高像素时钟的视频流。流水线延迟这是数据从输入到输出经过的时钟周期数。两种方案都基于流水线设计延迟主要来自于行缓存和窗口寄存器。对于3x3窗口两者延迟基本一致大约在“一行像素数 几个周期”的量级。这个延迟对于视频流来说是固定的、可预测的并且非常小微秒级完全满足实时性要求。所以在延迟上两者打平但在处理超高帧率或超高分辨率视频时均值滤波在时钟频率上的潜力更大。5.3 修复效果Image Quality主观与客观评价最后也是最重要的修得好不好我找了一幅带有典型簇状坏点的红外图像来自某国产探测器进行测试。均值滤波对于孤立的、分散的坏点修复效果很好坏点被平滑地抹去与周围区域融合自然。但是当坏点聚集时修复区域会出现明显的“污渍”状斑块因为坏点值参与了平均计算污染了结果。整体图像会显得稍微“模糊”一点因为均值操作本身就是一个低通滤波器。中值滤波无论是孤立坏点还是聚集坏点修复效果都非常出色。坏点被替换为邻域内最有可能的正常值图像细节尤其是边缘保持得比均值滤波更好。在聚集坏点区域没有出现均值滤波那样的污染现象。为了更量化地比较我计算了修复后图像与理想无坏点参考图之间的峰值信噪比PSNR。在测试图像上中值滤波方案的PSNR比均值滤波平均高出3-5 dB。这个提升在视觉上可能不那么惊人但在对图像质量要求严苛的定量分析应用中是非常有价值的。6. 进阶思考超越3x3更大窗口与自适应策略在实际项目中3x3窗口有时可能不够用。如果坏点非常大或者你想获得更平滑的效果可能会用到5x5甚至7x7的窗口。这里的水一下子就深了。均值滤波的扩展窗口从9点扩大到25点5x5加法树变得更大但结构是类似的资源增长大致是线性的O(n)。除以25的除法可以用乘法近似乘以一个定点小数来实现。中值滤波的扩展噩梦开始了。对25个数进行排序其比较网络的复杂度呈平方级增长O(n²)。直接实现一个5x5的完整排序网络其资源消耗可能会变得难以接受。这时候就需要更聪明的算法比如部分排序或近似中值滤波如基于直方图的算法在保证一定效果的前提下大幅降低硬件开销。另一个更高级的思路是自适应滤波。为什么非要固定用中值或均值呢我们可以根据坏点周围的局部图像特征动态选择。例如在平坦区域用均值滤波可以获得更平滑的效果在边缘区域则切换为中值滤波以保护边缘。这需要在FPGA里增加一个简单的纹理分析单元根据窗口内像素的方差等统计量来决定滤波策略。我最近在一个项目里尝试了这种方案虽然逻辑复杂了些但修复效果的主观感受提升了一个档次。最后别忘了坏点标记的准确性是修复的前提。如果标定不准把好点当坏点修了或者漏掉了坏点再好的算法也白搭。在FPGA前端可以集成一些简单的在线坏点检测逻辑比如持续监控各像素的响应稳定性对突然永久性变亮或变暗的像素进行动态标记与离线标定的静态地图结合形成“静态动态”的完整坏点管理系统。这套系统做下来才算是把红外图像质量的基础打牢了。从我这些年的经验来看没有一种方案是万能的。对于大多数追求高可靠性的工业红外应用我倾向于推荐中值滤波方案它用一定的逻辑资源换来了更稳健的修复效果。而对于成本极其敏感、且坏点分布非常稀疏的项目均值滤波则是性价比极高的选择。关键是要在项目初期就用真实的坏点图像数据对两种方案进行充分的仿真和上板测试让数据说话而不是凭感觉做决定。FPGA设计的乐趣不就在于这种在约束条件下寻找最优解的工程实践吗希望我分享的这些细节和踩过的坑能帮你更快地做出适合自己的选择。