android opencv开发基础:从零开始实现人眼识别与变色

1,484 阅读7分钟

写在前面:

opencv很多东西更新换代还是很快的,我一开始写这个项目的时候是参考书上写的,结果编译怎么都编不过,后来去了github上看才发现实现方式早已迭代。大家看opencv方面的代码还是以官网为主吧~

一些概念

opencv库本身是由一系列 C 函数和少量 C++ 类构成的,我们在android项目中引入opencv就离不开NDK和JNI。

NDK

Native Development Kit 在android应用中调用c或c++的工具。功能是讲.c/.cpp转换成.so文件,将.so文件和android应用一起打包成apk。

用途:

在平台之间移植其应用。 重复使用现有库,或者提供其自己的库供重复使用。 在某些情况下提高性能,特别是像游戏这种计算密集型应用。

JNI:

Java Native Interface。即java本地开发接口。本质是一种协议,用来沟通java代码和外部的本地代码。Java通过JNI来调用C/C++代码。

JNIEXPORT void JNICALL Java_ClassName_MethodName(JNIEnv *env, jobject obj){
    /*Implement Native Method Here*/}

在JNI框架中,native方法一般在单独的.c或者.cpp文件中实现。当JVM调用这些函数,就传递一个JNIEnv指针,一个jobject的指针,任何在Java方法中声明的Java参数。

Env: 指向一个结构包含了到JVM的界面,包含了所有必须的函数与JVM交互、访问Java对象。

下面函数将java字符串转化为本地字符串:

JNIEXPORT void JNICALLJava_ClassName_MethodName(JNIEnv *env, jobject obj,jstring javaString){

    //Get the native string from javaStrin
    const char *nativeString =env->GetStringUTFChars(javaString, 0);

    //Do something with the nativeString

    //DON'T FORGET THIS LINE!!!
    env ->ReleaseStringUTFChars(javaString,nativeString);
}

JNI性能缺点:

  1. 在使用JNI的过程中,可能因为某些微小的BUG,对整个JVM造成很难重现和调试的错误。
  2. 调用 JNI 方法是很笨重的操作,特别是在多次重复调用的情况下。
  3. Java 数组可能会被拷贝一份,以传递给 native 方法,执行完之后再拷贝回去. 其开销与数组的长度是线性相关的。
  4. 如果传递一个对象给方法,或者需要一个回调,那么 Native 方法可能会自己调用JVM。 访问Java对象的属性、方法和类型时,Native代码需要类似反射的东西。签名由字符串指定,通常从JVM中查询。这非常缓慢并且容易出错。

opencv

OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。

开发准备

  • kotlin + native库+自行封装的jni方法

    • 配置

      导入sdk包,在工程中加入opencv的native库,编辑Android.mk的文件内容,在gradle中添加externalNativeBuild,指定cmake的链接方式。

      配置好的项目结构
      配置好的项目结构↑

  • Android.mk

Android.mk是一个面向Android NDK构建系统描述NDK项目的GNU makefile片段。主要用来编译生成以下内容:

APK程序:一般的Android应用程序,系统级别的直接push即可。 JAVA库:JAVA类库,编译打包生成JAR文件。 C/C++应用程序:可执行的C/C++应用程序。 C/C++静态库:编译生成C/C++静态库,并打包成.a文件。 C/C++动态库:编译生成C/C++动态库,并打包成.so文件。

#定义模块当前路径(必须定义在文件开头,只需定义一次)
#利用该变量来定位源文件
# my-dir是编译系统提供的宏函数,返回当前Android.mk的路径
LOCAL_PATH := $(call my-dir)

#清空当前环境变量(LOCAL_PATH除外)
#CLEAR_VARS是编译系统提供的变量,指向特定的GNU的makefile片段,清除环境变量
#使用该语法原因是,编译系统在单次执行中会解析多个构建文件和模块定义。而LOCAL开头的变量是全局变量。所以每次都要先清除下环境变量,避免冲突
include $(CLEAR_VARS)

#当前模块名(这里会生成libhello-jni.so)
#必须唯一,不能包含空格
LOCAL_MODULE := hello-jni

#当前模块包含的源代码文件
#多个文件,空格分隔
LOCAL_SRC_FILES := hello-jni.c

