基于Modelsim的均值滤波仿真

一、前言

  本篇主要针对牟新刚编著《基于FPGA的数字图像图像原理及应用》中第7章第二节中的基于FPGA的均值滤波的内容进行仿真验证。2020-04-04 20:37:13

二、算法实现原理

  整体设计

  求图像均值的步骤:

  1. 获得当前窗口所有像素。
  2. 计算当前串口所有像素之和。
  3. 将第2步结果除以当前窗口数据总数。
  4. 滑动窗口到下一个窗口,直到遍历完整幅图像。

  滤波采用滑动窗口方法来实现整幅图像的遍历,因此,采用流水线结构来设计。对于流水线结构来说,每个像素的运算方法是一致的,所需考

虑的指示边界像素的处理问题。

    

  一般情况下,任何二维的计算步骤都可以化为一维的操作。由于行方向的数据是连续的,因此在流水线操作中,常常会首先进行行方向的操

作。假定现在已经完成第一行的求和操作,接下来需要“等”下行的求和完成。如何进行等待?在FPGA中,等待的实现方法就是进行行缓存。二维

操作转换为一维操作后的结构如下图所示。

  接下来的问题是,如何进行一维向量求和操作?对于1×5的向量求和而言,当前数据需要“等到”下4个数据到来之后才能得到连续5个数据,

并执行加法操作。可以预期的是,还需要把前几个数据到来之后单独缓存起来,一个指定位宽的寄存器即可满足要求。

      

    

  最后的问题是求取窗口的均值,需要将上述计算出来的和除以一个归一化系数,也就是整个窗口的像素数目。在FPGA里面对于一方确定的除法

操作,一般情况下不直接进行除法运算,而是通过近似的乘加方法来实现等效转换。在这里,对于固定的窗口,除法的分母是固定的。因此完全可

以用此方法来实现等效近似。

  子模块设计

  需要设计如下几个子模块:

  1. 一维求和模块,这里记为sum_1d。
  2. 二维求和模块这里记为sum_2d。
  3. 除法转换模块,此模块比较简单,一般情况下不进行模块封装。
  4. 行缓存电路实现行列间像素对齐。

  整个顶层模块调用sum_2d模块和除法转换电路来实现求均值,记为Mean_2D。

  1.一维求和模块设计(sum_1d)

  用FPGA来求和是再简单不过的操作了,求和操作也是FPGA所擅长的事情之一。所要注意的只是求和结果不要溢出。一般情况下, 2个位宽为

DW的数据相加,至少得用一个DW+1位宽的数据来存放。

  由于是求取连续数据流的和,最简单的办法是将数据连续打几拍,对齐后进行求和。假定窗口尺寸为5,则求和电路如下:

  图中求和电路的资源消耗为4个加法器、7个寄存器,运算开销为3个时钟。书中采用了另一种方法,利用增量更新的方式实现窗口横向求和。在

连续两个像素求和的过程中,仅仅有头尾的两个像素不同。假定当前计算地址为n+1,计算结果为Sum(n+1),上一个地址为n,计算结果为

Sum(n),输入数据为X(i),则有Sum(n+1) = Sum(n) + X(n+3) - X(n-2);也就是针对每一个窗口并不需要重新计算所有窗口内的像素和,可以通

过前一个中心点的像素和再通过加法将新增点和舍弃点之间的差计算进去就可以获得新窗口内像素和。

  具体到FPGA实现方面,同样需要把数据连续打几拍,同时计算首个数据与最后一个数据的差。当前求和结果为上一个求和结果与计算之差的

和。同样对于窗口尺寸为5的行方向求和操作,设计电路如下图所示。

     

  由图中可知,此电路只需1个加法器和1个减法器。无论窗口的尺寸多大,所需的加法器和减法器也都是1个。在窗口尺寸比较大的情况下,可得

到比第一个设计电路更优的资源消耗的目的。不仅如此,求和电路的计算开销为1个时钟。

  2.二维求和模块设计(sum_2d)

  目前我们已经实现了窗口内一维行方向上的求和工作,现在要得到整个窗口内的像素之和,还必须将每一行的计算结果再叠加起来。那么每一

行的计算结果是否也可以采取上面的增量更新的方法进行计算?答案显然是否定的,这是由于纵向的数据流不是流水线式的。

  同样,在进行列方向上的求和时,需要进行行缓存,并将一维行方向的求和结果打入行缓存,行缓存的个数为窗口尺寸减去1。就窗口尺寸5x5

的情况而言,二维求和模块的电路设计如下图所示。

  

  3.除法电路设计

  对于分母固定的除法操作,可以通过泰勒展开或是移位转换等方式转换为FPGA所擅长的移位、加法与乘法操作。假定窗口尺寸为5,

在求取窗口像素和之后需要除以25来求得均值。假定求得结果为Sum,计算后的均值为Average。则有

  Average = Sum/25

      = Sum×1/1024×1024/25

      = Sum/1024*40.96

           ≈ Sum/1024*(25 + 23 + 2-1 + 2-2 + 2-3 + 2-4 + 2-6 + 2-7)。

三、代码实现

  代码部分参照书中的代码,针对书中代码进行补充和调整并移植到quartus平台下,芯片选用cyclone系列EP4CE115芯片;具体代码如下:

  1.一维求和模块sum_1d.v,此部分代码需要注意的增量更新求和技术的实现,同时数据输出有效的判断,依据书中代码每行数据前两个数据

