本文首发于“小白学视觉”微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究!


经过几个月的努力,小白终于完成了市面上第一本OpenCV 4入门书籍《OpenCV 4开发详解》。为了更让小伙伴更早的了解最新版的OpenCV 4,小白与出版社沟通,提前在公众号上连载部分内容,请持续关注小白。

直方图不仅能够表示图像像素的统计特性,应用统计的直方图结果也可以增强图像的对比度,在图像中寻找相似区域等。本节中将重点介绍如果通过调整直方图分布提高图像的对比度、利用直方图反向投影寻找相同区域以及将图像的对比度调整为指定的形式。

直方图均衡化

如果一个图像的直方图都集中在一个区域,则整体图像的对比度比较小,不便于图像中纹理的识别。例如相邻的两个像素灰度值如果分别是120和121,仅凭肉眼是如法区别出来的。同时,如果图像中所有的像素灰度值都集中在100到150之间,则整个图像想会给人一种模糊的感觉,看不清图中的内容。如果通过映射关系,将图像中灰度值的范围扩大,增加原来两个灰度值之间的差值,就可以提高图像的对比度,进而将图像中的纹理突出显现出来,这个过程称为图像直方图均衡化。

在OpenCV 4中提供了equalizeHist()函数用于将图像的直方图均衡化,该函数的函数原型在代码清单4-7中给出。

代码清单4-7 equalizeHist()函数原型
1.  void cv::equalizeHist(InputArray  src,
2.                             OutputArray  dst
3.                             )
  • src:需要直方图均衡化的CV_8UC1图像。
  • dst:直方图均衡化后的输出图像,与src具有相同尺寸和数据类型。

该函数形式比较简单,但是需要注意该函数只能对单通道的灰度图进行直方图均衡化。对图像的均衡化示例程序在代码清单4-8中给出,程序中我们将一张图像灰度值偏暗的图像进行均衡化,通过结果可以发现经过均衡化后的图像对比度明显增加,可以看清楚原来看不清的纹理。通过绘制原图和均衡化后的图像的直方图可以发现,经过均衡化后的图像直方图分布更加均匀。

代码清单4-8 myEqualizeHist.cpp直方图均衡化实现
4.  #include <opencv2\opencv.hpp>
5.  #include <iostream>
6.
7.  using namespace cv;
8.  using namespace std;
9.
10. void drawHist(Mat &hist, int type, string name)  //归一化并绘制直方图函数
11. {12.     int hist_w = 512;
13.     int hist_h = 400;
14.     int width = 2;
15.     Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
16.     normalize(hist, hist, 1, 0, type, -1, Mat());
17.     for (int i = 1; i <= hist.rows; i++)
18.     {19.         rectangle(histImage, Point(width*(i - 1), hist_h - 1),
20.             Point(width*i - 1, hist_h - cvRound(hist_h*hist.at<float>(i - 1)) - 1),
21.             Scalar(255, 255, 255), -1);
22.     }
23.     imshow(name, histImage);
24. }
25. //主函数
26. int main()
27. {28.     Mat img = imread("gearwheel.jpg");
29.     if (img.empty())
30.     {31.         cout << "请确认图像文件名称是否正确" << endl;
32.         return -1;
33.     }
34.     Mat gray, hist, hist2;
35.     cvtColor(img, gray, COLOR_BGR2GRAY);
36.     Mat equalImg;
37.     equalizeHist(gray, equalImg);  //将图像直方图均衡化
38.     const int channels[1] = { 0 };
39.     float inRanges[2] = { 0,255 };
40.     const float* ranges[1] = { inRanges };
41.     const int bins[1] = { 256 };
42.     calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);
43.     calcHist(&equalImg, 1, channels, Mat(), hist2, 1, bins, ranges);
44.     drawHist(hist, NORM_INF, "hist");
45.     drawHist(hist2, NORM_INF, "hist2");
46.     imshow("原图", gray);
47.     imshow("均衡化后的图像", equalImg);
48.     waitKey(0);
49.     return 0;
50. }

图4-6 myEqualizeHist.cpp程序运行结果

直方图匹配

