基于Modelsim的直方图线性拉伸

一、前言

  本篇主要针对牟新刚编著《基于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这副图像的处理,所以对比度上虽然有所增强,但是效果并不明显。个人尝试调整过更改阈值,增大后对比度

确实有明显的增强,但随之而来也会引入大量的黑/白点噪声,因此这个值的选择是一个需要权衡的问题。

 

 

 

 

 

 

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

 

posted @ 2020-03-18 23:25  TheSkyIsMine  阅读(922)  评论(0编辑  收藏  举报