FPGA实现图像的直方图拉伸
在视频处理中,为了能够实时调节图像的对比度,通常需要对直方图进行拉伸处理。直方图拉伸是指将图像灰度直方图较窄的灰度级区间向两端拉伸,增强整幅图像像素的灰度级对比度,达到增强图像的效果。
常用的直方图拉伸方法有线性拉伸、3段式分段线性拉伸和非线性拉伸等。这里我们介绍FPGA中常见的线性拉伸。
线性拉伸也即灰度拉伸,属于线性点运算的一种。它扩展图像的直方图,使其充满整个灰度级范围内。设f(x,y)为输入图像,它的最小灰度级A和最大灰度级B的定义如下:

将A和B分别映射到0和255,则最终得到输出图像g(x,y)为

一、MATLAB实现
%-------------------------------------------------------------------------- %-- 灰度直方图拉伸 %-------------------------------------------------------------------------- close all clear all; clc; I = rgb2gray(imread('car.bmp')); %读图转灰度 Imin = min(min(I)); Imax = max(max(I)); [M,N] = size(I); C =255/(Imax-Imin); Inew = zeros(size(I)); for i=1:M for j=1:N if(I(i,j)==Imin) Inew(i,j) = 0; elseif(I(i,j)==Imax) Inew(i,j) = 255; else Inew(i,j)=(C.*(I(i,j)-Imin)); end end end Inew = uint8(Inew); subplot(221),imshow(I); title('灰度图'); subplot(223),imshow(Inew);title('灰度拉伸图'); subplot(222),imhist(I); title('灰度直方图统计'); subplot(224),imhist(Inew);title('灰度拉伸后的直方图统计');
点击运行,得到如下结果:

由此可以看出:线性拉伸达到了类似直方图均衡化的功能,将比较集中的直方图拉伸到整个灰度分布区域。
二、FPGA实现
1、理论分析
拉伸处理的公式如下,其中 A 为最大灰度级,B为最小灰度级,f(x,y)为原图像,g(x,y)为拉伸后的图像。

