直方图均衡的C++实现方法
一. 原理
灰度直方图是灰度级的函数,描述的是图像中具有该灰度级的像素的个数,即 h ( k ) = n k ( k = 0 , 1 , ⋯ , L − 1 ) h(k)=n_k\quad (k=0,1,\cdots,L-1) h(k)=nk(k=0,1,⋯,L−1),其中 L L L为图像的灰度级数。
直方图均衡的目的是将图像的直方图修正为均匀分布形式(离散分布做不到完全均匀,但可以接近),增加像素灰度值的动态范围,从而达到增强图像对比度,提高清晰度的效果。
因而我们需要找到一种变换 t = E H ( s ) t=E_{\rm H}(s) t=EH(s)使得直方图变平直,且要使变换后的灰度仍保持从黑到白的单一变化顺序、变换范围与原先一致。同时规定:
- 在 0 ≤ s ≤ 1 0\le s \le 1 0≤s≤1, E H ( s ) E_{\rm H}(s) EH(s)单调递增,且 0 ≤ E H ( s ) ≤ 1 0 \le E_{\rm H}(s) \le 1 0≤EH(s)≤1;
- 在 0 ≤ t ≤ 1 0\le t \le 1 0≤t≤1,其反变换 s = E H − 1 ( t ) s = E^{-1}_{\rm H}(t) s=EH−1(t)单调递增。
具体方法如下:
-
设一幅图像的像素总数为 n n n,共有 L L L个灰度级, n k n_k nk为第 k k k个灰度级出现的频率。则第 k k k个灰度级直方图均衡出现的概率为
p ( s k ) = n k n p(s_k) = \dfrac {n_k} n p(sk)=nnk -
计算累计概率
t k = E H ( s k ) = ∑ i = 0 k p ( s k ) , 0 ≤ t k ≤ 1 t_{k}=E_{\mathrm{H}}\left(s_{k}\right)=\sum_{i=0}^{k} p\left(s_{k}\right), \quad 0 \leq t_{k} \leq 1 tk=EH(sk)=i=0∑kp(sk),0≤tk≤1
将 s s s的分布转换为 t t t的均匀分布; -
将归一化的 t k t_k tk,映射回灰度值区间 [ 0 , L − 1 ] [0,L-1] [0,L−1]:
t k = i n t [ ( L − 1 ) t k + 0.5 ] t_k = {\rm int}\left[ (L-1)t_k+0.5 \right] tk=int[(L−1)tk+0.5]
其中加0.5再取整即为在程序中取四舍五入的算法; -
对图像中每一个像素的灰度值进行映射,得到均衡后的图像。
下面将对seed.yuv进行直方图均衡。
二. 实验代码
declarations.h
#pragma once
extern int w;
extern int h;
void Freq(unsigned char* yBuff, double freq[]);
void CumulativeFreq(double prob[], double cumProb[]);
void Mapping(double cumProb[], unsigned char* yBuffOri, unsigned char* yBuffEqu);
global.cpp
#include "declarations.h"
#include <iostream>
int w = 500;
int h = 500;
void Freq(unsigned char* yBuff, double prob[])
{
double count[256] = { 0 };
for (int i = 0; i < w * h; i++)
{
int greyIndex = (int)yBuff[i];
count[greyIndex]++;
}
for (int i = 0; i < 256; i++)
{
prob[i] = count[i] / (w * h);
//printf("%-5d%lf\n", i, prob[i]);
}
}
void CumulativeFreq(double prob[], double cumProb[])
{
cumProb[0] = 0;
//printf("%-5d%lf\n", 0, cumProb[0]);
for (int i = 1; i < 256; i++)
{
cumProb[i] = cumProb[i - 1] + prob[i - 1];
//printf("%-5d%lf\n", i, cumProb[i]);
}
}
void Mapping(double cumProb[], unsigned char* yBuffOri, unsigned char* yBuffEqu)
{
for (int i = 0; i < 256; i++)
{
cumProb[i] = floor(255 * cumProb[i] + 0.5);
}
for (int i = 0; i < w * h; i++)
{
int greyIndex = (int)yBuffOri[i];
yBuffEqu[i] = cumProb[greyIndex];
}
}
main.cpp
#include <iostream>
#include "declarations.h"
using namespace std;
int main(int argc, char* argv[])
{
FILE* oriImgPtr;
FILE* equImgPtr;
const char* oriImgName = argv[1];
const char* equImgName = argv[2];
int greyFreq[256] = { 0 };
double greyProb[256] = { 0 };
double greyCumProb[256] = { 0 };
/* Open the files */
if (fopen_s(&oriImgPtr, oriImgName, "rb") == 0)
{
cout << "Successfully opened \"" << oriImgName << "\"." << endl;
}
else
{
cout << "Failed to open \"" << oriImgName << "\"." << endl;
exit(-1);
}
if (fopen_s(&equImgPtr, equImgName, "wb") == 0)
{
cout << "Successfully opened \"" << equImgName << "\"." << endl;
}
else
{
cout << "Failed to open \"" << equImgName << "\"." << endl;
exit(-1);
}
/* Space allocation */
unsigned char* oriYBuff = new unsigned char[w * h];
unsigned char* equYBuff = new unsigned char[w * h];
unsigned char* equUBuff = new unsigned char[w * h / 4];
unsigned char* equVBuff = new unsigned char[w * h / 4];
/* Initialisation of U & V component (greyscale image) */
memset(equUBuff, 128, w * h / 4);
memset(equVBuff, 128, w * h / 4);
/* Read Y component into the buffer */
fread(oriYBuff, sizeof(unsigned char), w * h, oriImgPtr);
/* Calculate probabilities of each grey value */
Freq(oriYBuff, greyProb);
/* Calculate cumulative probabilites of each grey value */
CumulativeFreq(greyProb, greyCumProb);
/* Mapping */
Mapping(greyCumProb, oriYBuff, equYBuff);
/* Write histogram-equalised data into the new file */
fwrite(equYBuff, sizeof(unsigned char), w * h, equImgPtr);
fwrite(equUBuff, sizeof(unsigned char), w * h / 4, equImgPtr);
fwrite(equVBuff, sizeof(unsigned char), w * h / 4, equImgPtr);
delete[]oriYBuff;
delete[]equYBuff;
delete[]equUBuff;
delete[]equVBuff;
fclose(oriImgPtr);
fclose(equImgPtr);
}
三. 实验结果
实验结果如下:
可以看到,相比原图,直方图均衡很好地实现了增强对比度、提高清晰度的功能。