直方图均衡化函数可以自动的改变图像直方图的分布形式,这种方式极大的简化了直方图均衡化过程中需要的操作步骤,但是该函数不能指定均衡化后的直方图分布形式。在某些特定的条件下需要将直方图映射成指定的分布形式,这种将直方图映射成指定分布形式的算法称为直方图匹配或者直方图规定化。直方图匹配与直方图均衡化相似,都是对图像的直方图分布形式进行改变,只是直方图均衡化后的图像直方图是均匀分布的,而直方图匹配后的直方图可以随意指定,即在执行直方图匹配操作时,首先要知道变换后的灰度直方图分布形式,进而确定变换函数。直方图匹配操作能够有目的的增强某个灰度区间,相比于直方图均衡化操作,该算法虽然多了一个输入,但是其变换后的结果也更灵活。

由于不同图像间像素数目可能不同,为了使两个图像直方图能够匹配,需要使用概率形式去表示每个灰度值在图像像素中所占的比例。理想状态下,经过图像直方图匹配操作后图像直方图分布形式应与目标分布一致,因此两者之间的累积概率分布也一致。累积概率为小于等于某一灰度值的像素数目占所有像素中的比例。我们用Vs{V_s}Vs​表示原图像直方图的各个灰度级的累积概率,用Vz{V_z}Vz​表示匹配后直方图的各个灰度级累积概率。那么确定由原图像中灰度值n映射成r的条件如式(6.8)所示。
n,r=arg⁡min⁡n,r∣Vs(n)−Vz(r)∣(6.8)n,r = \arg \mathop {\min }\limits_{n,r} \left| {{V_s}(n) - {V_z}(r)} \right| \tag{6.8}n,r=argn,rmin​∣Vs​(n)−Vz​(r)∣(6.8)
为了更清楚的说明直方图匹配过程,在图4-7中给出了一个直方图匹配示例。示例中目标直方图灰度值2以下的概率都为0,灰度值3的累积概率为0.16,灰度值4的累积概率为0.35,原图像直方图灰度值为0时累积概率为0.19。0.19距离0.16的距离小于距离0.35的距离,因此需要将原图像中灰度值0匹配成灰度值3。同样,原图像灰度值1的累积概率为0.43,其距离目标直方图灰度值4的累积概率0.35的距离为0.08,而距离目标直方图灰度值5的累积概率0.64的距离为0.21,因此需要将原图像中灰度值1匹配成灰度值4。

图4-7 直方图匹配示例

这个寻找灰度值匹配的过程是直方图匹配算法的关键,在代码实现中我们可以通过构建原直方图累积概率与目标直方图累积概率之间的差值表,寻找原直方图中灰度值n的累积概率与目标直方图中所有灰度值累积概率差值的最小值,这个最小值对应的灰度值r就是n匹配后的灰度值。

在OpenCV 4中并没有提供直方图匹配的函数,需要自己根据算法实现图像直方图匹配。在代码清单4-9中给出了实现直方图匹配的示例程序。程序中待匹配的原图是一个图像整体偏暗的图像,目标直方图分配形式来自于一张较为明亮的图像,经过图像直方图匹配操作之后,提高了图像的整体亮度,图像直方图分布也更加均匀,程序中所有的结果在图4-8、图4-9给出。

