johnllon

[转载]基于FPGA的图像处理(七)--Verilog实现均值滤波

0
阅读(3402)

原文地址:http://blog.csdn.net/renshengrumenglibing/article/details/8872805

之前一直用Xilinx公司的SysGen搭建图像处理的算法,然后进行仿真,也可以直接编译下载到FPGA开发板上直接运行。但是算法实现之后却很难和其他模块一块使用。经过一段时间的反思,决定用Verilog直接写算法。


Verilog进行图像处理的难点:

1、图片获取

    C语言或者Matlab进行图像处理,图片的文件读取获取或者摄像头读取都能一个函数搞定,但是FPGA进行图像处理,如果从摄像头获取需要考虑很多时序问题,如果从存储区获取图片又不能太大,因为FPGA内部的RAM个数很有限,想存一张图片都比较困难。

[cpp] view plaincopy

[cpp] view plaincopy

  1. //C  

  2. IplImage *src = cvLoandImage("lena.png",0);  

  3. //Matlab  

  4. src = imread('lena.png');  


2、算法的编写

FPGA本身的并行性可以使处理速度大大提高,但是算法需要面对的时序问题很难解决

大部分的算法都需要对邻域进行操作,如中值滤波和均值滤波:

[cpp] view plaincopy

  1. //中值滤波和均值滤波  

  2. #include  

  3. #include  

  4. int main(){  

  5.     IplImage * image,*image2,*image3;  

  6.     image = cvLoadImage("E:\\image\\Dart.bmp",0);//以灰度图像的形式读入图片  

  7.     cvNamedWindow("image",CV_WINDOW_AUTOSIZE);  

  8.     cvNamedWindow("image2",CV_WINDOW_AUTOSIZE);  

  9.     cvNamedWindow("image3",CV_WINDOW_AUTOSIZE);  

  10.     //cvSaveImage("E:\\image\\moon.jpg",image,0);  

  11.     cvShowImage("image",image);  

  12.     //cvWaitKey(0);  

  13.     unsigned char * ptr,*dst;  

  14.     int i,j,m,n,sum,temp,r,s;  

  15.     image2 = cvCreateImage(cvGetSize(image),image->depth,1);  

  16.     image3 = cvCreateImage(cvGetSize(image),image->depth,1);  

  17.     //模板1 均值   

  18.     int tem[9] = {1,1,1,1,1,1,1,1,1};   

  19.     //也可以使用改进的高斯模板,但是效果相近   

  20.     int tem2[9] = {0};//获取中值时用于排序  

  21.     //均值滤波3*3模板的均值  

  22.     for( i = 0 ; i < image->height;i++){  

  23.         for( j = 0; j< image->width;j++){  

  24.             //边界处理  

  25.             if(i == 0 || i == image->height || j == 0 || j == image->width){  

  26.                 ptr = (unsigned char *)image->imageData + i*image->widthStep + j;  

  27.                 dst = (unsigned char *)image2->imageData+ i*image2->widthStep+ j;  

  28.                 *dst = *ptr; //边界值赋予源图像的值  

  29.             }  

  30.             else {  

  31.                 sum = 0;  

  32.                 for( m = -1 ; m <= 1; m++  ){  

  33.                     for( n = -1 ; n <= 1 ; n++){  

  34.                         ptr = (unsigned char *)image->imageData + (i + m)*image->widthStep + j + n;  

  35.                         sum += (*ptr) * tem[3*(m+1) + n+1];  

  36.                     }  

  37.                 }  

  38.                 dst = (unsigned char *)image2->imageData+ i *image2->widthStep+ j;      

  39.                 *dst = (unsigned char)((sum +4)/9);//赋新值,四舍五入  

  40.             }   

  41.         }  

  42.     }  

  43. //中值滤波 在去除噪声的同时,图像的模糊程度比较小,比均值滤波更加适合  

  44. //冲击噪声或者称为椒盐噪声  

  45.     for( i = 0 ; i < image->height;i++){  

  46.         for( j = 0; j< image->width;j++){  

  47.             //边界处理  

  48.             if(i == 0 || i == image->height || j == 0 || j == image->width){  

  49.                 ptr = (unsigned char *)image->imageData + i*image->widthStep + j;  

  50.                 dst = (unsigned char *)image3->imageData+ i*image3->widthStep+ j;  

  51.                 *dst = *ptr; //边界值赋予源图像的值  

  52.             }  

  53.             else {  

  54.                 temp = 0;  

  55.                 //将3*3模板覆盖的值拷贝进数组,一边查找中值  

  56.                 for( m = -1 ; m <= 1; m++  ){  

  57.                     for( n = -1 ; n <= 1 ; n++){  

  58.                         ptr = (unsigned char *)image->imageData + (i + m)*image->widthStep + j + n;  

  59.                         tem2[3*(m+1) +n +1] = *ptr;  

  60.                         //printf("%d",*ptr);  

  61.                     }  

  62.                 }  

  63.                 //对数组进行冒泡排序  

  64.                 for(r = 0 ; r <8; r ++){  

  65.                     for(s = 0 ; s< r -1; s++ ){  

  66.                         if(tem2[s] > tem2[s+1]){  

  67.                             temp = tem2[s];  

  68.                             tem2[s] = tem2[s+1];  

  69.                             tem2[s+1] = temp;  

  70.                         }  

  71.                     }  

  72.                 }  

  73.                 //printf("%d",tem2[4]);  

  74.                 //对新图赋予新值  

  75.                 dst = (unsigned char *)image3->imageData+ i *image3->widthStep+ j;      

  76.                 *dst = (unsigned char)(tem2[4]);//赋新值  

  77.             }   

  78.         }  

  79.     }  

  80.     cvShowImage("image2",image2);  

  81.     cvShowImage("image3",image3);  

  82.     cvWaitKey(0);  

  83.     cvSaveImage("E:\\image\\Dart2.bmp",image2,0);  

  84.     cvSaveImage("E:\\image\\Dart3.bmp",image3,0);  

  85.     return 0;  

  86. }  


