[ORB/BEBLID] 利用OpenCV(C++)实现尺度不变性与角度不变性的特征找图算法

发布时间 2023-11-19 22:18:52作者: Icys

本文只发布于利用OpenCV实现尺度不变性与角度不变性的特征找图算法和知乎

一般来说,利用OpenCV实现找图功能,用的比较多的是模板匹配(matchTemplate)。笔者比较喜欢里面的NCC算法。但是模板有个很明显的短板,面对尺度改变,角度改变的目标就无能为力了。因此本文旨在做到模板匹配做不到的这两点上。

当然也有人利用模板匹配实现上面的功能,但是方法之无语,效率之低下让我不禁想起了三体中的一句话:

“成吉思汗的骑兵,攻击速度与二十世纪的装甲部队相当;北宋的床弩,射程达一千五百米,与二十世纪的狙击步枪差不多;但这些仍不过是古代的骑兵与弓弩而已,不可能与现代力量抗衡。基础理论决定一切,未来史学派清楚地看到了这一点。而你们,却被回光返照的低级技术蒙住了眼睛。你们躺在现代文明的温床中安于享乐,对即将到来的决定人类命运的终极决战完全没有精神上的准备。”

本文并不涉及ORB或者BEBLID算法的具体实现,想了解的同学请移步论文。本文撰自一名非计算机系大一新生,有不到处还请见谅。

从特征匹配开始

特征匹配首先是需要获得特征点。通过特征检测算法,获得特征点后利用匹配器,或是计算汉明距离或是计算余弦距离,将两个图上的点连接在一起。

image21

看笔者往期文章浅谈OpenCV的多对象匹配透明图像的实现,以及如何匹配半透明控件,在这篇文章中用的是SURF算法。但是这个算法并不支持角度不变性。

笔者在阅读OpenCV-mobilereadme的时候意外了解到ORB算法。简单查找一下资料,了解到ORB(Oriented FAST and Rotated BRIEF)算法满足以下特点

  • 支持尺度不变性和角度不变性
  • 速度很快,甚至比特征匹配(NCC)还快(i9 13900hx)
  • 开源,免费,无专利
  • 目前最快速稳定的特征点检测和提取算法

ORB = Oriented FAST(特征点) + Rotated BRIEF(特征描述)

From https://zhuanlan.zhihu.com/p/91479558

在2020年提出了一个效果更好也更快的特征描述算法Boosted Efficient Binary Local Image Descriptor(笔者不清楚他算法这有没有专利,但是这个算法在nonfree里,如果需要商业化请慎用,或者替换回BRIEF算法)

代码实现
cv::Ptr<cv::ORB> orb = cv::ORB::create(nKeypoints); //寻找nKeypoints个特征点
cv::Ptr<cv::xfeatures2d::BEBLID> beblid = cv::xfeatures2d::BEBLID::create(0.75f, cv::xfeatures2d::BEBLID::SIZE_256_BITS);//用256个比特来描述特征

std::vector<cv::KeyPoint> src_keypoints, target_keypoints;
cv::Mat src_descriptors, target_descriptors;

orb->detect(src_mat, src_keypoints);
orb->detect(target_mat, target_keypoints);

beblid->compute(src_mat, src_keypoints, src_descriptors);
beblid->compute(target_mat, target_keypoints, target_descriptors);

cv::BFMatcher matcher(cv::NORM_HAMMING, true);//利用汉明距离的匹配器

std::vector<cv::DMatch> matches;
matcher.match(target_descriptors, src_descriptors, matches);

std::vector<cv::Point2d> matched_target, matched_src;
for(auto &match : matches)
{
    if(match.distance > max_distance)
        break;
    
	matched_target.push_back(target_keypoints[match.queryIdx].pt);
	matched_src.push_back(src_keypoints[match.trainIdx].pt);
}

这里创建了ORB和BEBLID对象,并且对目标图片完成了匹配,将彼此能够匹配的点对应存放到了matched_targetmatched_src里。

从透视变换继续

虽然我们这里成功建立了模板图和目标图的特征点对点关系,然而,很多时候我们想要的是特征匹配提供的那种更为直接的反映在目标图里的坐标。

如何将离散的的点集转换为模板在目标图上的大致区域,成了我们需要解决的一个问题。这里我使用的方法是findHomographyperspectiveTransform

  • findHomography 计算多个二维点对之间的最优单映射变换矩阵H(3行3列)。使用最小均方误差或RANSAC方法。比较好解释的是最小均方误差方法,但是为了效果笔者用的是RANSAC方法。关于这个矩阵作用,大家可以去B站上看3blue1brown的系列视频《线性代数的本质》。我们获取到这个矩阵,就可以估计出模板图怎么变化到目标图上。同理模板的四个角的坐标也可以通过这个矩阵变换到目标图上的坐标,就得到了模板在目标图上的大致位置和边界。

  • perspectiveTransform 透视变换,就是完成坐标的变换过程。

代码实现
cv::Mat H = cv::findHomography(matched_src, matched_target, cv::RANSAC);

std::vector<cv::Point2f> obj_corners(4);
obj_corners[0] = cv::Point2f(0, 0);
obj_corners[1] = cv::Point2f((float)target_mat.cols, 0);
obj_corners[2] = cv::Point2f((float)target_mat.cols, (float)target_mat.rows);
obj_corners[3] = cv::Point2f(0, (float)target_mat.rows);

std::vector<cv::Point2f> buf_corners(4);

cv::perspectiveTransform(obj_corners, buf_corners, H);

效果展示

9DAFD40DC56CFFF2D5F759E029DDE611

缩放 压扁 旋转
01261DB496B1EFC49EA1D88C8BAA16D8 CE9F5E7AE49EC75A0A74EAE88CF73913

可以发现实现的效果非常好。

还能更强?

这里只实现了单个目标的检测匹配,事实上这个算法不止步于此,我们可以使用KMeans算法,实现对于一个图片上多个目标一次检测。也可以多个图片同时匹配。但是鉴于时间,和笔者不容乐观的数学分析成绩,以及即将到来的数论期中考试,为了不被踢出强基计划,下次有时间再说吧。