刚好最近在做项目,老师让查模板匹配与特征点匹配的相关知识,搜了很多博客,整理成word文档,顺便也来发个博客。
模板匹配是一种最原始、最基本的识别方法。简单来说,模板匹配就是用一幅已知的模板图片在目标图片上依次滑动,每次滑动都计算模板与模板下方子图的相似度。如果是单个目标的匹配,只需要取相似度最大值所在的位置就可以得到匹配位置。如果要匹配多个目标,只需要设定阈值,只要相似度大于阈值则认为是匹配的目标。
模板匹配的主要应用场景:字符匹配,车牌识别,图像拼接
基于区域(灰度值)的模板匹配
最基础的一类模板匹配算法,这类算法是通过比较模板与图像之间的所有灰度值的相似度实现的。相似度的度量是通过比较两幅图灰度值的相关函数、协方差函数、差平方和、差绝对值和等测量极值,来判断两幅图的相似度。计算量大,一般用于简单的图像。OpenCV通过函数matchTemplate实现了模板匹配算法。minMaxLoc 函数:在给定的矩阵中寻找最大和最小值,并给出它们的位置。
在OpenCV中提供了6种匹配度量方法。
(1)差值平方和匹配(CV_TM_SQDIFF)
原理: 计算模板与某个子图的对应像素的差值平方和。
越相似该值越小
(2)标准化差值平方和匹配(CV_TM_SQDIFF_NORMED)
原理:标准化的差值平方和
越相似该值越小
(3)相关匹配(CV_TM_CCORR)
原理:模板与子图对应位置相乘
越相似值越大
(4)标准相关匹配(CV_TM_CCORR_NORMED)
原理:标准化的相关匹配,去除了亮度线性变化对相似度计算的影响。可以保证图像和模板同时变量或变暗k倍时结果不变。
越相似值越大
(5)系数匹配法(CV_TM_CCOEFF)
原理:把图像和模板都减去了各自的平均值,使得这两幅图像都没有直流分量。
越相似值越大
(6)标准相关系数匹配(CV_TMCCOEFF_NORMED)
原理:把图像和模板都减去了各自的平均值,再各自除以各自的方差,保证图像和模板分别改变光照不影响计算结果,计算出的相关系数限制在-1到1之间,1 表示完全相同,-1 表示两幅图像的亮度正好相反,0 表示没有线性关系。
越相似值越大
通常来讲,随着从简单测量方法(平方差)到更复杂的测量方法(相关系数法),可以获得越来越准确的匹配,但同时也会以越来越大的计算量为代价。
优点:简单直接。
缺点:不具有旋转不变性和尺度不变性。即当原图像发生旋转或缩放时,不能实现匹配,可以用特征点匹配的方法。
算法优化:
速度优化:
为了提高搜索的效率,模板匹配的运行时间的复杂度取决于需要检查的平移数量,序贯相似性检测法(SSDA)在计算匹配度的同时,不断累积模板和像元的灰度差,当累积值大于某一指定阈值时,则说明该点为非匹配点,进行下一个位置的计算,这样大大减少了计算复杂度。为进一步提高速度,可以先进行粗配准,即:隔行、隔离的选取子图,用上述算法进行粗糙的定位,然后再对定位到的子图,用同样的方法求其8个邻域子图的最大R值作为最终配准图像。这样可以有效的减少子图个数,减少计算量,提高计算速度。
精度优化:
Hadamard变换算法(SATD):将模板与原图做差后得到矩阵Q,再对矩阵Q求其hadamard变化(左右同时乘以H,即HQH),对变换得到的矩阵求其元素的绝对值之和即SATD值,作为相似度的判别依据。对所有子图都进行如上变换后,找到SATD值最小的子图,便是最佳匹配。
基于变换域的匹配
基于傅里叶变换,基于小波变换,对噪声不明显,检测结果不受光照强度影响,可以较好的处理图像之间的旋转和尺度变化。
基于矩阵分解的方法
方法描述:将图像做矩阵分解,比如SVD奇异值分解和NMF非负矩阵分解等,然后再做相似度的计算。
基于SVD分解的方法优点:奇异值的稳定性,比例不变性,旋转不变性和压缩性。即奇异值分解是基于整体的表示,不但具有正交变换、旋转、位移、镜像映射等代数和几何上的不变性,而且具有良好的稳定性和抗噪性,广泛应用于模式识别与图像分析中。对图像进行奇异值分解的目的是得到唯一、稳定的特征描述,降低特征空间的维度,提高抗干扰能力。
基于SVD分解的方法缺点是:奇异值分解得到的奇异矢量中有负数存在,不能很好的解释其物理意义。
基于NMF分解的方法:将非负矩阵分解为可以体现图像主要信息的基矩阵与系数矩阵,并且可以对基矩阵赋予很好的解释。源图像表示为基矩阵的加权组合,所以,NMF在人脸识别场合发挥着巨大的作用。
基于矩阵特征值计算的方法还有很多,比如Trace变换,不变矩计算等。
FAST-Match
FAST-Match是在2D仿射变换下用于近似模板匹配的快速算法,其最小化绝对差分和(SAD)误差测量。 通过图像平滑度的密度对其进行采样。 对于每个可能的变换,使用次线性算法来近似SAD误差, 同时使用分支定界法进一步加速算法。 由于已知图像是分段平滑的,因此结果是具有近似保证的实际仿射模板匹配算法。
特征点(角点)匹配是指寻找两幅图像之间的特征像素点的对应关系,从而确定图像的位置关系。
特征点匹配可分为四个步骤:
在现实生活中,我们从不同的距离,不同的方向、角度,不同的光照条件下观察一个物体时,物体的大小,形状,明暗都会有所不同,但我们仍可判断是同一物体,理想的特征描述子也应该是对光照不敏感,具备尺度一致性(大小),旋转一致性(角度)等。
ORB算法(特征点检测+特征点匹配)
特征点检测
对于 ORB 这类基于特征点匹配的算法来说,其对于检测那些有固定特征的且不易受背景环境影响的对象如人脸识别类的应用来说具有非常高的识别度和准确率,但对于更加一般的物体识别,如行人检测,则由于姿态和背景变化多样,因此并不能很好的寻找特定的特征点来完成。
ORB算法是基于FAST特征点检测和BRIEF描述子改进的,运算速度快,具有一定的旋转不变性。(FAST算子检测+检测的特征点的方向信息)+(BRIEF描述子+旋转不变性)
FAST角点:某像素点与其周围像素点邻域内有足够多的像素点相差较大,则该像素可能是角点。我们通常将这个邻域选为圆形,要判断是否为角点,将该点的灰度值和它邻域内16个像素点的灰度值进行比较,若圆圈上存在n个连续的像素点的灰度值大于P点的灰度值+t(t是阈值),则P点为角点,很多文献都推荐FAST-9,使用的比较多。
FAST角点检测本身的缺点:
针对这些缺陷,ORB的解决方法是:
(1)若想得到N个关键点, 先设置较低的阈值提取多于N个点, 再用Harris角点检测对其进行排序, 找到角点响应值较高的前N个FAST特征点,这也使特征点更能适应环境光照的变化。
(2)对图像构造金字塔,然后在金字塔的每一层提取FAST特征的方法,改善ORB的尺度一致性问题。
针对FAST检测算子没有方向性,定义一种邻域矩,并利用矩计算得到特征点邻域的灰度质心,以特征点中心到灰度质心的方向作为特征点的主方向。有了特征点的方向,也就实现了旋转不变性。
利用BRIEF描述子进行特征描述,在特征点附近随机选取若干点对,将这些点对的灰度值的大小,组合成一个二进制串,并将这个二进制串作为该特征点的特征描述子。
通过比较特征点领域范围内像素点对的灰度值,赋予0或者1,BRIEF描述子比较的是随机点对的像素值,容易受噪声影响,在31*31像素区域内随机选取5*5的子窗口,比较窗口的灰度积分来替换点对的像素值。
尺度不变性和旋转不变性就是在描述同一个特征之前,将两张图像都变换到同一个方向和同一个尺度上,然后在这个统一标准上描述这个特征。
特征点的匹配
根据特征向量间的汉明距离,在得到两幅图像分别的ORB描述子后,特征点的匹配方法通常有两种,一种是将汉明距离最小的两个特征点归为一对;另一种是计算最小汉明距离与次小汉明距离之比,这个比值越小越表明两个特征点相似。
汉明距离:两个字母中不同位值的数目称为汉明距离。
这两种方法都需要设置阈值来判定为合适的对应特征点,当阈值越小,越有可能得到置信度高的特征点,但是可能导致舍弃一些正确的特征匹配点。当阈值越大,得到的特征匹配点越多,但是其中可能掺杂一些错误的匹配点。
Opencv中有两种匹配模式:暴力匹配,快速最近邻匹配。
1.暴力匹配:测量描述子之间的距离(通常采用汉明距离和欧氏距离),汉明距离是采用BRIEF二进制描述子进行匹配,浮点型描述子用欧式距离匹配。
2.暴力匹配产生的特征点较多,采用FLANN方法可以提高质量,降低计算量。
使用特征提取过程得到的特征描述符(descriptor)数据类型有的是float类型的,比如说SurfDescriptorExtractor,SiftDescriptorExtractor,有的是uchar类型的,比如说有ORB,BriefDescriptorExtractor。对应float类型的匹配方式有:FlannBasedMatcher,BruteForce
查阅论文的收获
//ORB特征点检测算法
#include
#include
#include
#include
using namespace cv;
using namespace std;
Mat image1, image2;//中值滤波后的图像
Mat extracte_orb(Mat input, vector
{
Ptr
f2d->detect(input, keypoint);
Mat image_with_kp;
f2d->compute(input, keypoint, descriptor);
drawKeypoints(input, keypoint, image_with_kp, Scalar::all(-1), DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
return image_with_kp;
}
void match_two_image(Mat image1, Mat image2, vector
{
//BFMatcher matcher(NORM_HAMMING);//汉明距离作为相似度度量
FlannBasedMatcher matcher;//采用FLANN算法匹配描述符向量
vector
matcher.match(descriptor1, descriptor2, matches);
Mat good_matches_image;
drawMatches(image1, keypoint1, image2, keypoint2,
matches, good_matches_image, Scalar::all(-1), Scalar::all(-1),
vector
sort(matches.begin(), matches.end()); //筛选匹配点,根据match里面特征对的距离从小到大排序
int Prc = min(100, (int)(matches.size() * 0.15));
cout << "初始匹配点数为" << Prc << endl;
imshow("匹配效果图", good_matches_image);
{
vector
vector
for (int i = 0; i < matches.size(); i++)
{
keypoints1.push_back(keypoint1[matches[i].queryIdx].pt);
keypoints2.push_back(keypoint2[matches[i].trainIdx].pt);
RAN_KP1.push_back(keypoint1[matches[i].queryIdx]);
RAN_KP2.push_back(keypoint2[matches[i].trainIdx]);
}
vector
findFundamentalMat(keypoints1, keypoints2, RansacStatus, FM_RANSAC);
vector
vector
int index = 0;
for (size_t i = 0; i < matches.size(); i++)
{
if (RansacStatus[i] != 0)
{
ransac_keypoints1.push_back(RAN_KP1[i]);
ransac_keypoints2.push_back(RAN_KP2[i]);
matches[i].queryIdx = index;
matches[i].trainIdx = index;
ransac_matches.push_back(matches[i]);
index++;
}
}
sort(ransac_matches.begin(), ransac_matches.end()); //筛选匹配点,根据match里面特征对的距离从小到大排序
int Pairs = min(100, (int)(ransac_matches.size()));
cout << "ransac匹配点数为" << Pairs << endl;
Mat after_ransac_sift_match;
drawMatches(image1, ransac_keypoints1, image2, ransac_keypoints2,
ransac_matches, after_ransac_sift_match, Scalar::all(-1), Scalar::all(-1),
vector
imshow("ransac后匹配效果", after_ransac_sift_match);
}
}
//计算信噪比
double PSNR(const Mat &inImg, const Mat &referenceImg)
{
if (inImg.rows != referenceImg.rows || inImg.cols != referenceImg.cols || inImg.channels() != referenceImg.channels())
return -1000000.0;
//转换类型
Mat dImg, dRImg;
int imgType;
if (3 == inImg.channels())
imgType = CV_64FC3;
else
imgType = CV_64FC1;
inImg.convertTo(dImg, imgType);
referenceImg.convertTo(dRImg, imgType);
//计算均值
double *dataImg = dImg.ptr
double *dataRImg = dRImg.ptr
for (int idr = 0; idr
for (int idc = 0; idc
double difValue = *dataImg - *dataRImg;
*dataImg = difValue *difValue;
}
}
//计算插值均值
Scalar meanValue = cv::mean(dImg);
double mValue;
if (3 == inImg.channels())
mValue = (meanValue[0] + meanValue[2] + meanValue[2]) / 3.0;
else
mValue = meanValue[0];
//计算PSNR峰值信噪比
double val = 255.0;
double reValue = 10.0*log10((val*val) / mValue);
return reValue;
}
int main(int argc, char* argv)
{
Mat grayImage1,grayImage2,img1,img2;
Mat srcImage1 = imread("quexian1.jpg");
Mat srcImage2 = imread("good.jpg");
//转为灰度图
cvtColor(srcImage1, grayImage1, COLOR_BGR2GRAY);
cvtColor(srcImage2, grayImage2, COLOR_BGR2GRAY);
//进行直方图均衡化
equalizeHist(grayImage1, img1);
equalizeHist(grayImage2, img2);
imshow("经直方图均衡化后的图1", img1);
imshow("经直方图均衡化后的图2", img2);
//中值滤波
medianBlur(img1, image1, 3);
medianBlur(img2, image2, 3);
vector
Mat descriptor1, descriptor2;
double start = GetTickCount();//记录起始时间
Mat orb_image1=extracte_orb(image1, keypoint1, descriptor1);
imshow("orb识别效果图1" , orb_image1);
cout << "orb识别特征点数1: " << keypoint1.size() << endl;
Mat orb_image2=extracte_orb(image2, keypoint2, descriptor2);
imshow("orb识别效果图2", orb_image2);
cout << "orb识别特征点数2: " << keypoint2.size() << endl;
match_two_image(image1, image2, keypoint1, keypoint2, descriptor1, descriptor2);
double end = GetTickCount();
cout << "orb耗时" << (end - start) << "ms" << endl;
waitKey(0);
return 0;
}