直方图均衡化又称为灰度均衡化,是指通过某种灰度映射使输入图像转换为在每一灰度级上都有近似相同的输出图像(即输出的直方图是均匀的)。在经过均衡化处理后的图像中,像素将占有尽可能多的灰度级并且分布均匀。因此,这样的图像将具有较高的对比度和较大的动态范围。直方图均衡可以很好地解决相机过曝光或曝光不足的问题。
一、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、实现步骤
和直方图拉伸的情况一样,直方图均衡化也分为真均衡化和伪均衡化。本次设计采用伪均衡化,即采用前一帧的图像进行统计、累计和、归一化,当前帧做均衡化的数据输出。
统计工作至少要等到前一帧图像“流过”之后才能完成。此限制决定了我们难以在同一帧既统计又输出最终拉伸结果。必须对前期的统计结果进行缓存、累计和、归一化,这点是毋庸置疑的。在下一次统计前需要将缓存结果、累计和结果清零,而归一化的结果则留着给当前帧输出使用。我们可以按一下步骤来实现:
(1)前一帧:计算图像的直方图 H(i)
(2)前一帧:计算直方图累计和
(3)前一帧到当前帧的空隙:归一化,即
(4)当前帧:均衡化后的数据输出
3、Verilog设计
先说归一化,归一化就是 *255 / 分辨率,我此次的屏幕分辨率是 480x272,经过计算,归一化的参数为 255 / 480x272 = 2 ^(-9),即计算出累计和后,结果右移9位即可。
书本《基于FPGA的数字图像处理原理及应用》中采用了 RAM 的方式来实现上述设计,该设计代码较复杂。OpenS Lee 前辈给出了一种新的实现方式:数组。使得整个设计变得异常的简洁,关键代码如下所示:
1 //************************************************************************** 2 // *** 名称 : Hist_equalization.v 3 // *** 作者 : xianyu_FPGA 4 // *** 博客 : https://www.cnblogs.com/xianyufpga/ 5 // *** 日期 : 2020年3月 6 // *** 描述 : 直方图均衡化(伪),刚好右移9位即可 7 //-------------------------------------------------------------------------- 8 // Dmax = 255 255 9 // A0 = 480000(600x800) 130560(480x272) 10 // Dmax/A0 = 0.00053125 0.001953125 = 2^(-9) 11 // *2^20 = 557 12 //************************************************************************** 13 module Hist_equalization 14 //========================< 端口 >========================================== 15 ( 16 input wire clk , 17 input wire rst_n , 18 //--------------------------------------------------- 19 input wire Y_de , 20 input wire Y_hsync , 21 input wire Y_vsync , 22 input wire [ 7:0] Y_data , 23 //--------------------------------------------------- 24 output reg hist_de , 25 output reg hist_hsync , 26 output reg hist_vsync , 27 output reg [ 7:0] hist_data 28 ); 29 //========================< 参数 >========================================== 30 parameter IDLE = 4'b0001 ; //空闲 31 parameter STATISTICS = 4'b0010 ; //统计 32 parameter ACCUMULATION = 4'b0100 ; //累计和 33 parameter NORMALIZATION = 4'b1000 ; //归一化 34 //========================< 信号 >========================================== 35 reg [ 3:0] state ; 36 wire pos_vsync ; 37 wire neg_vsync ; 38 reg [16:0] statistics[0:255] ; //480x272 = 17位 39 reg [16:0] accumulation[0:255] ; 40 reg [ 7:0] normalization[0:255] ; 41 reg [ 7:0] i,j,k ; 42 //========================================================================== 43 //== 帧开始和结束标志 44 //========================================================================== 45 assign pos_vsync = Y_vsync && ~hist_vsync; 46 assign neg_vsync = ~Y_vsync && hist_vsync; 47 //========================================================================== 48 //== 前一帧:直方图统计、累计和、归一化 49 //========================================================================== 50 always @(posedge clk or negedge rst_n) begin 51 if(!rst_n) begin 52 i <= 0; 53 j <= 0; 54 k <= 0; 55 state <= IDLE; 56 end 57 else begin 58 case(state) 59 IDLE: begin 60 if(pos_vsync) begin 61 i <= 0; 62 j <= 0; 63 k <= 0; 64 state <= STATISTICS; 65 end 66 else begin 67 i <= i + 1; 68 statistics[i] <= 0; 69 accumulation[i] <= 0; 70 end 71 end 72 STATISTICS: begin 73 if(Y_de) begin 74 statistics[Y_data] <= statistics[Y_data] + 1; 75 end 76 else if(neg_vsync) 77 state <= ACCUMULATION; 78 end 79 ACCUMULATION:begin 80 if(j == 0) begin 81 accumulation[j] <= statistics[j]; 82 j <= j + 1; 83 end 84 else if(j > 0 && j < 255) begin 85 accumulation[j] <= statistics[j] + accumulation[j-1]; 86 j <= j + 1; 87 end 88 else if(j == 255) begin 89 accumulation[j] <= statistics[j] + accumulation[j-1]; 90 state <= NORMALIZATION; 91 end 92 end 93 NORMALIZATION:begin 94 if(k < 255) begin 95 normalization[k] <= accumulation[k][16:9] + {7'b0,accumulation[k][8]}; 96 k <= k + 1; 97 end 98 else if(k == 255) begin 99 normalization[k] <= accumulation[k][16:9] + {7'b0,accumulation[k][8]}; 100 state <= IDLE; 101 end 102 end 103 default:; 104 endcase 105 end 106 end 107 //========================================================================== 108 //== 当前帧:输出直方图均衡后的数据 109 //========================================================================== 110 always @(posedge clk or negedge rst_n) begin 111 if(!rst_n) begin 112 hist_data <= 8'd0; 113 end 114 else if(Y_de) begin 115 hist_data <= normalization[Y_data]; 116 end 117 end 118 //========================================================================== 119 //== 信号同步 120 //========================================================================== 121 always @(posedge clk) begin 122 hist_de <= Y_de; 123 hist_hsync <= Y_hsync; 124 hist_vsync <= Y_vsync; 125 end 126 127 128 129 endmodule
三、上板验证
失败......因为数组消耗资源过大,我的小梅哥AC620支持不了,不过理论上应该没问题,原作者 OpenS Lee 在 Xilinx 板卡中上板成功,感兴趣的朋友可以试试。
参考资料:
1] OpenS Lee:FPGA开源工作室(公众号)
[2] 牟新刚、周晓、郑晓亮.基于FPGA的数字图像处理原理及应用[M]. 电子工业出版社,2017.