实现原理
通过图像数据的直方图,可以快速判断图像的亮度和质量。而直方图均衡化就是通过图像变换使得直方图均匀分布,起到对比度增强的效果。在图像处理的课本中,针对离散形式的图像数据,最常用的一种方法就是累计概率分布。首先统计0-255灰度值所占像素个数;再计算出像素个数与总像素的比,表示为出现的概率;从0开始进行累计概率分布,即从0慢慢累加各层概率值直到1;则均衡化图像的灰度值=原灰度值所对应的累计概率*255。
基于上述原理,我自定义了一个简单的直方图均衡化函数EqualizeHist,并定义了直方图简易绘制函数drawHistImg,用来作直观对比。
功能函数代码
// 直方图均衡化 cv::Mat EqualizeHist(cv::Mat src) { cv::Mat h = cv::Mat::zeros(1, 256, CV_32FC1); cv::Mat hs = cv::Mat::zeros(1, 256, CV_32FC1); cv::Mat hp = cv::Mat::zeros(1, 256, CV_32FC1); cv::Mat result = cv::Mat::zeros(src.size(), src.type()); int sum = 0; for (int i = 0; i < src.rows; ++i) { for (int j = 0; j < src.cols; ++j) { h.at<float>(0, src.at <uchar>(i, j))++; sum++; } } for (int i = 0; i < 256; ++i) { hs.at<float>(0, i) = h.at<float>(0, i) / sum; if (i == 0) { hp.at<float>(0, i) = hs.at<float>(0, i); } else { hp.at<float>(0, i) = hp.at<float>(0, i - 1) + hs.at<float>(0, i); } } for (int i = 0; i < src.rows; ++i) { for (int j = 0; j < src.cols; ++j) { result.at <uchar>(i, j) = uchar(round(255 * hp.at<float>(0, src.at<uchar>(i, j)))); } } return result; } // 绘制简易直方图 cv::Mat drawHistImg(cv::Mat &src) { cv::Mat hist = cv::Mat::zeros(1, 256, CV_32FC1); for (int i = 0; i < src.rows; ++i) { for (int j = 0; j < src.cols; ++j) { hist.at<float>(0, src.at <uchar>(i, j))++; } } cv::Mat histImage = cv::Mat::zeros(540, 1020, CV_8UC1); const int bins = 255; double maxValue; cv::Point2i maxLoc; cv::minMaxLoc(hist, 0, &maxValue, 0, &maxLoc); int scale = 4; int histHeight = 540; for (int i = 0; i < bins; i++) { float binValue = (hist.at<float>(i)); int height = cvRound(binValue * histHeight / maxValue); cv::rectangle(histImage, cv::Point(i * scale, histHeight), cv::Point((i + 1) * scale-1, histHeight - height), cv::Scalar(255), -1); } return histImage; }
函数原型
官方OpenCV库中也有自带的直方图均衡化函数:
void equalizeHist( InputArray src, OutputArray dst );
参数说明
- InputArray类型的src,输入图像,如Mat类型。
- OutputArray类型的dst,输出图像。
C++测试代码
#include <iostream> #include <time.h> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; cv::Mat EqualizeHist(cv::Mat src); cv::Mat drawHistImg(cv::Mat &hist); int main() { cv::Mat src = imread("test.jpg",0); cv::Mat src1 = imread("test.jpg"); clock_t start1, start2, end1,end2; // 绘制原图直方图 cv::Mat hI = drawHistImg(src); // 自定义直方图均衡化 start1 = clock(); cv::Mat result1 = EqualizeHist(src); end1 = clock(); double dif1 = (end1 - start1) / CLOCKS_PER_SEC; cout << "time1:" << dif1 << endl; // 绘制均衡化后直方图 cv::Mat hrI = drawHistImg(result1); // 官方直方图均衡化函数 start2 = clock(); cv::Mat result2; equalizeHist(src, result2); end2 = clock(); double dif2 = (end2 - start2) / CLOCKS_PER_SEC; cout << "time2:" << dif2 << endl; // 绘制均衡化后直方图 cv::Mat hr2I = drawHistImg(result2); // 彩色直方图均衡化,三通道分别作均衡再合并 vector<cv::Mat> rgb,rgb_; cv::Mat r, g, b; cv::split(src1, rgb); equalizeHist(rgb[0], b); equalizeHist(rgb[1], g); equalizeHist(rgb[2], r); rgb_.push_back(b); rgb_.push_back(g); rgb_.push_back(r); cv::Mat src1_; cv::merge(rgb_, src1_); imshow("original", src1); imshow("result", src1_); waitKey(0); return 0; } // 直方图均衡化 cv::Mat EqualizeHist(cv::Mat src) { cv::Mat h = cv::Mat::zeros(1, 256, CV_32FC1); cv::Mat hs = cv::Mat::zeros(1, 256, CV_32FC1); cv::Mat hp = cv::Mat::zeros(1, 256, CV_32FC1); cv::Mat result = cv::Mat::zeros(src.size(), src.type()); int sum = 0; for (int i = 0; i < src.rows; ++i) { for (int j = 0; j < src.cols; ++j) { h.at<float>(0, src.at <uchar>(i, j))++; sum++; } } for (int i = 0; i < 256; ++i) { hs.at<float>(0, i) = h.at<float>(0, i) / sum; if (i == 0) { hp.at<float>(0, i) = hs.at<float>(0, i); } else { hp.at<float>(0, i) = hp.at<float>(0, i - 1) + hs.at<float>(0, i); } } for (int i = 0; i < src.rows; ++i) { for (int j = 0; j < src.cols; ++j) { result.at <uchar>(i, j) = uchar(round(255 * hp.at<float>(0, src.at<uchar>(i, j)))); } } return result; } // 绘制简易直方图 cv::Mat drawHistImg(cv::Mat &src) { cv::Mat hist = cv::Mat::zeros(1, 256, CV_32FC1); for (int i = 0; i < src.rows; ++i) { for (int j = 0; j < src.cols; ++j) { hist.at<float>(0, src.at <uchar>(i, j))++; } } cv::Mat histImage = cv::Mat::zeros(540, 1020, CV_8UC1); const int bins = 255; double maxValue; cv::Point2i maxLoc; cv::minMaxLoc(hist, 0, &maxValue, 0, &maxLoc); int scale = 4; int histHeight = 540; for (int i = 0; i < bins; i++) { float binValue = (hist.at<float>(i)); int height = cvRound(binValue * histHeight / maxValue); cv::rectangle(histImage, cv::Point(i * scale, histHeight), cv::Point((i + 1) * scale-1, histHeight - height), cv::Scalar(255), -1); } return histImage; }
测试效果
图1 灰度原图
其直方图如下:
图2 直方图
图3 均衡化后灰度图
两个函数运行时间都在0.001s以下,其均衡化后直方图略有差异,但都实现了均衡效果。
自定义函数:
图4 自定义函数均衡化后直方图
官方函数:
图5 官方函数均衡化后直方图
对彩色图的三通道分别进行直方图均衡化,实现彩色均衡效果:
图6 彩色原图
图7 彩色均衡化效果
如果函数有什么可以改进完善的地方,非常欢迎大家指出,一同进步何乐而不为呢~
如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!