文章目录
一、什么是直方图?
在前文《Verilog和Matlab实现RGB888互转YUV444》中我们知道,人眼对亮度的敏感度要大于色度的敏感度,图像的直方图是用来表示图像中像素亮度或颜色分布的一种图形表示方法。它通过统计图像中每个亮度值(或颜色值)出现的频率,如下图所示:
X轴表示亮度的范围;对于8色深的图像,亮度范围就是0-255 ,Y轴表示每亮度下所拥有的像素数量。在图像处理中,直方图的作用有:
- 对比度调整:通过分析直方图,可以了解图像的对比度,并进行相应的调整。
- 图像增强:可以使用直方图均衡化等技术来改善图像的视觉效果。
- 特征提取:在图像处理和计算机视觉中,直方图可以作为特征之一,用于图像分类、识别等任务。
- 分析图像质量:通过观察直方图,可以识别图像是否存在过曝或欠曝等问题。
如上面这张原始图片我们可以看出整体画面比较明亮,直方图显示各灰阶的数量都比较均衡,接下来我们看另一张图片的直方图:
我们可以看到这幅图整体画面比较暗,在直方图上面体现出来就是像素数量比较集中在左侧,相反我们也可也通过观测一张图片的直方图来大致判断这副图片的整体明暗程度。
二、什么是直方图均衡化?
直方图均衡化是一种经典的图像处理算法,用以改善图像的亮度和对比度。如果一副图片整体偏暗,灰度值大致分布在较低的范围内;图像进行直方图均衡化后会使其原本分布集中在低范围色阶的像素值,均衡的分布到所有可取值的范围,这样,图像就既有明亮也有灰暗,对比度和亮度就得到了改善,直方图均衡化的作用:
- 增强对比度:直方图均衡化通过重新分配图像的亮度值,使得图像的亮度范围更加均匀。这可以显著提高图像的对比度,使得细节更加清晰,特别是在原始图像对比度较低的情况下。
- 改善细节可见性:通过均衡化,暗部和亮部的细节会更加明显。这对于医学影像、卫星图像等需要清晰细节的应用非常重要。
- 去除光照不均:在某些情况下,图像可能由于光照条件不均而出现阴影或亮斑。均衡化可以帮助减轻这些问题,使得图像更加均匀。
- 增强特征提取:在计算机视觉和图像分析中,均衡化后的图像能更好地突出特征,使得后续的特征提取和分类任务更加有效。
- 提高图像质量:在某些情况下,直方图均衡化可以改善图像的整体视觉质量,使得图像在不同显示设备上表现得更加一致。
上面就是将一张整体偏暗的图片均衡化后的效果。可以看出,均衡化后的图片一些细节都显示出来了,均衡化后的直方图也比较均匀,我们也可以转成灰度图片来观察:
在实际实现过程中,就是要先将彩色图片转换成灰度图片后,在做均衡化处理,因为直方图均衡化只对亮度Y进行处理,对于色彩通道的Cb、Cr是不做处理的,这就是直方图均衡化的作用。
三、Matlab实现直方图均衡化的步骤
第一步: 彩色图像转成灰度图像
上文可以知道,直方图均衡化是对亮度进行操作的,因此我们处理图像时候,先将彩色图片转换成灰度图片,使得RGB三个分量的值都变成YYY,这样操作图像的数据就只是操作了亮度Y,颜色数据我们保留,彩色转灰度图像在matlab可以直接调用:
I = rgb2gray(RGB)
整体代码如下:
% 读取彩色图像
img = imread('....\....\....\...\.....bmp');
% 将彩色图像转换为灰度图像
grayImg = rgb2gray(img);
% 原图像
subplot(1, 2, 1);
imshow(img);
title('原始图像');
% 灰度图像
subplot(1, 2, 2);
imshow(grayImg);
title('灰度图像');
第二步:提取亮度通道的直方图
统计图像亮度通道各色阶中像素的个数,为后续做均衡化做准备,在matlab中直接调用:
[counts,binLocations] = imhist(I) %计算灰度图像 I 的直方图。imhist 函数在 counts 中返回直方图计数,在 binLocations 中返回 bin 位置。直方图中 bin 的数量由图像类型确定。
整体代码如下:
% 读取彩色图像
img = imread('...\....\.....\.....\....bmp');
% 将彩色图像转换为灰度图像
grayImg = rgb2gray(img);
% 计算灰度图像的直方图
[counts, grayLevels] = imhist(grayImg);
% 原图像
subplot(1, 3, 1);
imshow(img);
title('原始图像');
% 灰度图像
subplot(1, 3, 2);
imshow(grayImg);
title('灰度图像');
% 原始直方图
subplot(1, 3, 3);
bar(grayLevels, counts);
title('原始图像的直方图');
xlim([0 255]);
第三步:累计亮度通道的像素值频率
统计每一个灰度在原始图像上的像素所占总体的比例,例如一共有100个像素值,其中第26色阶有15个像素点,那么25色阶上的像素值频率 = 15/100 = 0.15 ,现在我们将每个色阶对应的频率都累加起来,可以得到一个最高值1 ;这也叫归一化累积分布函数(CDF)
举个例子:现在我有一张图像总共有1024个像素点,每个像素点的灰阶都是一样的,那么对应每一色阶的数量都是1024/256=4个,对应的分布直方图如下所示:
接下来要对灰度直方图进行归一化处理,也就是限制到[0:1]范围内。我们计算每个色阶中像素所占的总比,也叫做概率,即:每个色阶中像素的概率都是4/1024 = 0.00390625,则归一化后的直方图如下所示:
接下来对归一化后的直方图进行累加,画出累计归一化累积分布函数(CDF),如下所示:
5
这就是一张灰度不变图像归一化后累积分布函数,最高累计值为100%=1 ,接下来我们画出任意一幅图的归一化后的累计分布函数图,matlab代码如下:
% 读取图像
img = imread('...\...\.....\....bmp');
% 将图像转换为灰度图像
grayImg = rgb2gray(img);
% 计算灰度图像的直方图
[counts, grayLevels] = imhist(grayImg);
% 计算累积分布函数(CDF)
cdf = cumsum(counts);
cdf_normalized = cdf / max(cdf); % 归一化到 [0, 1] 范围
% 显示结果
figure;
% 原始图像
subplot(3, 1, 1);
imshow(img);
title('原始图像');
% 亮度直方图
subplot(3, 1, 2);
bar(grayLevels, counts, 'FaceColor', [0.2, 0.6, 0.8]);
title('灰度图像直方图');
xlabel('像素数量');
ylabel('色阶');
xlim([0 255]);
% 归一化累积分布函数
subplot(3, 1, 3);
plot(grayLevels, cdf_normalized, 'LineWidth', 2);
title('归一化累积分布函数 (CDF)');
xlabel('色阶');
ylabel('累计概率');
xlim([0 255]);
ylim([0 1]); % 设置 y 轴范围为 [0, 1]
grid on;
由此可见,理想情况下的归一化累计分布函数是一条斜直线,而实际的图片大多数都是这种不规则曲线,因此直方图均衡化的目的就是尽量把这种不规则曲线调成直线。
第四步: 映射到新的灰度值
在得到归一化的 CDF 后,为了将其映射回原始图像的灰度级范围(0 到 255),需要将其乘以 255。具体来说:归一化的 CDF 值是一个小数,乘以 255 后,可以得到一个新的灰度值,范围在 0 到 255 之间。
为什么要乘以255? 我们画出一副图像的归一化分布函数和灰度一致的图像归一化累计分布函数叠加:
橙色曲线是实际图像的累计归一化分布函数,蓝色的为灰度一致的标准累计归一化分布函数,直方图均衡化的目的就是让橙色的曲线变成直线,具体步骤就是:
假设这三个点的坐标如上所示,现在要把橙色图片变成直线,有两种方式:
- b点往上移动到a点
- c点往右移动到a点
我们可以分析出来第一种方式是不可取的,因为b点的含义表示色阶为15的像素点占比0.13,如果把b点往上移动到0.3,就相当于把色阶为15的像素点数量增加了,那么像素点总共的数量是不变的,这增加的像素点是从哪里来?所以这样不可取。
第二种方式就是把c点往左移到a点,含义就是色阶26的像素点占比0.3,把这占比0.3的像素点色阶变成15,这样的像素点占比依然是0.3,所以数量是没有变化的。具体公式就是
y
=
k
x
=
1
/
255
∗
x
y=kx=1/255 * x
y=kx=1/255∗x
x
=
255
∗
y
x = 255 * y
x=255∗y
matlab代码如下:
% 读取图像
img = imread('...\...\.....\....bmp');
% 将图像转换为灰度图像
grayImg = rgb2gray(img);
% 计算灰度图像的直方图
[counts, grayLevels] = imhist(grayImg);
% 计算累积分布函数(CDF)
cdf = cumsum(counts);
cdf_normalized = cdf / max(cdf); % 归一化到 [0, 1] 范围
% 进行直方图均衡化
equalizedImg = histeq(grayImg);
% 计算均衡化后的直方图
[counts_eq, grayLevels_eq] = imhist(equalizedImg);
% 计算均衡化后的累积分布函数(CDF)
cdf_eq = cumsum(counts_eq);
cdf_normalized_eq = cdf_eq / max(cdf_eq); % 归一化到 [0, 1] 范围
% 显示结果
figure;
% 原始图像
subplot(3, 2, 1);
imshow(grayImg);
title('原始图像');
% 原始图像的灰度直方图
subplot(3, 2, 2);
bar(grayLevels, counts);
title('原始图像的灰度直方图');
xlabel('色阶');
ylabel('像素数量');
xlim([0 255]);
% 原始图像的归一化累积分布函数
subplot(3, 2, 3);
bar(grayLevels, cdf_normalized);
title('原始图像的归一化累积分布函数');
xlabel('色阶');
ylabel('累计概率');
xlim([0 255]);
ylim([0 1]); % 设置 y 轴范围为 [0, 1]
% 均衡化后的图像
subplot(3, 2, 4);
imshow(equalizedImg);
title('均衡化后的图像');
% 均衡化后的灰度直方图
subplot(3, 2, 5);
bar(grayLevels_eq, counts_eq);
title('均衡化后的灰度直方图');
xlabel('色阶');
ylabel('像素数量');
xlim([0 255]);
% 均衡化后的归一化累积分布函数
subplot(3, 2, 6);
bar(grayLevels_eq, cdf_normalized_eq);
title('均衡化后的归一化累积分布函数');
xlabel('色阶');
ylabel('累计概率');
xlim([0 255]);
ylim([0 1]); % 设置 y 轴范围为 [0, 1]
四、Verilog实现直方图均衡化
第一步:Verilog实现彩色图像转灰度图像
这部分和可以直接使用前文《Verilog和Matlab实现RGB888互转YUV444》这个模块,把G、B也换成Y即可,代码如下:
4.1.1Verilog代码
`timescale 1ns / 1ps
module rgb2gray(
input clk ,
input rst ,
//输入RGB原始信号
input i_data_valid ,
input [7:0] i_data_r ,
input [7:0] i_data_g ,
input [7:0] i_data_b ,
//输出转换后的灰度信号
output o_data_valid ,
output [7:0] o_data_r ,
output [7:0] o_data_g ,
output [7:0] o_data_b
);
//Y= 0.299*R + 0.587*G + 0.114*B
/****************parameter********************/
parameter Y_PARA_R = 306, // 0.299*1024
Y_PARA_G = 601, // 0.587*1024
Y_PARA_B = 117; // 0.114*1024
/*******************reg***********************/
reg [1:0] ro_data_valid ;
reg [17:0] ro_data_r ;
reg [17:0] r_y1 ;
reg [17:0] r_y2 ;
reg [17:0] r_y3 ;
/******************wire***********************/
/******************assign*********************/
assign o_data_valid = ro_data_valid[1] ;
assign o_data_r = ro_data_r[17:10] ;
assign o_data_g = ro_data_r[17:10] ;
assign o_data_b = ro_data_r[17:10] ;
//1 clock
always @(posedge clk) begin
r_y1 = (i_data_r * Y_PARA_R);
r_y2 = (i_data_g * Y_PARA_G);
r_y3 = (i_data_b * Y_PARA_B);
end
//2 clock
always @(posedge clk) begin
ro_data_r = r_y1 + r_y2 + r_y3;
end
//sync_valid
always @(posedge clk) begin
ro_data_valid <= {ro_data_valid[0],i_data_valid};
end
endmodule
4.1.2 仿真截图
转换出来的灰度图像和matlab一致。
第二步:Verilog实现统计累计灰度直方图
用matlab软件实现直方图的统计非常简单,调用一个函数即可。但是FPGA内没有这种函数,只能靠自己计算。实现的思想就是用一个双口RAM,简单双口和真双口都可以:
- 第一步:把输入的灰度数据作为ram的读地址
- 第二步:把读出来的数据 + 1
- 第三步:再把+1后的数据写入相同地址
需要注意的是,从给出读地址到数据读出来需要花费一个时钟周期,再写入到ram里面也需要花费一个时钟周期,因此当遇到两个连续灰度值一样时,可能会出现漏写。因此再写入ram之前还要判断当前灰度值是否一致,不一样的话就+1,一样的话就+2,连续三个一样的话就+3,以此类推。
4.2.1 verilog代码
端口定义:
module histogram_stat#(
parameter IMG_WIDTH = 1920, //输入图像宽度
parameter IMG_HEIGHT = 1280, //输入图像高度
parameter GRAY_LEVEL = 256 //输入图像灰度等级
)
(
input wire clk ,
input wire rst ,
input wire i_hsync , //输入图像行同步信号
input wire i_vsync , //输入图像场同步信号
input wire i_data_valid , //输入图像数据有效信号
input wire [7:0] i_data , //输入灰度图像数据
output wire o_histo_valid , //输出累计直方图
output wire [31:0] o_histo_data //输出累计直方图数据
);
简单双口ram定义:
cal_ram u0_cal_ram (
.clka (clk ), // input wire clka
.wea (wr_ram_en ), // input wire [0 : 0] wea
.addra(wr_ram_addr), // input wire [7 : 0] addra
.dina (wr_ram_data), // input wire [23 : 0] dina
.clkb (clk ), // input wire clkb
.addrb(rd_ram_addr), // input wire [7 : 0] addrb
.doutb(rd_ram_data) // output wire [23 : 0] doutb
);
4.2.2 仿真结果
先打开matlab看直方图和累积直方图以及每一个色阶的数量:
色阶数量分别是:7798,557,14863,7908 …等等。
累计直方图是:7798,8355,23218,31126…等等,我们打开仿真波形来观察:
我们可以看到,ram从0-255存放的直方图灰度数量和matlab一致,我们接下来看累计直方图:
我们可以看到,累计直方图数量和matlab也一致,我们用模拟波形来观察:
直方图波形和matlab也一致。
累计直方图波形和matlab也一致。
第三步:Verilog实现直方图均衡化
归一化分布函数就是把累计分布函数除以总像素,得到一个小数。由于FPGA不擅长除法,因此我们可以先扩大再右移,例如:
4.3.1 Verilog代码
module cal_cdf#(
parameter IMG_WIDTH = 1920, //输入图像宽度
parameter IMG_HEIGHT = 1280 //输入图像高度
)
(
input clk ,
input rst ,
input i_histo_valid , //输入累计直方图数据有效信号
input [31:0] i_histo_data , //输入累计直方图数据
input [7:0] i_src_gray_data , //输入原始灰度图像
input i_src_gray_data_valid , //输入原始灰度图像有效信号
output reg o_post_cal_gray_data_valid , //输出直方图均衡化的
output [23:0] o_post_cal_gray_data //输出均衡化后的图像数据
);
/***************function**************/
/***************parameter*************/
parameter const = 36'd68451041280; //255*2^28
parameter cal_cdf = const / (IMG_WIDTH*IMG_HEIGHT); // 255*2^28/(IMG_WIDTH*IMG_HEIGHT) 15bit
我们先在上层定义好图像的长和宽,因为直方图均衡最重要的就是 x = 255 ∗ y x = 255 * y x=255∗y, 因此我们先定义好常数255 * 2^28 ,再除以总像素就得到了x, 再把新的灰度直方图写入到新的ram里,然后让原始灰度数据作为读ram地址,读出来的数值就是需要替换掉的灰度值。
4.3.2 仿真结果
先来观察我们均衡化后的灰度直方图为:
对比matlab均衡化后的直方图:
matlab均衡化后的灰度直方图数据为:0、0、1、2、6、8、11、19、24、33、42、51、59、69
Verilog均衡化后的灰度直方图数据为:0、0、2、3、7、9、12、19、24、34、43、51、60、69
可以看出我们均衡化后的灰度直方图和matlab均衡化后的灰度直方图有一些细微的差异,这些都是精度带来的差距,可以忽略不计,总体的数据是一致的。
仿真中,我们将均衡化后的图像生成在本文件夹里,命名为:new_gray_img.bmp,跑完仿真打开文件夹:
我们可以看到均衡化后的图片,我们来对比一下matlab均衡化后的图片:
我们可以看出用Verilog实现的直方图均衡化和matlab一致,我们用matlab统计我们Verilog均衡化后的图像的直方图以及累计直方图,matlab代码如下:
% 读取两幅灰度图像
img1 = imread('...\....\.....\.....\gray_image_24bit.bmp');
img2 = imread('...\....\.....\.....\new_gray_img.bmp');
% 确保图像是灰度图像
if size(img1, 3) == 3
img1 = rgb2gray(img1);
end
if size(img2, 3) == 3
img2 = rgb2gray(img2);
end
% 计算直方图和累计直方图
[counts1, x1] = imhist(img1);
[counts2, x2] = imhist(img2);
cumulativeHist1 = cumsum(counts1);
cumulativeHist2 = cumsum(counts2);
% 创建一个新的图形窗口
figure;
% 显示第一幅图像及其直方图和累计直方图
subplot(2, 3, 1);
imshow(img1);
title('第一幅灰度图像');
subplot(2, 3, 2);
bar(x1, counts1, 'FaceColor', 'b');
title('第一幅图像的灰度直方图');
xlabel('灰度值');
ylabel('像素数量');
subplot(2, 3, 3);
plot(x1, cumulativeHist1, 'r');
title('第一幅图像的累计直方图');
xlabel('灰度值');
ylabel('累计频数');
% 显示第二幅图像及其直方图和累计直方图
subplot(2, 3, 4);
imshow(img2);
title('第二幅灰度图像');
subplot(2, 3, 5);
bar(x2, counts2, 'FaceColor', 'g');
title('第二幅图像的灰度直方图');
xlabel('灰度值');
ylabel('像素数量');
subplot(2, 3, 6);
plot(x2, cumulativeHist2, 'm');
title('第二幅图像的累计直方图');
xlabel('灰度值');
ylabel('累计频数');
% 设置图形窗口的大小
set(gcf, 'Position', [100, 100, 800, 600]);
至此,Verilog实现直方图均衡化已完成。
标签:均衡化,归一化,直方图,灰度,Matlab,图像,data From: https://blog.csdn.net/qq_43156031/article/details/142175462