Android OpenCV(五十六):ORB特征点暴力匹配

这是我参与更文挑战的第 27 天,活动详情查看: 更文挑战

特征点匹配

特征点,表示图像内具有显著特征的像素点。采用不同的特征点检测算法,得到的特征点也不同,除了我们前面介绍的ORB特征点检测方法,还有SIFT、SURF等特征点算法可以用于特征点检测。虽然算法不同,但是在OpenCV API层面的操作方式基本相同。通过在不同的图像上使用同一种特征点检测算法,获取各自的特征点并进行匹配,可以帮助我们快速寻找不同图像之间的信息关联。那么如何匹配呢?前面我们介绍过,每个特征点都有标志其唯一身份和特点的描述子,那么我们就可以通过在两个图像中寻找相似描述子来间接的匹配特征点。那么如何定义描述子的相似性呢?特征描述子通常是一个向量,两个特征描述子的之间的距离可以反应出其相似的程度。根据描述子的不同,可以选择不同的距离度量。如果是浮点类型的描述子,如SIFT特征点、SURF特征点,可以使用其欧式距离;对于二进制的描述子(BRIEF)如ORB特征点,可以使用其汉明距离(两个不同二进制之间的汉明距离指的是两个二进制串不同位的个数)。

汉明距离

汉明距离是使用在数据传输差错控制编码里面的,汉明距离是一个概念,它表示两个(相同长度)字对应位不同的数量,我们以d(x,y)表示两个字x,y之间的汉明距离。对两个字符串进行异或运算,并统计结果为1的个数,那么这个数就是汉明距离。在信息论中,两个等长字符串之间的汉明距离是两个字符串对应位置的不同字符的个数。换句话说,它就是将一个字符串变换成另外一个字符串所需要替换的字符个数。例如:

1011101 与 1001001 之间的汉明距离是 2;

2143896 与 2233796 之间的汉明距离是 3;

“toned” 与 “roses” 之间的汉明距离是 3。

【引用自百度百科】

暴力匹配

**暴力匹配方法(Brute-Froce Matcher)**就是计算训练描述子集合中每个描述子与查询描述子之间的距离,然后将所有距离排序,按照一定的规则过滤后形成匹配结果。例如我们可以采用等于距离最小值作为过滤规则,也可以采用小于某个距离值的方式来过滤。

API

classcv_1_1DescriptorMatcher

DMatch

public class DMatch {
    public int queryIdx;   // 查询描述子集合内索引
    public int trainIdx;   // 训练描述子集合内索引
    public int imgIdx;     // 训练描述子图像所以
    public float distance; // 两个描述子之间的距离  
}
复制代码
public static BFMatcher create(int normType, boolean crossCheck) 
复制代码
  • 参数一:normType,计算描述子之间距离的标志位。可选值为NORM_L1, NORM_L2, NORM_HAMMING, NORM_HAMMING2。针对SIFT和SURF描述子,使用NORM_L1, NORM_L2,针对ORB,BRISK和BRIEF描述子,使用NORM_HAMMING,针对WTA_K==3或者4时的ORB描述子,使用NORM_HAMMING2
  • 参数二:crossCheck,是否进行交叉检测的标志位。默认为false。若为true,匹配条件就会更加严格,交叉检测,就是反过来使用被匹配到的点进行匹配,如果匹配到的仍然是第一次匹配的点的话,就认为这是一个正确的匹配,假如第一次特征点A使用暴力匹配的方法,匹配到的特征点是特征点B;反过来,使用特征点B进行匹配,如果匹配到的仍然是特征点A,则就认为这是一个正确的匹配,否则就是一个错误的匹配。
public void match(Mat queryDescriptors, Mat trainDescriptors, MatOfDMatch matches, Mat mask) 
复制代码
  • 参数一:queryDescriptors,查询描述子集合;
  • 参数二:trainDescriptors,训练描述子集合;
  • 参数三:matches,匹配结果;
  • 参数四:mask,掩码。