代码清单4-9 myHistMatch.cpp图像直方图匹配
1.  #include <opencv2\opencv.hpp>
2.  #include <iostream>
3.
4.  using namespace cv;
5.  using namespace std;
6.
7.  void drawHist(Mat &hist, int type, string name)  //归一化并绘制直方图函数
8.  {9.      int hist_w = 512;
10.     int hist_h = 400;
11.     int width = 2;
12.     Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
13.     normalize(hist, hist, 1, 0, type, -1, Mat());
14.     for (int i = 1; i <= hist.rows; i++)
15.     {16.         rectangle(histImage, Point(width*(i - 1), hist_h - 1),
17.             Point(width*i - 1,hist_h - cvRound(20 * hist_h*hist.at<float>(i-1)) - 1),
18.             Scalar(255, 255, 255), -1);
19.     }
20.     imshow(name, histImage);
21. }
22. //主函数
23. int main()
24. {25.     Mat img1 = imread("histMatch.png");
26.     Mat img2 = imread("equalLena.png");
27.     if (img1.empty()||img2.empty())
28.     {29.         cout << "请确认图像文件名称是否正确" << endl;
30.         return -1;
31.     }
32.     Mat hist1, hist2;
33.     //计算两张图像直方图
34.     const int channels[1] = { 0 };
35.     float inRanges[2] = { 0,255 };
36.     const float* ranges[1] = { inRanges };
37.     const int bins[1] = { 256 };
38.     calcHist(&img1, 1, channels, Mat(), hist1, 1, bins, ranges);
39.     calcHist(&img2, 1, channels, Mat(), hist2, 1, bins, ranges);
40.     //归一化两张图像的直方图
41.     drawHist(hist1, NORM_L1, "hist1");
42.     drawHist(hist2, NORM_L1, "hist2");
43.     //计算两张图像直方图的累积概率
44.     float hist1_cdf[256] = { hist1.at<float>(0) };
45.     float hist2_cdf[256] = { hist2.at<float>(0) };
46.     for (int i = 1; i < 256; i++)
47.     {48.         hist1_cdf[i] = hist1_cdf[i - 1] + hist1.at<float>(i);
49.         hist2_cdf[i] = hist2_cdf[i - 1] + hist2.at<float>(i);
50.
51.     }
52.     //构建累积概率误差矩阵
53.     float diff_cdf[256][256];
54.     for (int i = 0; i < 256; i++)
55.     {56.         for (int j = 0; j < 256; j++)
57.         {58.             diff_cdf[i][j] = fabs(hist1_cdf[i] - hist2_cdf[j]);
59.         }
60.     }
61.
62.     //生成LUT映射表
63.     Mat lut(1, 256, CV_8U);
64.     for (int i = 0; i < 256; i++)
65.     {66.         // 查找源灰度级为i的映射灰度
67.         // 和i的累积概率差值最小的规定化灰度
68.         float min = diff_cdf[i][0];
69.         int index = 0;
70.         //寻找累积概率误差矩阵中每一行中的最小值
71.         for (int j = 1; j < 256; j++)
72.         {73.             if (min > diff_cdf[i][j])
74.             {75.                 min = diff_cdf[i][j];
76.                 index = j;
77.             }
78.         }
79.         lut.at<uchar>(i) = (uchar)index;
80.     }
81.     Mat result, hist3;
82.     LUT(img1, lut, result);
83.     imshow("待匹配图像", img1);
84.     imshow("匹配的模板图像", img2);
85.     imshow("直方图匹配结果", result);
86.     calcHist(&result, 1, channels, Mat(), hist3, 1, bins, ranges);
87.     drawHist(hist3, NORM_L1, "hist3");  //绘制匹配后的图像直方图
88.     waitKey(0);
89.     return 0;
90. }

图4-8 myHistMatch.cpp程序中匹配图像原图、模板以及匹配后图像 图4-9 myHistMatch.cpp程序中给图像的直方图

直方图反向投影

如果一张图像的某个区域中显示的是一种结构纹理或者一个独特的形状,那么这个区域的直方图就可以看作是这个结构或者形状的概率函数,在图像中寻找这种概率分布就是在图像中寻找该结构纹理或者独特形状。反向投影(back projection)就是一种记录给定图像中的像素点如何适应直方图模型像素分布方式的一种方法。简单的讲,所谓反向投影就是首先计算某一特征的直方图模型,然后使用模型去寻找图像中是否存在该特征的方法。

OpenCV 4提供了calcBackProject()函数用于对图像直方图反向投影,该函数的函数原型在代码清单4-10中给出。

代码清单4-10 calcBackProject()函数原型
1.  void cv::calcBackProject(const Mat *  images,
2.                                int  nimages,
3.                                const int *  channels,
4.                                InputArray  hist,
5.                                OutputArray  backProject,
6.                                const float **  ranges,
7.                                double  scale = 1,
8.                                bool  uniform = true
9.                                )
  • images:待统计直方图的图像数组,数组中所有的图像应具有相同的尺寸和数据类型,并且数据类型只能是CV_8U、CV_16U和CV_32F三种中的一种,但是不同图像的通道数可以不同。
  • nimages:输入图像数量
  • channels:需要统计的通道索引数组,第一个图像的通道索引从0到images[0].channels()-1,第二个图像通道索引从images[0].channels()到images[0].channels()+ images[1].channels()-1,以此类推。
  • hist:输入直方图
  • backProject:目标反投影图像,与images[0]具有相同尺寸和数据类型的单通道图像。
  • ranges:每个图像通道中灰度值的取值范围
  • scale:输出反投影矩阵的比例因子。
  • uniform:直方图是否均匀的标志符,默认状态下为均匀(true)。

