|
@
目录
1.模板匹配
1.1 原理
模板图像在原图像上从原点开始移动,计算模板与原图被模板覆盖的地方的差别程度,计算方法有几种,然后将每次计算的结果放进输出矩阵。若原图像为A*B大小,模板为a*b大小,则 输出矩阵为(A-a+1)*(B-b+1) 大小。
1.2 API
- CV_EXPORTS_W void matchTemplate( InputArray image, InputArray templ,
- OutputArray result, int method, InputArray mask = noArray() );
复制代码 参数含义image输入图像,数据类型Mattempl(template)模板图像,数据类型Matresult输出矩阵,深度为CV_32FC1。若原图像为A*B大小,模板为a*b大小,则 输出矩阵为(A-a+1)*(B-b+1) 大小。method模板匹配计算方法。详见下文mask掩码图像。其大小与模板图像必须相同,且必须为灰度图。匹配时,对于掩码中的非0像素匹配算法起作用,掩码中的灰度值为0的像素位置,匹配算法不起作用。1.3 模板匹配计算方法
- enum TemplateMatchModes {
- TM_SQDIFF = 0,
- TM_SQDIFF_NORMED = 1,
- TM_CCORR = 2,
- TM_CCORR_NORMED = 3,
- TM_CCOEFF = 4,
- TM_CCOEFF_NORMED = 5
- };
复制代码 method可选值含义式子TM_SQDIFF计算平方误差,计算出来的值越小,则匹配得越好$R_{sq_diff}=\sum_{x{\prime},y{\prime}}\left[T(x{\prime},y)-I(x+x{\prime},y+y) \right]^2$TM_SQDIFF_NORMED计算归一化平方误差,计算出来的值越接近0,则匹配得越好$R_{sq_diff_normed}=\frac{\sum_{x{\prime},y{\prime}}\left[T(x{\prime},y)-I(x+x{\prime},y+y) \right]2}{\sqrt{\sum_{x,y{\prime}}T(x,y{\prime})2 \cdot \sum_{x{\prime},y{\prime}}I(x+x{\prime},y+y)^2 }}$TM_CCORR计算相关性,计算出来的值越大,则匹配得越好$R_{ccorr}=\sum_{x{\prime},y{\prime}}T(x{\prime},y) \cdot I(x+x{\prime},y+y)$TM_CCORR_NORMED计算归一化相关性,计算出来的值越接近1,则匹配得越好$R_{ccorr_normed}=\frac{\sum_{x{\prime},y{\prime}} T(x{\prime},y) \cdot I(x+x{\prime},y+y) }{\sqrt{\sum_{x{\prime},y{\prime}}T(x{\prime},y)^2 \cdot \sum_{x{\prime},y{\prime}}I(x+x{\prime},y+y)^2 }}$TM_CCOEFF计算相关系数,计算出来的值越大,则匹配得越好$R_{ccoff}=\sum_{x{\prime},y{\prime}}T{\prime}(x,y^{\prime}) \cdot I{\prime}(x+x,y+y^{\prime}), \ T{\prime}(x,y{\prime})=T(x,y^{\prime}) - \frac{\sum_{x{\prime},y{\prime}} T(x{\prime\prime},y) }{w \cdot h} ,\ I{\prime}(x,y{\prime})=I(x,y^{\prime}) - \frac{\sum_{x{\prime},y{\prime}} I(x{\prime\prime},y) }{w \cdot h} $TM_CCOEFF_NORMED计算归一化相关系数,计算出来的值越接近1,则匹配得越好$R_{ccoeff_normed}=\frac{\sum_{x{\prime},y{\prime}} T{\prime}(x,y^{\prime}) \cdot I{\prime}(x+x,y+y^{\prime}) }{\sqrt{\sum_{x{\prime},y{\prime}}T{\prime}(x,y{\prime})2 \cdot \sum_{x{\prime},y{\prime}}I{\prime}(x+x,y+y{\prime})2 }}$1.4 掩码的使用
在进行特征匹配时,我们有时并不需要用整个图片作为模板,因为模板的背景可能会干扰匹配的结果。因此,我们需要加入掩码,就可以屏蔽掉背景进行模板匹配
获得掩码
1.5 效果
- Mat xuenai = imread("xuenai.jpg");
- imshow("xuenai",xuenai);
- Mat templ= imread("xuenai_rect.jpg");
- imshow("template",templ);
- Mat match_result;
- matchTemplate(xuenai,templ,match_result,TM_SQDIFF);
- Point temLoc;
- Point minLoc;
- Point maxLoc;
- double min,max;
- minMaxLoc(match_result,&min,&max,&minLoc,&maxLoc);
- temLoc=minLoc;
- rectangle(xuenai,Rect(temLoc.x,temLoc.y,templ.cols,templ.rows),Scalar(0,0,255));
- imshow("xuenai_match",xuenai);
- waitKey();
复制代码
1.5 模板匹配的缺陷
无法应对旋转
- Mat xuenai = imread("xuenai.jpg");
- rotate(xuenai,xuenai,ROTATE_90_CLOCKWISE);
- imshow("xuenai",xuenai);
- Mat templ= imread("xuenai_rect.jpg");
- Mat match_result;
- matchTemplate(xuenai,templ,match_result,TM_SQDIFF);
- Point temLoc;
- Point minLoc;
- Point maxLoc;
- double min,max;
- minMaxLoc(match_result,&min,&max,&minLoc,&maxLoc);
- temLoc=minLoc;
- rectangle(xuenai,Rect(temLoc.x,temLoc.y,templ.cols,templ.rows),Scalar(0,0,255));
- imshow("xuenai_match",xuenai);
- waitKey();
复制代码
无法应对缩放
- Mat xuenai = imread("xuenai.jpg");
- resize(xuenai,xuenai,Size(500,500));
- imshow("xuenai",xuenai);
- Mat templ= imread("xuenai_rect.jpg");
- Mat match_result;
- matchTemplate(xuenai,templ,match_result,TM_SQDIFF);
- Point temLoc;
- Point minLoc;
- Point maxLoc;
- double min,max;
- minMaxLoc(match_result,&min,&max,&minLoc,&maxLoc);
- temLoc=minLoc;
- rectangle(xuenai,Rect(temLoc.x,temLoc.y,templ.cols,templ.rows),Scalar(0,0,255));
- imshow("xuenai_match",xuenai);
- waitKey();
复制代码
2.cornerHarris(对灰度图)
2.1 角点的描述
- 一阶导数(即灰度的梯度)的局部最大所对应的像素点;
- 两条及两条以上边缘的交点;
- 图像中梯度值和梯度方向的变化速率都很高的点;
- 角点处的一阶导数最大,二阶导数为零,指示物体边缘变化不连续的方向。
2.2 原理(前置知识要求:线性代数)(以下为bolcksize=2的情况)
使用一个固定窗口在图像上进行任意方向上的滑动,比较滑动前与滑动后两种情况,窗口中的像素灰度变化程度,如果存在任意方向上的滑动,都有着较大灰度变化,那么我们可以认为该窗口中存在角点。
考虑到一个灰度图像 . 划动窗口 (with displacements 在x方向和 方向) 计算像素灰度变化。
$$
E(u,v)=\sum w(x,y) \left[ I(x+u,y+v)-I(x,y) \right]^2
$$
其中:
- w(x,y) is the window at position (x,y)
- I(x,y) is the intensity at (x,y)
- I(x+u,y+v) is the intensity at the moved window (x+u,y+v)、
为了寻找带角点的窗口,搜索像素灰度变化较大的窗口。于是, 我们期望最大化以下式子:
$$
\sum_{x,y}\left[ I(x+u,y+v)-I(x,y) \right]^2
$$
泰勒展开:
$$
E(u,v) \approx \sum_{x,y} \left[ I(x,y) +u I_x +v I_y - I(x,y) \right]^2
$$
矩阵化:
$$
E(u,v) \approx \begin{bmatrix} u & v \end{bmatrix} \left( \sum_{x,y} w(x,y)\begin{bmatrix} I_x^2 & I_x I_y \ I_x I_y & I_y^2\end{bmatrix} \right) \begin{bmatrix} u \ v \end{bmatrix}
$$
得二次型:
$$
M=\sum_{x,y} w(x,y)\begin{bmatrix} I_x^2 & I_x I_y \ I_x I_y & I_y^2\end{bmatrix}
$$
因此有等式:
$$
E(u,v) \approx \begin{bmatrix} u & v \end{bmatrix} M \begin{bmatrix} u \ v \end{bmatrix}
$$
每个窗口中计算得到一个值。这个值决定了这个窗口中是否包含了角点。
$$
R=\det{M}-k \cdot\text{trace}(M)^2
$$
其中,det(M) = 矩阵M的行列式,trace(M) = 矩阵M的迹
- R为正值时,检测到的是角点,R为负时检测到的是边,R很小时检测到的是平坦区域。
2.3 API
- CV_EXPORTS_W void cornerHarris( InputArray src, OutputArray dst, int blockSize,
- int ksize, double k,
- int borderType = BORDER_DEFAULT );
复制代码 参数含义src(source)输入图片(灰度图),深度要求:CV_8UC1或CV_32FC1dst(destination)输出图片,数据类型MatbolckSize检测窗口的大小,越大则对角点越敏感,一般取2ksize(kernal size)使用sobel算子计算一阶导数时的滤波器大小,一般取3即可。k计算用到的系数,公认一般取值在0.02~0.06。borderType边界填充方式,默认为黑边。2.4 流程
- 转灰度图
- 使用cornerHarris函数检测
- 使用normalize函数归一化处理和convertScaleAbs绝对化
- 遍历输出图像并筛选角点。不要使用迭代器的遍历方式,因为太慢!
- 经过实测,以下这种用行数调用ptr函数的遍历方式是最快的
- Mat xuenai = imread("xuenai.jpg");
- imshow("xuenai", xuenai);
- //转灰度图
- Mat xuenai_gray(xuenai.size(),xuenai.type());
- cvtColor(xuenai,xuenai_gray,COLOR_BGR2GRAY);
- Mat xuenai_harris;
- cornerHarris(xuenai_gray,xuenai_harris,2,3,0.04);
- normalize(xuenai_harris,xuenai_harris,0,255,NORM_MINMAX,-1);
- convertScaleAbs(xuenai_harris,xuenai_harris);
- namedWindow("xuenai_harris");
- createTrackbar("threshold","xuenai_harris", nullptr,255);
- while (1) {
- int thres = getTrackbarPos("threshold", "xuenai_harris");
- if(thres==0)thres=100;
- Mat harris_result=xuenai.clone();
- for(int i=0;i<xuenai_harris.rows;i++){
- uchar * ptr =xuenai_harris.ptr(i);
- for(int j=0;j<xuenai_harris.cols;j++){
- int value=(int) *ptr;
- if(value>thres){
- circle(harris_result, Point(j,i), 3, Scalar(0, 0, 255));
- }
- ptr++;
- }
- }
- imshow("xuenai_harris",harris_result);
- if (waitKey(0) == 'q')break;
- }
复制代码
进行缩放和旋转
- 可以看到,无论是旋转还是缩放,关键点都保持得非常稳定。
5.FAST到OBR(对灰度图)
5.1 概述
前文已经阐述,SIFT和SURF已经做到了角点在旋转和缩放下的稳定性,但是它们还有一个致命的缺陷,就是它们难以做到实时运算,因此,FAST和OBR应运而生了。
FAST原理
从图片中选取一个坐标点P,获取该点的像素值,接下来判定该点是否为特征点.
选取一个以选取点P坐标为圆心的半径等于r的Bresenham圆(一个计算圆的轨迹的离散算法,得到整数级的圆的轨迹点),一般来说,这个圆上有16个点,如下所示
- p在图像中表示一个被识别为兴趣点的像素。令它的强度为 Ip;
- 选择一个合适的阈值t;
- 考虑被测像素周围的16个像素的圆圈。 如果这16个像素中存在一组ñ个连续的像素的像素值,比 Ip+t 大,或比 Ip−t小,则像素p是一个角点。ñ被设置为12。
- 使用一种快速测试(high-speed test)可快速排除了大量的非角点。这个方法只检测在1、9、5、13个四个位置的像素,(首先检测1、9位置的像素与阈值比是否太亮或太暗,如果是,则检查5、13)。如果p是一个角点,则至少有3个像素比 Ip+t大或比 Ip−t暗。如果这两者都不是这样的话,那么p就不能成为一个角点。然后可以通过检查圆中的所有像素,将全部分段测试标准应用于通过的对候选的角点。这种探测器本身表现出很高的性能,但有一些缺点:
<ul>它不能拒绝n detect(xuenai_gray,xuenai_ObrKp); Mat fast_result=xuenai_transform.clone(),obr_result=xuenai_transform.clone(); drawKeypoints(fast_result,xuenai_FastKp,fast_result,Scalar::all(-1),DrawMatchesFlags::DRAW_RICH_KEYPOINTS); drawKeypoints(obr_result,xuenai_ObrKp,obr_result,Scalar::all(-1),DrawMatchesFlags::DRAW_RICH_KEYPOINTS); imshow("fast_result",fast_result); imshow("obr_result",obr_result); if (waitKey(0) == 'q')break;}[/code]调整threshold
进行缩放和旋转
错误
- Mat xuenai = imread("xuenai.jpg");
- imshow("xuenai", xuenai);
- namedWindow("panel");
- createTrackbar("threshold","panel", nullptr,255);
- createTrackbar("angle","panel", nullptr,360);
- createTrackbar("width","panel", nullptr,1000);
- createTrackbar("height","panel", nullptr,1000);
- while (1) {
- int thres = getTrackbarPos("threshold", "panel");
- if(thres==0)thres=100;
- int width = getTrackbarPos("width", "panel");
- if(width==0)width=xuenai.cols;
- int height = getTrackbarPos("height", "panel");
- if(height==0)height=xuenai.rows;
- int angle = getTrackbarPos("angle","panel");
-
- Mat xuenai_harris, xuenai_transform=xuenai.clone();
- resize(xuenai_transform,xuenai_transform,Size(width,height));
- Mat M= getRotationMatrix2D(Point2f(xuenai.cols/2,xuenai.rows/2),angle,1);
- warpAffine(xuenai_transform,xuenai_transform,M,xuenai_transform.size());
- Mat xuenai_gray(xuenai.size(),xuenai.type());
- cvtColor(xuenai_transform,xuenai_gray,COLOR_BGR2GRAY);
- cornerHarris(xuenai_gray,xuenai_harris,2,3,0.04);
- normalize(xuenai_harris,xuenai_harris,0,255,NORM_MINMAX,-1);
- convertScaleAbs(xuenai_harris,xuenai_harris);
- Mat harris_result=xuenai_transform.clone();
- for(int i=0;i<xuenai_harris.rows;i++){
- uchar * ptr =xuenai_harris.ptr(i);
- for(int j=0;j<xuenai_harris.cols;j++){
- int value=(int) *ptr;
- if(value>thres){
- circle(harris_result, Point(j,i), 3, Scalar(0, 0, 255));
- }
- ptr++;
- }
- }
- imshow("xuenai_harris",harris_result);
- if (waitKey(0) == 'q')break;
- }
复制代码 前文已经提及,FAST算法不支持描述子的计算- CV_EXPORTS_W void goodFeaturesToTrack( InputArray image, OutputArray corners,
- int maxCorners, double qualityLevel, double minDistance,
- InputArray mask = noArray(), int blockSize = 3,
- bool useHarrisDetector = false, double k = 0.04 );
- CV_EXPORTS_W void goodFeaturesToTrack( InputArray image, OutputArray corners,
- int maxCorners, double qualityLevel, double minDistance,
- InputArray mask, int blockSize,
- int gradientSize, bool useHarrisDetector = false,
- double k = 0.04 );
复制代码 6.Brute-Force与FLANN特征匹配
6.1 概述
Brute-Force
暴力匹配(Brute-force matcher)是最简单的二维特征点匹配方法。对于从两幅图像中提取的两个特征描述符集合,对第一个集合中的每个描述符Ri,从第二个集合中找出与其距离最小的描述符Sj作为匹配点。
暴力匹配显然会导致大量错误的匹配结果,还会出现一配多的情况。通过交叉匹配或设置比较阈值筛选匹配结果的方法可以改进暴力匹配的质量。
- 如果参考图像中的描述符Ri与检测图像中的描述符Sj的互为最佳匹配,则称(Ri , Sj)为一致配对。交叉匹配通过删除非一致配对来筛选匹配结果,可以避免出现一配多的错误。
- 比较阈值筛选是指对于参考图像的描述符Ri,从检测图像中找到距离最小的描述符Sj1和距离次小的描述符Sj2。设置比较阈值t∈[0.5 , 0.9],只有当最优匹配距离与次优匹配距离满足阈值条件d (Ri , Sj1) ⁄ d (Ri , Sj2) < t时,表明匹配描述符Sj1具有显著性,才接受匹配结果(Ri , Sj1)。
FLANN
- 相比于Brute-Force,FLANN的速度更快
- 由于使用的是邻近近似值,所以精度较差
6.2 API
构造函数
- Mat xuenai = imread("xuenai.jpg");
- imshow("xuenai", xuenai);
- namedWindow("panel");
- createTrackbar("threshold","panel", nullptr,255);
- createTrackbar("angle","panel", nullptr,360);
- createTrackbar("width","panel", nullptr,1000);
- createTrackbar("height","panel", nullptr,1000);
- while (1) {
- int thres = getTrackbarPos("threshold", "panel");
- if(thres==0)thres=100;
- int width = getTrackbarPos("width", "panel");
- if(width==0)width=xuenai.cols;
- int height = getTrackbarPos("height", "panel");
- if(height==0)height=xuenai.rows;
- int angle = getTrackbarPos("angle","panel");
- Mat xuenai_transform=xuenai.clone();
- resize(xuenai_transform,xuenai_transform,Size(width,height));
- Mat M= getRotationMatrix2D(Point2f(xuenai.cols/2,xuenai.rows/2),angle,1);
- warpAffine(xuenai_transform,xuenai_transform,M,xuenai_transform.size());
- Mat xuenai_gray(xuenai.size(),xuenai.type());
- cvtColor(xuenai_transform,xuenai_gray,COLOR_BGR2GRAY);
- vector<Point2f>xuenai_cornersSet;
- goodFeaturesToTrack(xuenai_gray,xuenai_cornersSet,0,0.1,10);
- for(auto corner:xuenai_cornersSet){
- circle(xuenai_transform,corner,3,Scalar(0,0,255));
- }
- imshow("xuenai_corners",xuenai_transform);
- if (waitKey(0) == 'q')break;
- }
复制代码 参数含义normType计算距离用到的方法,默认是欧氏距离。详见下表crossCheck是否使用交叉验证,默认不使用。
normType可选值含义NORM_L1L1范数,曼哈顿距离NORM_L2L2范数,欧氏距离NORM_HAMMING汉明距离NORM_HAMMING2汉明距离2,对每2个比特相加处理。
- NORM_L1、NORM_L2适用于SIFT和SURF检测算法
- NORM_HAMMING、NORM_HAMMING2适用于OBR算法
描述子匹配
匹配方式一
- CV_WRAP static Ptr<SIFT> SIFT::create(int nfeatures = 0, int nOctaveLayers = 3,
- double contrastThreshold = 0.04, double edgeThreshold = 10,
- double sigma = 1.6);
-
- CV_WRAP static Ptr<SIFT> SIFT::create(int nfeatures, int nOctaveLayers,
- double contrastThreshold, double edgeThreshold,
- double sigma, int descriptorType);
-
- CV_WRAP static Ptr<SURF> SURF::create(double hessianThreshold=100,
- int nOctaves = 4, int nOctaveLayers = 3,
- bool extended = false, bool upright = false);
复制代码 参数含义queryDescriptors描述子的查询点集,数据类型Mat,即参考图像的特征描述符的集合。trainDescriptors描述子的训练点集,数据类型Mat,即检测图像的特征描述符的集合。matches匹配结果,长度为成功匹配的数量。mask掩码图像。其大小与输入图像必须相同,且必须为灰度图。计算时,对于掩码中的非0像素算法起作用,掩码中的灰度值为0的像素位置,算法不起作用。
匹配方式二
- CV_WRAP virtual void Feature2D::detect( InputArray image,
- CV_OUT std::vector<KeyPoint>& keypoints,
- InputArray mask=noArray() );
-
- CV_WRAP virtual void Feature2D::detect( InputArrayOfArrays images,
- CV_OUT std::vector<std::vector<KeyPoint> >& keypoints,
- InputArrayOfArrays masks=noArray() );
复制代码 参数含义queryDescriptors描述子的查询点集,数据类型Mat,即参考图像的特征描述符的集合。trainDescriptors描述子的训练点集,数据类型Mat,即检测图像的特征描述符的集合。matchesvector类型,对每个特征点返回k个最优的匹配结果k返回匹配点的数量mask掩码图像。其大小与输入图像必须相同,且必须为灰度图。计算时,对于掩码中的非0像素算法起作用,掩码中的灰度值为0的像素位置,算法不起作用。
Brute-Force与FLANN对输入描述子的要求
- Brute-Force要求输入的描述子必须是CV_8U或者CV_32S
- FLANN要求输入的描述子必须是CV_32F
drawMatches绘制匹配结果
- CV_WRAP virtual void Feature2D::compute( InputArray image,
- CV_OUT CV_IN_OUT std::vector<KeyPoint>& keypoints,
- OutputArray descriptors );
- CV_WRAP virtual void Feature2D::compute( InputArrayOfArrays images,
- CV_OUT CV_IN_OUT std::vector<std::vector<KeyPoint> >& keypoints,
- OutputArrayOfArrays descriptors );
- CV_WRAP virtual void Feature2D::detectAndCompute( InputArray image, InputArray mask,
- CV_OUT std::vector<KeyPoint>& keypoints,
- OutputArray descriptors,
- bool useProvidedKeypoints=false );
复制代码 参数含义img1(image1)源图像1,数据类型Matkeypoints1源图像1的关键点img2(image2)源图像2,数据类型Matkeypoints2源图像2的关键点matches1to2源图像1的描述子匹配源图像2的描述子的匹配结果outImg(out image)输出图像,数据类型MatmatchColor匹配的颜色(特征点和连线),默认Scalar::all(-1),颜色随机singlePointColor单个点的颜色,即未配对的特征点,默认Scalar::all(-1),颜色随机matchesMask掩码,决定哪些点将被画出,若为空,则画出所有匹配点flags特征点的绘制模式,其实就是设置特征点的那些信息需要绘制,那些不需要绘制。6.3 流程
- 实例化BFMatcher对象
- 根据需要,调用match函数或knnMatch函数,进行特征匹配
- 调用drawMatches函数呈现原图,并且绘制匹配点
6.4 效果
- CV_EXPORTS_W void drawKeypoints( InputArray image, const std::vector<KeyPoint>& keypoints, InputOutputArray outImage,
- const Scalar& color=Scalar::all(-1), DrawMatchesFlags flags=DrawMatchesFlags::DEFAULT );
- enum struct DrawMatchesFlags
- {
- DEFAULT = 0, //!< Output image matrix will be created (Mat::create),
- //!< i.e. existing memory of output image may be reused.
- //!< Two source image, matches and single keypoints will be drawn.
- //!< For each keypoint only the center point will be drawn (without
- //!< the circle around keypoint with keypoint size and orientation).
- DRAW_OVER_OUTIMG = 1, //!< Output image matrix will not be created (Mat::create).
- //!< Matches will be drawn on existing content of output image.
- NOT_DRAW_SINGLE_POINTS = 2, //!< Single keypoints will not be drawn.
- DRAW_RICH_KEYPOINTS = 4 //!< For each keypoint the circle around keypoint with keypoint size and
- //!< orientation will be drawn.
- };
复制代码
本文由博客一文多发平台 OpenWrite 发布!
来源:https://www.cnblogs.com/UnderTurrets/p/18369311
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|