ORB特征点检测
onlyloveyd 209 4

概念

特征点

特征点和角点都是图像中具有局部特征的点,但是特征点具有唯一描述像素点特征的描述子。特征点由关键点和描述子组成,例如ORB特征点,我们需要先使用FAST算法求取关键点,然后再计算BRIEF描述子,从而形成ORB特征点。所谓关键点,字面理解为图像内关键像素点,OpenCV中对应的类型为KeyPoint,里面包含像素点的坐标、直径、方向、强度等有用的信息;而描述子(descriptor),是用来唯一描述关键点的一串数字,通过描述子可以区分两个不同的关键点,也可以用来匹配相同的关键点,所以选择合适的方式构建描述子很重要。

BRIEF描述子

BRIEF(Binary Robust Independent Elementary Features)是由Michael Calonder等人在ECCV2010上提出的一种二进制字符串特征点描述子,相较于SIFT、SURF,它更简单且存储空间更小,采用Hamming Distance进行比较,匹配速度更快。BRIEF仅仅是特征描述子,所以还需要配合特征点检测算法一起使用,如FAST算法、Harris算法等。

基本过程:

先平滑图像,然后在特征点周围选择一个Patch,在这个Patch内通过一种选定的方法来挑选出来$n_d$个点对。然后对于每一个点对$(p,q)$,我们比较这两个点的像素值,如果$I(p) < I(q)$,则对应在二值串中的值为1,否则为0。所有$n_d$个点对,都进行比较之间,我们就生成了一个$n_d$长的二进制串。

优缺点:

它抛弃了传统的利用图像局部邻域的灰度直方图或梯度直方图提取特征方法,改用检测随机响应,大大加快了描述子的建立速度;生成的二进制特征描述子便于高速匹配(计算汉明距离只需要通过异或操作加上统计二进制编码中"1"的个数,这些通过底层运算可以实现),且便于在硬件上实现。BRIEF的优点主要在于速度,缺点在于不具有旋转不变形、对噪声敏感、不具有尺度不变性。

ORB特征点

ORB,即Oriented BRIEF,ORB特征点由FAST角点与BRIEF描述子组成,先通过FAST角点检测确定关键点,然后计算每个关键点的BRIEF描述子,这样就确定了ORB特征点。FAST(Features from Accelerated Segment Test)算法理念:若某像素与其周围邻域内足够多的像素点相差较大,则该像素可能是角点。FAST角点不具有尺寸不变形和旋转不变性,所以ORB特征点检测,在FAST算法基础上做了针对尺寸不变性和旋转不变性的后续处理。

API

OpenCV Feature2D模块,字面理解为处理二维特征的模块 ,其API集中于特征点的检测和匹配。

绘制关键点

public static void drawKeypoints(Mat image, MatOfKeyPoint keypoints, Mat outImage, Scalar color, int flags) 
  • 参数一:image,原图;
  • 参数二:keypoints,原图关键点;
  • 参数三:outImage,绘制关键点后的输出图像;
  • 参数四:color,关键点绘制颜色;
  • 参数五: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;      // 关键点位置绘制圆形,体现关键点的大小和方向

计算关键点

单个图像检测以及多个图像检测

public void detect(Mat image, MatOfKeyPoint keypoints, Mat mask)
  • 参数一:image,待计算关键点的输入图像;
  • 参数二:keypoints,计算得到的关键点;
  • 参数三:mask,掩码矩阵,大小必须与输入图像相同且类型为CV_8U。
public void detect(List<Mat> images, List<MatOfKeyPoint> keypoints, List<Mat> masks)
  • 参数一:images,待计算关键点的输入图像"们";
  • 参数二:keypoints,计算得到的关键点“们”;
  • 参数三:masks,掩码矩阵“们”。

计算描述子

public void compute(Mat image, MatOfKeyPoint keypoints, Mat descriptors)
  • 参数一:image,关键点对应的输入图像;
  • 参数二:keypoints,已计算出的关键点;
  • 参数三:descriptors,每个关键点对应的描述子。
public void compute(List<Mat> images, List<MatOfKeyPoint> keypoints, List<Mat> descriptors) 
  • 参数一:images,关键点对应的输入图像“们”;
  • 参数二:keypoints,已计算出的关键点“们”;
  • 参数三:descriptors,关键点“们”对应的描述子“们”。

