直方图处理
一副数字影像的直方图是一个离散函数h(rk)=nk,其中rk是第k个灰阶而nk是影像中带有灰阶rk的像素个数。一个正规化的直方图中p(rk)=nk/n,其中n为总的像素个数,也就是说p(rk)代表灰阶rk出现的概率,加起来等于1。
如果直方图的分布不均匀,高峰处集中在低区域说明图像整体较暗,集中在高区域说明图像整体亮,如果集中在中间较窄的区域,则图像的对比度较低。
可以使用直方图的相似性来比较图像是否相似。
直方图均衡化
将一个直方图不均匀的图像的直方图调整均匀,使图像效果更好
令r是图像某个像素的灰度,已被正则化到[0,1],寻找一个T(r)将r从[0,1]映射到[0,1](将每个像素按照灰度值变换到另一个灰度值),且T(r)单调递增(可逆),使输出图像的灰阶更加均匀
使用源图像的概率密度函数(直方图)的分布函数定义T(r)(分布函数还能这么用~)
s=T(r)=∫orpr(w)dw
无论pr(r)的形式如何,所得的ps(s)永远是均匀的
离散形式
对于离散的直方图,其离散形式为
sk=T(rk)=j=0∑kpr(rj)=j=0∑knnj
虽然离散变换不能产生均匀的直方图,但是可以解决对比度较低的问题
代码实现
计算灰度对应的灰阶
1 2 3 4 5 6
| 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,k−1],方便对原图进行转化
1 2 3 4 5 6
| const unsigned int k = 255;
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值的选取决定了结果图像由几种不同的灰度(颜色)构成,如果设置太低,结果图像的灰阶就会很低。
结果展示
图像压缩过
原图
![原图](gray.webp)
结果
![结果](out.webp)