特征匹配在计算机视觉中常用于图像或视频中检测目标,首先我们先要了解下什么是特征。特征是独特的具体模式,并且易于跟踪和比较。通过一些寻找特征的算法来实现图像的特征检测,但仅仅是特征检测是不够的,还需要能够将一种特征与另一种特征区分开,这时就用到了特征描述来描述检测到的特征。这些描述能帮助我们在其他图像中找到相似的特征,并能够识别目标。
常用的特征检测算法包括SIFT、SURF、BRIEF、FAST、BRISEK和FREAK等。这里说明一点,SIFT和SURF是专利算法,免费使用仅限于学术和研究目的。下面让我们一起学习下上述几种算法原理以及在Android平台上的使用方法。
1.SIFT-尺度不变特征变换
SIFT算法于2004年David Lowe提出,是一种被广泛认可的特征监测算法之一。
SIFT算法的一些特性:
- 对目标的缩放和旋转变化具有不变性
- 对三位视角和光照变化具有部分不变性
- 绝大部分特征关键点可以从单幅图像中获取
SIFT的原理需要我们对原文章仔细认真的阅读和理解,它遵循匹配稳健局部特征的策略:
- 尺度空间极值检测:利用高斯模糊对图像逐步模糊化,去除图像的部分细节。通过高斯差分图像计算获得图像尺度不变性。
- 关键点定位:迭代地将每个像素与其相邻像素比较。
- 方向分配:为每个关键点计算搞死模糊图像的幅值和方向获得旋转不变性。
- 关键点描述子:为不同关键点建立描述子并加以区分。
在Android上的使用方法:
- 创建一个名为Detection的应用 ,并将OpenCV库部署添加到项目中,不熟悉的话可以参考文章:Eclipse与AndroidStudio关于OpenCV4Android库的部署。在项目res文件夹下创建menu/main_menu.xml文件并创建两个元素。第一个元素用于加载目标匹配的图像,第二个元素用于目标检测的场景图像。
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_load_first_image"
android:orderInCategory="1"
android:title="加载第一幅图"
app:showAsAction="never"/>
<item
android:id="@+id/action_load_second_image"
android:orderInCategory="2"
android:title="加载第二幅图"
app:showAsAction="never"/>
</menu>
修改activity_main.xml文件内容
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_Image"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<TextView
android:id="@+id/tv_Object1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="666"/>
<TextView
android:id="@+id/tv_Object2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="444"/>
<TextView
android:id="@+id/tv_Matches"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="777"/>
<TextView
android:id="@+id/tv_Time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="333"/>
</LinearLayout>
图像检测是个耗时操作,所以这里使用AsyncTask来执行实际的计算
new AsyncTask<Void, Void, Bitmap>() {
@Override
protected void onPreExecute() {
super.onPreExecute();
mStartTime = System.currentTimeMillis();
}
@Override
protected Bitmap doInBackground(Void... params) {
return executeTask();//目标检测(计算)
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
mEndTiem = System.currentTimeMillis();
mImage1.setImageBitmap(bitmap);
mObject1.setText("目标1:" + keypointsObject1);
mObject2.setText("目标2:" + keypointsObject2);
mMatches.setText("关键点匹配:" + keypointMatches);
mTime.setText("耗费时间:" + (mEndTiem - mStartTime) + "ms");
}
}.execute();
在doInBackground()方法中,返回executeTask()方法用于目标检测计算
FeatureDetector detector;
MatOfKeyPoint keypoints1, keypoints2;
DescriptorExtractor descriptorExtractor;
Mat descriptors1, descriptors2;
DescriptorMatcher descriptorMatcher;
MatOfDMatch matches = new MatOfDMatch();
keypoints1 = new MatOfKeyPoint();
keypoints2 = new MatOfKeyPoint();
descriptors1 = new Mat();
descriptors2 = new Mat();
//特征匹配算法
detector = FeatureDetector.create(FeatureDetector.SIFT);
descriptorExtractor = DescriptorExtractor.create(DescriptorExtractor.SIFT);
//检测关键点
detector.detect(src2, keypoints2);
detector.detect(src1, keypoints1);
//添加变量,用于显示关键点数量
keypointsObject1 = keypoints1.toArray().length;
keypointsObject2 = keypoints2.toArray().length;
//计算描述子
descriptorExtractor.compute(src1, keypoints1, descriptors1);
descriptorExtractor.compute(src2, keypoints2, descriptors2);
- OpenCV提供的两种特征匹配算法
(1)暴力匹配器
该匹配器取出第一个图像集的特征描述子,根据距离计算将其与第二个图像集内的所有其他特征进行匹配,返回距离最近的特征。它有两个可选参数,第一个是距离测量形式,第二个参数是crosscheck。用法:
descriptorMatcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_SL2);//暴力匹配器
(2)基于FLANN匹配器–快速近似最近邻库
它 包含一个为在大型数据集中快速最近邻搜索和高维度特征而优化的算法的集合,对于大多数据集,其处理速度高于暴力匹配器。用法:
descriptorMatcher = DescriptorMatcher.create(DescriptorMatcher.FLANNBASED);//基于FLANN匹配器
- 匹配点
计算完所有描述子之后,使用下面代码进行匹配关键点:
descriptorMatcher.match(descriptors1, descriptors2, matches);
调用drawMatches()方法绘制结果,显示得到的匹配。
static Mat drawMatches(Mat img1, MatOfKeyPoint key1, Mat img2, MatOfKeyPoint key2, MatOfDMatch matches, boolean imageOnly) {
Mat out = new Mat();
Mat im1 = new Mat();
Mat im2 = new Mat();
Imgproc.cvtColor(img1, im1, Imgproc.COLOR_BGR2RGB);
Imgproc.cvtColor(img2, im2, Imgproc.COLOR_BGR2RGB);
if (imageOnly) {
MatOfDMatch emptyMatch = new MatOfDMatch();
MatOfKeyPoint emptyKey1 = new MatOfKeyPoint();
MatOfKeyPoint emptyKey2 = new MatOfKeyPoint();
Features2d.drawMatches(im1, emptyKey1, im2, emptyKey2, emptyMatch, out);
} else {
Features2d.drawMatches(im1, key1, im2, key2, matches, out);
}
Bitmap bmp = Bitmap.createBitmap(out.cols(), out.rows(), Bitmap.Config.ARGB_8888);
Imgproc.cvtColor(out, out, Imgproc.COLOR_BGR2RGB);
Core.putText(out, "FRAME", new org.opencv.core.Point(im1.width()/2,30),Core.FONT_HERSHEY_PLAIN, 2, new Scalar(0, 255, 255), 3);
Core.putText(out, "MATCHED", new org.opencv.core.Point(im1.width()+im2.width()/2,30),Core.FONT_HERSHEY_PLAIN, 2, new Scalar(255, 0, 0), 3);
return out;
}
- 由于SIFT和SURF是专利算法,没有被OpenCV自动编译,所以需要我们完成以下步骤:
- 下载AndroidNDK,目前AndroidStudio最新版本已经可以直接下载NDK了,自行下载即可。
- 从OpenCV源码库中下载nonfree_init.cpp、precomp.cpp和surf.cpp文件,下载地址:cpp文件。在src目录下创建jni文件夹,并将下载的文件拷贝进去。
- 修改precomp.cpp文件,去掉#include “cvconfig.h”和#include “opencv2/ocl/private/util.cpp”。
- 修改nonfree_init.cpp文件,去掉从#ifdef HAVE_OPENCV_OCL开始,直到#endif结束的代码行。
- 分别创建一个名为Android.mk、Application.mk的文件,并分别加入以下代码
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
OPENCV_CAMERA_MODULES:=on
OPENCV_INSTALL_MODULES:=on
include E:/OpenCV4Android SDK/OpenCV-2.4.11-android-sdk/OpenCV-android-sdk/native/jni/OpenCV.mk
LOCAL_MODULE := nonfree
LOCAL_SRC_FILES := nonfree_init.cpp \
sift.cpp \
surf.cpp
LOCAL_LDLIBS += -llog -ldl
include $(BUILD_SHARED_LIBRARY)
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI := armeabi-v7a
APP_PLATFORM := android-8
- 打开app文件中的build.gradle文件,在android块下添加以下代码
sourceSets.main {
jniLibs.srcDir 'src/main/libs'
jni.srcDirs = [] //关闭自动调用ndk-build命令
}
完成编译后,在java代码中加载这个库。在 LoaderCallbackInterface.SUCCESS的case下添加代码:
System.loadLibrary("nonfree");
将在下文中给出ORB的一个具体demo
2.SURF-加速稳健特征
该算法由Herbert Bay等人于2006年提出,它针对SIFT算法匹配速度慢并且计算繁琐这个问题而提出。
- 原文链接:SURF算法
SURF算法的一些特性:
- 图像旋转不变性
- 尺度变换不变性
- 光照变化不变性
- SURF比SIFT快3倍
- 擅长处理存在模糊或旋转的图像
- 对存在视角变化的图像处理效果欠佳
在Android上的使用方法:
只需要替换掉doInBackground()方法中的以下代码即可
//特征匹配算法
detector = FeatureDetector.create(FeatureDetector.SIFT);
descriptorExtractor = DescriptorExtractor.create(DescriptorExtractor.SIFT);
换成
detector = FeatureDetector.create(FeatureDetector.SURF);
descriptorExtractor = DescriptorExtractor.create(DescriptorExtractor.SURF);
descriptorMatcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_SL2);
3.ORB算法
该算法由Ethan Rublee等人于2011年在OpenCV实验室开发,它是一种替代SIFT和SURF的可行而搞笑的描述子。
SURF算法的一些特性:
- 对旋转BRIEF特征的高效计算
- 对旋转BRIEF特征的方差和相关分析
- 为FAST加入快速精确的方向成分
在Android上的使用方法:
只需要替换掉doInBackground()方法中的以下代码即可
//特征匹配算法
detector = FeatureDetector.create(FeatureDetector.SIFT);
descriptorExtractor = DescriptorExtractor.create(DescriptorExtractor.SIFT);
换成
detector = FeatureDetector.create(FeatureDetector.ORB);
descriptorExtractor = DescriptorExtractor.create(DescriptorExtractor.ORB);
descriptorMatcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_HAMMING);
4.BRISK算法
该算法由Leutenegger等人提出的对最先进的特征检测、描述和匹配算法的代替。在某些情况下,它它是一种替代SIFT和SURF的可行而高效的描述子。
SURF算法的一些特性:
- 对旋转BRIEF特征的高效计算
- 对旋转BRIEF特征的方差和相关分析
- 为FAST加入快速精确的方向成分
在Android上的使用方法:
只需要替换掉doInBackground()方法中的以下代码即可
//特征匹配算法
detector = FeatureDetector.create(FeatureDetector.SIFT);
descriptorExtractor = DescriptorExtractor.create(DescriptorExtractor.SIFT);
换成
detector = FeatureDetector.create(FeatureDetector.BRISK);
descriptorExtractor = DescriptorExtractor.create(DescriptorExtractor.BRISK);
descriptorMatcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_HAMMING);
5.FREAK算法
该算法提出一种能唯一识别关键点的稳健描述子,在计算过程中耗费更少的计算时间按和存储空间,其受人类视网膜启发而来。
在Android上的使用方法:
只需要替换掉doInBackground()方法中的以下代码即可
//特征匹配算法
detector = FeatureDetector.create(FeatureDetector.SIFT);
descriptorExtractor = DescriptorExtractor.create(DescriptorExtractor.SIFT);
换成
detector = FeatureDetector.create(FeatureDetector.FAST);
descriptorExtractor = DescriptorExtractor.create(DescriptorExtractor.FREAK);
descriptorMatcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_HAMMING);
最后附上 一个应用ORB的完整Demo:
效果图: