用Android studio 编写程序实现识别图片中手指数目

原问题:用Android studio 编写程序实现识别图片中手指数目
分类:编程开发 > 最后更新时间:【2016-12-14 10:58:19】
问题补充:

写一个安卓APP,实现功能:识别手机相册中手的图片有几根手指

最佳答案

1,过程感慨(想直接看教程,请跳过此部分)

在写具体内容之前,我先说下我搞这个东西的过程,由于导师之前说过要搞个图像匹配的androiAPP,具体就是匹配前后两张图片的相似度,类似安卓5.0引入的刷脸解锁。

当时觉得,要实现这样一个东西,肯定没现成的API可供使用,第一时间想到的无疑就是opencv,这个拥有一套强大的图像处理函数的库,它的开发语言主要是C++,但是,也有jar包可供android开发使用,如果单单是使用里面已经写好了的效果的话,肯定是不能完成图像匹配的。

也就是说,我必须要调用它里面的函数再结合自己算法重新去实现这样一个功能,再使用ndk环境去实现jni编程,把我自己写好的c++代码,在生成.so动态库的基础上,引入并使用。

刚开始,思路很清晰,然后便着手百度androidstudio(下面简称as)的opencvjni编程使用教程,十分遗憾,所能搜到的,关于as和opencv、jni搭边的例子几乎为0,很多的例子是eclipse。没办法,只有自己亲手搞了。

刚动手的时候,很快地把所有装备工作都搞定了,.so动态库文件(下面会介绍)也编译出来了,但是,就在此时,我遇到了一个令我第一阶段切底放弃的bug!!

这个bug是:(下面我会说明白,它的真实起因和解决方法)

fatalerror:opencv2/opencv.hpp:Nosuchfileordirectory,意思是我所要编译的cpp文件中的头文件opencv2/opencv.hpp找不到。当时,无论是自己请教别人、百度、google还是查书,都无法解决,足足耗时一星期!!

逐保留项目信息,放弃不搞。

直到2天前,开始决定重新尝试,并于今天正式解决后,现发表此文。

2,运行环境

win7,系统;

androidstudio版本0.8.0beta,使用build:gradle:0.12.+,tools版本:21.1.2,api21;

opencvforandroid包,我使用的版本是OpenCV-3.0.0-android-sdk,2.4.9的也可以,可以到opencv官网下载,我这里提供个链接

../project/opencvlibrary/opencv-android/3.0.0/OpenCV-3.0.0-android-sdk-1.html

编译.so动态库使用cygwin,安装了所有包,这里提示,不一定要用它,可以直接使用cmd进行编译;

ndk为android-ndk-r10d(强烈建议使用r9或r10系列,因为这两个能在cmd中编译出.so),r10d能够支持的androidapi最高到21,如果你的是22的请修改,否则会有会编译不出jni.h头文件,或者其他的头文件,你会发现,别人的源码在你这编译不出了。

3,准备工作

1,---ndk的下载、安装和配置,此部分不说,网上教程很多,很多可行。

2,---cygwin的下载和安装,参照http://blog.csdn.net/asmcvc/article/details/9311573,我上面说了,不一定要用它,win自带的cmd也可以编译。如果使用cygwin,要做好心理准备,下载和安装它,非常非常的久,文件总体积20多G!!!!我是用了9个多小时。

3,---opencvforandroid的sdk下载完成后。打开该文件夹,sdk/native/libs,里面有很多平台的文件夹,能在里面出现的,证明你能够在下面的Application.mk中设置生成对应的架构的.so文件,我举个例子,我的是:

在下面介绍的Application.mk文件中有一句话,它是用来设置生成对应架构的.so文件,我这里是armeabi-7a,如果要生所有的,写出:=all,注意,这样很可能会报错,错误信息是,某种架构找不到,所以,我要你看清楚,上面文件夹里面有哪些架构,这些坑是网上找不到,如果你要生成两种,可以轮着来编译,第二次的编译,不同的架构是不会覆盖的。现在打开sdk/native/jni,如无意外,里面肯定有个文件叫做OpenCV.mk,它就是我们在android.mk脚本文件中要引入opencvC++库所要参照的文件。请用记事本或者Notepad++打开。

4,---了解Android.mk和Application.mk文件的基本内容信息:下面我使用默认的Android.mk来说明,和我的例子的Application.mk来说明。

它们都是脚本文件。

Android.mk

Application.mk

4,编译.so

使用你的as创建一个新项目,然后在你的项目的main目录下创建一个一个jni文件夹,这样创建:

创建好了之后,是这样的:

 首先编译项目的头文件.h,一般编译出来后,它的名字结构是:包名_类名.h

 编译命令如下,请在你的as下面的Terminal里面输入:

