【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.{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程序运行结果

直方图匹配

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

由于不同图像间像素数目可能不同,为了使两个图像直方图能够匹配,需要使用概率形式去表示每个灰度值在图像像素中所占的比例。理想状态下,经过图像直方图匹配操作后图像直方图分布形式应与目标分布一致,因此两者之间的累积概率分布也一致。累积概率为小于等于某一灰度值的像素数目占所有像素中的比例。我们用 V s {V_s} Vs表示原图像直方图的各个灰度级的累积概率,用 V z {V_z} Vz表示匹配后直方图的各个灰度级累积概率。那么确定由原图像中灰度值n映射成r的条件如式(6.8)所示。
n , r = arg ⁡ min ⁡ n , r ∣ V s ( n ) − V z ( 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到17937.float h_ranges[] = { 0, 180 };38.//S通道值的范围由0到25539.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;//直方图反向投影47.imshow("反向投影后结果", backproj);48.waitKey(0);49.return 0;50.}

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

(0)

相关推荐

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

    重磅干货,第一时间送达 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4入门书籍<从零学习OpenCV 4>.为了更让小伙伴更早的了解最新版的OpenCV 4,小白与出版社沟通 ...

  • python+opencv图像处理(十四)

    图像直方图 1.灰度图像的直方图 灰度图像的直方图是灰度级和这种灰度级的概率之间关系的图形. 直接看图,下图中左侧是原图,右图为其直方图. 完整代码如下: import cv2 as cv impor ...

  • 【从零学习OpenCV 4】直方图比较

    重磅干货,第一时间送达 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4入门书籍<从零学习OpenCV 4>.为了更让小伙伴更早的了解最新版的OpenCV 4,小白与出版社沟通 ...

  • python+opencv图像处理(十五)

    直方图均衡 直方图均衡化是通过对图像的直方图进行修正来获得图像增强效果的方法,主要是进行对比度增强,就是让亮的更亮,暗的更亮. 1.灰度图像的直方图均衡 先上图看效果. 如图上标题所示,第一张是原图, ...

  • 【从零学习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】边缘检测原理

    重磅干货,第一时间送达 经过几个月的努力,小白终于完成了市面上第一本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开发详解>.为 ...

  • 知荐 | 基于视觉系统的ADAS横向开发详解

    自动驾驶2级以上包含了对横向控制的不同算法策略,包含对中控制.纠偏控制及预警控制,涉及不同范围的功能,如车道保持辅助LKA/紧急车道保持ELK.交通拥堵辅助/高速集成式巡航TJAICA.车道偏离预警L ...

  • iMX8MQ MCUXpresso SDK开发详解

    飞凌iMX8MQ 平台内部有一个Cortex M4内核,支持使用MCUXpresso SDK进行开发. MCUXpresso SDK是微控制器软件支持的集合,它包含外围驱动程序,RPMSG多核通信,以 ...

  • 【精品博文】《FPGA设计技巧与案例开发详解》初探(二)之工程文件管理

    对于彬哥书中所提到的工程文件管理,由于只给了一个框架,简要介绍了各个文件夹的作用,进而有许多初学者不知道具体怎么操作,会在群里问这类问题.下面,我将详细介绍一下如何构建彬哥的工程目录. 首先,我还是简 ...

  • 【精品博文】《FPGA设计技巧与案例开发详解(第二版)》上线

    呕心沥血几春秋,携友再创第二版,<FPGA设计技巧与案例开发详解>,在去年经历了<FPGA设计技巧与案例开发详解>第一版的各种风风雨雨后,我们响应广大网友与高校学生的号召,精心 ...

  • 直播电商、直播购物,商城系统开发模式详解

    商城直播系统是在直播系统与商城系统的基础上开发出的一种新的模式,利用直播带货商城源码,可以搭建购物商城直播平台,这样做可以让消费者实现在看视频直播的同时看到自己喜欢的商品直接下单消费的功能. 电商平台 ...