该函数用于在输入图像中寻找与特定图像最匹配的点或者区域,即对图像进行反向投影。该函数输入参数与计算图像直方图函数calcHist()大致相似,都需要输入图像和需要进行反向投影的通道索引数目。区别之处在于该函数需要输入模板图像的直方图统计结果,并返回的是一张图像,而不是直方图统计结果。根据该函数所需要的参数可知,该函数在使用时主要分为四个步骤:

  1. Step1:加载模板图像和待反向投影图像;
  2. Step2:转换图像颜色空间,常用的颜色空间为灰度图像和HSV空间;
  3. Step3:计算模板图像的直方图,灰度图像为一维直方图,HSV图像为H-S通道的二维直方图;
  4. tep4:将待反向投影的图像和模板图像的直方图赋值给反向投影函数calcBackProject(),最终得到反向投影结果。

为了更加熟悉该函数的使用方式,了解图像反向投影的作用,在代码清单4-11中给出了对图像进行反向投影的示例程序。程序中首先加载待反向投影图像和模板图像,模板图像从待反向投影的图像中截取,之后将两张图像由RGB颜色空间转成HSV空间中,统计H-S通道的直方图,将直方图归一化后绘制H-S通道的二维直方图。最后将待反向投影和模板图像的直方图输入给函数calcBackProject(),得到图像反向投影结果。

代码清单4-11 myCalcBackProject.cpp图像直方图反向投影
1.  #include <opencv2\opencv.hpp>
2.  #include <iostream>
3.
4.  using namespace cv;
5.  using namespace std;
6.
7.  void drawHist(Mat &hist, int type, string name)  //归一化并绘制直方图函数
8.  {9.      int hist_w = 512;
10.     int hist_h = 400;
11.     int width = 2;
12.     Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
13.     normalize(hist, hist, 255, 0, type, -1, Mat());
14.     namedWindow(name, WINDOW_NORMAL);
15.     imshow(name, hist);
16. }
17. //主函数
18. int main()
19. {20.     Mat img = imread("apple.jpg");
21.     Mat sub_img = imread("sub_apple.jpg");
22.     Mat img_HSV, sub_HSV, hist, hist2;
23.     if (img.empty() || sub_img.empty())
24.     {25.         cout << "请确认图像文件名称是否正确" << endl;
26.         return -1;
27.     }
28.
29.     imshow("img", img);
30.     imshow("sub_img", sub_img);
31.     //转成HSV空间,提取S、V两个通道
32.     cvtColor(img, img_HSV, COLOR_BGR2HSV);
33.     cvtColor(sub_img, sub_HSV, COLOR_BGR2HSV);
34.     int h_bins = 32; int s_bins = 32;
35.     int histSize[] = { h_bins, s_bins };
36.     //H通道值的范围由0到179
37.     float h_ranges[] = { 0, 180 };
38.     //S通道值的范围由0到255
39.     float s_ranges[] = { 0, 256 };
40.     const float* ranges[] = { h_ranges, s_ranges };  //每个通道的范围
41.     int channels[] = { 0, 1 };  //统计的通道索引
42.     //绘制H-S二维直方图
43.     calcHist(&sub_HSV, 1, channels, Mat(), hist, 2, histSize, ranges, true, false);
44.     drawHist(hist, NORM_INF, "hist");  //直方图归一化并绘制直方图
45.     Mat backproj;
46.     calcBackProject(&img_HSV, 1, channels, hist, backproj,ranges,1.0);  //直方图反向投影
47.     imshow("反向投影后结果", backproj);
48.     waitKey(0);
49.     return 0;
50. }

图4-10 myCalcBackProject.cpp程序运行结果

