安卓平台上实现SIFT特征点监测

作者:刘衍

最近开始学习安卓上的图像处理,刚开始困难重重。没有很好的教材,网上的例子也比较少,走了不少弯路。安卓上使用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.在工程中加入你想处理的图片,运行一下,哈哈,是不是很有趣



最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容