opencv的官网中下载android sdk,我这边下载的opencv-4.3.0
将opencv-4.3.0-sdk解压到本地,解压后文件主要包括:
选择带有c++开发环境的Native C++
模板,创建项目
C++Standard选择 c++14
创建好项目如下:
其中cpp文件夹下包括: include
,CMakeLists.txt
,native-lib
文件
点击File -> Setting -> Android SDK->SDK Tools,勾选好NDK,CMAKE
点击Show Package Details
可选择详细的版本进行安装
如果NDK报错,可能是版本太高了。选择File -> Project Structure
,配置低版本的NDK的路径,这里选择ndk-r16b
,
通过选择File -> New -> import Module
选择opencv-android-sdk/sdk/java,选择sdk下面的java目录。Module name
命名为OpenCVLibrary430
,选择Next默认,Finsh.
创建好后,成功导入了OpenCVLibrary430
库。
将项目切换为Project
模式,选择OpenCV Library430 ->build gradle
将 build gradle第一行的
apply plugin: 'com.android.application'
改为:
apply plugin: 'com.android.library'
删除defaultConfig 中的applicationId
。
选择File -> Project Structure ->Dependencies
可以看到OpenCVLibrary430前面变成了库文件的图标。
选择app -> src ->main
在main文件中右键选择New -> Folder ->JNI Folder
,创建Jni文件
将opencv-android-sdk下面的sdk ->native ->libs
中的文件拷贝到JNI文件中。
然后选中File -> Project Structure ->Dependencies
,点击app
选择+
号,添加Module Dependency
,将OpenCVLibrary430
库添加到app中
将OpenCVLibrary430中的compileSdkVersion
和buildToolVersion
的版本设置与app中build.gradle的compileSdkVersion
和buildToolVersion
的版本一致。可以在Project Structure
中的Modules中进行设置。
查看app所属的build.gradle,将
sourceSets { main { jni.srcDirs = ['src/main/jni', 'src/main/jniLibs'] } }
改为
sourceSets { main { jniLibs.srcDirs = [ 'src/main/jniLibs'] } }
注: 如果提示OpenCV error: Cannot load info library for OpenCV
,说明系统找不到OpenCV的so文件。确认是否把jniLibs.srcDirs
错误写成了jni.srcDirs
。因为我们配置的是jniLibs,所以需要写成jniLibs.srcDirs
查看dependencies
中是否进入了OpenCVLibrary430
库
并且,在defaultCofig中的externalNativeBuild配置cmake参数
externalNativeBuild {
cmake {
cppFlags "-std=c++14 -frtti -fexceptions"
abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
arguments 'DANDROID_STL=c++_shared'
}
}
环境配置好后,运行程序,如果程序运行没出错,说明已经正确添加好程序。
注:如果提示ERROR: Cause: exception while building Json D:\works\Android_cv\project\OpenCVCplus\app\.cxx\cmake\debug\x86\CMakeFiles\CMakeTmp\.ninja_deps: 另一个程序正在使用此文件,进程无法访问
。通过删除app文件下的 .cxx
和build
文件重新运行即可。
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:layout_width="100dp"
android:layout_height="40dp"
android:id="@+id/loadButton"
android:text="load"/>
<Button
android:layout_width="100dp"
android:layout_height="40dp"
android:layout_below="@id/loadButton"
android:id="@+id/processButton"
android:text="Detect Face"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:id="@+id/imageView1"
android:layout_below="@+id/processButton"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:id="@+id/imageView2"
android:layout_below="@+id/imageView1"/>
RelativeLayout>
创建res资源文件下创建raw文件用来存放人脸识别的lbpcascade_frontalface.xml模型文件。
方法:res ->New ->Android Resource Directory
设置:Directory name为raw
, Resource type 下拉选择raw。然后将模型资源文件lbpcascade_frontalface.xml粘贴到该文件下。
代码
package com.example.myopencvdemo;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfRect;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
public class MainActivity extends AppCompatActivity {
Button loadButton;
Button detectFaceButton;
ImageView imageView1;
ImageView imageView2;
String imagePathLoaded;
Mat matrix;
Bitmap bitmap;
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// OpenCVLoader.initDebug();
ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},200);
loadButton=findViewById(R.id.loadButton);
detectFaceButton=findViewById(R.id.processButton);
imageView1=findViewById(R.id.imageView1);
imageView2=findViewById(R.id.imageView2);
loadButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent=new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI);
//打开一个带有返回值的交互界面
startActivityForResult(intent,300); //300 requestCode设置为300
}
});
detectFaceButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
detectFace();
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data){
super.onActivityResult(requestCode,resultCode,data);
if(requestCode == 300){
if(resultCode == RESULT_OK){
Uri uri=data.getData();
imageView1.setImageURI(uri);
imagePathLoaded=getPathFromURI(MainActivity.this,uri);
try {
FileInputStream fis = new FileInputStream(imagePathLoaded);
bitmap=BitmapFactory.decodeStream(fis);
//imageView1.setImageBitmap(bitmap);
} catch (Exception e) {
e.printStackTrace();
}
// matrix= Imgcodecs.imread(imagePathLoaded);
}
}
}
// 根据相册的Uri获取图片的路径
public static String getPathFromURI(Context context, Uri uri) {
String result;
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
if (cursor == null) {
result = uri.getPath();
} else {
cursor.moveToFirst();
int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
result = cursor.getString(idx);
cursor.close();
}
return result;
}
public void detectFace(){
if(bitmap == null)
{
return ;
}
Bitmap bit=bitmap.copy(Bitmap.Config.ARGB_8888,false);
Mat src=new Mat(bit.getHeight(),bit.getWidth(), CvType.CV_8UC(3));
Utils.bitmapToMat(bit,src);
Mat dst=src.clone();
Imgproc.cvtColor(src,dst, Imgproc.COLOR_BGR2GRAY);
Mat matrix =dst.clone();
CascadeClassifier cascadeClassifier=new CascadeClassifier();
try {
//Copy the resource into a temp file so OpenCV can load it
InputStream is=this.getResources().openRawResource(R.raw.lbpcascade_frontalface);
//在内存中创建名为cascade的目录 Context.MODE_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问
File cascadeDir=getDir("cascade", Context.MODE_PRIVATE);
//android 中raw文件下面的模型文件不支持直接读取,我们需要把模型保存到一个缓存目录,然后再从缓存目录加载模。
File mCascadeFile = new File(cascadeDir,"lbpcascade_frontalface.xml");
// 创建一个向指定 File 对象表示的文件中写入数据的文件输出流
FileOutputStream os=new FileOutputStream((mCascadeFile));
byte[] buffer=new byte[4096];
int bytesRead;
while((bytesRead = is.read(buffer))!=-1){
os.write(buffer,0,bytesRead);
}
is.close();
os.close();
//Load the cascade classifer
cascadeClassifier=new CascadeClassifier(mCascadeFile.getAbsolutePath());
}catch (Exception e){
Log.e("OpenCVActivity","Error loading casce",e);
}
MatOfRect faceArray = new MatOfRect();
cascadeClassifier.detectMultiScale(matrix,faceArray);
int numFaces=faceArray.toArray().length;
for(Rect face:faceArray.toArray()){
Imgproc.rectangle(src,new Point(face.x,face.y),new Point(face.x+face.width,face.y+face.height),new Scalar(0,0,255),3);
}
Mat finalMatrix = src.clone();
Bitmap bitmap =Bitmap.createBitmap(finalMatrix.cols(),finalMatrix.rows(),Bitmap.Config.ARGB_8888);
Utils.matToBitmap(finalMatrix,bitmap);
imageView2.setImageBitmap(bitmap);
Toast.makeText(getApplicationContext(),numFaces+ " faces found!",Toast.LENGTH_SHORT).show();
}
//需要在onResume方法中验证是否可以使用内置的OpenCV库
@Override
protected void onResume() {
super.onResume();
if(!OpenCVLoader.initDebug()){
Log.i("cv","未收到内置OpenCV库,使用OpenCV Manager进行初始化。");
// OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_4_0, this, mLoaderCallback);
}else
{
Log.i("cv","发现了内置OpenCV库,使用OpenCV 库进行初始化。");
// mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
}
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
MediaStore.Images.Media.INTERNAL_CONTENT_URI
和 MediaStore.Images.Media.EXTERNAL_CONTENT_URI
两个基础地址。其值分别是 content://media/internal/images/media 和 content://media/external/images/media ,对应内部库和外部库地址。每一张图片的地址基本上是上面的基础URL地址下加上图片的内部ID。打个比方一张存储卡上的图片ID为2,其对应的Uri地址就是 content://media/external/images/media/2. 知道了这个地址,基本上就可以操作这张图片的所有信息了。参考startActivityForResul
t主要用来从FirstActivity
跳转到SecondActivity
然后返回FirstActivity并且获取从SecondActivity传回来的参数。参考点击LOAD
按钮导入本地一张带有人脸的图片,点击DETECT
按钮就可以识别出人脸,效果如下:
该项目github中的源码地址为: https://github.com/yuanxinshui/TFLiteClassificationPic/tree/main/OpenCVFaceDetect