一、前言
本篇主要针对牟新刚编著《基于FPGA的数字图像图像原理及应用》中第7章第二节中的基于FPGA的均值滤波的内容进行仿真验证。2020-04-04 20:37:13
二、算法实现原理
整体设计
求图像均值的步骤:
- 获得当前窗口所有像素。
- 计算当前串口所有像素之和。
- 将第2步结果除以当前窗口数据总数。
- 滑动窗口到下一个窗口,直到遍历完整幅图像。
滤波采用滑动窗口方法来实现整幅图像的遍历,因此,采用流水线结构来设计。对于流水线结构来说,每个像素的运算方法是一致的,所需考
虑的指示边界像素的处理问题。
一般情况下,任何二维的计算步骤都可以化为一维的操作。由于行方向的数据是连续的,因此在流水线操作中,常常会首先进行行方向的操
作。假定现在已经完成第一行的求和操作,接下来需要“等”下行的求和完成。如何进行等待?在FPGA中,等待的实现方法就是进行行缓存。二维
操作转换为一维操作后的结构如下图所示。
接下来的问题是,如何进行一维向量求和操作?对于1×5的向量求和而言,当前数据需要“等到”下4个数据到来之后才能得到连续5个数据,
并执行加法操作。可以预期的是,还需要把前几个数据到来之后单独缓存起来,一个指定位宽的寄存器即可满足要求。
最后的问题是求取窗口的均值,需要将上述计算出来的和除以一个归一化系数,也就是整个窗口的像素数目。在FPGA里面对于一方确定的除法
操作,一般情况下不直接进行除法运算,而是通过近似的乘加方法来实现等效转换。在这里,对于固定的窗口,除法的分母是固定的。因此完全可
以用此方法来实现等效近似。
子模块设计
需要设计如下几个子模块:
- 一维求和模块,这里记为sum_1d。
- 二维求和模块这里记为sum_2d。
- 除法转换模块,此模块比较简单,一般情况下不进行模块封装。
- 行缓存电路实现行列间像素对齐。
整个顶层模块调用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
五、结论
如下图示为图像处理结果,原始灰度图像经过直方图均衡和均值滤波后图像亮度明显增强同时细节处可以看出均值滤波后图像变的光滑,同时可以看到在边界处理上外围的两行两列被置零处理。