计算关键点和描述子

public void detectAndCompute(Mat image, Mat mask, MatOfKeyPoint keypoints, Mat descriptors, boolean useProvidedKeypoints)
  • 参数一:image,待计算关键点的输入图像;
  • 参数二:mask,掩码矩阵,大小必须与输入图像相同且类型为CV_8U;
  • 参数三:keypoints,作为输入表示已计算好的关键点,用于输出用于存储本方法计算出的关键点。具体取决于第五个参数;
  • 参数四:descriptors,每个关键点对应的描述子;
  • 参数五:useProvidedKeypoints,是否使用已有关键点标志符,默认为false,表示本方法自己计算关键点,第三个参数不作为输入;若设置为true,则第三个参数作为输入表示已计算好的关键点,那么这个方法的作用就和compute一样,只计算描述子。

ORB

public static ORB create(int nfeatures, float scaleFactor, int nlevels, int edgeThreshold, int firstLevel, int WTA_K, int scoreType, int patchSize, int fastThreshold)
  • 参数一:nfeatures,最大特征点检测数量;
  • 参数二:scaleFactor,金字塔尺寸缩放比例,必须大于1。如果scaleFactor=2,表示古典金字塔的下一层宽高缩小一半,也就是下一级的像素比上一级少4倍,这么大的缩放比例会显著地降低特征匹配得分。但是另一方面,如果scaleFactor和1过于接近会让缩放到指定大小需要更多的金字塔层次,运行速度将会受到影响;
  • 参数三:nlevels,金字塔层数。最小级别的线性大小等于$(input_image_linear_size)/pow(scaleFactor, nlevels - firstLevel)$;
  • 参数四:edgeThreshold,边缘阈值,它应该与patchSize参数大致匹配;
  • 参数五:firstLevel,将原图放入金字塔中的等级。若前面存在层次,将放大图像来填充;
  • 参数六:WTA_K,生成每个描述子时需要的像素点数目。若WTA_K为默认值2,表示通过随机选取一对点并比较像素值,获取0/1结果的BRIEF描述子;若WTA_K=3,表示通过3个随机点的像素值比较得出描述子;若WTA_K=4,表示用过4个随机点的像素值比较得出描述子。
  • 参数七:scoreType,关键点评价方法,默认值为HARRIS_SCORE
// C++: enum ScoreType
public static final int
        HARRIS_SCORE = 0,
        FAST_SCORE = 1;
  • 参数八:patchSize,生成描述子时关键点周围邻域的尺寸;
  • 参数九:fastThreshold,FAST角点计算阈值。

操作

/**
 * 关键点与描述子
 * author: yidong
 * 2021-03-28
 */
class ORBActivity : AppCompatActivity() {

    private val bgr by lazy {
        Utils.loadResource(this, R.drawable.lena)
    }
    private val rgb by lazy { bgr.toRgb() }

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

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


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

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

    private fun doORB() {
        val orbDetector = ORB.create(
            500,
            1.2f,
            8,
            31,
            0,
            2,
            ORB.HARRIS_SCORE,
            31,
            20
        )
        val points = MatOfKeyPoint()
        orbDetector.detect(rgb, points)
        val description = Mat()
        orbDetector.compute(rgb, points, description)
        val result = Mat()
        rgb.copyTo(result)
        drawKeypoints(rgb, points, result, Scalar.all(255.0), DrawMatchesFlags_DRAW_RICH_KEYPOINTS)
        GlobalScope.launch(Dispatchers.Main) {
            mBinding.ivResult.showMat(result)
        }
    }

    override fun onDestroy() {
        bgr.release()
        rgb.release()
        super.onDestroy()
    }
}

效果

ORB特征点检测

源码

https://github.com/onlyloveyd/LearningAndroidOpenCV

参考文档:

https://docs.opencv.org/master/d0/d13/classcv_1_1Feature2D.html

https://docs.opencv.org/master/db/d95/classcv_1_1ORB.html

https://blog.csdn.net/songzitea/article/details/18272559

https://www.cnblogs.com/zyly/p/9615280.html#_label3_1

评论区

索引目录