Android Camera动态人脸识别+人脸检测基于OpenCV(无需OpenCVManager) - 无网不进

时间:2024-03-01 20:41:47

       最近因为需要实现了一下人脸识别,实现了这个功能之后,就想着把踩过的坑总结出来。参考过许多博客,发现主要有两种形式,一种是基于 android SDK 实现人脸检测,而另一种是利用openCvManager来实现。这两种方式都实现了动态的人脸检测,当然网上也有许多关于检测静态图片中的人脸的文章。这里我就不详细介绍了。我这里实现的功能主要是启动Camera之后,当照相机中有人脸出现时,Camera界面动态的绘制出人脸矩形框,获取到人脸框之后,Camera拍照,获取到图片,将图片传送到后台来验证人脸。这里需要区分一下人脸检测和人脸识别。人脸检测是检测camera中是否有人脸存在,如果存在的话会绘制出矩形框将人脸标志出来,而人脸识别则是根据两张人脸验证是否是一个人的概念。 
        先大概讲一下第一种实现动态人脸检测的方式.这是Camera基于Google自带的FaceDetectionListener进行人脸检测,当Camera拍摄到人脸后,会将人脸矩形绘制出来。这个具体大家可以参考这位大神的博客,http://blog.csdn.net/yanzi1225627/article/details/38098729/ 
他已经讲的很细致了。但感觉google自带检测算法在测试的时候检测人脸比较慢,而且有些机型不支持这种人脸检测接口,不过他的文章还是很具有参考价值的。那么我就在这里就具体介绍一下使用openCv实现动态人脸检测的具体方法。
 
       先提供下opencv下载地址http://opencv.org/ 我下载的是最新的3.2版本,本来用的是2.4,但会发现比较卡顿。公司一直使用的eclipse,而且demo中的工程也是eclipse项目,所以直接使用eclipse来进行调试。当然网上也有许多在As中使用的教程,大家可以去多找找。 
在我们下载sdk后解压后,进入到samples目录下会见到如下结构:

       我们直接运行example-face-detection.apk后,会发现提示要安装opencv-manager,在sdk/apk目录下,我们会发现不同种类的opencvmanager,我们根据不同的机型选择对应的manager安装,我选择的是OpenCV_3.2.0_Manager_3.20_armeabi-v7a.apk这个包,安装之后,我们打开应用后就可以发现摄像头进行动态的人脸检测了。 
运行完官方的apk后,我们开始自己进行调试。我们导入OpenCV-android-sdk/samples的face-detection工程,然后新建个工程作为Library,在我们的library中导入OpenCV-android-sdk/java中相关类和资源文件,

       然后对face-detection进行关联,这时候我的工程仍然报错,发现下面的错误:

       没有找到ndk-build.cmd.原来是没有配置ndk-build路径,配置完NDKROOT即可。这里就是右键properties-C/C++build,如下图:

       这里的ndk我下载的是android-ndk-r9d,在下载完毕之后我们直接解压就行,这里的NDKROOT就是我们解压后的ndk的路径。附带上Ndk的下载路径: 
http://dl.google.com/android/ndk/android-ndk-r9d-windows-x86.zip 
       之后又碰见如下错误:

       这是因为在jni/Android.mk中没配置opencv的Opencv.mk的根路径,同理和ndk配置方式一样,我们配置完我们自己的${OPENCV_ANDROID_SDK}就行了

       至此,我们运行官方的demo,可直接就运行出来,但我们发现总需要安装opencvmanager,很明显,有时候这是不合适的。所以接下来我介绍下不需要安装opencvmanager的方法,很简单,分下面4步: 
1.修改Android.mk前几行 为下列形式,

include $(CLEAR_VARS)
OPENCV_CAMERA_MODULES:=on
OPENCV_INSTALL_MODULES:=on
OPENCV_LIB_TYPE:=SHARED
  • 1
  • 2
  • 3
  • 4

