OPENCV直方图与匹配

直方图可以用来描述不同的参数和事物,如物体的色彩分布,物体的边缘梯度模版以及目标位置的当前假设的概率分布.

         直方图就是对数据进行统计的一种方法,并且将统计值定义到一系列定义好的bin(组距)中,获得一张数据分布的统计图.

         比如,现在有一个一维数组,其值从0-255,我们可以以20为组距,来分别统计数组中0-20的数据的总量,20-40的数据的总量,最后,以这个bin作为横轴,统计值作为y轴,得到一张统计图,这就是数据范围的直方图,再比如,一张灰度图像,值也是0-255,我们也可以这样做,这样也能得到一张灰度的统计图(实际上前面所说的直方图均衡化,第一步就是做的这个工作)

         图像直方图,是表示数字图像中亮度的直方图,标识了图像中每个亮度值的像素数量,计算机领域中,常借助于图像的直方图来实现图像的二值化.

         总结来说两点1.直方图正对图像来说,是图像中像素强度分布的图形表达方式2.统计的是每一个强度值所具有的像素个数.

         直方图并不局限于统计像素灰度,他可以统计图像的任何特征,如梯度,方向等,

         术语1.dims 需统计的特征的数量,仅统计灰度,dims = 12.bin 每个特征空间中子区段的数目,也叫直条或者组距.3.range 每个特征空间的取值范围,如灰度特征空间,取值就是0-255

一.直方图计算

         API:void calcHist(源图或者元数据指针,int 输入的源的个数,需要统计的通道索引dims,inputarray 可选的操作掩码,outputarray 输出目标直方图,int 需计算的直方图维度,int* 直方图每个维度的尺寸构成的数组,float** 每个维度的取值范围构成的数组的指针,bool 直方图是否均匀,bool 累计标识符)

         注:源数组中每一个元素的深度和尺寸应该相同,如果操作掩码不为noarray(),则操作掩码必须为八位,而且和源数组中的每一个元素的尺寸相同,掩码中不为0区域的坐标对应的源数组中的相应坐标的元素才会被统计,直方图是否均匀,默认取值为true(均匀),累计标识符默认为false,该标识符的作用是允许从多个阵列中计算直方图,或者在特定的时间更新直方图.

         API:Point* minMaxLoc(inputarray 输入数组,最小值指针,最大值指针,point* 最小元素对应的坐标,point* 最大元素对应的坐标,inputarray 子阵列可选掩码,)

         注:MatND是直方图对应的数据类型,用来存储直方图.

         就这么说显得不知道干嘛的,那让我们看看例子怎么用直方图

//计算色调 饱和度 二维直方图 h-s hist
int main(int argc,char* argv[])
{
   Mat srcImagergb,srcImagehsv;
   srcImagergb = imread("F:\opencv\OpenCVImage\hsvHist.jpg");
   cvtColor(srcImagergb, srcImagehsv, CV_RGB2HSV);
   
   int hueBinNumber = 30;//色调bin量化级别
   int saturationBinNumber = 30;//饱和度bin量化级别
   
   int histSize[2] = {hueBinNumber,saturationBinNumber};
   //定义色调变化范围
   float hueRanges[2] = {0,180};
   //定义饱和度变化范围
   float saturationRanges[2] = {0,256};
   
   const float* ranges[2] = {hueRanges,saturationRanges};
   
   MatND distHist;
   int channels[2] = {0,1};//h s 通道
   calcHist(&srcImagehsv, //输入的数组
            1,//输入数组中图像个数
            channels,//通道索引 0 1 通道
            Mat(),//掩码为空
            distHist,//输出的目标直方图
            2,//需要计算的直方图的维度
            histSize,//存放每个维度直方图尺寸的数组
            ranges,//每一维数值取值范围数组
            true,//直方图均匀
            false);//直方图在配置阶段被清零
   //为绘制直方图准备参数
   double maxValue = 0;
   minMaxLoc(distHist, 0,&maxValue,0,0);
   int scale = 10;
   Mat histImage = Mat(saturationBinNumber*scale,hueBinNumber*10,CV_8UC3);
   
   for(int i = 0; i < hueBinNumber; i++)
   {
       for (int j = 0; j < saturationBinNumber; j++) {
           float binValue = distHist.at<float>(i,j);//直方图直条的值
           int intensity = cvRound(binValue*255/maxValue);//强度
           rectangle(histImage, Point(i*scale,j*scale), Point((i+1)*scale-1,(j+1)*scale-1), Scalar::all(intensity),FILLED);
       }
   }
   
   imshow("src image", srcImagehsv);
   imshow("hist image", histImage);
   
   moveWindow("src image", 0, 0);
   moveWindow("hist image", srcImagehsv.cols, 0);
   
   waitKey(0);
   return 0;
}


