【从零学习OpenCV 4】图像直方图绘制

  • 2019 年 12 月 13 日
  • 笔记

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

图像直方图是图像处理中非常重要的像素统计结果,图像直方图不再表征任何的图像纹理信息,而是对图像像素的统计。由于同一物体无论是旋转还是平移在图像中都具有相同的灰度值,因此直方图具有平移不变性、放缩不变性等优点,因此可以用来查看图像整体的变化形式,例如图像是否过暗、图像像素灰度值主要集中在哪些范围等,在特定的条件下也可以利用图像直方图进行图像的识别,例如对数字的识别。

图像直方图简单来说就是统计图像中每个灰度值的个数,之后将图像灰度值作为横轴,以灰度值个数或者灰度值所占比率作为纵轴绘制的统计图。通过直方图可以看出图像中哪些灰度值数目较多,哪些较少,可以通过一定的方法将灰度值较为集中的区域映射到较为稀疏的区域,从而使得图像在像素灰度值上分布更加符合期望状态。通常情况下,像素灰度值代表亮暗程度,因此通过图像直方图可以分析图像亮暗对比度,并调整图像的亮暗程度。

在OpenCV 4中只提供了图像直方图的统计函数calcHist(),该函数能够统计出图像中每个灰度值的个数,但是对于直方图的绘制需要使用者自行绘制。我们首先学习统计灰度值数目的函数calcHist()的使用,该函数的原型在代码清单4-1中给出。

代码清单4-1 calcHist()函数原型  1.  void cv::calcHist(const Mat * images,  2.                        int  nimages,  3.                        const int * channels,  4.                        InputArray mask,  5.                        OutputArray hist,  6.                        int  dims,  7.                        const int * histSize,  8.                        const float ** ranges,  9.                        bool  uniform = true,  10.                        bool  accumulate = false  11.                        )
  • images:待统计直方图的图像数组,数组中所有的图像应具有相同的尺寸和数据类型,并且数据类型只能是CV_8U、CV_16U和CV_32F三种中的一种,但是不同图像的通道数可以不同。
  • nimages:输入的图像数量
  • channels:需要统计的通道索引数组,第一个图像的通道索引从0到images[0].channels()-1,第二个图像通道索引从images[0].channels()到images[0].channels()+ images[1].channels()-1,以此类推。
  • mask:可选的操作掩码,如果是空矩阵则表示图像中所有位置的像素都计入直方图中,如果矩阵不为空,则必须与输入图像尺寸相同且数据类型为CV_8U。
  • hist:输出的统计直方图结果,是一个dims维度的数组。
  • dims:需要计算直方图的维度,必须是整数,并且不能大于CV_MAX_DIMS,在OpenCV 4.0和OpenCV 4.1版本中为32。
  • histSize:存放每个维度直方图的数组的尺寸。
  • ranges:每个图像通道中灰度值的取值范围。
  • uniform:直方图是否均匀的标志符,默认状态下为均匀(true)。
  • accumulate:是否累积统计直方图的标志,如果累积(true),则统计新图像的直方图时之前图像的统计结果不会被清除,该同能主要用于统计多个图像整体的直方图。

该函数用于统计图像中每个灰度值像素的个数,例如统计一张CV_8UC1的图像,需要统计灰度值从0到255中每一个灰度值在图像中的像素个数,如果某个灰度值在图像中没有,那么该灰度值的统计结果就是0。由于该函数具有较多的参数,并且每个参数都较为复杂,因此作者建议读者在使用该函数时只统计单通道图像的灰度值分布,对于多通道图像可以将图像每个通道分离后再进行统计。

为了使读者更加了解函数的使用方法,我们在代码清单4-2中提供了绘制灰度图像的图像直方图的示例程序。在程序中我们首先使用calcHist()函数统计灰度图像里面每个灰度值的数目,之后通过不断绘制矩形的方式实现直方图的绘制。由于图像中部分灰度值像素数目较多,因此我们将每个灰度值数目缩小了20倍后再进行绘制,绘制的直方图在图4-1中所示。在程序中我们使用了OpenCV 4提供的四舍五入的取整函数cvRound(),该函数输入参数为double类型的变量,返回值为对该变量四舍五入后的int型数值。

代码清单4-2 myCalHist.cpp绘制图像直方图  1.  #include <opencv2opencv.hpp>  2.  #include <iostream>  3.  4.  using namespace cv;  5.  using namespace std;  6.  7.  int main()  8. {  9.    Mat img = imread("apple.jpg");  10.    if (img.empty())  11.    {  12.      cout << "请确认图像文件名称是否正确" << endl;  13.      return -1;  14.    }  15.    Mat gray;  16.    cvtColor(img, gray, COLOR_BGR2GRAY);  17.    //设置提取直方图的相关变量  18.    Mat hist; //用于存放直方图计算结果  19.    const int channels[1] = { 0 }; //通道索引  20.    float inRanges[2] = { 0,255 };  21.    const float* ranges[1] = { inRanges }; //像素灰度值范围  22.    const int bins[1] = { 256 }; //直方图的维度,其实就是像素灰度值的最大值  23.    calcHist(&img, 1, channels, Mat(), hist, 1, bins, ranges); //计算图像直方图  24.    //准备绘制直方图  25.    int hist_w = 512;  26.    int hist_h = 400;  27.    int width = 2;  28.    Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);  29.    for (int i = 1; i <= hist.rows; i++)  30.    {  31.      rectangle(histImage, Point(width*(i - 1), hist_h - 1),  32.        Point(width*i - 1, hist_h - cvRound(hist.at<float>(i - 1) / 20)),  33.        Scalar(255, 255, 255), -1);  34.    }  35.    namedWindow("histImage", WINDOW_AUTOSIZE);  36.    imshow("histImage", histImage);  37.    imshow("gray", gray);  38.    waitKey(0);  39.    return 0;  40.  }

图4-1 myCalHist.cpp程序运行结果