2.打开FdActivity.java文件,在其中添加一个静态初始化块代码,

 static {
        Log.i(TAG, "OpenCV library load!");
        if (!OpenCVLoader.initDebug()) {
            Log.i(TAG, "OpenCV load not successfully");
        } else {
           System.loadLibrary("detection_based_tracker");// load other libraries
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

它是用来加载OpenCV_java库的,如果没有加回报类似于native method not found的错误 
3.注释掉onResume中的initAsync那句,让程序不去访问OpenCV Manager。

@Override
public void onResume()
    {
        super.onResume();
        if (!OpenCVLoader.initDebug()) {
            Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");
       //     OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_0_0, this, mLoaderCallback);
        } else {
            Log.d(TAG, "OpenCV library found inside package. Using it!");
            mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

4.修改FdActivity.java的OnCreate()方法,从上面的private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this)代码块中拷贝try-catch块放到OnCreate的setContentView()之后。

      try {
            // load cascade file from application resources
            InputStream is = getResources().openRawResource(R.raw.lbpcascade_frontalface);
            File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
            mCascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml");
            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();

            mJavaDetector = new CascadeClassifier(mCascadeFile.getAbsolutePath());
            if (mJavaDetector.empty()) {
                Log.e(TAG, "Failed to load cascade classifier");
                mJavaDetector = null;
            } else
                Log.i(TAG, "Loaded cascade classifier from " + mCascadeFile.getAbsolutePath());

            mNativeDetector = new DetectionBasedTracker(mCascadeFile.getAbsolutePath(), 0);

            cascadeDir.delete();

        } catch (IOException e) {
            e.printStackTrace();
            Log.e(TAG, "Failed to load cascade. Exception thrown: " + e);
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

之后运行,即可发现不用在依赖OpenCvManager,程序也可以直接运行了。 
这里上述进行的主要是camera动态监测出人脸,其实这基本上已经算完成了。现在我们集成到我们自己的工程中,通过利用百度的人脸识别,来判断人脸是否一致,从而实现人脸识别。
 
首先,我们可以在百度AI开放平台上 http://ai.baidu.com/ 创建我们自己的人脸识别应用 
![这里写图片描述](http://img.blog.csdn.net/20170712160653919? /2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMjg5MzE2MjM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
 
创建完后 我们的应用会有一个APPiD和ApI key我们利用这两个属性值可生成access_token具体的调用方式我们可参考 
https://cloud.baidu.com/doc/FACE/Face-API.html生成access_token后,当发送post请求时,附带上这个token即可,我们可以进行人脸识别了。我这里调用的是人脸注册和人脸认证接口,通过认证返回的分数值,我们可判断是否是同一个人。具体请求参数和返回参数,大家可以参考百度人脸识别的API。 
好了,现在开始创建我们的应用。在创建完工程后,我们把face-detection中的FdActivity和DetectionBasedTracker导入到我们自己的工程中,这之中的FdActivity相当于相机界面,这里注意不要改动包名,否则可能会出现找不到本地方法的错误。同时,我们要将jni和Lib armead目录复制到我们的目录里,之后我们要利用Eclipse自动编译NDK/JNI。下面简单介绍一下自动编译的方法:
 
1.将Android项目转换为C/C++项目,如下图,New -> Other -> C/C++ -> Convert to a C/C++ Project.

2. 配置NDK编译路径,Project->Properties,如下图,其中Build-Command中ANDROID_NDK为环境变量中配置的Android-NDK路径;Build-Directory为当前工程目录

3.Project->Properties,CNU C和GNU C++中配置OpenCV的链接库


配置完指后,我们需要稍微修改一下我们自己创建的opencvLibrary的内容。因为camera中出现人脸框后需要拍照功能,我们需要简单的修改OpenCvLibrary中的JavaCameraView,在这个类里面我们可以调用camera的takepicture方法。同时通过PictureCallback将我们拍的照片保存到我们指定的路径。

 

mCamera.takePicture(mShutterCallback, null, mJpegPictureCallback);
ShutterCallback mShutterCallback = new ShutterCallback() 
    {
        public void onShutter() {
        }
    };

    PictureCallback mJpegPictureCallback = new PictureCallback() 
    {
        public void onPictureTaken(byte[] data, Camera camera) {
            Log.d("hr", "拍照回调");
            Bitmap b = null;
            if (null != data) {
                b = BitmapFactory.decodeByteArray(data, 0, data.length);
                mCamera.stopPreview();
            }
            if (null != b) {
                Bitmap rotaBitmap = ImageUtil.getRotateBitmap(b, 00.0f);
                boolean bitmap = FileUtil.saveBitmap(rotaBitmap,idTag);
                if (bitmap) {
                    if (handler!=null) {
                        handler.sendEmptyMessage(2000);
                    }       
                }
            }
            mCamera.startPreview();
        }};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

在FdActivity的 onCameraFramed中 facesArray 得长度 即是人脸显示的数目

Rect[] facesArray = faces.toArray();
        for (int i = 0; i < facesArray.length; i++)
            Imgproc.rectangle(mRgba, facesArray[i].tl(), facesArray[i].br(), FACE_RECT_COLOR, 3);
  • 1
  • 2
  • 3

当相机捕捉到人脸后,我们可以通过handle自动拍照也可以手动拍照,这就看具体的需求了。保存图片之后,我们调用百度的接口,根据返回值来判断人体的相似度。

            url = new URL(urlPath);
            Bitmap bmp = FileUtil.getValidateBitmap();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            bmp.compress(Bitmap.CompressFormat.JPEG, 100, baos);
            try {
                baos.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            byte[] buffer = baos.toByteArray();
            // 将图片的字节流数据加密成base64字符输出
            String photo = Base64.encodeToString(buffer, 0, buffer.length,Base64.DEFAULT);
            HashMap<String, String> map=new HashMap<String, String>();
            map.put("uid", uid);
            map.put("group_id", groupid);
            map.put("image", photo);
            map.put("ext_fields", "faceliveness");
            String str=HttpClientUtil.doPost(urlPath, map);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

获取验证和注册的方式代码基本上一样,就是发送普通的网络请求,我们根据返回值可以判断出人脸是否一致。至此整个人脸验证流程就算结束了。我这里就光展示一下动态检测人脸的效果 
这里写图片描述 
当然还有认证的流程,因为感觉难点主要在动态识别人脸这块,所以调用百度的接口,实现人脸识别的过程就不再详细介绍了。当然 ,还有一些小问题,当竖屏时,检测不准确或者Camera界面没有全屏, 或者是前置摄像头的开启。大家有兴趣的话可以去研究一下。

posted on 2017-11-23 13:48  无网不进  阅读(2661)  评论(1编辑  收藏  举报