OpenCV 4开发详解
往期推荐
【OpenCV 4开发详解】Image Watch插件的使用
【OpenCV 4开发详解】安装过程中问题解决方案
【OpenCV 4开发详解】了解OpenCV的模块架构
【OpenCV 4开发详解】Mat类介绍
【OpenCV 4开发详解】Mat类构造与赋值
【OpenCV 4开发详解】4种读取Mat类元素的的方法
【OpenCV 4开发详解】图像的读取与显示
【OpenCV 4开发详解】视频加载与摄像头调用
【OpenCV 4开发详解】图像与视频的保存
【OpenCV 4开发详解】保存和读取XML和YMAL文件
【OpenCV 4开发详解】颜色模型与转换
【OpenCV 4开发详解】多通道分离与合并
【OpenCV 4开发详解】图像像素统计
【OpenCV 4开发详解】两图像间的像素操作
【OpenCV 4开发详解】图像二值化
【OpenCV 4开发详解】图像LUT查找表
【OpenCV 4开发详解】图像连接
【OpenCV 4开发详解】图像仿射变换
【OpenCV 4开发详解】图像透视变换
【OpenCV 4开发详解】图像极坐标变换
【OpenCV 4开发详解】图像上绘制几何图形
【OpenCV 4开发详解】图像金字塔
【OpenCV 4开发详解】窗口交互操作
【OpenCV 4开发详解】图像直方图绘制
【OpenCV 4开发详解】直方图操作
经过几个月的努力,市面上第一本OpenCV 4入门书籍《OpenCV 4开发详解》将春节后由人民邮电出版社发行。如果小伙伴觉得内容有帮助,希望到时候多多支持!
关注小白的小伙伴可以提前看到书中的内容,我们创建了学习交流群,欢迎各位小伙伴添加小白微信加入交流群,添加小白时请备注“学习OpenCV 4”。

【OpenCV 4开发详解】直方图应用相关推荐

  1. 【OpenCV 4开发详解】直方图操作

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  2. 【OpenCV 4开发详解】图像直方图绘制

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  3. 【OpenCV 4开发详解】漫水填充法

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  4. 【OpenCV 4开发详解】点集拟合

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  5. 【OpenCV 4开发详解】轮廓外接多边形

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  6. 【OpenCV 4开发详解】轮廓面积与长度

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  7. 【OpenCV 4开发详解】轮廓发现与绘制

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  8. 【OpenCV 4开发详解】圆形检测

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  9. 【OpenCV 4开发详解】形态学应用

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

最新文章

  1. C#在DataTable中使用LINQ
  2. 视频播放的方法Android,详解Android App中使用VideoView来实现视频播放的方法
  3. 【面试】Java面试常见问题汇总(不含答案)、面试指导学习笔记
  4. Linux删除证书文件命令,Linux基础面面观之文件复制、移动、删除的命令
  5. del服务器如何收系统日志,利用Rsyslog集中收集系统日志和用户操作记录以及相关处理方法...
  6. 零基础跨行学习Java是否可行
  7. 【0x50 动态规划】Mobile Service【线性DP】
  8. c语言程序设计运动的小球,课程设计--运动的小球(15页)-原创力文档
  9. 如何利用Python爬虫获取网络小说
  10. 为何不能直接拷贝软件安装好的目录运行
  11. 极简教程!教你快速将K3s与Cloud Controller集成
  12. windows系统设置定时开关机的方法
  13. 如何增加微信阅读量,新手公众号如何增加阅读量
  14. wilcoxon rank sum test R代码
  15. c语言数码管中断器,数码管动态显示 定时器中断 数码管动态显示不正常
  16. 简单易懂!!shell循环语句!for、while、until
  17. 项目35.5 在北京买房
  18. unity ugui android 小键盘,Unity inputfield 实现显示 隐藏密码功能(在安卓中切换不打开下虚拟键盘)...
  19. 新手做网站只需要4个步骤
  20. ATX框架下 app的webview操作

热门文章

  1. 京东金融副总裁曹鹏:不做“浮冰”,深挖AI技术和场景 | AI聚变
  2. 北京大兴要打造成未来科技新中心?
  3. 网易有道周枫:AI正带来革命性变化,但在线教育的核心是内容
  4. 架构师实践日 · 6.30 杭州站 | 视觉 AI 技术如何助力行业提升?来西子湖畔与业内大咖面对面交流!
  5. Python需求增速达174%,AI人才缺口仍超百万!这份来自2017年的实际招聘数据如是说
  6. NIPS | 谷歌AI大军来袭,看450多名员工如何横扫今年大会
  7. 观点 | 转行人士如何在人工智能领域保持一定的竞争力?
  8. 图片提取文字功能很神奇?Java几行代码搞定它!
  9. redisTemplate分布式锁演变、redission分布式锁实现!
  10. 使用Kubespray部署Kubernetes集群