#表示当前模块被编译成库的类型。静态库、共享库。
#当前为共享库
Include $(BUILD_SHARED_LIBRARY)

人眼检测

先放一张实现效果图:

绿绿的就是识别出眼睛并替换的颜色。

在这里插入图片描述

流程:

在这里插入图片描述

  1. 摄像头预览帧图像:

    使用opencv中自带的javacameraview,调用相机。在回调函数onCameraFrame中完成对预览帧图像的处理。

  2. 人脸检测:

使用级联分类器进行人脸识别。

级联分类器: 级联分类器是基于LBP特征与HAAR特征实现的,基于LBP与HAAR特征针对特定目标训练得到的分类器数据,可以保存、加载、有效的进行对象识别。

LBP特征: LBP是Local Binary Pattern(局部二值模式)的缩写,具有灰度不变性和旋转不变性等显著优点。由于该特征的简单易算性,虽然其总体效果不如Haar特征,但速度则快于Haar,所以也得到了广泛的使用。

在这里插入图片描述
LBP算子的优缺点:

  • 优点: 一定程度上消除了光照变化的问题 具有旋转不变性 纹理特征维度低,计算速度快

  • 缺点: 当光照变化不均匀时,各像素间的大小关系被破坏,对应的LBP算子也就发生了变化。 通过引入旋转不变的定义,使LBP算子更具鲁棒性。但这也使得LBP算子丢失了方向信息。

LBP用于检测的原理:

LBP算子在每个像素点都可以得到一个LBP编码,那么对一副图像提取其原始的LBP算子之后,得到的原始LBP特征仍然是”一副图片“(图片中记录的是每个像素的LBP算子值)

opencv中人脸识别过程:

  1. 计算图像的LBP特征图像。
  2. 将LBP特征图像进行分块,Opencv中默认将LBP特征图像分成8行8列64块区域
  3. 计算每块区域特征图像的直方图cell_LBPH,将直方图进行归一化,直方图大小为1∗numPatterns
  4. 将每块区域的直方图按空间顺序依次排列成一行,形成LBP特征向量,大小为1∗(numPatterns∗64)
  5. 用机器学习的方法对LBP特征向量进行训练,用来检测和识别目标。

应用:

opencv中的人脸检测是基于训练好的LBP特征级联检测器,在使用的时候通过调用相关新联好的人脸检测级联分析器数据。

    public void detect(Mat imageGray, MatOfRect faces) {
        nativeDetect(mNativeObj, imageGray.getNativeObjAddr(), faces.getNativeObjAddr());
    }


JNIEXPORT void JNICALL Java_com_example_opencvdemo_util_DetectionBasedTracker_nativeDetect
(JNIEnv * jenv, jclass, jlong thiz, jlong imageGray, jlong faces)
{
    try
    {
        vector<Rect> RectFaces;
        ((DetectorAgregator*)thiz)->tracker->process(*((Mat*)imageGray));
        ((DetectorAgregator*)thiz)->tracker->getObjects(RectFaces);
        *((Mat*)faces) = Mat(RectFaces, true);
    }
}

人眼识别:

在截取人脸区域后,取其上半部分,然后将上半部分均匀分为左右两个部分,再根据眼睛所占的上半部分的比例截取眼睛区域,完成对眼睛区域的选择标定,为下一步进行绘制做好准备。

再通过opencv自带的眼睛级联检测器实现眼睛检测,将检测到的眼睛对象子图像缓存作为模板。下一次当检测器无法检测到眼睛区域时,使用模板图像来完成对眼睛区域的匹配与box区域的绘制。

黑眼球定位:

对已知的眼睛候选区域,通过图像二值化分析,得到眼睛的轮廓区域,进而得到眼球区域。

渲染与优化

找到瞳孔/眼球区域后,进行适当的渲染。使用高斯模糊生成权重系数的方法对眼球区域进行深度渲染,然后叠加融合。

可优化点:

  • 眼睛区域选择
  • 对预览帧的灰度图像使用直方图均衡化加强对比度,提高级联分类器检测率。
  • 眼球提取,可采用基于整体和眼睛检测BOX区域二值来分析眼球。

代码我就不一一贴了,有需要源码的朋友可以留言~