要进行维纳滤波,首先就是要将图像矩阵通过二维fft变换到频域。
但是hls_fft.h中只有一维fft的函数,因此我们采用对行逐行进行一维fft,再对列逐行进行一维fft来实现二维fft的效果。
自带的fft例程中包括有如下四个函数
void dummy_proc_fe(
bool direction,
config_t* config,
cmpxData in[FFT_LENGTH],
cmpxData out[FFT_LENGTH])
void dummy_proc_middle(
config_t* config_in,
config_t* config_out,
status_t* st_in,
cmpxData in[FFT_LENGTH],
cmpxData out[FFT_LENGTH])
void dummy_proc_be(
status_t* status_in,
bool* ovflo,
cmpxData in[FFT_LENGTH],
cmpxData out[FFT_LENGTH])
void fft_top(
bool direction,
cmpxData in[FFT_LENGTH], //xn1 -> in
cmpxData out[FFT_LENGTH], //out -> xk1
bool* ovflo)
其中void dummy_proc_fe
、void dummy_proc_middle
和void dummy_proc_be
是对fft的前后处理,我们大可不必管他,照搬就是
唯一需要主要的就是在void dummy_proc_middle
中有一句bool ovflw= st_in->getOvflo();
这个ovflw
顾名思义就是overflow,是溢出,而void dummy_proc_fe
中有一个config->setSch(0x2AB);
是设置系数,我们需要调整好这个系数来防止计算的溢出,可以参考一位大佬的文章
接下来还有一个问题,就是我们想用这个fft函数来进行fft计算,而不是单独做一个模块,所以需要去除函数开头的一些硬件接口的预定义,接下来就可以放到我们的主程序来调用了。
#pragma HLS interface ap_hs port=direction
#pragma HLS interface ap_fifo depth=1 port=ovflo
#pragma HLS interface ap_fifo depth=1024 port=in,out
#pragma HLS data_pack variable=in
#pragma HLS data_pack variable=out
接下来要在主程序中调用这个fft函数来实现二维fft。
首先来看下函数接口,我们需要复数类型的数据输入和输出。
typedef std::complex<float> cmpxData;
因此我们定义二维复数数组来获取图像矩阵数值。
这个复数是使用的c++标准库complex,所以其赋值方法是
cmpx x;
cmpx y;
x = y;
/******************or********************/
x.real(1);
x.imag(1);
/******************or**********************/
x = (1,1);
接下来进行fft2d,此处需要主要的是我们的数组要用静态数组,static
/***********get FFT_array(2D FFT)**************/
bool ovflo;
//傅里叶正变换(行)
for(int r = 0; r < rows; r++)
{
#pragma HLS pipeline
for(int c = 0; c < cols; c++)
{
in1[c].real(xn1[r][c].real());
in1[c].imag(xn1[r][c].imag());
//in[c] = xn1[r][c];
//printf("in %d num %f \n",c,in[c].real());
}
fft_top(direction,in1,out1,&ovflo);
for(int c = 0; c < cols; c++)
{
middle[r][c].real(out1[c].real()/256);
middle[r][c].imag(out1[c].imag()/256);
//middle[r][c] = out[c];
//printf("in %d real: %f image: %f \n",r,out[c].real(),out[c].imag());
}
}
//傅里叶正变换(列)
for(int c = 0; c < col; c++)
{
#pragma HLS pipeline
for(int r = 0; r < row; r++)
{
in1[r].real(middle[r][c].real());
in1[r].imag(middle[r][c].imag());
//in[r] = middle[r][c];
}
fft_top(direction,in1,out1,&ovflo);
for(int r = 0; r < row; r++)
{
xk1[r][c].real(out1[r].real()/256);
xk1[r][c].imag(out1[r].imag()/256);
//xk1[r][c] = out[r];
//printf("xk1 %d real: %f image: %f \n",r,xk1[r][c].real(),xk1[r][c].imag());
}
}
由代码可见就是逐行逐列去进行一维的fft,而逆变换同理,有
//傅里叶逆变换(行)
for(int r = 0; r < rows; r++)
{
for(int c = 0; c < cols; c++)
{
in1[c].real(xk1[r][c].real());
in1[c].imag(xk1[r][c].imag());
//in[c] = xk1[r][c];
//printf("in %d num %f \n",c,in[c].real());
}
fft_top(~direction,in1,out1,&ovflo);
for(int c = 0; c < cols; c++)
{
middle2[r][c].real(out1[c].real());
middle2[r][c].imag(out1[c].imag());
//middle2[r][c] = out[c];
//printf("in %d num %f \n",c,out[c].real());
}
}
//傅里叶逆变换(列)
for(int c = 0; c < cols; c++)
{
for(int r = 0; r < rows; r++)
{
in1[r].real(middle2[r][c].real());
in1[r].imag(middle2[r][c].imag());
//in[r] = middle2[r][c];
}
fft_top(~direction,in1,out1,&ovflo);
for(int r = 0; r < rows; r++)
{
xk2[r][c].real(out1[r].real());
xk2[r][c].imag(out1[r].imag());
//xk2[r][c] = out[r];
//printf("xk2 %d real: %f image: %f \n",r,xk2[r][c].real(),xk2[r][c].imag());
}
}
最外层函数的硬件接口定义如下,其输入为数据流
void WienerDeblur(wide_stream* in_stream, wide_stream* out_stream, ap_uint<32> rows, ap_uint<32> cols){
#pragma HLS INTERFACE axis port=in_stream bundle=INPUT
#pragma HLS INTERFACE axis port=out_stream bundle=OUTPUT
#pragma HLS INTERFACE s_axilite port=rows bundle=CONTROL_BUS offset=0x14 clock=AXI_LITE_clk
#pragma HLS INTERFACE s_axilite port=cols bundle=CONTROL_BUS offset=0x1C clock=AXI_LITE_clk
#pragma HLS INTERFACE s_axilite port=return bundle=CONTROL_BUS clock=AXI_LITE_clk
#pragma HLS INTERFACE ap_stable port=rows
#pragma HLS INTERFACE ap_stable port=cols
与常见的c++不太一样,这不能直接读一个二维数组进来,只能一个一个读,毕竟实际上是硬件。
接下来的读取可以用如下方式循环读出,将dat的32位数据分别写入src_bw中。
GRAY_IMAGE src_bw(rows, cols);
for(int r = 0; r < packets; r++){
#pragma HLS pipeline II=4
ap_uint<32> dat = in_stream->data;
src_bw.write(GRAY_PIXEL(dat.range(7,0)));
src_bw.write(GRAY_PIXEL(dat.range(15,8)));
src_bw.write(GRAY_PIXEL(dat.range(23,16)));
src_bw.write(GRAY_PIXEL(dat.range(31,24)));
++in_stream;
}
读出与读入同理,我们需要将我们的二维数组转化成数据流,然后逐一吐给外面的设备。
那么我们就先把ifft得到的复数矩阵逐一写到一个GRAY_IMAGE res(rows, cols);
变量中,这个GRAY_IMAGE的定义为
typedef hls::Mat<MAX_HEIGHT, MAX_WIDTH, HLS_8UC1> GRAY_IMAGE;
是hls_video库中的内容。
接下来和读入数据一样,通过循环将数据写入out_stream
for(int r = 0; r < rows; r++)
{
for(int c = 0; c < cols; c++)
{
element_pixel.val[0] = (abs(xk2[r][c].real()));
element_pixel_imag.val[0] = (abs(xk2[r][c].imag()));
res << element_pixel;
res_imag << element_pixel_imag;
}
}
for(int r = 0; r < rows; r++){
#pragma HLS pipeline II=4
for(int c = 0; c < col_packets; c++){
ap_uint<32> dat;
dat.range(7,0) = res.read().val[0];
dat.range(15,8) = res.read().val[0];
dat.range(23,16) = res.read().val[0];
dat.range(31,24) = res.read().val[0];
out_stream->data = dat;
out_stream->user = (r == 0 && c == 0)? 1: 0;
out_stream->last = (r == rows-1 && c == col_packets-1)? 1: 0;
++out_stream;
}
}
在完成了二维fft的实现后,维纳滤波就很简单了
其表达式为
F ^ ( u , v ) = [ 1 H ( u , b ) ∣ H ( u , v ) ∣ 2 ( ∣ H ( u , v ) ∣ 2 + k ) ] G ( u , v ) \hat F (u,v)=[\frac{1}{H(u,b)} \frac{ |H(u,v)|^2}{(|H(u,v)|^2+k)}]G(u,v) F^(u,v)=[H(u,b)1(∣H(u,v)∣2+k)∣H(u,v)∣2]G(u,v)
我们只需要将变换后的频域矩阵进行相应的点乘除即可。
可以定义相关的函数
void InnerProd(
cmpxData in1[Kernel_Size][Kernel_Size],
cmpxData in2[Kernel_Size][Kernel_Size],
cmpxData out[Kernel_Size][Kernel_Size])
{
for(int r = 0; r < Kernel_Size; r++)
#pragma HLS PIPELINE rewind
for(int c = 0; c < Kernel_Size; c++ )
#pragma HLS UNROLL
out[r][c] = in1[r][c]*in2[r][c];
}
void matrix_conj(
cmpxData mat_in[Kernel_Size][Kernel_Size],
cmpxData mat_out[Kernel_Size][Kernel_Size])
{
for(int r = 0; r < Kernel_Size; r++)
for(int c = 0; c < Kernel_Size; c++ )
{
mat_out[r][c].real( mat_in[r][c].real());
mat_out[r][c].imag(-mat_in[r][c].imag());
}
}
void matrix_modulus(
cmpxData mat_in[Kernel_Size][Kernel_Size],
cmpxData mat_out[Kernel_Size][Kernel_Size])
{
for(int r = 0; r < Kernel_Size; r++)
for(int c = 0; c < Kernel_Size; c++ )
{
mat_out[r][c].real(sqrt(pow(mat_in[r][c].real(),2) +
pow(mat_in[r][c].imag(),2)));
mat_out[r][c].imag(0);
}
}
void matrix_plus_SNR(
cmpxData mat_in[Kernel_Size][Kernel_Size],
cmpxData mat_out[Kernel_Size][Kernel_Size],
data_t k)
{
for(int r = 0; r < Kernel_Size; r++)
for(int c = 0; c < Kernel_Size; c++ )
{
mat_out[r][c].real(mat_in[r][c].real() + k);
mat_out[r][c].imag(mat_in[r][c].imag());
}
}
void matrix_div(
cmpxData mat_in1[Kernel_Size][Kernel_Size],
cmpxData mat_in2[Kernel_Size][Kernel_Size],
cmpxData mat_out[Kernel_Size][Kernel_Size])
{
for(int r = 0; r < Kernel_Size; r++)
for(int c = 0; c < Kernel_Size; c++ )
{
mat_out[r][c] = mat_in1[r][c] / mat_in2[r][c];
}
}
然后调用函数完成计算。
static cmpxData gauss_blur[Kernel_Size][Kernel_Size];
InnerProd(xk1,fft_kernel,gauss_blur);
static cmpxData fft_kernel_modu[Kernel_Size][Kernel_Size];
matrix_modulus(fft_kernel,fft_kernel_modu);//get modulus of the kernel
static cmpxData fft_kernel_modu2[Kernel_Size][Kernel_Size];
InnerProd(fft_kernel_modu,fft_kernel_modu,fft_kernel_modu2);//get square modelus
static cmpxData G1[Kernel_Size][Kernel_Size];
#pragma HLS interface ap_fifo depth=1024 port=G1,fft_kernel_modu2
matrix_plus_SNR(fft_kernel_modu2,G1,0);
matrix_div(fft_kernel_modu2,G1,G1);
static cmpxData G[Kernel_Size][Kernel_Size];
matrix_div(G1,fft_kernel,G);
InnerProd(gauss_blur,G,xk1);