作者:刘衍
最近开始学习安卓上的图像处理,刚开始困难重重。没有很好的教材,网上的例子也比较少,走了不少弯路。安卓上使用opencv进行图像处理需要较多方面的知识,如NDK、JNI、OPENCV、安卓等。我会一步一步走下去,并且不断更新博客。朋友们可以在下面留言交流。
【嵌牛导读】:安卓上使用opencv实现SIFT特征点监测,由于SIFT是专利算法,所以OPENCV4ADNROID没有将其打包,所以我们需要自己将改算法打包成.so动态链接库,加载到程序中。本文的软件版本:android studio3.0.1,opencv4android 2.4.11,NDK 12b,opencv源码 2.4.11
【嵌牛鼻子】:opencv4android SIFT NDK
【嵌牛提问】:如何在安卓上实现SIFT?
【嵌牛正文】:
1.首先新建一个工程,不用多说,大家应该都轻车熟路
2.写好布局文件,很简单,一个按钮,一个ImageView就行
3.下载opencv4android 2.4.11,解压文件,apk文件夹里面放的是opencvManager的各个版本,需要根据手机CPU架构的不同下载不同版本的opencvManager。samples里面是例程,sdk里面是我们需要的库文件。将SDK文件夹下面的java文件夹作为module引入到工程中。
引入的module的build.gradle中的sdk版本可能与APPmodule中的不一致,需要将引入的module的build.gradle中的sdk版本修改
4.下载NDK,修改gradle.properties,
修改local.properties,(添加下载的NDK路径)
修改APP下的build.gradle,在android{}中添加
sourceSets.main.jni.srcDirs= []
//禁止自带的ndk功能
sourceSets.main.jniLibs.srcDirs= ['src/main/libs','src/main/jniLibs']
//重定向so目录为src/main/libs和src/main/jniLibs,原来为src/main/jniLibs
task ndkBuild(type:Exec,description:'Compile JNI source with NDK') {
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
def ndkDir = properties.getProperty('ndk.dir')
if (org.apache.tools.ant.taskdefs.condition.Os.isFamily(org.apache.tools.ant.taskdefs.condition.Os.FAMILY_WINDOWS)) {
commandLine "$ndkDir/ndk-build.cmd",'-C',file('src/main/jni').absolutePath
} else {
commandLine "$ndkDir/ndk-build",'-C',file('src/main/jni').absolutePath
}
}
tasks.withType(JavaCompile) {
compileTask ->compileTask.dependsOn ndkBuild
}
task ndkClean(type:Exec,description:'Clean NDK Binaries') {
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
def ndkDir = properties.getProperty('ndk.dir')
if (org.apache.tools.ant.taskdefs.condition.Os.isFamily(org.apache.tools.ant.taskdefs.condition.Os.FAMILY_WINDOWS)) {
commandLine "$ndkDir/ndk-build.cmd",'clean','-C',file('src/main/jni').absolutePath
} else {
commandLine "$ndkDir/ndk-build",'clean','-C',file('src/main/jni').absolutePath
}
}
clean.dependsOn 'ndkClean'
5.在app/main下面新建文件夹jni。在文件夹下创建两个.mk文件---Android.mk和Application.mk。
在Android.mk中添加如下代码
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
OPENCV_INSTALL_MODULES:=on
OPENCV_CAMERA_MODULES:=off
include ..\..\..\..\native\jni\OpenCV.mk
LOCAL_MODULE := nonfree
LOCAL_LDLIBS += -llog
LOCAL_SRC_FILES := nonfree_init.cpp \
precomp.hpp \
sift.cpp \
surf.cpp
include $(BUILD_SHARED_LIBRARY)
在Application.mk中添加如下代码
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI := armeabi armeabi-v7a x86
6.下载opencv源码。找到nonfree_init.cpp,precomp.cpp,sift.cpp,surf.cpp,复制到jni文件夹下。修改precomp.cpp文件,去掉#include “cvconfig.h”和#include “opencv2/ocl/private/util.cpp”。修改nonfree_init.cpp文件,去掉从#ifdef HAVE_OPENCV_OCL开始,直到#endif结束的代码行。
7.将之前下载的opencv4android中的sdk文件夹下面的native复制到与工程中APP同级的地方。
点击android studio右侧的gradle,找到ndkbuild,点击。这时候可能会出现错误
不用着急,我们在下载好的opencv源码中找到nonfree文件夹,复制到工程中native/jni/include/opencv2下面,然后再次点击ndkbuild。
这时已经可以成功生成.so文件了。
8.我们已经将SIFT算法打包成.so动态链接库了,下面只需要在程序里加载动态链接库,然后处理就行了。
MainActivity.class中添加如下代码
package com.tinymonster.opencvstudy1;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.core.MatOfDMatch;
import org.opencv.core.MatOfKeyPoint;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.features2d.DescriptorExtractor;
import org.opencv.features2d.DescriptorMatcher;
import org.opencv.features2d.FeatureDetector;
import org.opencv.features2d.Features2d;
import org.opencv.features2d.KeyPoint;
import org.opencv.imgproc.Imgproc;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private Button button1;
private ImageView imageView1;
private ImageView imageView2;
private static String TAG="MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (!OpenCVLoader.initDebug()) {
Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_11, this, mLoaderCallback);
} else {
Log.d(TAG, "OpenCV library found inside package. Using it!");
mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
}
initView();
}
private void initView(){
button1=(Button)findViewById(R.id.button1);
imageView1=(ImageView) findViewById(R.id.imageView1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new AsyncTask(){
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Bitmap doInBackground(Void...voids) {
Mat test1=new Mat();
Mat test2=new Mat();
Mat out;
FeatureDetector SIFTdter = FeatureDetector.create(FeatureDetector.SIFT);//创建特征监测
DescriptorExtractor descriptorExtractor=DescriptorExtractor.create(DescriptorExtractor.SIFT);//描述子提取
DescriptorMatcher descriptorMatcher=DescriptorMatcher.create(DescriptorMatcher.FLANNBASED);//描述子匹配 暴力匹配器
MatOfDMatch matchs=new MatOfDMatch();
Bitmap src_bitmap1= BitmapFactory.decodeResource(getResources(),R.drawable.photo7);
Bitmap src_bitmap2= BitmapFactory.decodeResource(getResources(),R.drawable.photo6);
Utils.bitmapToMat(src_bitmap1,test1);
Utils.bitmapToMat(src_bitmap2,test2);
Mat descriptors1=new Mat();
Mat descriptors2=new Mat();
MatOfKeyPoint kp1 = new MatOfKeyPoint();//特征点
MatOfKeyPoint kp2 = new MatOfKeyPoint();//特征点
SIFTdter.detect(test1,kp1);
SIFTdter.detect(test2,kp2);//监测特征点
descriptorExtractor.compute(test1,kp1,descriptors1);//计算描述子
descriptorExtractor.compute(test2,kp2,descriptors2);
descriptorMatcher.match(descriptors1,descriptors2,matchs);//进行匹配
out=drawMatchs(test1,kp1,test2,kp2,matchs,false);
Bitmap out_drawable =Bitmap.createBitmap(out.cols(),out.rows(),Bitmap.Config.ARGB_8888);
Utils.matToBitmap(out,out_drawable);
return out_drawable;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
imageView1.setImageBitmap(bitmap);
}
}.execute();
}
});
}
Mat drawMatchs(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);
}
Imgproc.cvtColor(out,out, Imgproc.COLOR_BGR2RGB);
Core.putText(out,"src",new Point(img1.width()/2,30),Core.FONT_HERSHEY_PLAIN,2,new Scalar(0,255,255),3);
Core.putText(out,"matched",new Point((img1.width()+img2.width())/2,30),Core.FONT_HERSHEY_PLAIN,2,new Scalar(255,0,0),3);
return out;
}
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS: {
Log.i(TAG, "OpenCV loaded successfully");
System.loadLibrary("nonfree");
// mOpenCvCameraView.enableView();
// mOpenCvCameraView.setOnTouchListener(ColorBlobDetectionActivity.this);
}
break;
default: {
super.onManagerConnected(status);
}
break;
}
}
};
}
9.在工程中加入你想处理的图片,运行一下,哈哈,是不是很有趣