均值滤波FPGA的实现方法
线性滤波实现简单,十分适合用FPGA来实现。一般情况下,FPGA在前端捕获到视频数据之后首先需对图像数据做一个简单的预处理,然后根据噪声的来源,针对椒盐噪声进行中值滤波处理,针对高斯噪声进行高斯滤波处理,均值滤波在预处理中也十分常见。同时,边缘提取及梯度计算也是许多复杂处理算法的基础。
再把均值滤波的数学表达式列出如下:
由上述公式列出求图像均值的步骤:
- 获得当前窗口所有像素。
- 计算当前窗口所有像素之和。
- 将第(2)步结果除以当前窗口数据总数。
滑动窗口到下一个窗口,直到遍历完整幅图像。
滤波采用滑动窗口方法来实现整幅图像的遍历,因此,采用流水线结构来设计是再也合适不过的了。对于流水线结构来说,每个像素的运算方法是一致的,所需考虑的只是边界像素的处理问题。
一般情况下,任何二维的计算步骤都可以化为一维的操作。由于行方向的数据流是连续的,因此在流水线操作中,常常会首先进行行方向的操作。
假定现在已经完成了第一行的求和操作,接下来需要“等”下一行的求和完成。如何进行等待?在FPGA中,等待的实现方法就是进行缓存。
接下来的问题是,如何进行一维向量求和操作?对于1×5的向量求和而言,当前数据需要“等到”下4个数据到来之后才能得到连续5个数据,并执行加法操作。可以预期的是,还是需要把前几个数据单独缓存起来,一个指定位宽的寄存器即可满足要求。同步5个连续的输入数据并完成求和的流图如图4-8所示。
如图所示,连续输入4个时钟节拍的数据,加上当前时钟的数据就是5个数据,经过3个时钟的两两相加完成运算,共使用了4个加法器。
当然上面的电路确实可以实现预定的功能,然而本书中采用另外一种方法。这种方法就是利用增量更新的方式来实现窗口横向求和,这种求和方式在大尺寸的窗口计算中十分有用。处理流图如下图4-9所示。
此电路只需1个加法器和1个减法器。可以预见的是,无论窗口尺寸多大,所需的加法器和减法器也都是1个。因此,在窗口尺寸比较大的情况下,可得到比第一个设计电路更优的资源消耗的目的。不仅如此,求和电路的计算开销仅为1个时钟。
现在我们已经实现了窗口内一维行方向上的求和工作,现在要得到整个窗口内的像素之和,还必须将每一行的计算结果再叠加起来。那么每一行的计算结果是否也可以采取上面的增量更新的方法进行计算?答案显然是否定的,这是由于纵向的数据流不是流水线式,而是并行的。这时,就必须要采用第一种方法(见图4-8)所采用并行求和方式,如图4-10所示。
这里求均值的方式可能不太好理解,下面再将该方式简化为单行的方式——增量更新
增量更新
增量更新是指在进行更新操作时,只更新需要改变的地方,不需要更新或者已经更新过的地方则不会重复更新,增量更新与完全更新相对。增量更新在流水线处理中,特别是二维卷积处理中特别有用。这是由于在两个连续的卷积窗口中有大量的相同元素。
假定要计算连续5个数据流的和,在上一个时刻,这5个待计算的数值是a0,a1,a2,a3,a4, 在本时刻待计算的数值是a1,a2,a3,a4,a5。中间有4个值是相同元素。如此如果每次计算都将5个数重新相加,就有点浪费资源。正确的做法是加上一个新值,再减去一个最老的值。
a0 | a1 | a2 | a3 | a4 | a5 | a6 |
对应的增量计算方法如下图所示,当然5个数值求和,可以扩展到10个数值求和,但增量运算的运算量不变:
时钟 | 增量运算 | 实际 |
t1 | s1 = 0 + a0 | s1 = a0 |
t2 | s2 = s1 + a1 | s1 = a0 + a1 |
t3 | s3 = s2 + a2 | s1 = a0 +a1 +a2 |
t4 | s4 = s3 + a3 | s1 = a0 + a1 + a2 + a3 |
t5 | s5 = s4 + a4 | s1 = a0 + a1 + a2 + a3 + a4 |
t6 | s6 = s5 + a5 - a0 | s1 = a1 + a2 + a3 + a4 + a5 |
t7 | s7 = s6 + a6 - a1 | s1 = a2 + a3 + a4 + a5 + a6 |
这就是增量更新方式。其实现代码如下:
如程序所示,除滑动窗口外,每个时钟计算一次加法与一次减法,即可完成任意长度数据的均值滤波运算。如对图像处理中的均值滤波窗的实现有疑惑,请参考sobel算子的FPGA实现部分。
module meanfilter #
(
parameter integer DATA_WITH = 24,
parameter integer mean_num = 4 // 均值滤波mean_numv = 2^mean_num次
)
(
clk,
resetn,
signal_in,
update, //数据更新锁存信号
signal_out,
done
);
input clk;
input resetn;
input update;
input [DATA_WITH-1:0] signal_in;
output [DATA_WITH-1:0] signal_out;
output done;
parameter integer mean_numv = 32'h1<< mean_num;
reg [9:0] update_last = 0;
reg [(DATA_WITH*((mean_numv)+1)-1): 0] signal_buf = 0;
reg [DATA_WITH + mean_num: 0] sum1 = 0;
wire [DATA_WITH-1:0] s1add_new,s1add_old;
//移位寄存器
always@(posedge clk)
begin
update_last <= {update_last[8:0], update};
if(~update_last[1] && update_last[0])
begin
signal_buf <= {signal_buf[(DATA_WITH*mean_numv)-1:0],signal_in};
end
end
//求和运算
//第一组数据求和
assign #1 s1add_new = signal_buf[DATA_WITH-1: 0];//第一个数据
assign #1 s1add_old = signal_buf[(DATA_WITH*(mean_numv+1 ))-1 : DATA_WITH*(mean_numv)];
always@(posedge clk)
begin
if(~update_last[2] && update_last[1])
begin
sum1 <= sum1 + s1add_new - s1add_old;
end
end
assign done = update_last[2];
assign signal_out = (sum1 >> mean_num);
endmodule