SourcePath:D:\work\androidstudio\VisualRecognition\app\src\main\java(绝对路径)

TargetPath:D:\work\androidstudio\VisualRecognition\visual\src\main\jni(绝对路径)

TargetClassName:com.yf.visualrecognition.UnityPlayerActivity(你的包名+类名)

格式:javah-d${SourceFile}-classpath${TargetPath}${TargetClassName}

控制台指令:javah-dD:\work\androidstudio\VisualRecognition\visual\src\main\jni-classpath        D:\work\androidstudio\VisualRecognition\app\src\main\javaio.github.froger.jni.MyActivity

然后在你的jni文件夹下面分别创建Android.mk、Application.mk和你要编译的.cpp或者.c文件,前两个的内容可以模仿我上面介绍的,.cpp我这里提供一个。

  Android.mk、Application.mk、ImgFuncpp分别如下,util.c是空文件,之所以创建它是为了避免另外一个bug,这不说:

Android.mk文件如下

LOCAL_PATH:=$(callmy-dir)include$(CLEAR_VARS)OPENCV_LIB_TYPE:=STATICifeq("$(wildcard$(OPENCV_MK_PATH))","")includeE:\OpenCV-3.0.0-android-sdk-1\OpenCV-android-sdk\sdk\native\jni\OpenCV.mkelseinclude$(OPENCV_MK_PATH)endifLOCAL_MODULE:=ImgFunLOCAL_SRC_FILES:=ImgFun.cppLOCAL_LDLIBS+=-lm-lloginclude$(BUILD_SHARED_LIBRARY)

Application.mk文件如下

APP_STL:=gnustl_staticAPP_CPPFLAGS:=-frtti-fexceptionsAPP_ABI:=armeabi-v7a#这句是设置生成的cpu指令类型,提示,目前绝大部分安卓手机支持armeabi,libs下太多类型,编译进去apk包会过大APP_PLATFORM:=android-8#这句是设置最低安卓平台,可以不弄

ImgFun.cpp文件如下

1#include<io_github_froger_jni_MyActivity.h>2#include<stdio.h>3#include<stdlib.h>4#include<opencv2/opencv.hpp>5usingnamespacecv;6IplImage*change4channelTo3InIplImage(IplImage*src);78extern"C"{9JNIEXPORTjintArrayJNICALLJava_io_github_froger_jni_MyActivity_ImgFun(10JNIEnv*env,jobjectobj,jintArraybuf,intw,inth);11JNIEXPORTjintArrayJNICALLJava_io_github_froger_jni_MyActivity_ImgFun(12JNIEnv*env,jobjectobj,jintArraybuf,intw,inth){1314jint*cbuf;15cbuf=env->GetIntArrayElements(buf,false);16if(cbuf==NULL){17return0;18}1920Matmyimg(h,w,CV_8UC4,(unsignedchar*)cbuf);21IplImageimage=IplImage(myimg);22IplImage*image3channel=change4channelTo3InIplImage(&image);2324IplImage*pCannyImage=cvCreateImage(cvGetSize(image3channel),IPL_DEPTH_8U,1);2526cvCanny(image3channel,pCannyImage,50,150,3);2728int*outImage=newint[w*h];29for(inti=0;i<w*h;i++)30{31outImage[i]=(int)pCannyImage->imageData[i];32}3334intsize=w*h;35jintArrayresult=env->NewIntArray(size);36env->SetIntArrayRegion(result,0,size,outImage);37env->ReleaseIntArrayElements(buf,cbuf,0);38returnresult;39}40}4142IplImage*change4channelTo3InIplImage(IplImage*src){43if(src->nChannels!=4){44returnNULL;45}4647IplImage*destImg=cvCreateImage(cvGetSize(src),IPL_DEPTH_8U,3);48for(introw=0;row<src->height;row++){49for(intcol=0;col<src->width;col++){50CvScalars=cvGet2D(src,row,col);51cvSet2D(destImg,row,col,s);52}53}5455returndestImg;56}

  上面.cpp文件的有几句话要说明下,注意.c文件和.cpp文件是不一样的:

  1,请用extern"C"{}包住你要你的c++函数体的定义和里面的变量,函数声明可以在外面。

  2,JNIEXPORTjintArrayJNICALLJava_io_github_froger_jni_MyActivity_ImgFun(JNIEnv*env,jobjectobj,jintArraybuf,intw,inth);

  3,jintArray是你定义的函数的返回值,我这里的是int数组,它在类型的前面有一个j,如果是字符串,那么就是jstring,数组加上Array;

  4,JNICALLJava这句不变,所有都一样,注意java的j是大写;

  5,io_github_froger_jni这里是你的包名;

  6,MyActivity你的类名,要引用这个这里C++函数的类名;

  7,ImgFun是你要在java中调用的函数名字,哪些不用直接被调用的,不用写;

  8,JNIEnv*env,jobjectobj,这个固定不变,第一个的意思是虚拟机引用,第二个是项目;

  9,jintArraybuf,intw,inth函数的参数。

  好了,上面该介绍的已经介绍完了,接下来是编译.so的正式操作(我这里使用cmd做例子,因为它更简单操作,cygwin也可以)。

