一、前言
本篇主要针对牟新刚编著《基于FPGA的数字图像处理原理及应用》一书中第六章第5.3小节FPGA直方图线性拉伸的内容进行modelsim
层面的仿真,做必要的总结及解读。 2020-03-18 13:35:14
二、FPGA直方图线性拉伸原理
2.1 直方图线性拉伸基本原理
在视频处理中,为了能够实时调节图像的对比度,通常需要对直方图进行拉伸处理。直方图拉伸是指将图像灰度直方图较窄的灰度区间
向两端拉伸,增强整幅图像像素的灰度级对比度,达到增强图像的效果。
线性拉伸也即灰度拉伸,属于线性点运算的一种。它扩展图像的直方图,使其充满整个灰度级范围内。
设f(x,y)为输入图像,它的最小灰度级A和最大灰度级B的定义如下:
A = min[f(x,y)], B = max[f(x,y)];
将A和B分别映射到0和255,则最终得到输出图像g(x,y)为
g(x,y) = 255*[f(x,y) - A]/(B-A)。
基本意思就是将整个图像的灰度等级拉伸到0到255的像素等级区间,使得图像对比度进一步提高。
2.2 直方图线性拉伸的FPGA实现
计算直方图线性拉伸处理后的像素值的步骤如下:
(1)确定高低阈值Thr_Min和Thr_Max。
(2)计算系数A和B。
(3)计算当前像素与A的差值。
(4)计算255与(B-A)的商。
(5)计算第(3)步与第(4)步的乘积。
其具体实现电路如下图所示;
其中,Thr_Min和Thr_Max为给定的两个截断区间,用来定义首尾被截断的直方图统计数目。截断区间由自己定义,根据前一帧的累加和统计
结果,可以计算出A和B两个系数。
三、代码实现
代码参照书中代码稍做调整,包括直方图累加和统计文件histogram_2d.v、直方图线性拉伸文件hist_linear_transform.v、顶层文件及用于测试
仿真的文件。
(1)histogram_2d.v,累加和统计文件,视频流的第一帧用于累加和统计,后续前一阵的累加和统计结果用作后一帧图像的线性拉伸处理。代码
中新增例化了DPRAM用于存储图像的直方图统计累加和。提供读写端口,用于累加和的读取。
映射区间主要由输入阈值和直方图累加和计算出来,因此映射区间的计算同样可以放在直方图统计阶段进行。
同时还需要注意,和直方图均衡一样,我们不考虑帧缓存的问题,也就是当前的查找结果为上一帧的结果。
查找步骤如下:
(1)当前图像到来之前,加载默认映射值。
(2)根据上一帧的查找结果输出映射值。
(3)对本帧进行直方图统计。
(4)统计过程中根据定义查找区间映射值。
1 `timescale 1ps/1ps 2 3 `define Equalize 0 4 `define LinearTransfer 1 5 //==============================================================================// 6 //FileName: histogram_2d.v 7 //Date: 2020-02-29 8 //==============================================================================// 9 10 module histogram_2d( 11 rst_n, 12 clk, 13 din_valid, //输入有效 14 din, //输入待统计的数据 15 dout, //统计输出 16 vsync, //输入场同步 17 dout_valid, //输出有效 18 rdyOutput, //数据读出请求 19 //dout_clk //数据输出时钟 20 21 `ifdef Equalize 22 hist_cnt_addr, 23 hist_cnt_out, 24 `endif 25 26 `ifdef LinearTransfer 27 lowCnt, //低阈值输入 28 highCnt, //高阈值输入 29 lowIndex, //映射区间左侧输出 30 highIndex, //映射区间右侧输出 31 `endif 32 33 int_flag //中断输出 34 35 ); 36 37 //模块入口参数 38 parameter DW = 14; //数据位宽 39 parameter IH = 512; //图像高度 40 parameter IW = 640; //图像宽度 41 parameter TW = 32; //直方图统计数据位宽 42 43 localparam TOTAL_CNT = IW * IH; //像素总数 44 localparam HALF_WIDTH = (TW >> 1); //将32位的数据位宽拆分为高低16位 45 46 47 //输入输出声明 48 input rst_n; 49 input clk; 50 input din_valid; 51 input [DW-1:0] din; 52 input rdyOutput; 53 54 output reg [HALF_WIDTH:0] dout; 55 56 //output wire [TW-1:0] dout; 57 58 input vsync; 59 output reg dout_valid; 60 output reg int_flag; 61 //output dout_clk; 62 63 `ifdef Equalize 64 input [DW-1:0] hist_cnt_addr; 65 output reg [TW-1:0] hist_cnt_out; 66 `endif 67 68 `ifdef LinearTransfer 69 input [TW-1:0] lowCnt; 70 input [TW-1:0] highCnt; 71 output reg [DW-1:0] lowIndex; 72 output reg [DW-1:0] highIndex; 73 `endif 74 75 //变量声明 76 reg vsync_r; 77 reg dvalid_r; 78 reg dvalid_r2; 79 reg [DW-1:0] din_r; 80 reg [DW-1:0] din_r2; 81 82 wire hsync_fall; 83 wire hsync_rise; 84 85 reg [9:0] hsync_count; 86 reg count_en; 87 wire [DW-1:0] mux_addr_b; 88 wire [DW-1:0] mux_addr_b2; 89 90 wire [TW-1:0] q_a; 91 wire [TW-1:0] q_b; 92 reg [TW-1:0] counter; 93 94 wire [TW-1:0] count_value; 95 wire rst_cnt; //统计计数器复位信号 96 wire inc_en; //递增使能信号 97 98 //DPRAM 信号 99 wire we_a; 100 wire we_b; 101 wire we_b_l; 102 reg we_b_h; 103 104 wire [DW-1:0] addr_a; 105 //中断寄存器 106 reg int_r; 107 wire [DW-1:0] clr_addr; //清零地址 108 reg [DW-1:0] clr_addr_r; 109 reg [DW:0] out_pixel; //输出计数 110 111 reg count_all; //统计完成信号 112 //reg count_en_r; 113 reg count_en_r; 114 115 reg [TW-1:0] hist_cnt; //直方图统计累加寄存器 116 wire rstOutput; //读出电路复位信号 117 118 wire [TW-1:0] dataTmp2; 119 wire clr_flag; //全局清零 120 121 //将输入数据打两拍 122 always@(posedge clk or negedge rst_n)begin 123 if(((~(rst_n))) == 1'b1) 124 begin 125 vsync_r <= #1 1'b0; 126 dvalid_r <= #1 1'b0; 127 dvalid_r2 <= #1 1'b0; 128 din_r <= #1 {DW{1'b0}}; 129 din_r2 <= #1 {DW{1'b0}}; 130 end 131 else 132 begin 133 vsync_r <= #1 vsync; 134 dvalid_r <= #1 din_valid; 135 dvalid_r2 <= #1 dvalid_r; 136 din_r <= #1 din; 137 din_r2 <= #1 din_r; 138 end 139 end 140 141 //输入行同步计数,确定统计的开始和结束时刻 142 assign #1 hsync_fall = dvalid_r & (~(din_valid)); 143 assign #1 hsync_rise = (~(dvalid_r)) & din_valid; 144 145 always@(posedge clk or negedge rst_n)begin 146 if(((~(rst_n))) == 1'b1) 147 hsync_count <= #1 {10{1'b0}}; 148 else 149 begin 150 if(vsync_r == 1'b1) 151 hsync_count <= #1 {10{1'b0}}; 152 else if(hsync_fall == 1'b1) 153 hsync_count <= hsync_count + 10'b1; 154 else 155 hsync_count <= hsync_count; 156 end 157 end 158 159 //一帧图像结束后停止统计 下一帧图像到来时开始计数 160 always@(posedge clk or negedge rst_n)begin 161 if(((~(rst_n))) == 1'b1) 162 count_en <= #1 1'b0; 163 else 164 begin 165 if(hsync_count >= IH) 166 count_en <= #1 1'b0; 167 else if(hsync_rise == 1'b1) 168 count_en <= #1 1'b1; 169 else 170 count_en <= #1 count_en; 171 end 172 end 173 174 assign mux_addr_b = ((count_en == 1'b1)) ? din_r : clr_addr; 175 assign mux_addr_b2 = ((count_en == 1'b1)) ? din_r : clr_addr_r; 176 177 //统计递增计数器 178 always@(posedge clk)begin 179 if(rst_cnt == 1'b1) 180 counter <= #1 {{TW-1{1'b0}},1'b1}; //复位值为1 181 else if(inc_en == 1'b1) 182 counter <= #1 counter + {{TW-1{1'b0}},1'b1}; 183 else 184 counter <= #1 counter; 185 end 186 187 assign #1 rst_cnt = ((din_r != din_r2) | ((dvalid_r2 == 1'b1) & (dvalid_r == 1'b0))) ? 1'b1 : 1'b0; 188 assign #1 inc_en = (((din_r == din_r2) & (dvalid_r2 == 1'b1))) ? 1'b1 : 1'b0; 189 assign #1 we_a = ((((din_r != din_r2) & (dvalid_r2 == 1'b1)) |((dvalid_r2 == 1'b1) & (dvalid_r == 1'b0)))) ? 1'b1 : 1'b0; 190 assign #1 count_value = ((count_en == 1'b1)) ? counter+q_b : {TW{1'b0}}; 191 assign #1 addr_a = din_r2; 192 193 194 //直方图存储器 分高16位和低16位分别存储 195 hist_buffer dpram_bin_l( 196 .address_a(addr_a), //输入地址为像素灰度值 197 .address_b(mux_addr_b), //读出和清零地址 198 .clock(clk), //同步时钟 199 .data_a(count_value[HALF_WIDTH-1:0]), //当前计数值 200 .data_b({HALF_WIDTH{1'b0}}), //清零数据 201 .wren_a(we_a), 202 .wren_b(we_b_l), 203 .q_a(q_a[HALF_WIDTH-1:0]), 204 .q_b(q_b[HALF_WIDTH-1:0]) 205 ); 206 207 208 hist_buffer dpram_bin_h( 209 .address_a(addr_a), 210 .address_b(mux_addr_b2), 211 .clock(clk), 212 .data_a(count_value[TW-1:HALF_WIDTH]), 213 .data_b({HALF_WIDTH{1'b0}}), 214 .wren_a(we_a), 215 .wren_b(we_b_h), 216 .q_a(q_a[TW-1:HALF_WIDTH]), 217 .q_b(q_b[TW-1:HALF_WIDTH]) 218 ); 219 220 always@(posedge clk or negedge rst_n)begin 221 if(((~(rst_n))) == 1'b1) 222 count_en_r <= #1 1'b0; 223 else 224 count_en_r <= #1 count_en; 225 end 226 227 //读出电路逻辑,计数时不能输出,读出请求时才输出 228 assign rstOutput = count_en_r | (~(rdyOutput)); 229 230 //输出像素计数 231 always@(posedge clk)begin 232 if(rstOutput == 1'b1) 233 out_pixel <= {DW+1{1'b0}}; 234 else begin 235 if((~count_all) == 1'b1) 236 begin 237 if(out_pixel == (((2 ** (DW + 1)) - 1))) 238 out_pixel <= #1 {DW+1{1'b0}}; //输出完毕 239 else 240 out_pixel <= #1 out_pixel + 1'b1; 241 end 242 end 243 end 244 245 //统计结束信号 246 always@(posedge clk)begin 247 //count_all_r <= (~rstOutput); 248 we_b_h <= we_b_l; 249 clr_addr_r <= clr_addr; 250 if(out_pixel == (((2 ** (DW + 1)) - 1))) 251 count_all <= #1 1'b1; 252 else if(count_en == 1'b1) 253 count_all <= #1 1'b0; 254 end 255 256 //全局清零信号 257 assign clr_flag = vsync; 258 259 //中断输出 信号读出操作完成 260 always@(posedge clk or negedge rst_n)begin 261 if((~(rst_n)) == 1'b1) 262 begin 263 int_flag <= 1'b1; 264 int_r <= 1'b1; 265 end 266 else 267 begin 268 int_flag <= #1 int_r; 269 if(clr_flag == 1'b1) 270 int_r <= #1 1'b1; 271 else if(out_pixel >= (((2 ** (DW + 1)) - 1))) 272 int_r <= #1 1'b0; 273 end 274 end 275 276 assign we_b_l = (((out_pixel[0] == 1'b1) & (count_all == 1'b0))) ? 1'b1 : 1'b0; 277 278 //清零地址,与读出地址反相 279 assign clr_addr = out_pixel[DW:1]; 280 281 wire dout_valid_temp; 282 283 wire [HALF_WIDTH-1:0] dout_temp; 284 285 always@(posedge clk or negedge rst_n)begin 286 if((~(rst_n)) == 1'b1) 287 begin 288 dout <= {HALF_WIDTH{1'b0}}; 289 dout_valid <= 1'b0; 290 end 291 else 292 begin 293 dout <= #1 dout_temp; 294 dout_valid <= #1 dout_valid_temp; 295 end 296 end 297 298 assign dout_temp = (we_b_l == 1'b1) ? q_b[HALF_WIDTH-1:0] : q_b[TW-1:HALF_WIDTH]; 299 300 assign dout_valid_temp = we_b_h | we_b_l; //输出使能 301 302 //assign dout_clk = (dout_valid) ? we_b_h : 1'b0; 303 304 //assign dout = q_b; 305 306 always@(posedge clk or negedge rst_n)begin 307 if((~(rst_n)) == 1'b1) 308 hist_cnt <= {TW{1'b0}}; //复位清零 309 else begin 310 if(vsync_r == 1'b0 & vsync == 1'b1) //新的一帧到来时清零 311 hist_cnt <= {TW{1'b0}}; 312 else if(out_pixel[0] == 1'b1) //每个像素读出时刻 313 hist_cnt <= hist_cnt + q_b; //将结果累加 314 else 315 hist_cnt <= hist_cnt; 316 end 317 end 318 319 reg [DW:0] out_pixel_r; 320 reg [DW-1:0] out_pixel_r2; 321 322 wire [TW-1:0] hist_cnt_temp; 323 324 always@(posedge clk or negedge rst_n)begin 325 if((~(rst_n)) == 1'b1)begin 326 out_pixel_r <= {DW+1{1'b0}}; 327 out_pixel_r2 <= {DW{1'b0}}; 328 hist_cnt_out <= {TW{1'b0}}; 329 end 330 else begin 331 out_pixel_r <= #1 out_pixel; 332 out_pixel_r2 <= #1 out_pixel_r[DW:1]; 333 hist_cnt_out <= #1 hist_cnt_temp; //将数据打一拍后输出 334 end 335 end 336 337 hist_buffer_cnt hist_cnt_buf( 338 .address_a(out_pixel_r2), //写入地址,直方图当前地址 339 .address_b(hist_cnt_addr), //读出地址 340 .clock(clk), //同步时钟 341 .data_a(hist_cnt), //写入数据 342 .data_b(), 343 .wren_a(dout_valid), //写入时刻:直方图数据有效 344 .wren_b(1'b0), 345 .q_a(), 346 .q_b(hist_cnt_temp) //输出数据 347 ); 348 349 `ifdef LinearTransfer 350 reg [DW-1:0] lowIndex_tmp; 351 reg [DW-1:0] highIndex_tmp; 352 reg [DW-1:0] highIndex_tmp2; 353 reg bFindMax; 354 reg bFindMin; 355 356 always@(posedge clk or negedge rst_n)begin 357 if((~(rst_n)) == 1'b1) 358 begin 359 lowIndex_tmp <= {DW{1'b0}}; 360 highIndex_tmp <= {DW{1'b1}}; 361 bFindMin <= 1'b0; 362 bFindMax <= 1'b0; 363 highIndex_tmp2 <= {DW{1'b0}}; 364 end 365 else 366 begin 367 if(vsync_r == 1'b0 & vsync == 1'b1) 368 begin 369 lowIndex_tmp <= {DW{1'b0}}; 370 highIndex_tmp <= {DW{1'b1}}; 371 highIndex_tmp2 <= {DW{1'b0}}; 372 373 //A 374 lowIndex <= lowIndex_tmp; 375 //B 376 if(bFindMax == 1'b1) 377 highIndex <= highIndex_tmp; 378 else 379 highIndex <= highIndex_tmp2; 380 381 bFindMin <= 1'b0; 382 bFindMax <= 1'b0; 383 end 384 else 385 begin 386 if(out_pixel[0] == 1'b1) 387 begin 388 if((~(q_b == {HALF_WIDTH{1'b0}}))) 389 highIndex_tmp2 <= clr_addr - 4'h1; 390 391 if((hist_cnt >= lowCnt) & bFindMin == 1'b0) 392 begin 393 lowIndex_tmp <= clr_addr - 4'h1; 394 bFindMin <= 1'b1; 395 end 396 397 if(hist_cnt >= (TOTAL_CNT - highCnt) & bFindMax == 1'b0) 398 begin 399 highIndex_tmp <= clr_addr - 4'h1; 400 bFindMax <= 1'b1; 401 end 402 end 403 end 404 end 405 end 406 `endif 407 408 endmodule
(2)hist_linear_transform.v,直方图线性拉伸文件。该部分代码唯一要注意的时,除法IP核的例化,计算时钟延迟为15个时钟,与书中所提19个时钟有偏差,事实上两个16位的数据除法
最大结果输出延迟应该只有16个,不应该有19个。
1 `timescale 1ps/1ps 2 3 //===================================================================================================// 4 //FileName: hist_linear_transform.v 5 //Date: 2020-03-16 6 //===================================================================================================// 7 8 module hist_linear_transform( 9 rst_n, 10 clk, 11 din_valid, //输入有效 12 din, //输入数据流 13 dout, //输出数据 14 vsync, //输入场同步 15 dout_valid, //输出有效 16 vsync_out, //输出场同步 17 lowCnt, //输入低阈值 18 highCnt //输入高阈值 19 ); 20 21 parameter DW = 8; 22 parameter IH = 512; 23 parameter IW = 640; 24 parameter TW = 32; 25 parameter DW_DIVIDE = 16; //除法器位宽 26 localparam TOTAL_CNT = IW * IH; 27 localparam HALF_WIDTH = (TW >> 1); 28 29 localparam divide_latency = 15; //除法器计算延迟 30 localparam latency = divide_latency + 2; 31 32 //port declared 33 input rst_n; 34 input clk; 35 36 input din_valid; 37 input [DW-1:0] din; 38 output [DW-1:0] dout; 39 input vsync; 40 output vsync_out; 41 output dout_valid; 42 input [TW-1:0] lowCnt; 43 input [TW-1:0] highCnt; 44 45 //variable declared 46 //索引值计算值,也就是公式中的A和B 47 wire [DW-1:0] lowIndex; 48 wire [DW-1:0] highIndex; 49 50 //首先例化一个直方图统计模块,计算两个截断索引值 51 histogram_2d histogram_2d_inst( 52 .rst_n(rst_n), 53 .clk(clk), 54 .din_valid(din_valid), 55 .din(din), 56 .dout(), 57 .vsync(vsync), 58 .dout_valid(), 59 .int_flag(), 60 .lowCnt(lowCnt), 61 .highCnt(highCnt), 62 .rdyOutput(1'b1), 63 .hist_cnt_addr(), 64 .hist_cnt_out(), 65 .lowIndex(lowIndex), //索引值输出A 66 .highIndex(highIndex) //索引值输出B 67 ); 68 69 defparam histogram_2d_inst.DW = DW; 70 defparam histogram_2d_inst.IH = IH; 71 defparam histogram_2d_inst.IW = IW; 72 73 //由于至少需要等待一帧输出数据,需对输入图像帧进行计数 74 wire vsync_fall; 75 wire valid; 76 reg [1:0] frame_cnt; 77 reg hist_valid_temp; 78 reg vsync_r; 79 80 always@(posedge clk or negedge rst_n)begin 81 if((~(rst_n)) == 1'b1)begin 82 vsync_r <= #1 1'b0; 83 hist_valid_temp <= 1'b0; 84 frame_cnt <= 2'b00; 85 end 86 else begin 87 vsync_r <= #1 vsync; 88 89 if(vsync_fall) 90 frame_cnt <= frame_cnt + 2'b01; 91 else 92 frame_cnt <= frame_cnt; 93 94 if(frame_cnt >= 2'b10) 95 hist_valid_temp <= 1'b1; 96 end 97 end 98 99 assign vsync_fall = (vsync & ~vsync_r); 100 101 //全局有效信号 102 assign valid = hist_valid_temp & din_valid; 103 104 reg [latency:0] vlaid_r; 105 //缓存有效信号,以等待除法运算 106 always@(posedge clk or negedge rst_n)begin 107 if((~(rst_n)) == 1'b1) 108 begin 109 vlaid_r[latency:0] <= {latency+1{1'b0}}; 110 end 111 else 112 begin 113 vlaid_r <= #1 {vlaid_r[latency-1:0],valid}; 114 end 115 end 116 117 /* 118 reg [DW-1:0] din_r; 119 always@(posedge clk or negedge rst_n)begin 120 if((~(rst_n)) == 1'b1) 121 begin 122 din_r <= {DW{1'b0}}; 123 end 124 else 125 begin 126 din_r <= #1 din; 127 end 128 end 129 */ 130 //1.首先计算(B-A) 131 reg [DW-1:0] diff; 132 reg [DW-1:0] diff_r; //将计算结果缓存一拍 133 134 always@(posedge clk or negedge rst_n)begin 135 if((~(rst_n)) == 1'b1) 136 begin 137 diff <= {DW{1'b0}}; 138 diff_r <= {DW{1'b0}}; 139 end 140 else 141 begin 142 diff_r <= #1 diff; 143 if(valid == 1'b1) 144 if(highIndex > lowIndex) 145 diff <= #1 highIndex - lowIndex; //(B-A)输出 146 else 147 diff <= {DW{1'b1}}; //指示异常 148 else 149 diff <= {DW{1'b0}}; 150 end 151 end 152 153 //2.接着计算(f(x,y)-A) 154 reg [DW-1:0] diff_l; 155 always@(posedge clk or negedge rst_n)begin 156 if((~(rst_n)) == 1'b1) 157 begin 158 diff_l <= {DW{1'b0}}; 159 end 160 else 161 begin 162 if(valid == 1'b1) 163 begin 164 if(din <= lowIndex) //当f(x,y) <= A时置零 165 diff_l <= {DW{1'b0}}; 166 else 167 diff_l <= #1 din - lowIndex; //这里也包含了当f(x,y)>=B的情况,我们将在除法之后进行处理 168 end 169 end 170 end 171 172 //3.下一个时钟计算(f(x,y)-A)*255 173 reg [2*DW-1:0] square; 174 always@(posedge clk or negedge rst_n)begin 175 if((~(rst_n)) == 1'b1) 176 begin 177 square <= {2*DW{1'b0}}; 178 end 179 else 180 begin 181 if(vlaid_r[0] == 1'b1) 182 begin 183 square <= #1 diff_l * {DW{1'b1}}; //直接相乘 184 end 185 end 186 end 187 188 //4.计算商 189 wire divide_en; 190 wire [DW_DIVIDE-1:0] quotient; //商 191 wire [DW_DIVIDE-1:0] nc_rem; //余数 192 wire [DW_DIVIDE-1:0] denom; //分子 193 wire [DW_DIVIDE-1:0] numer; //分母 194 195 assign denom = {diff_r == {DW{1'b0}}} ? {DW_DIVIDE{1'b1}} : diff_r; //防止分母出现0 196 197 assign numer = (vlaid_r[0] == 1'b1) ? square : {DW_DIVIDE{1'b0}}; 198 199 //调用除法器IP核 200 slope_cal cal_slope( 201 .clken(1'b1), 202 .clock(clk), 203 .denom(denom), //分母 204 .numer(numer), //分子 205 .quotient(quotient), //商 206 .remain(nc_rem) //余数总为正 207 ); 208 209 wire [DW-1:0] quotient_temp; 210 wire [DW-1:0] quotient_temp1; 211 212 //除法结果进行四舍五入处理 213 assign quotient_temp1 = (nc_rem >= diff[DW-1:1]) ? (quotient+1) : quotient; 214 215 //所得结果大于255? 说明输入像素大于B,直接置255 216 assign quotient_temp = (quotient_temp1 >= {DW{1'b1}}) ? ({DW{1'b1}}) : (quotient_temp1[DW-1:0]); 217 218 reg [DW-1:0] dout_temp; 219 220 always@(posedge clk or negedge rst_n)begin 221 if((~(rst_n)) == 1'b1) 222 begin 223 dout_temp <= {DW{1'b0}}; 224 end 225 else 226 begin 227 if(vlaid_r[latency-1]) 228 dout_temp <= #1 quotient_temp; //输出结果 229 end 230 end 231 232 assign dout = dout_temp; 233 assign dout_valid = vlaid_r[latency]; 234 assign vsync_out = vsync; 235 236 endmodule
(3)顶层代码设计,hist_transform.v文件。包括视频流捕获、灰度图像输出及直方图线性拉伸模块。
1 `timescale 1ps/1ps 2 3 //=========================================================================================// 4 //FileName: hist_transform.v TOP FILE 5 //Date: 2020-03-16 6 //=========================================================================================// 7 8 module hist_transform( 9 RSTn, //全局复位 10 CLOCK, //系统时钟 11 12 IMG_CLK, //像素时钟 13 IMG_DVD, //像素值 14 IMG_DVSYN, //输入场信号 15 IMG_DHSYN, //输入数据有效信号 16 HISTTRANS_DAT, //输出直方图线性拉伸数据 17 HISTTRANS_VALID, //输出直方图线性拉伸数据有效信号 18 HISTTRANS_VSYNC //输出直方图线性拉伸场有效信号 19 ); 20 21 /*image parameter*/ 22 parameter iw = 640; //image width 23 parameter ih = 512; //image height 24 parameter trig_value = 400; //250 25 parameter tw = 32; //直方图统计数据位宽 26 27 localparam half_width = (tw >> 1); //将32位的数据位宽拆分为高低16位 28 29 /*data width*/ 30 parameter dvd_dw = 8; //image source data width 31 parameter dvd_chn = 3; //channel of the dvd data: when 3 it's rgb or 4:4:YCbCr 32 parameter local_dw = dvd_dw * dvd_chn; //local algorithem process data width 33 parameter cmd_dw = dvd_dw * dvd_chn; //local algorithem process data width 34 35 //Port Declared 36 input RSTn; 37 input CLOCK; 38 input IMG_CLK; 39 input [dvd_dw-1:0] IMG_DVD; 40 input IMG_DVSYN; 41 input IMG_DHSYN; 42 output [dvd_dw-1:0] HISTTRANS_DAT; 43 output HISTTRANS_VALID; 44 output HISTTRANS_VSYNC; 45 46 47 //Variable Declared 48 wire GRAY_CLK; 49 wire GRAY_VSYNC; 50 wire GRAY_DVALID; 51 wire [dvd_dw-1:0] Y_DAT; 52 wire [dvd_dw-1:0] Cb_DAT; 53 wire [dvd_dw-1:0] Cr_DAT; 54 55 wire [local_dw-1:0] RGB_DAT; 56 wire RGB_DVALID; 57 wire RGB_VSYNC; 58 59 video_cap video_cap_inst( 60 .reset_l(RSTn), //异步复位信号 61 .DVD(IMG_DVD), //输入视频流 62 .DVSYN(IMG_DVSYN), //输入场同步信号 63 .DHSYN(IMG_DHSYN), //输入行同步 64 .DVCLK(IMG_CLK), //输入DV时钟 65 .cap_dat(RGB_DAT), //输出RGB通道像素流,24位 66 .cap_dvalid(RGB_DVALID), //输出数据有效 67 .cap_vsync(RGB_VSYNC), //输出场同步 68 .cap_clk(CLOCK), //本地逻辑时钟 69 .img_en(), 70 .cmd_rdy(), //命令行准备好,代表可以读取 71 .cmd_rdat(), //命令行数据输出 72 .cmd_rdreq() //命令行读取请求 73 ); 74 75 defparam video_cap_inst.DW_DVD = dvd_dw; 76 defparam video_cap_inst.DW_LOCAL = local_dw; 77 defparam video_cap_inst.DW_CMD = cmd_dw; 78 defparam video_cap_inst.DVD_CHN = dvd_chn; 79 defparam video_cap_inst.TRIG_VALUE = trig_value; 80 defparam video_cap_inst.IW = iw; 81 defparam video_cap_inst.IH = ih; 82 83 RGB2YCrCb RGB2YCrCb_Inst( 84 .RESET(RSTn), //异步复位信号 85 86 .RGB_CLK(CLOCK), //输入像素时钟 87 .RGB_VSYNC(RGB_VSYNC), //输入场同步信号 88 .RGB_DVALID(RGB_DVALID), //输入数据有信号 89 .RGB_DAT(RGB_DAT), //输入RGB通道像素流,24位 90 91 .YCbCr_CLK(GRAY_CLK), //输出像素时钟 92 .YCbCr_VSYNC(GRAY_VSYNC), //输出场同步信号 93 .YCbCr_DVALID(GRAY_DVALID), //输出数据有效信号 94 .Y_DAT(Y_DAT), //输出Y分量 95 .Cb_DAT(Cb_DAT), //输出Cb分量 96 .Cr_DAT(Cr_DAT) //输出Cr分量 97 ); 98 99 defparam RGB2YCrCb_Inst.RGB_DW = local_dw; 100 defparam RGB2YCrCb_Inst.YCbCr_DW = dvd_dw; 101 102 103 hist_linear_transform hist_linear_transform_Inst( 104 .rst_n(RSTn), 105 .clk(GRAY_CLK), 106 .din_valid(GRAY_DVALID), //输入数据有效 107 .din(Y_DAT), //输入数据 108 .dout(HISTTRANS_DAT), //输出数据 109 .vsync(GRAY_VSYNC), //输入场同步 110 .dout_valid(HISTTRANS_VALID), //输出有效 111 .vsync_out(HISTTRANS_VSYNC), //输出场同步 112 .lowCnt(32'd5000), //输入低阈值 113 .highCnt(32'd5000) //输入高阈值 114 ); 115 116 defparam hist_linear_transform_Inst.DW = dvd_dw; 117 defparam hist_linear_transform_Inst.IH = ih; 118 defparam hist_linear_transform_Inst.IW = iw; 119 defparam hist_linear_transform_Inst.TW = tw; 120 121 endmodule
(4)用于Modelsim仿真的Testbench文件,同样参照书中代码。
1 `timescale 1ps/1ps 2 3 module hist_transform_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 = 1; 30 31 32 /*signal group*/ 33 reg pixel_clk = 1'b0; 34 reg reset_l; 35 reg [3:0] src_sel; 36 37 38 /*input dv group*/ 39 wire dv_clk; 40 wire dvsyn; 41 wire dhsyn; 42 wire [dvd_dw-1:0] dvd; 43 44 /*dvd source data generated for simulation*/ 45 image_src image_src_inst//#(iw*dvd_chn, ih+1, dvd_dw, h_total, v_total, sync_b, sync_e, vld_b) 46 ( 47 .clk(pixel_clk), 48 .reset_l(reset_l), 49 .src_sel(src_sel), 50 .test_data(dvd), 51 .test_dvalid(dhsyn), 52 .test_vsync(dvsyn), 53 .clk_out(dv_clk) 54 ); 55 56 defparam image_src_inst.iw = iw*dvd_chn; 57 defparam image_src_inst.ih = ih + 1; 58 defparam image_src_inst.dw = dvd_dw; 59 defparam image_src_inst.h_total = h_total; 60 defparam image_src_inst.v_total = v_total; 61 defparam image_src_inst.sync_b = sync_b; 62 defparam image_src_inst.sync_e = sync_e; 63 defparam image_src_inst.vld_b = vld_b; 64 65 /*local clk: also clk of all local modules*/ 66 reg cap_clk = 1'b0; 67 68 /*hist equalized operation module*/ 69 generate 70 if(hist_equalized_en != 0)begin : equalized_operation 71 wire equalized_dvalid; 72 wire [dvd_dw-1:0] equalized_data; 73 wire equalized_vsync; 74 75 wire equalized_dvalid_in; 76 wire [dvd_dw-1:0] equalized_data_in; 77 wire equalized_vsync_in; 78 79 integer fp_equalized,cnt_equalized = 0; 80 81 /*video capture: capture image src and transfer it into local timing*/ 82 hist_equal hist_equal_inst( 83 .RSTn(reset_l), //全局复位 84 .CLOCK(cap_clk), //系统时钟 85 86 .IMG_CLK(pixel_clk), //像素时钟 87 .IMG_DVD(equalized_data_in), //像素值 88 .IMG_DVSYN(equalized_vsync_in), //输入场信号 89 .IMG_DHSYN(equalized_dvalid_in), //输入数据有效信号 90 .HISTEQUAL_DAT(equalized_data), //输出直方图统计数据 91 .HISTEQUAL_VALID(equalized_dvalid), //输出直方图统计有效 92 .HISTEQUAL_VSYNC(equalized_vsync) //数据读出请求 93 ); 94 95 assign equalized_data_in = dvd; 96 assign equalized_dvalid_in = dhsyn; 97 assign equalized_vsync_in = dvsyn; 98 99 always@(posedge cap_clk or posedge equalized_vsync)begin 100 if((~(equalized_vsync)) == 1'b0) 101 cnt_equalized = 0; 102 else begin 103 if(equalized_dvalid == 1'b1)begin 104 fp_equalized = $fopen("E:/Modelsim/hist_equalized/sim/equalized.txt","r+"); 105 $fseek(fp_equalized,cnt_equalized,0); 106 $fdisplay(fp_equalized,"%02X",equalized_data); 107 $fclose(fp_equalized); 108 cnt_equalized <= cnt_equalized + 4; 109 end 110 end 111 end 112 end 113 endgenerate 114 115 /*hist linear transform module*/ 116 generate 117 if(display_transform_en != 0) begin: display_transform_operation 118 wire dis_trans_dvalid; 119 wire [dvd_dw-1:0] dis_trans_data; 120 wire dis_trans_vsync; 121 wire dis_trans_dvalid_in; 122 wire [dvd_dw-1:0] dis_trans_data_in; 123 wire dis_trans_vsync_in; 124 125 integer fp_dis_trans,cnt_dis_trans = 0; 126 127 hist_transform hist_transform_inst( 128 .RSTn(reset_l), //全局复位 129 .CLOCK(cap_clk), //系统时钟 130 131 .IMG_CLK(pixel_clk), //像素时钟 132 .IMG_DVD(dis_trans_data_in), //像素值 133 .IMG_DVSYN(dis_trans_vsync_in), //输入场信号 134 .IMG_DHSYN(dis_trans_dvalid_in), //输入数据有效信号 135 .HISTTRANS_DAT(dis_trans_data), //输出直方图线性拉伸数据 136 .HISTTRANS_VALID(dis_trans_dvalid), //输出直方图线性拉伸数据有效信号 137 .HISTTRANS_VSYNC(dis_trans_vsync) //输出直方图线性拉伸场有效信号 138 ); 139 140 assign dis_trans_data_in = dvd; 141 assign dis_trans_dvalid_in = dhsyn; 142 assign dis_trans_vsync_in = dvsyn; 143 144 always@(posedge cap_clk or posedge dis_trans_vsync)begin 145 if((~(dis_trans_vsync)) == 1'b0) 146 cnt_dis_trans = 0; 147 else 148 begin 149 if(dis_trans_dvalid == 1'b1) 150 begin 151 fp_dis_trans = $fopen("E:/Modelsim/hist_transform/sim/dis_trans.txt","r+"); 152 $fseek(fp_dis_trans,cnt_dis_trans,0); 153 $fdisplay(fp_dis_trans,"%02x",dis_trans_data); 154 $fclose(fp_dis_trans); 155 cnt_dis_trans <= cnt_dis_trans + 4; 156 end 157 end 158 end 159 end 160 endgenerate 161 162 initial 163 begin: init 164 reset_l <= 1'b1; 165 src_sel <= 4'b0000; 166 #(100); //reset the system 167 reset_l <= 1'b0; 168 #(100); 169 reset_l <= 1'b1; 170 end 171 172 //dv_clk generate 173 always@(reset_l or pixel_clk)begin 174 if((~(reset_l)) == 1'b1) 175 pixel_clk <= 1'b0; 176 else 177 begin 178 if(clk_freq == 48) //48MHz 179 pixel_clk <= #10417 (~(pixel_clk)); 180 181 else if(clk_freq == 51.84) //51.84MHz 182 pixel_clk <= #9645 (~(pixel_clk)); 183 184 else if(clk_freq == 72) //72MHz 185 pixel_clk <= #6944 (~(pixel_clk)); 186 end 187 end 188 189 //cap_clk generate: 25MHz 190 always@(reset_l or cap_clk)begin 191 if((~(reset_l)) == 1'b1) 192 cap_clk <= 1'b0; 193 else 194 cap_clk <= #20000 (~(cap_clk)); 195 end 196 197 endmodule 198
四、实验结果
(1)仿真过程中,设置Thr_Min和Thr_Max的值均为100,这两个值定义了首尾被截断的直方图统计数目(其实就是累加和小于100或者大于IH*IW-100的部分被剔除掉)。
lowIndex的计算过程时序图;从图中可以看出当clr_addr=10的时候,累加和为105大于100,因此lowIndex(即A)=9。
highIndex的计算过程时序图;当clr_addr为223时,累加和为327600大于(327680-100),因此highIndex(即B) = 222。
(3)线性拉伸计算过程的仿真时序,从时序中可以看出经过两个时钟后得到了f(x,y)-A的值,同时除法器消耗了15个时钟,最后在数据有效后的第17个时钟输出了正确的计算结果。其中以第一个像素值
为例,255*(137-9)/(222-9) = 153,与实际仿真结果相符合。
(4)图像仿真结果,如下图所示,为图像直方图拉伸前后的统计直方图对比。从图中可以看出经过算法处理后直方图由原来的[9 222]的区间映射到了[0 255]的区间。由于小于100和大于327580的部分被
置成了0和255,所以拉伸后的图像中有许多黑/白点。另外由于100的阈值可能不是很适合Lena这副图像的处理,所以对比度上虽然有所增强,但是效果并不明显。个人尝试调整过更改阈值,增大后对比度
确实有明显的增强,但随之而来也会引入大量的黑/白点噪声,因此这个值的选择是一个需要权衡的问题。