//图像的一维直方图
int main(int argc,char* argv[])
{
   Mat srcImage;
   srcImage = imread("F:\opencv\OpenCVImage\hist01.jpg");
   
   MatND dstHist;
   int dims = 1;
   float hanges[] = {0,255};
   const float* ranges[] = {hanges};
   int size = 256;
   int channel = 0;
   calcHist(&srcImage, 1, &channel, Mat(), dstHist, dims, &size, ranges);
   
   int scale = 1;
   double minValue = 0;
   double maxValue = 0;
   minMaxLoc(dstHist, &minValue, &maxValue,0,0);
   Mat histImage = Mat(size*scale,size,CV_8U,Scalar(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(histImage, Point(i*scale,size-1), Point((i+1)*scale-1,size-realvalue), Scalar(255));
   }
   
   imshow("src image", srcImage);
   imshow("hist image", histImage);
   
   moveWindow("src image", 0, 0);
   moveWindow("hist image", srcImage.cols, 0);
   
   waitKey(0);
   return 0;
}
//显示图像的R G B 分量直方图
//与上一个程序的主要不同为提取了三个分量的直方图,上一个程序只提取了b分量的直方图
int main(int argc,char* argv[])
{
   Mat srcImage;
   srcImage = imread("F:\opencv\OpenCVImage\histRgb.jpg");
   
   MatND dstHistr,dstHistg,dstHistb;
   int dims = 1;
   float hanges[] = {0,255};
   const float* ranges[] = {hanges};
   int size = 256;
   int channel[] = {0};
   calcHist(&srcImage, 1, channel, Mat(), dstHistb, dims, &size, ranges);
   channel[0] = 1;
   calcHist(&srcImage, 1, channel, Mat(), dstHistg, dims, &size, ranges);
   channel[0] = 2;
   calcHist(&srcImage, 1, channel, Mat(), dstHistr, dims, &size, ranges);
   
   
   int scale = 1;
   double minValueb = 0;
   double maxValueb = 0;
   double minValueg = 0;
   double maxValueg = 0;
   double minValuer = 0;
   double maxValuer = 0;
   minMaxLoc(dstHistb, &minValueb, &maxValueb,0,0);
   minMaxLoc(dstHistg, &minValueg, &maxValueg,0,0);
   minMaxLoc(dstHistr, &minValuer, &maxValuer,0,0);
   Mat histImage = Mat(size*scale,size*3,CV_8UC3,Scalar::all(0));
   
   int hpt = saturate_cast<int>(0.9*size);
   for(int i = 0 ; i < 256; i++)
   {
       float binValue = dstHistb.at<float>(i);//直方图的统计数量
       int realvalue = saturate_cast<int>(binValue*hpt/maxValueb);
       rectangle(histImage, Point(i*scale,size-1), Point((i+1)*scale-1,size-realvalue), Scalar(255,0,0));
   }
   
   for(int i = 0 ; i < 256; i++)
   {
       float binValue = dstHistg.at<float>(i);
       int realvalue = saturate_cast<int>(binValue*hpt/maxValueg);
       rectangle(histImage, Point(i*scale+size,size-1), Point((i+1)*scale-1+size,size-realvalue), Scalar(0,255,0));
   }
   
   for(int i = 0 ; i < 256; i++)
   {
       float binValue = dstHistr.at<float>(i);
       int realvalue = saturate_cast<int>(binValue*hpt/maxValuer);
       rectangle(histImage, Point(i*scale+size*2,size-1), Point((i+1)*scale-1+size*2,size-realvalue), Scalar(0,0,255));
   }
   
   imshow("src image", srcImage);
   imshow("hist image", histImage);
   
   moveWindow("src image", 0, 0);
   moveWindow("hist image", srcImage.cols, 0);
   
   waitKey(0);
   return 0;
}

二.直方图的匹配

         虽然直方图是一个统计值,但是有时候我们也需要比较两个直方图的相似度,作为我们判定依据的一部分,这时候就需要用到直方图的匹配了.

         API: double compareHist(源直方图1,源直方图2,int 直方图匹配方法).

         注:该API返回值就是匹配的结果,匹配方法有四种 CV_COMP_CHISQ卡方,返回值越小匹配度越高 CV_COMP_CORREL相关性匹配,返回值越大匹配程度越高 CV_COMP_INTERSECT 直方图相交,返回值越大匹配度越高CV_COMP_BHATTACHARYYA返回值越小匹配度越高.

         直方图匹配例子如下

int main(int argc,char* argv[])
{
   Mat src1Image,src1HalfImage,src2Image,src3Image;
   Mat src1HsvImage,src1HalfHsvImage,src2HsvImage,src3HsvImage;
   
   src1Image = imread("F:\opencv\OpenCVImage\compareHist01.jpg");
   src2Image = imread("F:\opencv\OpenCVImage\compareHist02.jpg");
   src3Image = imread("F:\opencv\OpenCVImage\compareHist03.jpg");
   src1HalfImage = Mat(src1Image, Range(src1Image.rows/2,src1Image.rows-1),Range(0,src1Image.cols-1));
   
   cvtColor(src1Image, src1HsvImage, CV_RGB2HSV);
   cvtColor(src2Image, src2HsvImage, CV_RGB2HSV);
   cvtColor(src3Image, src3HsvImage, CV_RGB2HSV);
   cvtColor(src1HalfImage, src1HalfHsvImage, CV_RGB2HSV);
   
   int hbins = 50,sbins = 60;
   int histSize[] = {hbins,sbins};
   int channels[] = {0,1};
   float hRange[] = {0,256};
   float sRange[] = {0,180};
   const float* ranges[] = {hRange,sRange};
   
   MatND src1Hist,src2Hist,src3Hist,srcHalfHist;
   
   calcHist(&src1HsvImage, 1, channels, Mat(), src1Hist, 2, histSize, ranges,true,false);
   normalize(src1Hist, src1Hist, 0,1, NORM_MINMAX,-1,Mat());
   
   calcHist(&src2HsvImage, 1, channels, Mat(), src2Hist, 2, histSize, ranges,true,false);
   normalize(src2Hist, src2Hist, 0,1, NORM_MINMAX,-1,Mat());
   
   calcHist(&src3HsvImage, 1, channels, Mat(), src3Hist, 2, histSize, ranges,true,false);
   normalize(src3Hist, src3Hist, 0,1, NORM_MINMAX,-1,Mat());
   
   calcHist(&src1HalfHsvImage, 1, channels, Mat(), srcHalfHist, 2, histSize, ranges,true,false);
   normalize(srcHalfHist, srcHalfHist, 0,1, NORM_MINMAX,-1,Mat());
   
   imshow("src1 image", src1Image);
   imshow("src2 image", src2Image);
   imshow("src3 image", src3Image);
   imshow("src1Half image", src1HalfImage);
   
   moveWindow("src1 image", 0, 0);
   moveWindow("src2 image", src1Image.cols, 0);
   moveWindow("src3 image", src2Image.cols+src1Image.cols, 0);
   moveWindow("src1 half image", src1Image.cols+src2Image.cols+src3Image.cols, 0);
   
   for (int i = 0; i < 4; i++) {
       int compareMethod = i;
       double compareResultA = compareHist(src1Hist, src1Hist, i);
       double compareResultB = compareHist(src1Hist, src2Hist, i);
       double compareResultC = compareHist(src1Hist, src3Hist, i);
       double compareResultD = compareHist(src1Hist, srcHalfHist, i);
       
       printf("直方图比对结果为
 compareResultA = %.3f  
compareResultB = %.3f  
compareResultC = %.3f  
compareResultD = %.3f  
",compareResultA,compareResultB,compareResultC,compareResultD);
       printf("比对方法为 %d
",compareMethod);
   }
   
   waitKey(0);
   return 0;
}

三.反向投影

         反向投影是一种首先寻找某一特征的直方图模型,然后根据这个模型去寻找图像中是否存在这个特征的解决方案.

         反向投影储存的亮度值,代表测试图像中该像素属于某个特征的概率,也就是说,亮度值相同的位置,属于同一个特征的概率越大,亮起的地方概率更大,内部和边缘之间的阴影影响了检测的精度.

         反向投影的作用是在输入图像中寻找特定图像中最匹配的点或者区域,也就是定位模版图像在输入图像的位置.

         投影的结果以每个输入图像像素为起点的直方图对比结果,可以看作是单通道浮点型图像,或者是一个二维的概率数组集合.

         API:void calcBackProject(mat* 输入图像数组指针,int 图像数组个数,int*需要统计的通道索引,inputarray 输入直方图,outputarray 目标反向投影阵列,float** 输入数组的每一维的边界阵列,int 缩放因子,bool 直方图是否均匀).

         注:该函数用来计算反向投影

         有时候我们计算复杂图像的反向投影的时候需要抽取出图像的某个通道单独使用,这时候就涉及到图像的通道复制,从输入图像中复制某个通道到输出图像的指定通道中.

         API:mixChannels(mat* 输入图像数组,Size_t 输入数组数量,Mat*输出数组,size_t 输出图像数量,const int * 指定复制通道索引数组,Size 通达索引的数量).

         注:该函数属于splite 和mege的高阶通用版本.

使用前面两个API的例子如下

//反向投影技术
#define WINDOW_NAME1 "src image"        //为窗口标题定义的宏


//-----------------------------------【全局变量声明部分】--------------------------------------
//          描述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage; Mat g_hsvImage; Mat g_hueImage;
int g_bins = 30;//直方图组距

//-----------------------------------【全局函数声明部分】--------------------------------------
//          描述:全局函数声明
//-----------------------------------------------------------------------------------------------
void on_BinChange(int, void* );

//--------------------------------------【main( )函数】-----------------------------------------
//          描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//-----------------------------------------------------------------------------------------------
int main( )
{
   //【1】读取源图像,并转换到 HSV 空间
   g_srcImage = imread( "F:\opencv\OpenCVImage\backProject.jpg", 1 );
   if(!g_srcImage.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定图片存在~! 
"); return false; }
   cvtColor( g_srcImage, g_hsvImage, COLOR_BGR2HSV );
   //【2】分离 Hue 色调通道
   g_hueImage.create( g_hsvImage.size(), g_hsvImage.depth() );
   int ch[ ] = { 0, 0 };
   mixChannels( &g_hsvImage, 1, &g_hueImage, 1, ch, 1 );
   //【3】创建 Trackbar 来输入bin的数目
   namedWindow( WINDOW_NAME1 , WINDOW_AUTOSIZE );
   createTrackbar("bin size ", WINDOW_NAME1 , &g_bins, 180, on_BinChange );
   on_BinChange(0, 0);//进行一次初始化
   //【4】显示效果图
   imshow( WINDOW_NAME1 , g_srcImage );
   // 等待用户按键
   waitKey(0);
   return 0;
}


//-----------------------------------【on_HoughLines( )函数】--------------------------------
//          描述:响应滑动条移动消息的回调函数
//---------------------------------------------------------------------------------------------
void on_BinChange(int, void* )
{
   //【1】参数准备
   MatND hist;
   int histSize = MAX( g_bins, 2 );
   float hue_range[] = { 0, 180 };
   const float* ranges = { hue_range };
   
   //【2】计算直方图并归一化
   calcHist( &g_hueImage, 1, 0, Mat(), hist, 1, &histSize, &ranges, true, false );
   normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );
   //【3】计算反向投影
   MatND backproj;
   calcBackProject( &g_hueImage, 1, 0, hist, backproj, &ranges, 1, true );
   //【4】显示反向投影
   imshow( "back project", backproj );
   //【5】绘制直方图的参数准备
   int w = 400; int h = 400;
   int bin_w = cvRound( (double) w / histSize );
   Mat histImg = Mat::zeros( w, h, CV_8UC3 );
   //【6】绘制直方图
   for( int i = 0; i < g_bins; i ++ )
   { rectangle( histImg, Point( i*bin_w, h ), Point( (i+1)*bin_w, h - cvRound( hist.at<float>(i)*h/255.0 ) ), Scalar( 100, 123, 255 ), -1 ); }
   
   //【7】显示直方图窗口
   imshow( "hist", histImg );
}

三.模版匹配

         从一幅图像中寻找和模版最相似的部分的技术,叫做模版匹配,不是基于直方图的匹配技术,而是通过在输入图像上滑动图像,对实际的图像块和输入图像进行匹配的一种匹配方法.

         API:double matchTemplate(输入图像,模版图像,匹配结果的映射图像,指定的匹配方法)

         注:输入图像为八位图像或者三十二位浮点型图像,模版和输入图像的类型一致,大小一般不一致,但是不能大于输入图像,比较结果的映射图像,必然是单通道32位浮点型图像,尺寸为src1.size-temple.size

         匹配方法有以下选择 TM_SQDIFF平方差匹配法,最好的匹配是0,匹配结果越差,结果越大,TM_SQDIFF_NORMED归一化平方差匹配,最差匹配是1,最好匹配是0.TM_CCORR相关匹配0是最坏结果,结果越大匹配效果越好,TM_CCORR_NORMED归一化相关匹配,1完美匹配,0最坏结果,TM_CCOEFF系数匹配,.

         匹配时,对于不同类型的图像,可以使用不同的方法看看哪一种的匹配结果最好,使用的例程代码如下

    //模板匹配
//需要一个源文件和一个模板文件
Mat g_srcImage,g_templeImage;
const int g_matchMethodMax = 5;
int g_matchMethodValue;

void onMatchMethod(int pos,void* userData);

int main(int argc,char* argv[])
{
    g_srcImage = imread("F:\opencv\OpenCVImage\match.jpg");
    g_templeImage = imread("F:\opencv\OpenCVImage\temple.jpg");

    namedWindow("src image");
    namedWindow("temp image");
    namedWindow("match image");

    g_matchMethodValue = 0;
    createTrackbar("match method", "src image", &g_matchMethodValue, g_matchMethodMax,onMatchMethod,0);
    onMatchMethod(0,0);


    //imshow("src image", g_srcImage);
    imshow("temp image", g_templeImage);
    moveWindow("src image", 0, 0);
    moveWindow("temp image", g_srcImage.cols, 0);
    moveWindow("match image", g_srcImage.cols+g_templeImage.cols, 0);


    waitKey(0);
    return 0;
}

void onMatchMethod(int pos,void* userData)
{
    Mat srcImage;
    g_srcImage.copyTo(srcImage);

    Mat tempImage;
    g_templeImage.copyTo(tempImage);

    Mat resultImage = Mat(srcImage.rows-tempImage.cols+1,srcImage.cols-tempImage.cols+1,CV_32FC1);

    matchTemplate(srcImage, tempImage, resultImage, g_matchMethodValue);

    normalize(resultImage, resultImage, 0, 1,NORM_MINMAX,-1,Mat());

    double min_value,max_value;
    Point minLocation,maxLocation;
    Point matchLocation;

    minMaxLoc(resultImage, &min_value, &max_value,&minLocation,&maxLocation,Mat());

    if(pos == TM_SQDIFF || pos == TM_SQDIFF_NORMED)
    {
        matchLocation = minLocation;
        printf("匹配度为%.3f
",min_value);
    }
    else
    {
        matchLocation = maxLocation;
        printf("匹配度为%.3f
",max_value);
    }
    rectangle(srcImage, matchLocation, Point(matchLocation.x+tempImage.cols,matchLocation.y+tempImage.rows), Scalar(0,0,255),2,8,0);
    rectangle(resultImage, matchLocation, Point(matchLocation.x+tempImage.cols,matchLocation.y+tempImage.rows), Scalar(0,0,255),2,8,0);

    imshow("src image", srcImage);
    imshow("match image", resultImage);
}
原文地址:https://www.cnblogs.com/dengxiaojun/p/5255797.html