你可以在as的cmd中或者系统的cmd框中实现编译,首先使用命令进入到当前的jni文件夹的目录,例如,我的是

D:asproject/JniDemo/app/main/jni,然后使用命令ndk-build,(使用ndk-build命令这一步,需要你已经配置好了ndk环境,请参照百度上面的教程)然后回车,如无意外,将会生成如下文件:

其中的.so文件就是我们所需要的,现在打开你项目app下的build.gradle文件,在android{}里面加入:

sourceSets{  main(){    jniLibs.srcDirs=['src/main/libs']  }}

这样是为了使用.so文件,上面我们仅仅是生产!

  OK,到这里基本大功告成了,不过,笔者我就是在这一步之后,运行程序的时候,出现的简单的致命的bug,导致我找了近2星期,现在想起来真是蠢..............

5,遇到的关键问题及其解决方法

  运行程序,出现,如下错误,这里声明下,不仅仅是opencv2/opencv.hpp,还可能是其他的hpp。

  出现的原因:

原来是这样的,androidstudio在我们编译完.so文件后,我们在Android.mk文件中设置引入的opencv函数库,是已经被编译进去.so动态库里面了的,而我们编译所需要的cpp文件,它在jni文件夹呢,自然就没有opencv库可依赖,所以。

  解决方法:

在你编译完.so文件后,就可以把cpp或者c文件里面的内容注释或者删除了,不然在你运行程序的时候就会抛出头文件找不到的错误,哎,真是辛酸泪,这样一个bug搞了我那么多时间,不过还好,还是解决了。

6,实现效果截图。

1packageio.github.froger.jni;23importandroid.app.Activity;4importandroid.graphics.Bitmap;5importandroid.graphics.drawable.BitmapDrawable;6importandroid.os.Bundle;7importandroid.view.View;8importandroid.widget.Button;9importandroid.widget.ImageView;1011publicclassMyActivityextendsActivity{12/**Calledwhentheactivityisfirstcreated.*/13ImageViewimgView;14ButtonbtnNDK,btnRestore;15publicstaticnativeint[]ImgFun(int[]buf,intw,inth);16static{17System.loadLibrary("ImgFun");18}19@Override20publicvoidonCreate(BundlesavedInstanceState){21super.onCreate(savedInstanceState);22setContentView(R.layout.activity_my);2324this.setTitle("使用NDK转换灰度图");25btnRestore=(Button)this.findViewById(R.id.btnRestore);26//btnRestore.setText(ImgFun());27btnRestore.setOnClickListener(newClickEvent());28btnNDK=(Button)this.findViewById(R.id.btnNDK);29btnNDK.setOnClickListener(newClickEvent());30imgView=(ImageView)this.findViewById(R.id.ImageView01);31Bitmapimg=((BitmapDrawable)getResources().getDrawable(32R.drawable.ic_launcher)).getBitmap();33imgView.setImageBitmap(img);34}3536classClickEventimplementsView.OnClickListener{37publicvoidonClick(Viewv){38//btnRestore.setText(ImgFun());39if(v==btnNDK){40longcurrent=System.currentTimeMillis();41Bitmapimg1=((BitmapDrawable)getResources().getDrawable(42R.drawable.ic_launcher)).getBitmap();43intw=img1.getWidth(),h=img1.getHeight();44int[]pix=newint[w*h];45img1.getPixels(pix,0,w,0,0,w,h);46int[]resultInt=ImgFun(pix,w,h);47BitmapresultImg=Bitmap.createBitmap(w,h,Bitmap.Config.RGB_565);48resultImg.setPixels(resultInt,0,w,0,0,w,h);49longperformance=System.currentTimeMillis()-current;50imgView.setImageBitmap(resultImg);51}elseif(v==btnRestore){52Bitmapimg2=((BitmapDrawable)getResources().getDrawable(53R.drawable.ic_launcher)).getBitmap();54imgView.setImageBitmap(img2);55}56}57}585960}

最佳答案由网友  AK神哪救救我吧  提供
公告: 为响应国家净网行动,部分内容已经删除,感谢网友理解。
5

分享到:

其他回答

暂无其它回答!

    推荐