直方图均衡化

直方图处理

一副数字影像的直方图是一个离散函数h(rk)=nkh(r_k)=n_k,其中rkr_k是第kk个灰阶而nkn_k是影像中带有灰阶rkr_k的像素个数。一个正规化的直方图中p(rk)=nk/np(r_k)=n_k/n,其中nn为总的像素个数,也就是说p(rk)p(r_k)代表灰阶rkr_k出现的概率,加起来等于1。
如果直方图的分布不均匀,高峰处集中在低区域说明图像整体较暗,集中在高区域说明图像整体亮,如果集中在中间较窄的区域,则图像的对比度较低。
可以使用直方图的相似性来比较图像是否相似。

直方图均衡化

将一个直方图不均匀的图像的直方图调整均匀,使图像效果更好
rr是图像某个像素的灰度,已被正则化到[0,1][0,1],寻找一个T(r)T(r)rr[0,1][0,1]映射到[0,1][0,1](将每个像素按照灰度值变换到另一个灰度值),且T(r)T(r)单调递增(可逆),使输出图像的灰阶更加均匀

使用源图像的概率密度函数(直方图)的分布函数定义T(r)T(r)(分布函数还能这么用~)

s=T(r)=orpr(w)dws = T(r) = \int_o^r p_r(w)dw

无论pr(r)p_r(r)的形式如何,所得的ps(s)p_s(s)永远是均匀的

离散形式
对于离散的直方图,其离散形式为

sk=T(rk)=j=0kpr(rj)=j=0knjns_k = T(r_k)=\sum_{j=0}^kp_r(r_j)=\sum_{j=0}^k\frac{n_j}{n}

虽然离散变换不能产生均匀的直方图,但是可以解决对比度较低的问题

代码实现

计算灰度对应的灰阶

1
2
3
4
5
6
//k:划分的灰阶的总阶数
inline unsigned int calulate_r(float gray, unsigned int k){
unsigned int scope = floor(gray * (float)k);
scope = (scope >= (k - 1)) ? (k - 1) : scope;
return scope;
}

定义灰度图的直方图

1
2
3
4
5
6
7
8
9
10
11
12
//灰度图像的直方图
class GrayImageHist{
public:
unsigned int k;//总阶数
std::vector<float> r;//每一阶的概率密度函数

GrayImageHist(unsigned int k, std::vector<float>&& r)
:k(k), r(r)
{
assert(k == r.size());
}
}

从灰度图像生成直方图

1
2
3
4
5
6
7
8
9
10
11
12
static GrayImageHist from_gray_image(unsigned int width, unsigned int height, const std::vector<float> framebuffer, unsigned int k){
std::vector<float> r(k);
std::vector<unsigned int> sum(k, 0);
for(float p : framebuffer){
sum[calulate_r(p, k)]++;
}
float pixel_count_inv = 1.0f / (float)(width * height);
for(unsigned int i = 0;i < k;i++){
r[i] = sum[i] * pixel_count_inv;
}
return GrayImageHist(k, std::move(r));
}

计算直方图的累计分布函数,得到均衡后的直方图

1
2
3
4
5
6
7
8
9
10
static GrayImageHist equalize_hist(const GrayImageHist& origin){
unsigned int k = origin.k;
//计算累加分布
std::vector<float> sum_r(k, 0);
sum_r[0] = origin.r[0];
for(unsigned int i = 1;i < k;i++){
sum_r[i] = sum_r[i - 1] + origin.r[i];
}
return GrayImageHist(k, std::move(sum_r));
}

将均衡后的直方图的每个灰阶的概率密度从[0,1][0,1]映射到[0,k1][0, k - 1],方便对原图进行转化

1
2
3
4
5
6
const unsigned int k = 255;//阶数
//取整拓展,对equalized_hist中的概率离散化
std::vector<unsigned int> sum_r(k, 0);
for(unsigned int i = 0;i < equalized_hist.k;i++){
sum_r[i] = lroundf(equalized_hist.r[i] * (k - 1));
}

转化原图,将转化得到的灰度写入新图像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
std::vector<Vector3f> image(width * height);//结果图像
//计算每一个灰阶对应的灰度
std::vector<float> gray_map(k);
for(unsigned int i = 0;i < k;i++){
gray_map[i] = (i + 0.5) / (float)k;
}

//对源图像灰度进行映射
for(unsigned int i = 0;i < width * height;i++){
float origin = gray_image[i];
unsigned int scope = sum_r[calulate_r(origin, k)];
float target = gray_map[scope];
image[i] = {target, target, target};
}

k值的选取决定了结果图像由几种不同的灰度(颜色)构成,如果设置太低,结果图像的灰阶就会很低。

结果展示

图像压缩过
原图
原图
结果
结果


直方图均衡化
https://9-extra.github.io/2023/03/21/直方图均衡化/
作者
9_Extra
发布于
2023年3月21日
许可协议