入门系列索引:OpenCV入门索引目录
直方图直方图(Histogram),又称质量分布图,是一种统计报告图,由一系列高度不等的纵向条纹或线段表示数据分布的情况。 一般用横轴表示数据类型,纵轴表示分布情况。
直方图是数值数据分布的精确图形表示。 这是一个连续变量(定量变量)的概率分布的估计,并且被卡尔·皮尔逊(Karl Pearson)首先引入。它是一种条形图。 为了构建直方图,第一步是将值的范围分段,即将整个值的范围分成一系列间隔,然后计算每个间隔中有多少值。 这些值通常被指定为连续的,不重叠的变量间隔。 间隔必须相邻,并且通常是(但不是必须的)相等的大小。
直方图也可以被归一化以显示“相对”频率。 然后,它显示了属于几个类别中的每个案例的比例,其高度等于1。
灰度直方图(Histogram)是数字图像处理中最简单、最有用的工具之一,它概括了一幅图像的灰度级内容此处对直方图的数学原理以及OpenCV中的示例进行了展示。
定义灰度直方图(histogram)是灰度级的函数,描述的是图像中每种灰度级像素的个数,反映图像中每种灰度出现的频率。横坐标是灰度级,纵坐标是灰度级出现的频率(对数字图像来说,意味着该灰度级像素的个数)。
过通常会将纵坐标归一化到[0,1]区间内,也就是将灰度级出现的频率(像素个数)除以图像中像素的总数。
对于连续图像,平滑地从中心的高灰度级变化到边缘的低灰度级。直方图定义为:
H ( D ) = lim δ D → + ∞ A ( D ) − A ( D + δ D ) δ D = − d δ D A ( D ) H(D)={\lim_{\delta D \to +\infty}}{\frac {A(D)-A(D+\delta D)} {\delta D}}=-\frac {d} {\delta D} A(D) H ( D ) = lim δ D → + ∞ δ D A ( D ) − A ( D + δ D ) = − δ D d A ( D )
其中A(D)A(D)为阈值面积函数:为一幅连续图像中被具有灰度级D的所有轮廓线所包围的面积。
上式说明,一幅连续图像的直方图是其面积函数的导数的负值。
直方图的绘制 将图像的灰度归一化若图像的灰度级为0,1,……,L-1,令r k = k L − 1 , k = 0 , 1 , ⋯ , L − 1 r_k=\frac {k} {L-1},k=0,1,⋯,L-1 r k = L − 1 k , k = 0 , 1 , ⋯ , L − 1
则0 ≤ r k ≤ 1 0≤r_k≤1 0 ≤ r k ≤ 1 ,L为灰度级的层数,Δ L k = r k + 1 − r k ΔL_k=r_{k+1}-r_k Δ L k = r k + 1 − r k 为灰度间隔。
计算各灰度级的像素频数(或概率)设n k n_k n k 表示灰度级为r k r_k r k 的像素的个数,N为总的像素个数,计算像素频数的公式为:p r ( r k ) = n k N p_r(r_k)=\frac {n_k} {N} p r ( r k ) = N n k
作图建立直角坐标系,横轴表示灰度级r k r_k r k 的取值,纵轴表示灰度级对应的概率p r ( r k ) p_r(r_k) p r ( r k )
下面为对一幅10X10的图像求其8级灰度直方图的示例
直方图的性质图像被缩减成直方图后,所有的空间信息都丢失了,也就是说,直方图不能提供像素在图像中的位置信息。因此不同的图像可以有相同的直方图。
OpenCV绘制直方图直方图的计算是很简单的,无非是遍历图像的像素,统计每个灰度级的个数。在OpenCV中封装了直方图的计算函数calcHist,为了更为通用,该函数的参数有些复杂,其声明如下:
calcHist (const Mat* images,int images,const int * channels, InputArray mask, OutputArray hist,int dims,const int * histsize, const float * ranges,bool uniform,bool accumulate )
测试代码:
普通的一维直方图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> using namespace cv;using namespace std;int main () { Mat srcImage = imread ("1.jpg" , 0 ); imshow ("Original" , srcImage); if (!srcImage.data) { cout << "fail to load image" << endl; return 0 ; } MatND dstHist; int dims = 1 ; float hranges[] = {0 , 255 }; const float *ranges[] = {hranges}; int size = 256 ; int channels = 0 ; calcHist (&srcImage, 1 , &channels, Mat (), dstHist, dims, &size, ranges); int scale = 1 ; Mat dstImage (size * scale, size, CV_8U, Scalar(0 )) ; double minValue = 0 ; double maxValue = 0 ; minMaxLoc (dstHist, &minValue, &maxValue, 0 , 0 ); int hpt = saturate_cast <int >(0.9 * size); for (int i = 0 ; i < 256 ; i++) { float binValue = dstHist.at <float >(i); int realValue = saturate_cast <int >(binValue * hpt / maxValue); rectangle (dstImage, Point (i * scale, size - 1 ), Point ((i + 1 ) * scale - 1 , size - realValue), Scalar (255 )); } imshow ("Result" , dstImage); waitKey (0 ); return 0 ; }
测试结果:
动态调整直方图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 #include <opencv2/opencv.hpp> cv::Mat src; cv::Mat histimg; cv::MatND hist;int histSize = 50 ; void HIST (int t,void *) { char string[10 ]; if (histSize==0 ) { printf ("直方图条数不能为零!\n" ); } else { int dims = 1 ; float hranges[2 ] = {0 , 255 }; const float *ranges[1 ] = {hranges}; int channels = 0 ; histimg.create (512 ,256 *4 ,CV_8UC3); histimg.setTo (cv::Scalar (0 ,0 ,0 )); calcHist (&src, 1 , &channels, cv::Mat (), hist, dims, &histSize, ranges); double maxVal = 0 ; cv::Point maxLoc; cv::minMaxLoc (hist, NULL , &maxVal, NULL , &maxLoc); double bin_w =(double ) histimg.cols / histSize; double bin_u = (double )histimg.rows/ maxVal; for (int i=0 ;i<histSize;i++) { cv::Point p0=cv::Point (i*bin_w,histimg.rows); float binValue = hist.at <float >(i); cv::Point p1=cv::Point ((i+1 )*bin_w,histimg.rows-binValue*bin_u); cv::rectangle (histimg,p0,p1,cv::Scalar (0 ,255 ,0 ),2 ,8 ,0 ); } for (int i = 0 ; i < histSize; i++) { cv::line (histimg, cv::Point (bin_w*i+bin_w/2 , histimg.rows-hist.at <float >(i)*bin_u), cv::Point (bin_w*(i+1 )+bin_w/2 , histimg.rows-hist.at <float >(i+1 )*bin_u), cv::Scalar (255 , 0 , 0 ), 2 , 8 , 0 ); } int kedu=0 ; for (int i=1 ;kedu<maxVal;i++) { kedu=i*maxVal/10 ; sprintf (string,"%d" ,kedu); cv::putText (histimg, string , cv::Point (0 ,histimg.rows-kedu*bin_u), cv::FONT_HERSHEY_SIMPLEX,0.5 ,cv::Scalar (0 ,255 ,255 ),1 ); cv::line ( histimg,cv::Point (0 ,histimg.rows-kedu*bin_u),cv::Point (histimg.cols-1 ,histimg.rows-kedu*bin_u),cv::Scalar (0 ,0 ,255 )); } kedu=0 ; for (int i=1 ;kedu<256 ;i++) { kedu=i*20 ; sprintf (string,"%d" ,kedu); putText (histimg, string , cv::Point (kedu*(histimg.cols / 256 ),histimg.rows), cv::FONT_HERSHEY_SIMPLEX,0.5 ,cv::Scalar (0 ,255 ,255 ),2 ); } cv::imshow ( "Histogram" , histimg ); } }int main ( int argc, char ** argv ) { src = cv::imread ("../pictures/lena.jpg" ,0 ); cv::namedWindow ( "src" , 1 ); cv::imshow ( "src" , src); cv::namedWindow ( "Histogram" , 1 ); int maxvalue = 256 ; cv::createTrackbar ( "histSize" , "src" , &histSize, maxvalue, HIST ); HIST (0 ,0 ); cv::waitKey (0 ); cv::destroyWindow ("src" ); cv::destroyWindow ("Histogram" ); return 0 ; }
测试结果:
对一副彩色图的每个通道进行绘制直方图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/opencv.hpp> using namespace cv;int main () { Mat srcImage; srcImage = imread ("1.jpg" ); imshow ("Original" , srcImage); int bins = 256 ; int hist_size[] = {bins}; float range[] = {0 , 256 }; const float *ranges[] = {range}; MatND redHist, grayHist, blueHist; int channels_r[] = {0 }; calcHist (&srcImage, 1 , channels_r, Mat (), redHist, 1 , hist_size, ranges, true , false ); int channels_g[] = {1 }; calcHist (&srcImage, 1 , channels_g, Mat (), grayHist, 1 , hist_size, ranges, true , false ); int channels_b[] = {2 }; calcHist (&srcImage, 1 , channels_b, Mat (), blueHist, 1 , hist_size, ranges, true , false ); double maxValue_red, maxValue_green, maxValue_blue; minMaxLoc (redHist, 0 , &maxValue_red, 0 , 0 ); minMaxLoc (grayHist, 0 , &maxValue_green, 0 , 0 ); minMaxLoc (blueHist, 0 , &maxValue_blue, 0 , 0 ); int scale = 1 ; int histHeight = 256 ; Mat histImage = Mat::zeros (histHeight, bins * 3 , CV_8UC3); for (int i = 0 ; i < bins; i++) { float binValue_red = redHist.at <float >(i); float binValue_green = grayHist.at <float >(i); float binValue_blue = blueHist.at <float >(i); int intensity_red = cvRound (binValue_red * histHeight / maxValue_red); int intensity_green = cvRound (binValue_green * histHeight / maxValue_green); int intensity_blue = cvRound (binValue_blue * histHeight / maxValue_blue); rectangle (histImage, Point (i * scale, histHeight - 1 ), Point ((i + 1 ) * scale - 1 , histHeight - intensity_red), Scalar (255 , 0 , 0 )); rectangle (histImage, Point ((i + bins) * scale, histHeight - 1 ), Point ((i + bins + 1 ) * scale - 1 , histHeight - intensity_green), Scalar (0 , 255 , 0 )); rectangle (histImage, Point ((i + bins * 2 ) * scale, histHeight - 1 ), Point ((i + bins * 2 + 1 ) * scale - 1 , histHeight - intensity_blue), Scalar (0 , 0 , 255 )); } imshow ("Result" , histImage); waitKey (0 ); return 0 ; }
测试结果: