图像处理之直方图增强(C++)
文章目录
前言
直方图增强的基本思想是通过对图像的直方图进行变换,将原始图像的像素值映射到新的像素值范围上,从而改变图像的亮度和对比度。
直方图增强(直方图统计、直方图均衡化、自适应直方图均衡化)C++实现(opencv/自己实现)。
一、直方图统计
1.原理
直方图统计原理是将数据按照一定的区间划分,然后统计每个区间内数据的个数或者频率,最后将这些统计结果用柱形图表示出来。
2.代码实现
/*
* @param cv::Mat src 输入图像
* @param
* @breif 针对灰度图的直方图统计,彩色图像分离通道后同理
*/
void CalcHistograms(cv::Mat& src)
{
//确定数据范围
int histSize = 256;
//划分区间
float ranges[] = { 0,256 };
const float* histRanges[] = {ranges};
//统计频数或者频率
cv::Mat hist;
bool uniform = true, accmulate = false;
/* key function:
void calcHist(const Mat * images, int nimages, const int* channels, InputArray mask,
OutputArray hist, int dims, const int* histSize,const float** ranges, bool uniform = true, bool accumulate = false );
• images:输入的图像的指针;
• nimages:输入图像个数;
• channels:需要统计直方图的第几通道;
• mask:掩模,mask必须是一个8位(CV_8U)的数组并且和images的数组大小相同;
• hist:直方图计算的输出值;
• dims:输出直方图的维度(由channels指定);
• histSize:直方图中每个dims维度需要分成多少个区间(如果把直方图看作一个一个竖条的话,就是竖条的个数);
• ranges:统计像素值的区间;
• uniform=true:是否对得到的直方图数组进行归一化处理;
• accumulate=false:在多个图像时,是否累积计算像素值的个数;
【注】:在计算图像直方图的时候一般配合minMaxLoc()和normalize()函数一起使用。*/
cv::calcHist(&src,1,0,cv::Mat(),hist,1,&histSize,histRanges,uniform,accmulate);
//绘制直方图
int hist_w = 512, hist_h = 400;
int bin_w = cvRound(hist_w / histSize);
cv::normalize(hist, hist, 0, hist_h, cv::NORM_MINMAX);
cv::Mat histImg(hist_h, hist_w, CV_8UC3, cv::Scalar(0,0,0));
for (int i = 0; i < histSize; i++)
{
cv::line(histImg, cv::Point(i * bin_w, hist_h), cv::Point(i * bin_w, hist_h - cvRound(hist.at<float>(i))), cv::Scalar(255, 0, 0));
}
cv::imshow("histImg", histImg);
cv::waitKey(0);
}
int main()
{
//读取图片
string filepath = "F://work_study//algorithm_demo//zidane.jpg";
cv::Mat src = cv::imread(filepath, cv::IMREAD_GRAYSCALE);
if (src.empty())
{
std::cout << "imread error" << std::endl;
return -1;
}
CalcHistograms(src);
return 0;
}
3.结果展示
二、直方图均衡化
1.原理
直方图均衡化的目的是将一幅图像的直方图变成一个均匀分布的直方图,从而增强图像的对比度。
实现步骤:
1.统计原始图像的直方图。计算每个像素值出现的次数,然后将它们归一化,得到每个像素值的频率。
2.计算每个灰度级别的累计概率,即从灰度级别0到当前灰度级别的像素数量之和除以总像素数量。
3.计算均衡化后的像素值。我们需要将原始图像中的每个像素值映射到一个新的像素值,使得均衡化后的直方图近似为一个均匀分布的直方图。这个映射函数可以通过以下公式计算:
其中,f(i)表示映射后的像素值,M 和 N 分别表示图像的宽度和高度,L 表示像素值的范围,min 表示原始图像中的最小像素值。
4.根据灰度级别转换映射表,重新分配图像的像素灰度值。
通过直方图均衡化,可以将原始图像的像素灰度值重新分布,使得图像的动态范围更大,从而增强图像的对比度和细节。这对于一些灰度分布不均匀的图像特别有效,例如在低光照环境下拍摄的图像或者有明显光照差异的图像。
但是,存在一些缺陷:全局变换导致信息丢失或者模糊、非线性变换导致图像失真、对噪声敏感。可以采用限制对比度自适应直方图均衡化(CLAHE)在图像局部区域进行直方图均衡化,以保留图像的局部细节。
2.代码实现
/*
* @param cv::Mat src 输入图像
* @param cv::Mat dst 输出图像
* @breif 基于opencv的直方图均衡化
*/
void EqualHist(const cv::Mat& src, cv::Mat& dst)
{
cv::equalizeHist(src, dst);
}
/*
* @param cv::Mat src 输入图像
* @param cv::Mat dst 输出图像
* @breif 自己实现的直方图均衡化
*/
void myEqualHist(const cv::Mat& src, cv::Mat& dst)
{
int src_h = src.rows;
int src_w = src.cols;
//1.统计直方图
double array[256] = { 0 };
for (int i = 0; i < src_h; i++)
for (int j = 0; j < src_w; j++)
++array[src.at<uchar>(i, j)];
//2.归一化直方图
int min = 0;
bool flag = false;
for (int i = 0; i < 256; i++)
{
array[i] /= (src_h * src_w);
if (!flag && array[i] != 0)
{
min = i;
flag = true;
}
}
//3.计算累计分布直方图
double sum = 0;
for (int i = 0; i < 256; i++)
{
sum += array[i];
array[i] =sum;
}
//4.灰度映射
double grayTable[256];
double minValue = array[min];
for (int i = 0; i < 256; i++)
{
grayTable[i] = ((array[i] - array[min]) / (src_h * src_w - 1) * (256 - 1));
}
dst.convertTo(dst, CV_32FC1);
for (int i = 0; i < src_h; i++)
for (int j = 0; j < src_w; j++)
dst.at<float>(i, j)=grayTable[src.at<uchar>(i, j)];
cv::normalize(dst, dst, 0, 255, cv::NORM_MINMAX, CV_8UC1);
}
int main()
{
//读取图片
string filepath = "F://work_study//algorithm_demo//baby.jpg";
cv::Mat src = cv::imread(filepath, cv::IMREAD_GRAYSCALE);
if (src.empty())
{
std::cout << "imread error" << std::endl;
return -1;
}
cv::Mat dst(src.size(), src.type());
cv::Mat dst1(src.size(), src.type());
//基于opencv的直方图均衡化
EqualHist(src, dst1);
//自己实现的直方图均衡化
myEqualHist(src, dst);
//显示图片
imshow("dst", dst);
imshow("dst1", dst1);
cv::waitKey(0);
return 0;
}
3.结果展示
自己实现的直方图均衡化结果
基于opencv实现的直方图均衡化结果
三、限制对比度的自适应直方图均衡化
1.原理
Histogram Equalization的缺点:
1.对于灰度非常集中的区域,直方图会被拉的非常稀疏,从而导致对比度增强过大,成为噪音;
2.一些区域调整后丢失细节。
针对第一个问题,提出了CLHE(Contrast Limited HE),加入对比度限制,设置直方图分布的阈值,将超过该阈值的分布“均匀”分散至概率密度分布上,由此来限制转换函数(累计直方图)的增幅。
实现步骤:
1.将原始图像分成许多小块(或称为子图像),每个小块大小为 N × N。
2.对于每个小块,计算其直方图,并将直方图进行均衡化,得到映射函数。
3.对于每个小块,使用对应的映射函数对其像素值进行变换。
4.由于像素值在小块之间可能存在不连续的变化,因此需要进行二次线性插值处理,以使得整个图像的对比度保持连续。
2.代码实现
int main()
{
//读取图片
string filepath = "F://work_study//algorithm_demo//baby.jpg";
cv::Mat src = cv::imread(filepath, cv::IMREAD_GRAYSCALE);
if (src.empty())
{
std::cout << "imread error" << std::endl;
return -1;
}
cv::Mat dst(src.size(), src.type());
//创建CLAHE对象指针
cv::Ptr<cv::CLAHE> clahe=cv::createCLAHE();
//设置对比度参数,需要实验确定,默认值是40.0
clahe->setClipLimit(4.);
//设置图像分割成的块数,此处是16*16,默认值是8*8
clahe->setTilesGridSize(cv::Size(16, 16));
//调用函数
clahe->apply(src,dst);
//显示图片
imwrite("dst.bmp", dst);
cv::waitKey(0);
return 0;
}
3.结果展示
总结
本文主要介绍了直方图增强的三种方法(直方图统计、直方图均衡化、限制对比度的直方图均衡化)C++实现,其中直方图均衡化自己使用C++实现,可供参考。三种方法中,限制对比度的直方图均衡化方法较为优异,但是计算复杂度稍高,实验参数主要包括限制的对比度值和划分的块数,需要自行尝试。
因为笔者水平有限,有错误欢迎指出,代码本人均在本地运行实验正确,大家放心使用。