处于图像的边界,因此数据有效信号延迟两个时钟周期后进行输出。

 1 `timescale 1ps/1ps
 2 
 3 //=========================================================================//
 4 //FileName: sum_1d.v
 5 //Function: 一维求和电路的设计,设计的重点在于前端数据寄存器的设计。
 6 //Date: 2020-03-19
 7 //========================================================================//
 8 
 9 module sum_1d(
10     clk,        //同步时钟
11     din,        //输入数据流
12     din_valid,    //输入数据有效
13     dout_valid,    //输出数据有效
14     dout        //输出数据流
15 );
16 
17     parameter DW = 14;    //数据位宽参数
18     parameter KSZ = 3;    //求和窗口参数
19     
20     //port declared
21     input clk;
22     input [DW-1:0] din;
23     input din_valid;
24     output dout_valid;
25     output [2*DW-1:0] dout;
26     
27     //variable declared
28     reg  [KSZ:0]     din_valid_r;
29     integer j;
30     
31     //定义KSZ+1个输入寄存器
32     reg [DW-1:0] reg_din[0:KSZ];
33     
34     //定义上一个求和寄存器
35     reg [2*DW-1:0] sum;
36     
37     //定义中间信号
38     wire [2*DW-1:0] sub_out;
39     
40     //定义减法器输出信号
41     wire [2*DW-1:0] diff;
42     
43     //连续缓存KSZ拍信号,同时缓存输入有效信号
44     always@(posedge clk)begin
45         din_valid_r <= #1 ({din_valid_r[KSZ-1:0],din_valid});
46         reg_din[0] <= #1 din;
47         for(j = 1; j <= KSZ; j = j + 1)
48             reg_din[j] <= #1 reg_din[j - 1];
49     end
50     
51     //做减法计算差值
52     assign sub_out = ((din_valid_r[0] == 1'b1 & din_valid_r[KSZ] == 1'b1)) ? 
53                     ({{DW{1'b0}},reg_din[KSZ]}) : ({2*DW{1'b0}});
54                     
55     assign diff = ({{DW{1'b0}},reg_din[0]}) - sub_out;
56     
57     //计算最后的求和结果
58     always@(posedge clk)begin
59         if(din_valid == 1'b1 & ((~(din_valid_r[0]))) == 1'b1)
60             sum <= #1 {2*DW-1 + 1{1'b0}};
61         else if((din_valid_r[0]) == 1'b1)
62             sum <= #1 sum + diff;
63     end
64     
65     //输出信号
66     
67     assign dout_valid = din_valid_r[1];
68     assign dout = sum;
69     
70 endmodule

  2、二维求和模块设计sum_2d.v,该部分代码需要列缓存电路的实现、行列同步电路的生成、列方向的求和及边界置零处理。具体代码如下:

  1 `timescale 1ps/1ps
  2 
  3 //========================================================================================//
  4 //FileName: sum_2d.v
  5 //Function: 将数据流接入sum_1d模块进行行方向的求和,同时将行方向的求和结果依次打入行缓存,
  6 //对齐后输出进行列方向上的求和工作。
  7 //Date: 2020-03-19
  8 //========================================================================================//
  9 
 10 module sum_2d(
 11     rst_n,            //异步复位信号
 12     clk,            //同步时钟
 13     din_valid,        //输入数据有效
 14     din,            //输入数据流
 15     dout,            //输出数据流
 16     vsync,            //输入场同步信号
 17     vsync_out,        //输出场同步信号
 18     is_boarder,        //输出边界信息
 19     dout_valid        //输出数据有效
 20 );
 21 
 22     //参数定义
 23     parameter DW = 14;                    //数据位宽参数
 24     parameter KSZ = 3;                    //求和窗口参数
 25     parameter IH = 512;                    //图像高度
 26     parameter IW = 640;                    //图像宽度
 27     parameter radius = ((KSZ >> 1));    //边界参数
 28     
 29     //port declared
 30     input                rst_n;
 31     input                clk;
 32     input                din_valid;
 33     input [DW-1:0]         din;
 34     output [2*DW-1:0]    dout;
 35     input                vsync;
 36     output                vsync_out;
 37     output                is_boarder;
 38     output                dout_valid;
 39     
 40     //variable declared
 41     reg                    rst_all;
 42     reg  [DW-1:0]        line_dinl[0:KSZ-2];
 43     wire [DW-1:0]        line_doutl[0:KSZ-2];
 44     reg     [DW-1:0]        data_temp_l[0:KSZ-2];
 45     reg  [DW-1:0]        line_dinh[0:KSZ-2];
 46     wire [DW-1:0]        line_douth[0:KSZ-2];
 47     reg     [DW-1:0]        data_temp_h[0:KSZ-2];
 48     wire                 line_emptyl[0:KSZ-2];
 49     wire                 line_fulll[0:KSZ-2];
 50     wire                 line_rdenl[0:KSZ-2];
 51     wire                 line_wrenl[0:KSZ-2];
 52     wire                 line_emptyh[0:KSZ-2];
 53     wire                 line_fullh[0:KSZ-2];
 54     wire                 line_rdenh[0:KSZ-2];
 55     wire                 line_wrenh[0:KSZ-2];
 56     wire [9:0]            line_countl[0:KSZ-2];
 57     wire [9:0]            line_counth[0:KSZ-2];
 58     
 59     //wire                din_valid_r;
 60     wire [2*DW-1:0]        sum;
 61     wire [2*DW-1:0]        sum_row;
 62     
 63     reg  [KSZ-2:0]        buf_pop_en;
 64     reg                    valid_r;
 65     
 66     reg  [10:0]            in_line_cnt;
 67     reg  [15:0]            flush_cnt;
 68     reg                    flush_line;
 69     reg  [15:0]            out_pixel_cnt;
 70     reg  [10:0]            out_line_cnt;
 71     reg  [2*DW-1:0]        dout_temp_r;
 72     reg                 dout_valid_temp_r;
 73     //wire [2*DW-1:0]        dout_temp;
 74     wire                dout_valid_temp;
 75     
 76     wire [2*DW-1:0]        sum_row1;
 77     wire [2*DW-1:0]        sum_row2;
 78     wire [2*DW-1:0]        sum_row3;
 79     wire [2*DW-1:0]        sum_row4;
 80     reg  [2*DW-1:0]        sum_1_2;
 81     reg  [2*DW-1:0]        sum_3_4;
 82     reg  [2*DW-1:0]        sum_0_1_2;
 83     reg  [2*DW-1:0]        sum_3_4_r;
 84     reg  [2*DW-1:0]        sum_row_r;
 85     reg  [2*DW-1:0]        sum_all;
 86     
 87     wire                is_boarder_tmp;
 88     reg                    is_boarder_r;
 89     wire                valid;
 90     wire                row_valid;
 91     //reg                 row_valid_r;
 92     //wire                vsync_out_temp;
 93     
 94     reg  [10:0]            line_valid_r;
 95     
 96     assign valid = din_valid | flush_line;
 97     
 98     always@(posedge clk or negedge rst_n)begin
 99         if(rst_n == 1'b0)
100             rst_all <= #1 1'b1;
101         else
102         begin
103             if(vsync == 1'b1)
104                 rst_all <= #1 1'b1;
105             else
106                 rst_all <= #1 1'b0;
107         end
108     end
109     
110     /*
111     always@(posedge clk)
112     begin
113         if(rst_all == 1'b1)
114             row_valid_r <= #1 1'b1;
115         else
116             row_valid_r <= #1 row_valid;
117     end
118     */
119     
120     //首先例化一个行方向上的求和模块
121     //wire [2*DW-1:0] sum_row;        //行求和信号
122     
123     sum_1d#(DW,KSZ)
124         row_sum(
125             .clk(clk),
126             .din(din),
127             .din_valid(valid),
128             .dout(sum_row),                //输出行求和结果
129             .dout_valid(row_valid)        //行求和结果有效
130         );
131 
132     //例化(KSZ-1)个行缓存
133     generate
134     begin : line_buffer_inst
135         genvar i;
136         for(i = 0; i <= KSZ - 2; i = i + 1)
137         begin : line_buf
138             if(i == 0)
139                 begin : row_1st    //第一行缓存,输入数据为行求和结果
140                     always@(*) line_dinl[i] <= sum_row[DW-1:0];
141                     always@(*) line_dinh[i] <= sum_row[2*DW-1:DW];
142                     assign line_wrenl[i] = row_valid;
143                     assign line_wrenh[i] = row_valid;
144                 end
145 
146             if((~(i == 0)))
147                 begin : row_others //其余行缓存,输入数据为上一行的输出
148                     always@(*) line_dinl[i] <= line_doutl[i - 1];
149                     always@(*) line_dinh[i] <= line_douth[i - 1];
150                     assign line_wrenh[i] = line_rdenh[i - 1];
151                     assign line_wrenl[i] = line_rdenl[i - 1];
152                 end
153 
154             assign line_rdenl[i] = buf_pop_en[i] & row_valid;
155             assign line_rdenh[i] = buf_pop_en[i] & row_valid;
156             
157             //行缓存装满一行后打出
158             always@(posedge clk)
159             begin
160                 if(rst_all == 1'b1)
161                     buf_pop_en[i] <= #1 1'b0;
162                 else if(line_countl[i] == IW)
163                     buf_pop_en[i] <= #1 1'b1;
164             end
165     
166             //输入数据缓存
167             always@(*) data_temp_l[i] <= line_dinl[i];
168             
169             //行缓存低半部分
170                 line_buffer_row line_buf_l(
171                     .aclr(rst_all),
172                     .clock(clk),
173                     .data(data_temp_l[i]),
174                     .rdreq(line_rdenl[i]),
175                     .wrreq(line_wrenl[i]),
176                     .empty(line_emptyl[i]),
177                     .full(line_fulll[i]),
178                     .q(line_doutl[i]),
179                     .usedw(line_countl[i])
180                 );    
181                 
182             always@(*) data_temp_h[i] <= line_dinh[i];
183 
184             //行缓存高半部分
185             line_buffer_row line_buf_h(
186                     .aclr(rst_all),
187                     .clock(clk),
188                     .data(data_temp_h[i]),
189                     .rdreq(line_rdenh[i]),
190                     .wrreq(line_wrenh[i]),
191                     .empty(line_emptyh[i]),
192                     .full(line_fullh[i]),
193                     .q(line_douth[i]),
194                     .usedw(line_counth[i])
195                 );    
196                 
197         end
198     end
199     endgenerate
200 
201     //列方向求和,窗口尺寸为5*5
202     generate
203     if(KSZ == 5)begin : sum_ksz_5
204         //首先得到之前已经缓冲的4行的求和结果
205         assign sum_row1 = ({line_douth[0][DW - 1:0],line_doutl[0][DW - 1]});
206         assign sum_row2 = ({line_douth[1][DW - 1:0],line_doutl[1][DW - 1]});
207         assign sum_row3 = (((buf_pop_en[2]) == 1'b1)) ? ({line_douth[2][DW - 1:0],line_doutl[2][DW - 1]}) : {2*DW{1'b0}};
208         assign sum_row4 = (((buf_pop_en[3]) == 1'b1)) ? ({line_douth[3][DW - 1:0],line_doutl[3][DW - 1]}) : {2*DW{1'b0}};
209         
210         
211         //运算延时为4个时钟
212         assign dout_valid_temp = line_valid_r[2 + 2];
213 
214         always@(posedge clk)
215         begin
216             line_valid_r[4:0] <= ({line_valid_r[3:0],line_rdenl[1]});
217             
218             //缓存5拍行读取信号
219             
220             if((line_rdenl[1]) == 1'b1)
221                 sum_row_r <= #1 sum_row;        //缓存当前行
222             
223             if((line_rdenl[1]) == 1'b1)
224             begin
225                 sum_1_2 <= #1 sum_row1 + sum_row2;    //1,2行相加
226                 sum_3_4 <= #1 sum_row3 + sum_row4;    //3,4行相加
227             end
228             
229             if((line_valid_r[1]) == 1'b1)
230             begin
231                 
232                 //当前行与1,2行求和后相加
233                 sum_0_1_2 <= #1 sum_row_r + sum_1_2;
234                 
235                 //3,4行求和结果缓存
236                 sum_3_4_r <= #1 sum_3_4;    
237                     
238             end
239             
240             //得到5行的求和结果
241             if((line_valid_r[1]) == 1'b1)
242                 sum_all <= #1 sum_0_1_2 + sum_0_1_2 + sum_3_4_r;
243             
244         end
245     end
246     endgenerate
247     
248     wire [2*DW - 1:0] dout_reg;
249     
250     //边界设置零处理
251     assign dout_reg = ((is_boarder_tmp == 1'b1)) ? {DW+1{1'b0}} : sum_all;
252     
253     assign dout = dout_temp_r;
254     assign dout_valid = dout_valid_temp_r;
255     assign is_boarder = is_boarder_r;
256     
257     //求和结果打一拍后输出
258     always@(posedge clk)
259     begin
260         if(rst_all)
261         begin
262             dout_temp_r <= #1 {2*DW{1'b0}};
263             dout_valid_temp_r <= #1 1'b0;
264             valid_r <= #1 1'b0;
265             is_boarder_r <= 1'b0;
266         end
267         else 
268         begin
269             if(dout_valid_temp == 1'b1)
270                 dout_temp_r <= #1 dout_reg;
271             else
272                 dout_temp_r <= {2*DW{1'b0}};
273                 
274             dout_valid_temp_r <= #1 dout_valid_temp;
275             valid_r <= #1 valid;
276             is_boarder_r <= is_boarder_tmp;        
277         end
278     end
279     
280     /*输入行计数*/
281     always@(posedge clk)
282     begin
283         if(rst_all == 1'b1)
284             in_line_cnt <= #1 {11{1'b0}};
285         else if(((~(valid))) == 1'b1 & valid_r == 1'b1)
286             in_line_cnt <= #1 in_line_cnt + 11'b00000000001;
287     end
288     
289     /*溢出行行列计数*/
290     always@(posedge clk)
291     begin
292         if(rst_all)
293         begin
294             flush_line <= #1 1'b0;
295             flush_cnt <= #1 {16{1'b0}};
296         end
297         else 
298         begin
299             
300             //溢出行计数
301             if(flush_cnt >= ((IW -1)))
302                 flush_cnt <= #1 {16{1'b0}};
303             else if(flush_line == 1'b1)
304                 flush_cnt <= #1 flush_cnt + 16'b0000000000000001;
305             
306             //溢出行标记
307             if(flush_cnt >= ((IW - 1)))
308                 flush_line <= #1 1'b0;
309             else if(in_line_cnt >= IH & out_line_cnt < ((IH - 1)))
310                 flush_line <= #1 1'b1;
311             
312         end    
313     end
314     
315     /*输出行行列计数*/
316     always@(posedge clk)
317     begin
318         if(rst_all)
319         begin
320             out_pixel_cnt <= #1 {16{1'b0}};
321             out_line_cnt <= #1 {11{1'b0}};
322         end
323         else 
324         begin
325             
326             //输出行计数
327             if(dout_valid_temp_r == 1'b1 & ((~(dout_valid_temp))) == 1'b1)
328                 out_line_cnt <= #1 out_line_cnt + 11'b00000000001;
329             else
330                 out_line_cnt <= #1 out_line_cnt;
331                 
332             //输出像素计数
333             if(dout_valid_temp_r == 1'b1 & ((~(dout_valid_temp))) == 1'b1)
334                 out_pixel_cnt <= #1 {16{1'b0}};
335             else if(dout_valid_temp == 1'b1)
336                 out_pixel_cnt <= #1 out_pixel_cnt + 16'b0000000000000001;
337                     
338         end
339     end
340     
341     //边界判决电路
342     assign is_boarder_tmp = ((dout_valid_temp == 1'b1 & ((out_pixel_cnt <= (((radius - 1))))  | 
343                                                          (out_pixel_cnt >= (((IW - radius)))) |
344                                                          (out_line_cnt  <= (((radius - 1))))  |
345                                                          (out_line_cnt  >= (((IH - radius))))))) ? 1'b1 : 1'b0;
346     
347     assign vsync_out = vsync;
348     
349 endmodule

  3、顶层模块设计Mean_2D.v,例化了一个二维求和模块sum_2d,并将其输出做除法处理即可。此处依然要留意边界信号的处理和有效信号的处理。具体代码如下:

  1 `timescale 1ps/1ps 
  2 
  3 //====================================================================================================//
  4 //FileName: Mean_2D.v
  5 //Function: TOP File Video_Cap -> RGB2GRAY -> HIST_EUQALIZE -> Mean_2D
  6 //Date: 2020-03-20
  7 //====================================================================================================//
  8 
  9 module Mean_2D(
 10     RSTn,                //全局复位
 11     CLOCK,                //系统时钟
 12     
 13     IMG_CLK,            //像素时钟
 14     IMG_DVD,            //像素值
 15     IMG_DVSYN,            //输入场信号
 16     IMG_DHSYN,            //输入数据有效信号
 17     Mean_DOUT,
 18     Mean_FOUT,
 19     Mean_VALID,
 20     Mean_VSYNC,
 21     Mean_BOARDER
 22 );
 23 
 24 /*image parameter*/
 25     parameter iw             = 640;        //image width
 26     parameter ih            = 512;        //image height
 27     parameter trig_value    = 400;         //250
 28     parameter tw            = 32;        //直方图统计数据位宽
 29     
 30     localparam half_width    = (tw >> 1); //将32位的数据位宽拆分为高低16位
 31     
 32     /*data width*/
 33     parameter dvd_dw     = 8;    //image source data width
 34     parameter dvd_chn    = 3;    //channel of the dvd data: when 3 it's rgb or 4:4:YCbCr
 35     parameter local_dw    = dvd_dw * dvd_chn;    //local algorithem process data width
 36     parameter cmd_dw    = dvd_dw * dvd_chn;    //local algorithem process data width
 37     parameter ksz         = 5;    //窗函数尺寸
 38     parameter latency   = ksz + 2;
 39 
 40     //Port Declared
 41     input RSTn;
 42     input CLOCK;
 43     input IMG_CLK;
 44     input [dvd_dw-1:0] IMG_DVD;
 45     input IMG_DVSYN;
 46     input IMG_DHSYN;
 47     output reg [dvd_dw-1:0] Mean_DOUT;
 48     output reg [dvd_dw-1:0] Mean_FOUT;
 49     output reg Mean_VALID;
 50     output reg Mean_VSYNC;
 51     output reg Mean_BOARDER;
 52 
 53     //Variable Declared
 54     wire GRAY_CLK;
 55     wire GRAY_VSYNC;
 56     wire GRAY_DVALID;
 57     wire [dvd_dw-1:0] Y_DAT;
 58     wire [dvd_dw-1:0] Cb_DAT;
 59     wire [dvd_dw-1:0] Cr_DAT;
 60     
 61     wire [local_dw-1:0] RGB_DAT;
 62     wire RGB_DVALID;
 63     wire RGB_VSYNC;
 64     
 65     //Mean_2D Variable
 66     wire [2*dvd_dw-1:0] sum_dout;
 67     wire sum_is_boarder;
 68     wire sum_vsync_out;
 69     wire sum_dout_valid;
 70     wire [dvd_dw-1:0] HISTEQUAL_DAT;
 71     wire HISTEQUAL_VALID;
 72     wire HISTEQUAL_VSYNC;
 73 
 74     video_cap video_cap_inst(
 75             .reset_l(RSTn),                //异步复位信号
 76             .DVD(IMG_DVD),                //输入视频流
 77             .DVSYN(IMG_DVSYN),            //输入场同步信号
 78             .DHSYN(IMG_DHSYN),            //输入行同步
 79             .DVCLK(IMG_CLK),            //输入DV时钟
 80             .cap_dat(RGB_DAT),            //输出RGB通道像素流,24位
 81             .cap_dvalid(RGB_DVALID),    //输出数据有效
 82             .cap_vsync(RGB_VSYNC),        //输出场同步
 83             .cap_clk(CLOCK),            //本地逻辑时钟
 84             .img_en(),                
 85             .cmd_rdy(),                    //命令行准备好,代表可以读取
 86             .cmd_rdat(),                //命令行数据输出
 87             .cmd_rdreq()                //命令行读取请求
 88         );
 89     
 90     defparam video_cap_inst.DW_DVD         = dvd_dw;
 91     defparam video_cap_inst.DW_LOCAL     = local_dw;
 92     defparam video_cap_inst.DW_CMD         = cmd_dw;
 93     defparam video_cap_inst.DVD_CHN     = dvd_chn;
 94     defparam video_cap_inst.TRIG_VALUE  = trig_value;
 95     defparam video_cap_inst.IW             = iw;
 96     defparam video_cap_inst.IH             = ih;
 97     
 98     RGB2YCrCb RGB2YCrCb_Inst(
 99             .RESET(RSTn),                //异步复位信号
100             
101             .RGB_CLK(CLOCK),            //输入像素时钟
102             .RGB_VSYNC(RGB_VSYNC),        //输入场同步信号
103             .RGB_DVALID(RGB_DVALID),    //输入数据有信号
104             .RGB_DAT(RGB_DAT),            //输入RGB通道像素流,24位
105             
106             .YCbCr_CLK(GRAY_CLK),        //输出像素时钟
107             .YCbCr_VSYNC(GRAY_VSYNC),    //输出场同步信号
108             .YCbCr_DVALID(GRAY_DVALID),    //输出数据有效信号
109             .Y_DAT(Y_DAT),                //输出Y分量
110             .Cb_DAT(Cb_DAT),            //输出Cb分量
111             .Cr_DAT(Cr_DAT)                //输出Cr分量
112         );    
113 
114     defparam RGB2YCrCb_Inst.RGB_DW = local_dw;
115     defparam RGB2YCrCb_Inst.YCbCr_DW = dvd_dw;
116 
117 
118     hist_equalized hist_equalized_inst(
119         .rst_n(RSTn),
120         .clk(GRAY_CLK),
121         .din_valid(GRAY_DVALID),        //输入数据有效
122         .din(Y_DAT),                    //输入数据
123         .dout(HISTEQUAL_DAT),            //输出数据
124         .vsync(GRAY_VSYNC),                //输入场同步
125         .dout_valid(HISTEQUAL_VALID),    //输出有效
126         .vsync_out(HISTEQUAL_VSYNC)        //输出场同步
127     );
128     
129     defparam hist_equalized_inst.DW = dvd_dw;
130     defparam hist_equalized_inst.IH = ih;
131     defparam hist_equalized_inst.IW = iw;
132     defparam hist_equalized_inst.TW = tw;
133 
134     
135     //例化一个二维求和模块
136     sum_2d window_sum(
137         .rst_n(RSTn),                        //异步复位信号
138         .clk(GRAY_CLK),                        //同步时钟
139         .din_valid(HISTEQUAL_VALID),        //输入数据有效
140         .din(HISTEQUAL_DAT),                //输入数据流
141         .dout(sum_dout),                    //输出数据流
142         .vsync(HISTEQUAL_VSYNC),            //输入场同步信号
143         .vsync_out(sum_vsync_out),            //输出场同步信号
144         .is_boarder(sum_is_boarder),        //输出边界信息
145         .dout_valid(sum_dout_valid)            //输出数据有效
146     );
147     
148     defparam window_sum.DW = dvd_dw;
149     defparam window_sum.KSZ = ksz;
150     defparam window_sum.IH = ih;
151     defparam window_sum.IW = iw;
152     
153     //除法电路设计
154     /*首先定义中间计算寄存器*/
155     reg [2*dvd_dw-1:0]         Mean_temp;
156     reg [2*dvd_dw-1:0]         Mean_temp1;
157     reg [2*dvd_dw:0]         Mean_temp2;
158     reg [2*dvd_dw+5-1:0]     Mean_temp3;
159     reg [2*dvd_dw+6-1:0]     Mean_temp4;
160     reg [2*dvd_dw+1-1:0]     Mean_temp5;
161     reg [2*dvd_dw+6-1:0]     Mean_temp6;
162     reg [2*dvd_dw+6-1:0]     Mean_temp7;
163     wire [2*dvd_dw+6-1:0]     Mean_temp8;
164     wire [dvd_dw-1:0]         Mean_temp9;
165     wire [dvd_dw+3-1:0]     Mean_temp10;
166     wire [2*dvd_dw+6-1:0]     Mean_temp11;
167     wire [2*dvd_dw-1:0]        Mean_out_temp;
168     
169     
170     reg [2*dvd_dw-1:0] sum_dout_r[0:latency-1];
171     reg sum_vsync_out_r [0:latency-1];
172     reg sum_is_boarder_r [0:latency-1];
173     reg sum_dout_valid_r [0:latency-1];
174     
175     generate
176     
177         if(ksz == 5)
178         begin : divide_25 /*除以25操作*/
179             always@(posedge CLOCK or negedge RSTn)
180                 if(((~(RSTn))) == 1'b1) /*复位清零*/
181                 begin
182                     Mean_temp  <= {2*dvd_dw{1'b0}};
183                     Mean_temp1 <= {2*dvd_dw{1'b0}};
184                     Mean_temp2 <= {2*dvd_dw+1{1'b0}};
185                     Mean_temp3 <= {2*dvd_dw+5-1+1{1'b0}};
186                     Mean_temp4 <= {2*dvd_dw+6-1+1{1'b0}};
187                     Mean_temp5 <= {2*dvd_dw+1-1+1{1'b0}};
188                     Mean_temp6 <= {2*dvd_dw+6-1+1{1'b0}};
189                 end
190                 else
191                 begin
192                     
193                     //将二维求和结果缓存到Mean_temp
194                     if((sum_dout_valid_r[3]) == 1'b1)
195                         Mean_temp <= #1 sum_dout_r[2];
196                     
197                     //下一拍开始计算
198                     if((sum_dout_valid_r[4] == 1'b1))
199                     begin
200                         //计算Mean_temp (2^-6 + 2^-7)
201                         Mean_temp1 <= #1 ({6'b000000, Mean_temp[2*dvd_dw-1:6]} +
202                                           {7'b0000000, Mean_temp[2*dvd_dw-1:7]});
203                                           
204                         //计算Mean_temp (2^-3 + 2^-4)
205                         Mean_temp2 <= #1 ({4'b0000, Mean_temp[2*dvd_dw-1:3]} +
206                                           {5'b0000, Mean_temp[2*dvd_dw-1:4]});
207                                           
208                         //计算Mean_temp (2^-1 + 2^-2)
209                         Mean_temp3 <= #1 ({6'b000000, Mean_temp[2*dvd_dw-1:1]} +
210                                           {7'b0000000, Mean_temp[2*dvd_dw-1:2]});
211                                           
212                         //计算Mean_temp (2^-3 + 2^-5)
213                         Mean_temp4 <= #1 ({1'b0, Mean_temp[2*dvd_dw-1:0],5'b0000} +
214                                           {3'b000, Mean_temp[2*dvd_dw-1:0],3'b000});
215                                           
216                         //下一步开始计算上一拍的中间结果
217                         if((sum_dout_valid_r[5]) == 1'b1)
218                         begin
219                             Mean_temp5 <= #1 ({1'b0,Mean_temp1} + Mean_temp2);
220                             Mean_temp6 <= #1 ({1'b0,Mean_temp3} + Mean_temp4);
221                         end
222                         
223                         //下一拍开始计算上一拍的中间结果
224                         if((sum_dout_valid_r[6]) == 1'b1)
225                         begin
226                             Mean_temp7 <= #1 ({5'b00000,Mean_temp5} + Mean_temp6);
227                         end
228                     end
229                 end
230         end
231     endgenerate
232     
233     //求和结果/1024得到除以25的结果
234     assign #1 Mean_temp8 = ((sum_is_boarder_r[6] == 1'b0)) ? ((Mean_temp7 >> 10)) : {2*dvd_dw + 6 - 1 + 1{1'b0}};
235 
236     //四舍五入
237     assign #1 Mean_temp9 = (((Mean_temp7[9]) == 1'b0)) ? (Mean_temp8[dvd_dw - 1:0] + 1'b1) : Mean_temp8[dvd_dw - 1:0];
238     
239     //以下对输出结果保存三位小数
240     assign #1 Mean_temp11 = ((sum_is_boarder_r[6] == 1'b0)) ? ((Mean_temp7 >> 7)) : {2*dvd_dw + 6 - 1 + 1{1'b0}};
241     
242     assign #1 Mean_temp10 = (Mean_temp11[dvd_dw + 3 -1:0] + 1'b1);
243     
244     /*缓存输出求和结果,求和边界信息和求和有效信息等*/
245     generate
246         begin : xhd1
247             genvar i;
248             for(i = 0; i <= latency - 1; i = i + 1)
249             begin : buf_cmp_inst
250                 if(i == 0)
251                     begin : xhd2
252                     always@(posedge CLOCK or negedge RSTn)
253                         if(((~(RSTn))) == 1'b1)
254                             begin
255                                 sum_dout_r[i]         <= #1 {2*dvd_dw{1'b0}};
256                                 sum_vsync_out_r[i]     <= #1 1'b0;
257                                 sum_is_boarder_r[i] <= #1 1'b0;
258                                 sum_dout_valid_r[i] <= #1 1'b0;
259                             end
260                         else
261                             begin
262                                 sum_dout_r[i]         <= #1 sum_dout;
263                                 sum_vsync_out_r[i]     <= #1 sum_vsync_out;
264                                 sum_is_boarder_r[i] <= #1 sum_is_boarder;
265                                 sum_dout_valid_r[i] <= #1 sum_dout_valid;
266                             end
267                     end
268                     
269                 if((~(i == 0)))
270                     begin : xhd3
271                     always@(posedge CLOCK or negedge RSTn)
272                         if(((~(RSTn))) == 1'b1)
273                             begin
274                                 sum_dout_r[i]         <= #1 {2*dvd_dw{1'b0}};
275                                 sum_vsync_out_r[i]     <= #1 1'b0;
276                                 sum_is_boarder_r[i] <= #1 1'b0;
277                                 sum_dout_valid_r[i] <= #1 1'b0;
278                             end
279                         else
280                             begin
281                                 sum_dout_r[i]         <= #1 sum_dout_r[i - 1];
282                                 sum_vsync_out_r[i]     <= #1 sum_vsync_out_r[i - 1];
283                                 sum_is_boarder_r[i] <= #1 sum_is_boarder_r[i - 1];
284                                 sum_dout_valid_r[i] <= #1 sum_dout_valid_r[i - 1];
285                             end
286                     end    
287             end        
288         end
289     endgenerate
290 
291     //输出相应信号
292     always@(posedge CLOCK or negedge RSTn)
293         if(((~(RSTn))) == 1'b1)
294         begin
295             Mean_DOUT         <= #1 {dvd_dw{1'b0}};
296             Mean_FOUT        <= #1 {dvd_dw{1'b0}};
297             Mean_VALID         <= #1 1'b0;
298             Mean_VSYNC         <= #1 1'b0;
299             Mean_BOARDER     <= #1 1'b0;
300         end
301         else
302         begin
303             Mean_DOUT         <= #1 Mean_temp9;
304             Mean_FOUT        <= #1 Mean_temp10[7:0];
305             Mean_VALID         <= #1 sum_dout_valid_r[6];
306             Mean_BOARDER     <= #1 sum_is_boarder_r[6];
307             Mean_VSYNC         <= #1 sum_vsync_out_r[6];
308         end
309     
310 endmodule

  4、用于Modelsim仿真的Mean_2D_Tb.v文件,具体代码如下:

  1 `timescale 1ps/1ps
  2 
  3 module Mean_2D_Tb;
  4 
  5 
  6     /*image para*/
  7     parameter iw             = 640;        //image width
  8     parameter ih            = 512;        //image height
  9     parameter trig_value    = 400;     //250
 10 
 11     /*video parameter*/
 12     parameter h_total        = 2000;
 13     parameter v_total        = 600;
 14     parameter sync_b        = 5;
 15     parameter sync_e        = 55;
 16     parameter vld_b            = 65;
 17 
 18     parameter clk_freq         = 72;
 19 
 20     /*data width*/
 21     parameter dvd_dw     = 8;    //image source data width
 22     parameter dvd_chn    = 3;    //channel of the dvd data: when 3 it's rgb or 4:4:YCbCr
 23     parameter local_dw    = dvd_dw * dvd_chn;    //local algorithem process data width
 24     parameter cmd_dw    = dvd_dw * dvd_chn;    //local algorithem process data width
 25 
 26 
 27     /*test module enable*/
 28     parameter hist_equalized_en    = 0;
 29     parameter display_transform_en = 0;
 30     parameter mean_2d_en = 1;
 31     
 32 
 33     /*signal group*/
 34     reg pixel_clk = 1'b0;
 35     reg reset_l;
 36     reg [3:0] src_sel;
 37 
 38 
 39     /*input dv group*/
 40     wire dv_clk;
 41     wire dvsyn;
 42     wire dhsyn;
 43     wire [dvd_dw-1:0] dvd;
 44     
 45     /*dvd source data generated for simulation*/
 46     image_src image_src_inst//#(iw*dvd_chn, ih+1, dvd_dw, h_total, v_total, sync_b, sync_e, vld_b)
 47     (
 48         .clk(pixel_clk),
 49         .reset_l(reset_l),
 50         .src_sel(src_sel),
 51         .test_data(dvd),
 52         .test_dvalid(dhsyn),
 53         .test_vsync(dvsyn),
 54         .clk_out(dv_clk)
 55     );
 56         
 57     defparam image_src_inst.iw = iw*dvd_chn;
 58     defparam image_src_inst.ih = ih + 1;
 59     defparam image_src_inst.dw = dvd_dw;
 60     defparam image_src_inst.h_total = h_total;
 61     defparam image_src_inst.v_total = v_total;
 62     defparam image_src_inst.sync_b = sync_b;
 63     defparam image_src_inst.sync_e = sync_e;
 64     defparam image_src_inst.vld_b = vld_b;
 65     
 66     /*local clk: also clk of all local modules*/
 67     reg cap_clk = 1'b0;
 68         
 69     /*hist equalized operation module*/
 70     generate
 71         if(hist_equalized_en != 0)begin : equalized_operation
 72             wire equalized_dvalid;
 73             wire [dvd_dw-1:0] equalized_data;
 74             wire equalized_vsync;
 75             
 76             wire equalized_dvalid_in;
 77             wire [dvd_dw-1:0] equalized_data_in;
 78             wire equalized_vsync_in;
 79         
 80             integer fp_equalized,cnt_equalized = 0;
 81         
 82             /*video capture: capture image src and transfer it into local timing*/
 83             hist_equal hist_equal_inst(
 84                 .RSTn(reset_l),                        //全局复位
 85                 .CLOCK(cap_clk),                    //系统时钟
 86                 
 87                 .IMG_CLK(pixel_clk),                //像素时钟
 88                 .IMG_DVD(equalized_data_in),        //像素值
 89                 .IMG_DVSYN(equalized_vsync_in),        //输入场信号
 90                 .IMG_DHSYN(equalized_dvalid_in),    //输入数据有效信号
 91                 .HISTEQUAL_DAT(equalized_data),        //输出直方图统计数据
 92                 .HISTEQUAL_VALID(equalized_dvalid),    //输出直方图统计有效
 93                 .HISTEQUAL_VSYNC(equalized_vsync)    //数据读出请求
 94             );
 95             
 96             assign equalized_data_in = dvd;
 97             assign equalized_dvalid_in = dhsyn;
 98             assign equalized_vsync_in = dvsyn;
 99     
100             always@(posedge cap_clk or posedge equalized_vsync)begin
101                 if((~(equalized_vsync)) == 1'b0)
102                     cnt_equalized = 0;
103                 else begin
104                     if(equalized_dvalid == 1'b1)begin
105                         fp_equalized = $fopen("E:/Modelsim/hist_equalized/sim/equalized.txt","r+");
106                         $fseek(fp_equalized,cnt_equalized,0);
107                         $fdisplay(fp_equalized,"%02X",equalized_data);
108                         $fclose(fp_equalized);
109                         cnt_equalized <= cnt_equalized + 4;
110                     end
111                 end
112             end
113         end
114     endgenerate
115     
116     /*hist linear transform module*/
117     generate
118         if(display_transform_en != 0) begin: display_transform_operation
119             wire dis_trans_dvalid;
120             wire [dvd_dw-1:0] dis_trans_data;
121             wire dis_trans_vsync;
122             wire dis_trans_dvalid_in;
123             wire [dvd_dw-1:0] dis_trans_data_in;
124             wire dis_trans_vsync_in;
125             
126             integer fp_dis_trans,cnt_dis_trans = 0;
127             
128             hist_transform hist_transform_inst(
129                 .RSTn(reset_l),                                //全局复位
130                 .CLOCK(cap_clk),                            //系统时钟
131     
132                 .IMG_CLK(pixel_clk),                        //像素时钟
133                 .IMG_DVD(dis_trans_data_in),                //像素值
134                 .IMG_DVSYN(dis_trans_vsync_in),                //输入场信号
135                 .IMG_DHSYN(dis_trans_dvalid_in),            //输入数据有效信号
136                 .HISTTRANS_DAT(dis_trans_data),                //输出直方图线性拉伸数据
137                 .HISTTRANS_VALID(dis_trans_dvalid),            //输出直方图线性拉伸数据有效信号
138                 .HISTTRANS_VSYNC(dis_trans_vsync)            //输出直方图线性拉伸场有效信号
139             );
140             
141             assign dis_trans_data_in = dvd;
142             assign dis_trans_dvalid_in = dhsyn;
143             assign dis_trans_vsync_in = dvsyn;
144             
145             always@(posedge cap_clk or posedge dis_trans_vsync)begin
146                 if((~(dis_trans_vsync)) == 1'b0)
147                     cnt_dis_trans = 0;
148                 else 
149                 begin
150                     if(dis_trans_dvalid == 1'b1)
151                     begin
152                         fp_dis_trans = $fopen("E:/Modelsim/hist_transform/sim/dis_trans.txt","r+");
153                         $fseek(fp_dis_trans,cnt_dis_trans,0);
154                         $fdisplay(fp_dis_trans,"%02x",dis_trans_data);
155                         $fclose(fp_dis_trans);
156                         cnt_dis_trans <= cnt_dis_trans + 4;    
157                     end
158                 end
159             end    
160         end
161     endgenerate
162     
163     /*Mean Operation Module*/
164     generate
165         if(mean_2d_en != 0)begin : mean_operation
166             
167             /*mean data*/
168             wire         mean_dvalid;
169             wire [dvd_dw-1:0] mean_data;
170             wire [dvd_dw-1:0] mean_data_frac;
171             wire mean_vsync;
172             
173             /*mean data input*/
174             wire        mean_dvalid_in;
175             wire [dvd_dw-1:0] mean_data_in;
176             wire        mean_vsync_in;
177         
178             integer fp_mean,cnt_mean = 0;
179         
180             Mean_2D Mean_2D_New(
181                 .RSTn(reset_l),                    //全局复位
182                 .CLOCK(cap_clk),                //系统时钟
183     
184                 .IMG_CLK(pixel_clk),            //像素时钟
185                 .IMG_DVD(mean_data_in),            //像素值
186                 .IMG_DVSYN(mean_vsync_in),        //输入场信号
187                 .IMG_DHSYN(mean_dvalid_in),        //输入数据有效信号
188                 .Mean_DOUT(mean_data),
189                 .Mean_FOUT(mean_data_frac),
190                 .Mean_VALID(mean_dvalid),
191                 .Mean_VSYNC(mean_vsync),
192                 .Mean_BOARDER()
193             );
194         
195             assign mean_data_in     = dvd;
196             assign mean_dvalid_in     = dhsyn;
197             assign mean_vsync_in     = dvsyn;
198         
199             always@(posedge cap_clk or posedge mean_vsync)begin
200                 if(((~(mean_vsync))) == 1'b0)
201                     cnt_mean = 0;
202                 else 
203                 begin
204                     if(mean_dvalid == 1'b1)
205                     begin
206                         fp_mean = $fopen("E:/Modelsim/Mean_2D/sim/mean.txt","r+");
207                         $fseek(fp_mean,cnt_mean,0);
208                         $fdisplay(fp_mean,"%02x",mean_data);
209                         $fclose(fp_mean);
210                         cnt_mean <= cnt_mean + 4;    
211                     end
212                 end
213             end
214         end
215     endgenerate
216         
217     initial
218     begin: init
219         reset_l <= 1'b1;
220         src_sel <= 4'b0000;
221         #(100);            //reset the system
222         reset_l <= 1'b0;
223         #(100);    
224         reset_l <= 1'b1;
225     end
226     
227     //dv_clk generate
228     always@(reset_l or pixel_clk)begin
229         if((~(reset_l)) == 1'b1)
230             pixel_clk <= 1'b0;
231         else 
232         begin
233             if(clk_freq == 48)            //48MHz
234                 pixel_clk <= #10417 (~(pixel_clk));
235             
236             else if(clk_freq == 51.84)    //51.84MHz
237                 pixel_clk <= #9645 (~(pixel_clk));
238             
239             else if(clk_freq == 72)        //72MHz
240                 pixel_clk <= #6944 (~(pixel_clk));
241         end
242     end
243     
244     //cap_clk generate: 25MHz
245     always@(reset_l or cap_clk)begin
246         if((~(reset_l)) == 1'b1)
247             cap_clk <= 1'b0;
248         else
249             cap_clk <= #20000 (~(cap_clk));    
250     end
251     
252 endmodule
253     

四、仿真结果

  1、一维求和模块,sum_1d。从仿真结果中可以看出,数据输出有效信号比输入有效信号延迟了两个时钟。书中说由于前面两个时钟的像素处于边界,因此头两个时钟数据无效。比较难理解哈,另外需要注意的是前面六个时钟,减法运算的处理上设置sub_out的值为0,这个也属于边界处理的手段,避免了行与行之间的干扰。

  2、二维求和模块sum_2d仿真结果,分为如下几个模块分析:

  (1)行求和数据的缓存,从仿真结果中可以看出行缓存的写使能与一维求和模块数据有效信号同步;行求和数据存满一行后,行缓存读出有效直至下一帧图像的输入。

  (2)列求和运算消耗4个时钟的延迟,其中以考虑到边界处理的因素,从第二行数据读出有效起始进行求和运算。

   

   (3)溢出行标记,当输入行技术大于512,且输出行技术小于511时,输出溢出行标记有效,其实也就是最后两行的输出数据记为边界行数据。

   3、除法运算仿真分析:

  从仿真结果中可以看出,输出的前两列为边界数据被置零处理。计算分析如下:

  第一个时钟:

  Mean_temp1 = 2-6 * 5187 + 2-7 * 5187 = 121.57

  Mean_temp2 = 2-3 * 5187 + 2-4 * 5187 = 972.5625

  Mean_temp3 = 2-1 * 5187 + 2-2 * 5187 = 3890.25

  Mean_temp4 = 23 * 5187 + 25 * 5187 = 207480

  第二个时钟:

  Mean_temp5 = Mean_temp1 + Mean_temp2 = 1094.1325

  Mean_temp6 = Mean_temp3 + Mean_temp4 = 211370.25

  第三个时钟:

  Mean_temp7 = Mean_temp5 + Mean_temp6 = 212464.3825

  除以1024得到最终结果:

  Mean_temp8 = Mean_temp7 / 1204 = 207.484

 

 五、结论

  如下图示为图像处理结果,原始灰度图像经过直方图均衡和均值滤波后图像亮度明显增强同时细节处可以看出均值滤波后图像变的光滑,同时可以看到在边界处理上外围的两行两列被置零处理。

  

初入江湖,天下无敌;再学三年,寸步难行。
原文地址:https://www.cnblogs.com/huangwei0521/p/12639373.html