直方圖均衡化又稱為灰度均衡化,是指通過某種灰度映射使輸入圖像轉換為在每一灰度級上都有近似相同的輸出圖像(即輸出的直方圖是均勻的)。在經過均衡化處理后的圖像中,像素將占有盡可能多的灰度級並且分布均勻。因此,這樣的圖像將具有較高的對比度和較大的動態范圍。直方圖均衡可以很好地解決相機過曝光或曝光不足的問題。
一、MATLAB實現
%-------------------------------------------------------------------------- % 直方圖均衡化 %-------------------------------------------------------------------------- close all clear all; clc; I = rgb2gray(imread('car.bmp')); Ieq=histeq(I); subplot(221),imshow(I);title('原圖'); subplot(222),imhist(I); subplot(223),imshow(Ieq);title('直方圖均衡化'); subplot(224),imhist(Ieq);
點擊運行,得到如下結果:

從結果可以看出:圖片對比度顯著提高,直方圖變得更均勻。
二、FPGA實現
1、理論分析
直方圖均衡化的公式如下所示,H(i)為第 i 級灰度的像素個數,A0為圖像的面積(即分辨率),Dmax為灰度最大值,即255。

2、實現步驟
和直方圖拉伸的情況一樣,直方圖均衡化也分為真均衡化和偽均衡化。本次設計采用偽均衡化,即采用前一幀的圖像進行統計、幀間隙進行累計和與歸一化、當前幀做歸一化后的映射輸出。
統計工作至少要等到前一幀圖像“流過”之后才能完成。此限制決定了我們難以在同一幀既統計又輸出最終結果。必須對前期的統計結果進行緩存、累計和、歸一化,這點是毋庸置疑的。在下一次統計前需要將緩存結果、累計和結果清零(圖片則不需要清0),而歸一化的結果則留着給當前幀輸出使用。這里我考慮用 2 個 ram 來實現直方圖均衡化的整個過程,以圖片為例,用兩幀圖片的偽均衡化來實現。
整體構思如下所示:

我們可以按下面步驟來實現:
(1)前一幀:統計圖像的直方圖 H(i),統計結果實時輸入到 ram1,注意輸入數據要進行統計疊加,這算是一個難點;
//========================================================================== //== 前一幀:直方圖灰度統計 //========================================================================== //數據前后拍進行比較 //--------------------------------------------------- assign hist_cnt_yes = gray_data_vld && gray_data_r == gray_data; //相等,可以相加 assign hist_cnt_not = gray_data_vld && gray_data_r != gray_data; //不等,只是一個 //灰度計數器 //--------------------------------------------------- always @(posedge clk or negedge rst_n) begin if(!rst_n) begin hist_cnt <= 32'b0; end else if(hist_cnt_not) begin hist_cnt <= 32'b1; end else if(hist_cnt_yes) begin hist_cnt <= hist_cnt + 1'b1; end else begin hist_cnt <= 32'b0; end end //統計結果輸入到統計 ram1 中 //--------------------------------------------------- assign wr_en_1 = hist_cnt_not; assign wr_addr_1 = gray_data_r; assign wr_data_1 = rd_data_1 + hist_cnt; assign rd_addr_1 = gray_vsync ? addr_cnt : gray_data; //幀間隙按順序輸出,前一幀按像素地址輸出 //雙口ram,存儲統計結果 //--------------------------------------------------- ram_32x256 u_ram_1 ( .clock (clk ), .wren (wr_en_1 ), .wraddress (wr_addr_1 ), .data (wr_data_1 ), .rdaddress (rd_addr_1 ), .q (rd_data_1 ) );
(2)前一幀到當前幀的間隙:設計計數器addr_cnt,計數0-255,以此為地址從 ram1 中輸出統計結果,然后進行累加和
,注意一下時序對齊。
//給出addr_cnt,過1拍才出rd_data_1,相當於消耗1clk,與之對齊的是addr_flag_r1 //累加和的計算又耗費1clk //--------------------------------------------------- always @(posedge clk or negedge rst_n) begin if(!rst_n) begin sum <= 32'b0; end else if(addr_flag_r1) begin sum <= sum + rd_data_1; end else begin sum <= 32'b0; end end
(3)前一幀到當前幀的空隙:在進行第(2)步時,同時也要邊進行均衡化,即
,結果輸入到 ram2 中;這里公式計算要避免乘除法,需要一番技巧,注意一下時序對齊。
//========================================================================== //== 幀間隙,求和后進行均衡化運算 //== 圖像分辨率640*480,為避免乘除法采用640*512來處理 //== [(2^5+2^4)+(2^2+2^1)] / 2^16, 為優化時序用流水線花2拍處理 //==========================================================================
(4)當前幀:均衡化后的數據輸出,以像素為 ram2 地址,從 ram2 中實時輸出均衡化后的映射結果,代替原像素輸出,達到均衡化的目的。
//========================================================================== //== 當前幀,直方圖均衡化后的映射輸出 //========================================================================== ram_32x256 u_ram_2 ( .clock (clk ), .wren (addr_flag_r4 ), //寫使能 .wraddress (addr_cnt_r4 ), //順序地址 .data (step_2 ), //歸一化結果 .rdaddress (gray_data ), .q (hist_data ) ); //ram讀數據會落后讀使能一拍,因此其他信號也要打拍對齊 //--------------------------------------------------- always @(posedge clk) begin hist_vsync <= gray_vsync; hist_hsync <= gray_hsync; hist_data_vld <= gray_data_vld; end
3、注意要點
(1)公式計算化簡
以640x480為例,【255/(640x480)】的運算對於 FPGA 來說比較艱難,可以轉換為移位,建議按 640x512 來算:

(2)ram
單口 ram 貌似不行,申請雙口 ram IP 吧,一套接口就夠了。
(3)時序對齊
ram發出讀地址,數據要延遲一拍或兩拍才出來(看IP設置)。累加和、公式計算等又消耗一定的拍數,設計過程中要時刻關注時序對齊,建議邊仿真邊設計。
三、結果展示
板子壞了,這次就用仿真做一下吧,采用 Matlab 將圖片生成 pre_img.txt 文件,Verilog設計一個模仿 OV7725 的時序,圖像數據部分讀取該 pre_img.txt 文件。最后處理完的數據再寫入到一個 post_img.txt文件,最后 Matlab 讀取該 post_img.txt 文件,將結果還原為圖片。全程只需要用到 Modelsim 和 Matlab 軟件,比較方便。

參考資料:
1] OpenS Lee:FPGA開源工作室(公眾號)
[2] 牟新剛、周曉、鄭曉亮.基於FPGA的數字圖像處理原理及應用[M]. 電子工業出版社,2017.