public static void drawMatches(Mat img1, MatOfKeyPoint keypoints1, Mat img2, MatOfKeyPoint keypoints2, MatOfDMatch matches1to2, Mat outImg, Scalar matchColor, Scalar singlePointColor, MatOfByte matchesMask, int flags)
复制代码
  • 参数一:img1,第一幅图像;
  • 参数二:keypoints1,第一幅图像的特征点;
  • 参数三:img2,第二幅图像;
  • 参数四:keypoints2,第二幅图像的特征点;
  • 参数五:matches1to2,第一幅图像和第二幅图像特征点匹配结果;
  • 参数六:outImg,输入图像;
  • 参数七:matchColor,连接线和特征点的颜色;
  • 参数八:singlePointColor,没有匹配点的特征点的颜色;
  • 参数九:matchesMask,匹配掩码矩阵;
  • 参数十:flags,绘制功能标志位。
// C++: enum DrawMatchesFlags
public static final int
        DrawMatchesFlags_DEFAULT = 0,                  // 创建输出矩阵,绘制圆形中心,无围绕关键点的圆以及关键点的大小和方向
        DrawMatchesFlags_DRAW_OVER_OUTIMG = 1,         // 不创建输出矩阵,直接在原始图像中绘制关键点
        DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS = 2,   // 不绘制单个关键点
        DrawMatchesFlags_DRAW_RICH_KEYPOINTS = 4;      // 关键点位置绘制圆形,体现关键点的大小和方向
复制代码

操作

class ORBBFMatchActivity : AppCompatActivity() {

    private val firstBgr by lazy {
        Utils.loadResource(this, R.drawable.lena)
    }
    private val firstGray by lazy { firstBgr.toGray() }

    private val secondBgr by lazy {
        Utils.loadResource(this, R.drawable.lena_250)
    }
    private val secondGray by lazy { secondBgr.toGray() }

    private val mBinding: ActivityOrbBfmatchBinding by lazy {
        ActivityOrbBfmatchBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(mBinding.root)
        wrapCoroutine({ showLoading() }, { doORBBFMatch() }, { hideLoading() })
    }


    private fun showLoading() {
        mBinding.isLoading = true
    }

    private fun hideLoading() {
        mBinding.isLoading = false
    }

    private fun doORBBFMatch() {
        val firstKeyPoints = MatOfKeyPoint()
        val secondKeyPoints = MatOfKeyPoint()

        val firstDescriptor = Mat()
        val secondDescriptor = Mat()

        orbFeatures(firstGray, firstKeyPoints, firstDescriptor)
        orbFeatures(secondGray, secondKeyPoints, secondDescriptor)

        // 原始匹配结果
        val matches = MatOfDMatch()
        val matcher = BFMatcher.create(Core.NORM_HAMMING)
        matcher.match(firstDescriptor, secondDescriptor, matches)
        Log.e(App.TAG, " matchers size = ${matches.size()}")

        // 优化匹配结果
        val list = matches.toList()
        list.sortBy { it.distance }
        Log.e(App.TAG, "Min = ${list.first().distance}")
        val min = list.first().distance

        val goodMatchers = list.filter {
            it.distance == min
        }

        Log.e(App.TAG, " good matchers size = ${goodMatchers.size}")

        val result = Mat()
        val matOfDMatch = MatOfDMatch()
        matOfDMatch.fromList(goodMatchers)
        drawMatches(firstGray, firstKeyPoints, secondGray, secondKeyPoints, matOfDMatch, result)
        GlobalScope.launch(Dispatchers.Main) {
            mBinding.ivResult.showMat(result)
        }
    }

    private fun orbFeatures(source: Mat, keyPoints: MatOfKeyPoint, descriptor: Mat) {
        val orbDetector = ORB.create(
            1000,
            1.2f
        )
        orbDetector.detect(source, keyPoints)
        orbDetector.compute(source, keyPoints, descriptor)
    }

    override fun onDestroy() {
        firstBgr.release()
        secondBgr.release()
        firstGray.release()
        secondGray.release()
        super.onDestroy()
    }
}
复制代码

效果

暴力匹配针对查询描述子中的每个描述符都会在训练描述子中寻找匹配描述子,所以很容易出现错误匹配,如下方图一所示为原始暴力匹配结果,明显存在很多错误的匹配结果,因为暴力匹配的规则就是遍历计算,全部匹配。所以往往使用暴力匹配时,需要进行结果优化,如下方图二所示,就是经过距离最小值优化后的结果。

优化前

原始暴力匹配结果

优化后

匹配结果

源码

github.com/onlyloveyd/…

参考资料:

baike.baidu.com/item/%E6%B1…

zhuanlan.zhihu.com/p/54892014

docs.opencv.org/master/db/d…

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享