基于Java的Android OpenCV安装配置及人脸识别示例

发布时间:2023-06-23 14:30

1. OpenCV安装配置

1.1 开发环境

  • Android Studio 3.5.2
  • Opencv-4.3.0-android-sdk

1.2 安装OpenCV SDK

opencv的官网中下载android sdk,我这边下载的opencv-4.3.0
\"基于Java的Android
将opencv-4.3.0-sdk解压到本地,解压后文件主要包括:
\"基于Java的Android

1.3 创建android项目

选择带有c++开发环境的Native C++模板,创建项目
\"基于Java的Android
C++Standard选择 c++14
创建好项目如下:
\"基于Java的Android
其中cpp文件夹下包括: include,CMakeLists.txt,native-lib文件
点击File -> Setting -> Android SDK->SDK Tools,勾选好NDK,CMAKE
\"基于Java的Android
点击Show Package Details可选择详细的版本进行安装
\"基于Java的Android
如果NDK报错,可能是版本太高了。选择File -> Project Structure,配置低版本的NDK的路径,这里选择ndk-r16b
\"基于Java的Android

1.4 添加OpenCV开发包支持

通过选择File -> New -> import Module
\"基于Java的Android

选择opencv-android-sdk/sdk/java,选择sdk下面的java目录。Module name命名为OpenCVLibrary430,选择Next默认,Finsh.
\"基于Java的Android
\"基于Java的Android
创建好后,成功导入了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前面变成了库文件的图标。
\"基于Java的Android

1.5 创建JniLibs

选择app -> src ->main 在main文件中右键选择New -> Folder ->JNI Folder,创建Jni文件
\"基于Java的Android
\"基于Java的Android
将opencv-android-sdk下面的sdk ->native ->libs中的文件拷贝到JNI文件中。

然后选中File -> Project Structure ->Dependencies,点击app选择+号,添加Module Dependency,将OpenCVLibrary430库添加到app中

\"基于Java的Android将OpenCVLibrary430中的compileSdkVersionbuildToolVersion的版本设置与app中build.gradle的compileSdkVersionbuildToolVersion的版本一致。可以在Project Structure中的Modules中进行设置。
\"基于Java的Android
\"基于Java的Android
查看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\"基于Java的Android
并且,在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文件下的 .cxxbuild文件重新运行即可。
\"基于Java的Android

2. 人脸识别项目

2.1 页面布局

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>

界面效果:
\"基于Java的Android

2.2 存放模型资源文件

创建res资源文件下创建raw文件用来存放人脸识别的lbpcascade_frontalface.xml模型文件。
方法:res ->New ->Android Resource Directory
\"基于Java的Android

设置:Directory name为raw, Resource type 下拉选择raw。然后将模型资源文件lbpcascade_frontalface.xml粘贴到该文件下。
\"基于Java的Android
\"基于Java的Android

2.3 MainActivity.java

代码

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();
}

  • android 中raw文件下面的模型文件不支持直接读取,我们需要把模型保存到一个缓存目录,然后再从缓存目录加载模。
  • 片的地址就包括 MediaStore.Images.Media.INTERNAL_CONTENT_URIMediaStore.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. 知道了这个地址,基本上就可以操作这张图片的所有信息了。参考
  • startActivityForResult主要用来从FirstActivity跳转到SecondActivity然后返回FirstActivity并且获取从SecondActivity传回来的参数。参考

2.4 人脸识别效果

点击LOAD按钮导入本地一张带有人脸的图片,点击DETECT按钮就可以识别出人脸,效果如下:
\"基于Java的Android
该项目github中的源码地址为: https://github.com/yuanxinshui/TFLiteClassificationPic/tree/main/OpenCVFaceDetect

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号