3、处理效果的显示

 FPGA处理速度快,但是能进行显示的方法却比较少,一般的FPGA开发板(指的是价格低廉的)虽然带有VGA接口,但是一般都是IO直驱,导致显示的颜色一般只有8种而已,都无法进行灰度图的显示



我试着写一些简单的处理算法,第一次写的是中值滤波,在解决问题的过程中发现其实并没有特别的复杂。

测试平台:黑金动力社区的FPGA开发板,cyclone IV 

1、图片的获取

虽然Verilog读取CMOS摄像头的源码网上很多,但是我还是希望算法测试阶段,图片数据是固定的。因此将图片压缩到32*32 = 1024个像素。

Cyclone IV的M9K RAM的大小刚好是1024Byte,图片大一些也是可以的,只是这里没有必要。


QuartusII提供了很多现成的模块,这里使用ROM模块来存储原始图片,ROM的初始化使用mif文件,mif文件制定了ROM的深度,位宽,以及数据等。至于mif文件怎么生成,首选当然是matlab了。

matlab生成mif文件:

[cpp] view plaincopy

  1. %mcode to create a mif file  

  2. src = imread('lena.jpg');  

  3. gray = rgb2gray(src);  

  4. [m,n] = size( gray );                  % m行 n列  

  5. N = m*n;                               %%数据的长度,即存储器深度。  

  6. word_len = 8;                          %%每个单元的占据的位数,需自己设定  

  7. data = reshape(gray', 1, N);% 1行N列  

  8. fid=fopen('gray_image.mif', 'w');       %打开文件  

  9. fprintf(fid, 'DEPTH=%d;\n', N);  

  10. fprintf(fid, 'WIDTH=%d;\n', word_len);  

  11. fprintf(fid, 'ADDRESS_RADIX = UNS;\n'); %% 指定地址为十进制  

  12. fprintf(fid, 'DATA_RADIX = HEX;\n');    %% 指定数据为十六进制  

  13. fprintf(fid, 'CONTENT\t');  

  14. fprintf(fid, 'BEGIN\n');  

  15. for i = 0 : N-1  

  16.     fprintf(fid, '\t%d\t:\t%x;\n',i, data(i+1));  

  17. end  

  18. fprintf(fid, 'END;\n');                 %%输出结尾  

  19. fclose(fid);                            %%关闭文件  


mif文件的格式大致如此:

[cpp] view plaincopy

  1. DEPTH=1024;  

  2. WIDTH=8;  

  3. ADDRESS_RADIX = UNS;  

  4. DATA_RADIX = HEX;  

  5. CONTENT BEGIN  

  6.     0   :   9e;  

  7.     1   :   97;  

  8. ...................................  

  9.     1020    :   50;  

  10.     1021    :   65;  

  11.     1022    :   58;  

  12.     1023    :   3b;  

  13. END;  


使用megaWizard生成一个ROM模块,

ROM模块的调用格式:

[cpp] view plaincopy

  1. gray_image_ROM  gray_image_ROM_inst (  

  2.     .address ( address_sig ),  

  3.     .clock ( clock_sig ),  

  4.     .q ( q_sig )  

  5.     );  


可以通过如下的方式逐个获取像素值:

[cpp] view plaincopy

  1.   //rom          

  2.     reg [9:0] rd_addr = 10'b0_000_000_000;  

  3.     wire [7:0] raw_data;   

  4.     //address increase  

  5.     always @(posedge rCLK_1Hz or negedge iRST_n)  

  6.         if(!iRST_n)  

  7.             rd_addr <= 10'b0_000_000_000;  

  8.         else if(rd_addr == 10'b1_111_111_111)  

  9.             rd_addr <= 10'b0_000_000_000;  

  10.         else rd_addr <= rd_addr + 1'b1;  

  11.     //read from rom  

  12.     gray_image_ROM  gray_image_ROM_inst   

  13.     (  

  14.         .address ( rd_addr ),  

  15.         .clock ( rCLK_1Hz ),  

  16.         .q ( raw_data )  

  17.     );  


raw_data就会每个周期更新一次,逐次将所有的数据流出。下面就是怎么处理了。



2、数据在ROM中,需要让数据一个一个的流出来,不能像C那样想用谁用谁了。

数据一个一个的流出,但是均值滤波需要的是邻域操作,需要每次知道一个邻域内的所有值。

假设中值滤波使用的邻域为3*3,那么就需要知道9个数据。

同一行相邻的数据可以通过设置多个寄存器获取。

同一列的相邻数据的获取可以使用一个行缓存LineBuffer获取,行缓存的头部是ROM中流出的数据,行缓存的3个末端是相邻的三行,这样就能每个周期得出相邻行的三个数据。

这样每个周期就能获取邻域内的9个数据。

如果邻域更大只需要调整行缓存的末端个数以及寄存器个数即可。

LineBuffer可以用megaWizard生成,调用格式如下:

[cpp] view plaincopy

  1. shift_line_buffer   shift_line_buffer_inst (  

  2.     .clock ( clock_sig ),  

  3.     .shiftin ( shiftin_sig ),  

  4.     .shiftout ( shiftout_sig ),  

  5.     .taps0x ( taps0x_sig ),  

  6.     .taps1x ( taps1x_sig ),  

  7.     .taps2x ( taps2x_sig )  

  8.     );  

可以通过如的方式获取邻域内的9个数据:

[cpp] view plaincopy

  1.   wire [7:0]   wData0;  

  2.     wire    [7:0]   wData1;  

  3.     wire    [7:0]   wData2;  

  4.     reg [7:0]   wData0_d1,wData0_d2;  

  5.     reg [7:0]   wData1_d1,wData1_d2;  

  6.     reg [7:0]   wData2_d1,wData2_d2;  

  7.     //TODO   

  8.     shift_line_buffer   S1  (  

  9.                     .clock(wMeanFilter_clk),  

  10.                     .shiftin(iData),  

  11.                     .shiftout(),  

  12.                     .taps2x(wData0),  

  13.                     .taps1x(wData1),  

  14.                     .taps0x(wData2)  

  15.                 );  

  16.     //get data in the window  

  17.     always@(posedge wMeanFilter_clk or negedge iRST_n)  

  18.     begin  

  19.         if (!iRST_n)  

  20.             begin  

  21.                 wData0_d1<=0;  

  22.                 wData0_d2<=0;  

  23.                 wData1_d1<=0;  

  24.                 wData1_d2<=0;  

  25.                 wData2_d1<=0;  

  26.                 wData2_d2<=0;                  

  27.             end  

  28.         else  

  29.             begin  

  30.                 {wData0_d2,wData0_d1}<={wData0_d1,wData0};  

  31.                 {wData1_d2,wData1_d1}<={wData1_d1,wData1};  

  32.                 {wData2_d2,wData2_d1}<={wData2_d1,wData2};  

  33.             end  

  34.     end   

[cpp] view plaincopy

此时wData0 wData1 wData2 wData0_d1 ,wData0_d2, wData1_d1,wData2_d1,wData2_d2即为邻域内的9个数据,可以随便进行处理了。


3、均值滤波算法

由于是第一次用Verilog写算法,不敢写太复杂的,上个简单的吧,均值滤波,由于FPGA不擅长算乘除法(感觉好弱啊),因此将算法稍微改进,变成加权的均值滤波,权值如下

1 2 1

2 4 2

1 2 1

乘法可以用移位代替,最后的除法(除数刚好是16哦亲)也可以用移位来代替。

算法模块:

[cpp] view plaincopy

  1. module meanFilter  

  2. (     

  3.     input [7:0] p00,   

  4.     input [7:0] p01,   

  5.     input [7:0] p02,   

  6.     input [7:0] p10,   

  7.     input [7:0] p11,   

  8.     input [7:0] p12,   

  9.     input [7:0] p20,   

  10.     input [7:0] p21,   

  11.     input [7:0] p22,  

  12.     output [7:0] oMeanVal  

  13. );   

  14.     //weights  

  15.     //1 2 1  

  16.     //2 4 2  

  17.     //1 2 1  

  18.     wire [8:0] p01_w, p10_w, p12_w,p21_w;  

  19.     wire [9:0] p11_w;     

  20.     wire [11:0] sum;  

  21.     wire [10:0] sum2;  

  22.     wire [10:0] sum1;  

  23.     assign p01_w = { p01, 1'b0};  

  24.     assign p10_w = { p10, 1'b0};  

  25.     assign p12_w = { p12, 1'b0};  

  26.     assign p21_w = { p21, 1'b0};      

  27.     assign p11_w = { p11, 2'b0};      

  28.     assign sum1 = p00 + p02 + p20 + p22 + p11_w;  

  29.     assign sum2 = p01_w + p10_w + p12_w + p21_w;  

  30.     // assign sum4 = p11_w;  

  31.     assign sum = sum1 + sum2;     

  32.     assign oMeanVal = sum[11:4];  

  33. endmodule     


4、处理效果的显示

本来打算用VGA或者LCD显示处理后的图片,但是由于我的板子比较屌丝,VGA接口只能显示8种颜色,LCD只能显示2种颜色,因此决定将数据发给上位机,在上位机进行显示。

串口速率比较低,可能很多数据来不及发送诶上位机,因此决定换个高端的,买了个USB2.0的模块,测试通信速度可以达到40Mb/s,这个速度我还是很满意的。

这个必须秀一下:


在算法和USB模块之间加一个FIFO,当FIFO不满时就计算并往里塞数据,当FIFO慢时就停止计算,这样就能够避免低丢数的问题。

为了随时记录算法是否在进行,加上一个数码管,显示处理之后的数据的低四位。

由于计算的频率比较高,数码管一直显示8,假如上位机不接收的话,当FIFO满时,停止计算,此时数码管就不动了。


5、对比数据

用matlab进行一次均值滤波,然后和FPGA均值滤波的结果进行比较看看效果如何:

第一行是上位机均值滤波的结果,第二行是FPGA进行均值滤波的结果。



上位机使用matlab处理的:

[cpp] view plaincopy

  1. %mcode to create a mif file  

  2. src = imread('lena.jpg');  

  3. gray = rgb2gray(src);  

  4. imwrite(gray, 'gray.png');  

  5. [m,n] = size( gray );                  % m行 n列  

  6. dst = gray;  

  7. sum = 0;  

  8. gray = double(gray);  

  9. for i = 2 : m -1 %  i 行  

  10.     for j = 2 : n -1 %j 列  

  11.         sum = gray(i,j)*4 + gray(i, j -1)*2 + gray(i-1, j)*2 + gray(i, j + 1)*2 + gray(i +1, j) *2 + gray(i-1, j-1)+ gray(i-1,j+1) + gray(i+1, j-1) + gray(i +1, j+1);  

  12.         dst(i,j) = uint8(sum/16);  

  13.     end  

  14. end  

  15. %将数据写入文件  

  16. fid = fopen('meanFilter.txt','w');  

  17. for i = 1 : m  

  18.    for j = 1 :n  

  19.         fprintf(fid, '%x ',dst(i,j));  

  20.    end  

  21. end  

  22. fclose(fid);  

  23. imwrite(dst, 'meanFilter.png');  


注意到有些数据并不一致,有两个原因,一是Matlab处理时会有舍入,二是二者对边界的处理方式不同。