一、反向投影
如果一幅图像的区域中显示的是一种结构纹理或者一个独特的物体,那么这个区域的直方图可以看作一个概率函数,其表现形式是某个像素属于该纹理或物体的概率。
而反向投影(back projection)就是一种记录给定图像中的像素点如何适应直方图模型像素分布方式的一种方法。
简单的讲,所谓反向投影就是首先计算某一特征的直方图模型,然后使用模型去寻找图像中存在的该特征的方法。
1.1 原理
使用模型直方图来检测测试图像中的区域。以下是检测步骤。
(1)对测试图像中的每个像素(p(i,j)),获取色调数据并找到该色调(hij,sij)在直方图中的bin的位置。
(2)查询模型直方图中对应bin的数值。
(3)将此数值储存在新的反射投影图像中。也可以先归一化直方图数值到0-255范围,这样可以直接显示反射投影图像(单通道图像)。
(4)通过对测试图像中的每个像素采用以上步骤,可以得到最终的反射投影图像。
(5)使用统计学的语言进行分析。反向投影中储存的数值代表了测试图像中该像素属于区域的概率。
1.2 作用
反向投影用于在输入图像(通常较大)中查找与特定图像(通常较小或者仅1个像素,以下将其称为模板图像)最匹配的点或者区域,也就是定位模板图像出现在输入图像的位置。
1.3 结果
反向投影的结果包含了以每个输入图像像素点为起点的直方图对比结果。可以把它看成是一个二维的浮点型数组、二维矩阵,或者单通道的浮点型图像。
1.4 计算反向投影:calcBackProject()函数
函数calcBackProjec(t)函数用于计算直方图的反向投影。
void calcBackProject( const Mat* images, int nimages,
const int* channels, InputArray hist,
OutputArray backProject, const float** ranges,
double scale = 1, bool uniform = true );
- 第一个参数, const Mat*类型的images,输入的数组(或数组集),它们须为相同的深度(CV_8U或CV_32F)和相同的尺寸,而通道数则可以任意。
- 第二个参数, int类型的nimages,输入数组的个数,也就是第一个参数中存放了多少张“图像”,有几个原数组。
- 第三个参数, const int*类型的channels,需要统计的通道(dim)索引。第一个数组通道从0到images[0].channels()-1 ,而第二个数组通道从images[0].channels()计算到images[0].channels() + images[1].channels()-1.
- 第四个参数, InputArray类型的hist,输入的直方图
- 第五个参数, OutputArray类型的backProject, 目标反向投影阵列,其须为单通道,并且和image[0]有相同的大小和深度。
- 第六个参数, const float**类型的ranges,表示每一个维度数组(第六个参数dims)的每一维的边界阵列,可以理解为每一维数值的取值范围。
- 第七个参数, double scale,有默认值1,输出的方向投影可选的缩放因子,默认值为1
- 第八个参数, bool类型的uniform,指示直方图是否均匀的标识符,有默认值true.
1.5 通道复制: mixChannels()函数
此函数由输入参数复制某通道到输出参数特定的通道中。有两个版本的C+原型,采用函数注释方式分别介绍如下。
void mixChannels(const Mat* src, size_t nsrcs, Mat* dst, size_t ndsts,
const int* fromTo, size_t npairs);
void mixChannels(InputArrayOfArrays src, InputOutputArrayOfArrays dst,
const int* fromTo, size_t npairs);
此函数为重排图像通道提供了比较先进的机制。其实,之前我们接触到的,split()和merge(),以及cvtColor()的某些形式,都只是mixChannels()的一部分。
1.6 示例程序
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace cv;
#define WINDOW_NAME1 "【原始图】" //为窗口标题定义的宏
Mat g_srcImage; Mat g_hsvImage; Mat g_hueImage;
int g_bins = 30;//直方图组距
//-----------------------------------【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("反向投影图", 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("直方图", histImg);
}
int main()
{
//【1】读取源图像,并转换到 HSV 空间
g_srcImage = imread("F:\\CV\\LearnCV\\files\\hand.jpg", 1);
if (!g_srcImage.data) { printf("读取图片错误,请确定目录下是否有imread函数指定图片存在~! \n"); 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("色调组距 ", WINDOW_NAME1, &g_bins, 180, on_BinChange);
on_BinChange(0, 0);//进行一次初始化
//【4】显示效果图
imshow(WINDOW_NAME1, g_srcImage);
// 等待用户按键
waitKey(0);
return 0;
}
二、模板匹配
2.1 原理
模板匹配是一项在一幅图像中寻找与另一幅模板图像最匹配(相似)部分的,技术。在OpenCV2和OpenCV3中,模板匹配由MatchTemplate()函数完成。需要注意,模板匹配不是基于直方图的,而是通过在输入图像上滑动图像块,对实际的图像块和输入图像进行匹配的一种匹配方法。
2.2实现模板匹配: matchTemplate()函数
matchTemplate()用于匹配出和模板重叠的图像区域。
C++: void matchTemplate (InputArray image, InputArray templ, outputArray result, int method)
- 第一个参数, InputArray类型的image,待搜索的图像,且需为8位或32位浮点型图像。
- 第二个参数, InputArray类型的templ,搜索模板,需和源图片有一样的数据类型,且尺寸不能大于源图像。
- 第三个参数, OutputArray类型的result,比较结果的映射图像。其必须为单通道、32位浮点型图像,如果图像尺寸是wxH而templ尺寸是wxh,则此参数result一定是(W-w+1)x(H-h+1).
- 第四个参数, int类型的method,指定的匹配方法, OpenCV为我们提供了如下6种图像匹配方法可供使用。
- 1.平方差匹配法method=TM_SQDIFF
这类方法利用平方差来进行匹配,最好匹配为0。而若匹配越差,匹配值则越大。 - 2.归一化平方差匹配法method=TM_SQDIFF_NORMED
- 3.相关匹配法method=TM_CCORR
这类方法采用模板和图像间的乘法操作,所以较大的数表示匹配程度较高, 0标识最坏的匹配效果。 - 4.归一化相关匹配法method=TM_CCORR_NORMEDR
- 5.系数匹配法method=TM_CCOEFF
这类方法将模版对其均值的相对值与图像对其均值的相关值进行匹配, 1表示完美匹配, -1表示糟糕的匹配,而0表示没有任何相关性(随机序列)。 - 6.化相关系数匹配法method=TM_CCOEFF_NORMED
通常,随着从简单的测量(平方差)到更复杂的测量(相关系数),我们可获得越来越准确的匹配。然而,这同时也会以越来越大的计算量为代价。比较科学的办法是对所有这些方法多次测试实验,以便为自己的应用选择同时兼顾速度和精度的最佳方案。
2.3 示例程序
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace cv;
#define WINDOW_NAME1 "【原始图片】" //为窗口标题定义的宏
#define WINDOW_NAME2 "【匹配窗口】" //为窗口标题定义的宏
Mat g_srcImage; Mat g_templateImage; Mat g_resultImage;
int g_nMatchMethod;
int g_nMaxTrackbarNum = 5;
void on_Matching(int, void*)
{
//【1】给局部变量初始化
Mat srcImage;
g_srcImage.copyTo(srcImage);
//【2】初始化用于结果输出的矩阵
int resultImage_rows = g_srcImage.rows - g_templateImage.rows + 1;
int resultImage_cols = g_srcImage.cols - g_templateImage.cols + 1;
g_resultImage.create(resultImage_rows, resultImage_cols, CV_32FC1);
//【3】进行匹配和标准化
matchTemplate(g_srcImage, g_templateImage, g_resultImage, g_nMatchMethod);
normalize(g_resultImage, g_resultImage, 0, 1, NORM_MINMAX, -1, Mat());
//【4】通过函数 minMaxLoc 定位最匹配的位置
double minValue; double maxValue; Point minLocation; Point maxLocation;
Point matchLocation;
minMaxLoc(g_resultImage, &minValue, &maxValue, &minLocation, &maxLocation, Mat());
//【5】对于方法 SQDIFF 和 SQDIFF_NORMED, 越小的数值有着更高的匹配结果. 而其余的方法, 数值越大匹配效果越好
if (g_nMatchMethod == TM_SQDIFF || g_nMatchMethod == TM_SQDIFF_NORMED)
matchLocation = minLocation;
else
matchLocation = maxLocation;
//【6】绘制出矩形,并显示最终结果
rectangle(srcImage, matchLocation, Point(matchLocation.x + g_templateImage.cols, matchLocation.y + g_templateImage.rows), Scalar(0, 0, 255), 2, 8, 0);
rectangle(g_resultImage, matchLocation, Point(matchLocation.x + g_templateImage.cols, matchLocation.y + g_templateImage.rows), Scalar(0, 0, 255), 2, 8, 0);
imshow(WINDOW_NAME1, srcImage);
imshow(WINDOW_NAME2, g_resultImage);
}
int main()
{
//【1】载入原图像和模板块
g_srcImage = imread("F:\\CV\\LearnCV\\files\\Source.jpg", 1);
g_templateImage = imread("F:\\CV\\LearnCV\\files\\Source_target.jpg", 1);
//【2】创建窗口
namedWindow(WINDOW_NAME1, WINDOW_AUTOSIZE);
namedWindow(WINDOW_NAME2, WINDOW_AUTOSIZE);
//【3】创建滑动条并进行一次初始化
createTrackbar("方法", WINDOW_NAME1, &g_nMatchMethod, g_nMaxTrackbarNum, on_Matching);
on_Matching(0, 0);
waitKey(0);
return 0;
}