OpenCV联合C++/Qt 学习笔记(十)----图像直方图绘制、直方图均衡化及直方图匹配
一、图像直方图绘制1、图像直方图统计与绘制/* 用途用于统计图像像素值的分布情况生成图像直方图。 可计算灰度图单通道直方图也可计算彩色图多通道联合直方图 */ 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可选的操作掩码 hist输出的统计直方图结果是一个dims维度的数组 dims需要计算直方图的维度必须是整数 histSize存放每个维度直方图的数组的尺寸 ranges每个图像通道中灰度值的取值范围 uniform直方图是否均匀的标识符默认状态下为均匀true accumulate是否累计统计直方图的标志 */2、示例代码QString imgPath QApplication::applicationDirPath() /Images; cv::String s_imgPath imgPath.toLocal8Bit().data(); Mat img imread(s_imgPath /apple.jpg); if (img.empty()) { qDebug() 图片加载失败, 请确认图像文件名称是否正确; return; } Mat gray; cvtColor(img, gray, COLOR_BGR2GRAY); Mat hist;/*用于存放直方图计算结果*/ const int channels[1] { 0 };/*通道索引*/ const int bins[1] { 256 };/*直方图的维度即像素灰度值的最大值*/ float inRanges[2] { 0,255 }; const float* ranges[1] { inRanges };/*像素灰度值范围*/ calcHist(gray, 1, channels, Mat(), hist, 1, bins, ranges);/*计算图像直方图*/ /*准备绘制直方图*/ int hist_w 512; int hist_h 200; int width 2; Mat histImage Mat::zeros(hist_h, hist_w, CV_8UC3); //for (int i 1; i hist.rows; i) //{ // rectangle(histImage, Point(width * (i - 1), hist_h - 1), // Point(width * i - 1, hist_h - cvRound(hist.atfloat(i - 1) / 15)), // Scalar(255, 255, 255), -1); //} Mat hist_INF; normalize(hist, hist_INF, 1, 0, NORM_INF, -1, Mat());/*归一化*/ for (int i 1; i hist_INF.rows; i) { rectangle(histImage, Point(width * (i - 1), hist_h - 1), Point(width * i - 1, hist_h - cvRound(hist_h*hist_INF.atfloat(i - 1)) - 1), Scalar(255, 255, 255), -1); } imshow(gray, gray); imshow(histImage, histImage); waitKey(0); destroyAllWindows();二、直方图均衡化直方图均衡化是将原始图像中集中分布的灰度值重新映射到整个灰度范围0~255使灰度分布趋于均匀从而提升图像整体对比度增强细节表现。核心思想如果原图像素主要集中在某一小段灰度区间例如偏暗图像集中在低灰度区域偏亮图像集中在高灰度区域那么图像就会显得层次不足、对比度低。通过直方图均衡化可将这些灰度值“拉开”充分利用全部灰度动态范围使暗区域更清晰、亮区域层次更丰富。明亮图像的直方图通常偏向右侧高灰度区灰暗图像的直方图通常偏向左侧低灰度区高对比度图像的直方图通常覆盖整个灰度范围且分布较均匀1、直方图均衡化/* 用途用于对灰度图像进行直方图均衡化处理通过重新分布像素灰度值 拉伸图像的亮度动态范围使灰度分布更加均匀从而提升图像对比度 */ void equalizeHist( InputArray src, OutputArray dst ); /* src需要直方图均衡化的CV_8UC1图像 dst直方图均衡化后的输出图像与src具有相同尺寸和数据类型 */2、示例代码void drawHist(Mat hist, int type, string name)/*归一化并绘制直方图函数*/ { int hist_w 512; int hist_h 400; int width 2; Mat histImage Mat::zeros(hist_h, hist_w, CV_8UC3); normalize(hist, hist, 1, 0, type, -1, Mat());/*归一化*/ for (int i 1; i hist.rows; i) { rectangle(histImage, Point(width * (i - 1), hist_h - 1), Point(width * i - 1, hist_h - cvRound(hist_h * hist.atfloat(i - 1)) - 1), Scalar(255, 255, 255), -1); } imshow(name, histImage); } void equalizeHistStudy() { QString imgPath QApplication::applicationDirPath() /Images; cv::String s_imgPath imgPath.toLocal8Bit().data(); Mat img imread(s_imgPath /apple.jpg); if (img.empty()) { qDebug() 图片加载失败, 请确认图像文件名称是否正确; return; } Mat gray, hist, hist2; cvtColor(img, gray, COLOR_BGR2GRAY); Mat equalImg; equalizeHist(gray, equalImg);/*将图像直方图均衡化*/ const int channels[1] { 0 }; float inRanges[2] { 0,255 }; const float* ranges[1] { inRanges }; const int bins[1] { 256 }; calcHist(gray, 1, channels, Mat(), hist, 1, bins, ranges); calcHist(equalImg, 1, channels, Mat(), hist2, 1, bins, ranges); drawHist(hist, NORM_INF, hist); drawHist(hist2, NORM_INF, hist2); imshow(gray, gray); imshow(equalImg, equalImg); waitKey(0); destroyAllWindows(); }三、直方图匹配1、直方图匹配原理直方图匹配的本质是把一幅图像的灰度分布”转换成”另一幅目标图像的灰度分布。它不是简单地增强对比度而是主动去模仿某个指定的灰度统计特性。序号运算步骤和结果1原图像灰度级012345672原直方图概率0.190.240.20.170.090.050.030.023原直方图累计概率0.190.430.630.80.890.940.970.994目标直方图概率0000.160.190.290.20.165目标直方图累计概率0000.160.350.640.8416匹配的灰度值345667777映射关系0-31-42-53-64-65-76-77-7对源图像进行统计得到其灰度的累计分布函数CDF。该步骤将每个像素的灰度值转换为其在整体分布中的累计概率从而把原始灰度空间映射到一个标准化的概率空间中使不同灰度值的相对位置关系被保留下来。对目标直方图同样计算累计分布函数。利用第一步得到的概率值作为中间变量在目标CDF中找到与之对应的灰度值实现从概率空间到目标灰度空间的反向映射。源图像的灰度分布被重新塑形使其统计特性尽可能接近目标分布从而实现直方图匹配。2、示例代码QString imgPath QApplication::applicationDirPath() /Images; cv::String s_imgPath imgPath.toLocal8Bit().data(); Mat img1 imread(s_imgPath /apple.jpg, COLOR_BGR2GRAY); Mat img2 imread(s_imgPath /lena.jpg, COLOR_BGR2GRAY); if (img1.empty() || img2.empty()) { qDebug() 图片加载失败, 请确认图像文件名称是否正确; return; } Mat hist1, hist2; /*计算两张图像直方图*/ const int channels[1] { 0 }; float inRanges[2] { 0,255 }; const float* ranges[1] { inRanges }; const int bins[1] { 256 }; calcHist(img1, 1, channels, Mat(), hist1, 1, bins, ranges); calcHist(img2, 1, channels, Mat(), hist2, 1, bins, ranges); /*归一化两张图像的直方图*/ drawHist(hist1, NORM_INF, hist1); drawHist(hist2, NORM_INF, hist2); /*计算两张图像直方图的累计概率*/ float hist1_cdf[256] { hist1.atfloat(0) }; float hist2_cdf[256] { hist1.atfloat(0) }; for (int i 0; i 256; i) { hist1_cdf[i] hist1_cdf[i - 1] hist1.atfloat(i); hist2_cdf[i] hist2_cdf[i - 1] hist2.atfloat(i); } /*构建累计概率误差矩阵*/ float diff_cdf[256][256]; for (int i 0; i 256; i) { for (int j 0; j 256; j) { diff_cdf[i][j] fabs(hist1_cdf[i] - hist2_cdf[j]);/*取绝对值*/ } } /*生成LUT映射表*/ Mat lut(1, 256, CV_8U); for (int i 0; i 256; i) { /*查找源灰度级为i的映射灰度 和i的累计概率差值最小的规定化灰度*/ float min diff_cdf[i][0]; int index 0; /*寻找累计概率误差矩阵中每一行中的最小值*/ for (int j 1; j 256; j) { if (min diff_cdf[i][j]) { min diff_cdf[i][j]; index j; } } lut.atuchar(i) (uchar)index; } Mat result, hist3; LUT(img1, lut, result); imshow(QString(待匹配图像).toLocal8Bit().data(), img1); imshow(QString(匹配的模板图像).toLocal8Bit().data(), img2); imshow(QString(直方图匹配结果).toLocal8Bit().data(), result); calcHist(result, 1, channels, Mat(), hist3, 1, bins, ranges); waitKey(0); destroyAllWindows();