首先,直方图均衡发展到现在,以及有许多版本,比如CLAHE,笔者在这里先只写自己如何实现最普通的HE。
实现直方图均衡前,需要先实现直方图统计。
直方图统计就是统计一副图像中各灰度级的像素数量,比如:
FPGA实现:
首先,需要一个RAM来存储统计的数据,数据位宽视图像大小而定,比如一副1024x768的图,考虑最极端的情况,全部是同一个灰度级,那么数据位宽就是20位,地址位宽8位,因为灰度级是0到255。
其次,考虑如何统计,笔者参考《基于FPGA的数字图像处理原理及应用》,比较前后两个像素点,若相同则计数器加一;若不同则计数器重置为1,且此时写使能有效,写数据为读数据加计数器重置前的值。
可能有点绕,下面是时序图,de代表data_in有效,de1是de的一个延迟,de2是de的两个时延:
并且每行最后一个数据时,写使能也要有效,可以通过判断de的下降沿来实现。
每一行的计数都是这样,然后在计数完之后,还需要进行清零,以等待下一帧图像。清零的判断可以通过行计数器row_cnt来实现,每次在de的下降沿加一,假设图像的行数为IH,那么row_cnt计数到IH时表示清零开始,写使能有效,然后此时写地址从0加到255,写数据为0,当写地址加到255时,写使能无效。
完整代码如下:
1 module he_top( 2 input clk, 3 input reset_n, 4 input [7:0]data_in, 5 input vs, 6 input hs, 7 input de, 8 output [19:0]he_calculate, 9 output o_he_de, 10 output [7:0]o_he_addr 11 12 ); 13 localparam IH='d768; 14 reg de_r; 15 reg de_r2; 16 reg hs_r; 17 reg vs_r; 18 reg [9:0]CNT; 19 wire CNT_en; 20 reg [7:0]rd_addr; 21 reg [7:0]wr_addr; 22 wire wr_en; 23 24 wire [19:0]rd_data; 25 wire [19:0]wr_data; 26 reg [19:0]rd_data_r; 27 28 29 reg [9:0] row_cnt; 30 wire row_cnt_en; 31 reg [1:0] hs_reg; 32 33 reg [7:0]po_cnt; 34 reg calculate_end; 35 reg [1:0]calculate_end_r; 36 37 always@(posedge clk or negedge reset_n)begin 38 if(!reset_n)begin 39 de_r<=0; 40 de_r2<=0; 41 end 42 else begin 43 de_r<=de; 44 de_r2<=de_r; 45 end 46 end 47 48 always@(posedge clk or negedge reset_n)begin 49 if(!reset_n) 50 hs_r<=0; 51 else 52 hs_r<=hs; 53 end 54 55 always@(posedge clk or negedge reset_n)begin 56 if(!reset_n) 57 vs_r<=0; 58 else 59 vs_r<=vs; 60 end 61 62 always@(posedge clk or negedge reset_n)begin 63 if(!reset_n) 64 rd_addr<=0; 65 else if(de==1'b1) 66 rd_addr<=data_in; 67 else if(calculate_end==1'b1) 68 rd_addr<=po_cnt; 69 end 70 71 72 always@(posedge clk or negedge reset_n)begin 73 if(!reset_n) 74 wr_addr<=0; 75 else 76 wr_addr<=rd_addr; 77 end 78 79 assign CNT_en=(rd_addr==wr_addr)?de_r2:0; 80 always@(posedge clk or negedge reset_n)begin 81 if(!reset_n) 82 CNT<=1'b1; 83 else if(CNT_en==1'b1) 84 CNT<=CNT+1'b1; 85 else if(CNT_en==0) 86 CNT<=1'b1; 87 else 88 CNT<=CNT; 89 end 90 91 always@(posedge clk or negedge reset_n)begin 92 if(!reset_n) 93 rd_data_r<=0; 94 else 95 rd_data_r<=rd_data; 96 end 97 98 assign wr_en=(rd_addr==wr_addr)?0:de_r2; 99 assign wr_data=calculate_end_r[1]?0:(CNT+rd_data_r); 100 101 always@(posedge clk or negedge reset_n)begin 102 if(!reset_n)begin 103 hs_reg[0]<=0; 104 hs_reg[1]<=0; 105 end 106 else begin 107 hs_reg[0]<=hs; 108 hs_reg[1]<=hs_reg[0]; 109 end 110 end 111 112 assign row_cnt_en=((!de_r)&&(de_r2))?1'b1:0; 113 114 115 always@(posedge clk or negedge reset_n)begin 116 if(!reset_n) 117 row_cnt<=0; 118 else if((row_cnt_en==1'b1)&&(row_cnt<=IH-1)) 119 row_cnt<=row_cnt+1'b1; 120 else if(row_cnt==IH) 121 row_cnt<=0; 122 end 123 124 125 126 always@(posedge clk or negedge reset_n)begin 127 if(!reset_n) 128 calculate_end<=0; 129 else if(row_cnt==IH) 130 calculate_end<=1'b1; 131 else if(po_cnt==8'd255) 132 calculate_end<=0; 133 else 134 calculate_end<=calculate_end; 135 end 136 137 138 always@(posedge clk or negedge reset_n)begin 139 if(!reset_n) 140 calculate_end_r<=0; 141 else 142 calculate_end_r<={calculate_end_r[0],calculate_end}; 143 end 144 145 146 always@(posedge clk or negedge reset_n)begin 147 if(!reset_n) 148 po_cnt<=0; 149 else if(calculate_end==1'b1) 150 po_cnt<=po_cnt+1'b1; 151 end 152 153 assign he_calculate=calculate_end_r[0]?rd_data:0; 154 wire he_wr_en; 155 assign he_wr_en=(wr_en)||(row_cnt_en)||(calculate_end_r[1]); 156 157 assign o_he_de=calculate_end_r[0]; 158 assign o_he_addr=calculate_end_r[0]?rd_addr:0; 159 160 he_cal he_cal_t1 ( 161 .wr_data(wr_data), // input [19:0] 162 .wr_addr(wr_addr), // input [7:0] 163 .rd_addr(rd_addr), // input [7:0] 164 .wr_clk(clk), // input 165 .rd_clk(clk), // input 166 .wr_en(he_wr_en), // input 167 .rst(!reset_n), // input 168 .rd_data(rd_data) // output [19:0] 169 ); 170 171 endmodule
也可以用状态机来写,笔者就不详述了。