FPGA实现灰度图像的拉伸可分为真拉伸和伪拉伸,真拉伸是指公式所有值都是当前帧,处理后的结果也是当前帧。这需要对图像进行一帧的缓存,在实际应用中,处理帧缓存是费时费力费资源的一-件事情,在许多情况下,图像的变换比较慢,在这种情况下的一-个近似是当建立当前帧的直方图统计结果时使用从前一帧得到的映射。结果是直方图均衡化可能不是非常准确,但是消耗的资源和处理的延时都有显著地减少。我们称之为伪拉伸,其实就是在前一帧计算出公式所有值,用来对本帧图像进行拉伸处理,这样设计难度大大减小。
2、实现步骤
为得到A、B的值需要进行统计工作,这至少要等到前一帧图像“流过”之后才能完成。此限制决定了我们难以在同一帧既统计又输出最终拉伸结果。必须对前期的统计结果进行缓存。这点是毋庸置疑的。在下一次统计前需要将缓存结果清零。我们可以按一下步骤来实现:
(1)前一帧:统计一帧图像的最大值和最小值,即A和B
(2)前一帧到当前帧的空隙:保存前一帧的最大值和最小值。
(3)当前帧:进行直方图拉伸公式的计算:
①计算 255 * (f(x,y) - A 和 B - A。
②计算①中,二者的商
③计算大括号,对f(x,y)进行判断,输出最终直方图拉伸的结果。
3、Verilog设计

//**************************************************************************
// *** 名称 : hist_stretch.v.v
// *** 作者 : 咸鱼FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2020年3月
// *** 描述 : Y分量实现直方图拉伸
//--------------------------------------------------------------------------
// A = min[f(x,y)], B = max[f(x,y)], f(x,y)为原图
//
// g(x,y) = 255 * [f(x,y)-A] / (B-A), g(x,y)为拉伸图
//**************************************************************************
module hist_stretch
//========================< 端口 >==========================================
(
input wire clk , //时钟
input wire rst_n , //复位
//input ---------------------------------------------
input wire Y_de , //Y分量行同步
input wire Y_hsync , //Y分量场同步
input wire Y_vsync , //Y分量数据
input wire [ 7:0] Y_data , //Y分量数据使能
//output --------------------------------------------
output wire hist_de , //hist行同步
output wire hist_hsync , //hist场同步
output wire hist_vsync , //hist数据
output reg [ 7:0] hist_data //hist数据使能
);
//========================< 信号 >==========================================
wire neg_Y_vsync ;
reg [ 7:0] max ;
reg [ 7:0] min ;
reg [ 7:0] Y_max ;
reg [ 7:0] Y_min ;
//---------------------------------------------------
reg [15:0] mole ; //分子
reg [ 7:0] deno ; //分母
reg [15:0] quot ; //商
//---------------------------------------------------
reg [ 2:0] Y_de_r ;
reg [ 2:0] Y_hsync_r ;
reg [ 2:0] Y_vsync_r ;
//==========================================================================
//== 计算一帧图像的最大值最小值
//==========================================================================
//vsync下降沿
//---------------------------------------------------
assign neg_Y_vsync = Y_vsync_r[0] && ~Y_vsync;
//实时计算大小
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
max <= 8'd0;
min <= 8'd255;
end
else if(Y_vsync && Y_de) begin //像素有效时
max <= (max > Y_data) ? max : Y_data;
min <= (min < Y_data) ? min : Y_data;
end
else if(neg_Y_vsync) begin //一帧图像结束时
max <= 8'd0;
min <= 8'd255;
end
end
//保存上一帧最大值、最小值
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
Y_max <= 'd0;
Y_min <= 'd0;
end
else if(neg_Y_vsync) begin // 一帧图像结束时
Y_max <= max;
Y_min <= min;
end
end
//==========================================================================
//== 直方图拉伸,耗费3clk
//==========================================================================
//分子和分母,1clk
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
mole <= 'd0;
deno <= 'd0;
end
else begin
mole <= (Y_data - Y_min) * 255;
deno <= Y_max - Y_min;
end
end
//g(x,y),1clk
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
quot <= 'd0;
end
else begin
quot <= mole / deno;
end
end
//获得拉伸结果,1clk
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
hist_data <= 8'd0;
end
else if(Y_data < Y_min) begin
hist_data <= 8'd0;
end
else if(Y_data > Y_max) begin
hist_data <= 8'd255;
end
else begin
hist_data <= quot[7:0];
end
end
//==========================================================================
//== 信号同步
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
Y_de_r <= 3'b0;
Y_hsync_r <= 3'b0;
Y_vsync_r <= 3'b0;
end
else begin
Y_de_r <= {Y_de_r[1:0], Y_de};
Y_hsync_r <= {Y_hsync_r[1:0], Y_hsync};
Y_vsync_r <= {Y_vsync_r[1:0], Y_vsync};
end
end
assign hist_de = Y_de_r[2];
assign hist_hsync = Y_hsync_r[2];
assign hist_vsync = Y_vsync_r[2];
endmodule
三、上板验证
原图:

直方图拉伸处理后:

拉伸后,对比度明显增强,实验成功。
后记
同样的,也可以用彩色图像的RGB三通道来这样处理,最后再合并,也能起到不错的效果。
此外本博客设计的直方图拉伸是有缺陷的,当选取的图片有白色噪声时,直方图拉伸会失败。不过面对噪声我们可以在直方图拉伸前用中值滤波先处理一下,后续的博客会整理中值滤波的实现方法。此外,本博客的实现方法基于书本《基于FPGA的数字图像处理原理及应用》,该书提供了一种改良的方法,可以应对白色噪声,但是设计难度增加很多,感兴趣的朋友可以翻阅该书学学看。
参考资料:
[1] OpenS Lee:FPGA开源工作室(公众号)
[2] 牟新刚、周晓、郑晓亮.基于FPGA的数字图像处理原理及应用[M]. 电子工业出版社,2017.

浙公网安备 33010602011771号