OpenCV人脸校验,人脸识别

2019-04-23  本文已影响0人  小小混世魔王

年初来深圳的时候,好久都没有体验坐高铁我,看到了以前的人工检票换成了机器检票,让我对人脸识别的技术产生了极大的好奇心,于是就想着去了解人脸识别的技术。高铁站的智能检票就是对比你的人与身份证是否是同一个人(从技术上来看是属于人脸校验的范畴)在网上找了很久的高铁检票的实现机制 ,它的大概流程可以分为这样的,
1)证件读取 (拉取公安系统 个人信息 姓名 身份证号 人脸图像)
2)车票验证 (是否购票 购票信息 站点)
3)人脸检测 (摄像头,检测人脸位置,抓取图像)
4)特征提取 (抓取摄像头中的人脸 ,提取特征 特征值 特征向量)
5)基于人 证 的对比识别 (公安拉取图像信息与摄像头采取图像对比)
人脸校验(人脸校验是要给你两张脸比较是不是同一个人)人脸识别(人脸识别是给你一张脸和一个人脸库比较这张脸是库里的谁)
接下来我们在Android中实现,人脸识别功能。具体用到了OpenCV 和它的扩展模块的so库编译,这里不具体去讲了,以后有时间也会陆续去写一些关于Linux内核的文章,里面就会包含有so库的编译知识,人脸识别要用到这个库https://github.com/opencv/opencv_contrib,至于摄像头采取数据转化为Mat这里我采用了OpenCV中的代码做了一些修改。

opencv安卓代码.png
接下来我们看看具体是使用OpenCV Android Sdk 代码
public class MainActivity extends AppCompatActivity implements CameraBridgeViewBase.CvCameraViewListener, View.OnClickListener {
    JavaCameraView cameraView;
  //主要的逻辑写在这个方法里面
    ......

    @Override
    public Mat onCameraFrame(Mat inputFrame) {
        //输入的没
        if (this.getResources().getConfiguration().orientation
                == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
                && cameraView.getCameraIndex() == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            Core.rotate(inputFrame, inputFrame, Core.ROTATE_90_COUNTERCLOCKWISE);
            BitmapUtils.remap(inputFrame, inputFrame);

        } else if (this.getResources().getConfiguration().orientation
                == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
                && cameraView.getCameraIndex() == Camera.CameraInfo.CAMERA_FACING_BACK) {
            Core.rotate(inputFrame, inputFrame, Core.ROTATE_90_CLOCKWISE); //90
        }

        if(isStart){
            if(isDetection) {
                //人脸识别  脸部数据只加载一次即可
              //  BitmapUtils.loadPattern("/storage/emulated/0/face_pattern"+faceId+".xml");
                BitmapUtils.faceDetection(inputFrame);
            }else {
                //数据采集
                BitmapUtils.collection(inputFrame,faceId);
            }

        }

        return inputFrame;
    }
    ......

这里我们要解决的两个问题 一个是摄像机采集的数据不能够全屏,二个是前置摄像头画面左右互换了。至于全屏的问题留给大家自己去修改OpenCV 安卓sdk 的源码,左右调换画面怎么去调整呢,有两种方法:方法一:修改OpenCV源代码重新编译so库;方法二:自己写方法去解决。我是利用OpenCV 的重映射去把画面镜像过来的
BitmapUtils.remap(inputFrame, inputFrame);

/**
 * 镜像  调整画面
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_facedetection_BitmapUtils_nRemap(JNIEnv *env, jclass type, jlong nativeObj,
                                                    jlong dstnativeObj) {

    Mat *src = reinterpret_cast<Mat *>(nativeObj);
    Mat *dst = reinterpret_cast<Mat *>(dstnativeObj);


    Mat map_x(src->size(), CV_32FC1);
    Mat map_y(src->size(), CV_32FC1);
    for (int row = 0; row < src->rows; ++row) {
        for (int col = 0; col < src->cols; ++col) {
            map_x.at<float>(row, col) = src->cols - col;
            map_y.at<float>(row, col) = row;

        }
    }


    remap(*src, *dst, map_x, map_y, INTER_AREA);
}

准备工作做好,并且相机能够正常的显示采集数据。要实现人脸的识别就来先了解一下人脸的检测。从摄像头中采取帧数据传入ndk 交个OpenCV做处理,主要是级联分类器的使用,
加载级联分类器:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       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();


            BitmapUtils.load(mCascadeFile.getAbsolutePath());


        } catch (IOException e) {
            e.printStackTrace();
        }

    }
CascadeClassifier cascadeClassifier;

/**
 * 加载   lbpcascade_frontalface.xml
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_facedetection_BitmapUtils_load(JNIEnv *env, jclass type, jstring absolutePath_) {
    const char *filePath = env->GetStringUTFChars(absolutePath_, 0);

    cascadeClassifier.load(filePath);

    env->ReleaseStringUTFChars(absolutePath_, filePath);
}

人脸检测,框出人脸

/**
 * 框出人脸区域
 */
int frame_face(Mat *src, Rect &rect, bool isDrawRect) {
    Mat gary;
    //将输入的帧图像转为灰度图 ,(转灰度)减小算法复杂度提升处理速度
    cvtColor(*src, gary, COLOR_BGRA2GRAY);

    vector<Rect> faces;
    //检测人脸
    cascadeClassifier.detectMultiScale(gary, faces, 1.1, 3, 0, Size(src->cols / 3, src->rows / 3),
                                       src->size());

    if (faces.size() <= 0) {
        return 0;
    }

    Rect face = faces[0];
    if (isDrawRect) {
      //将人脸区域画上矩形
        rectangle(*src, face, Scalar(255, 0, 0, 255), 5, LINE_AA);
    }

    rect = face;
    return 1;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_facedetection_BitmapUtils_nFaceDetection(JNIEnv *env, jclass type,
                                                            jlong nativeMat) {

    Mat *src = reinterpret_cast<Mat *>(nativeMat);
    Rect face_rect;
    //检测并且框出人脸
    frame_face(src, face_rect, true)
}
cascadeClassifier.detectMultiScale(gary, faces, 1.1, 3, 0, Size(src->cols / 3, src->rows / 3),src->size());重点关注一下检测代码。gary输入图像,faces输出图像,第三个参数缩放比,内部会检多次检测是否是人脸,他会把图像缩放去多次比较是否是人脸,提高检测的准确度,第四个参数是检测的次数 ,这里是3次确认人脸,就确定他是人脸,参数五指定 Haar,后面指定最小脸和最大脸。具体效果 人脸检测.png

数据采集,样本的训练:

对于人脸的数据我们保存的并不是人脸的图片,而是人脸的特征。有特征脸方法 LDA线性判别分析等。特征脸方法大概流程是 读取训练数据,计算平均脸,计算协方差矩阵,计算特征值,特征矩阵,PCA降维,子控件模型。PCA降维有效的减少了数据量。准备ORL人脸数据库样本用来训练 人脸数据.png

里面包含了40个人的人脸,接下就利用 EigenFaceRecognizer 对人脸数据的训练及其识别

Ptr<BasicFaceRecognizer> model = EigenFaceRecognizer::create();

vector<Mat> faces_c;
vector<int> labels;

//添加人到集合中 每个人的数据对应一个label(标签) 识别的时候返回改标签
extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_facedetection_BitmapUtils_initCollection(JNIEnv *env, jclass type) {
    for (int i = 1; i <= 10; ++i) {
        for (int j = 1; j <= 5; ++j) {
            Mat face = imread(format("/storage/emulated/0/orl_faces/s%d/%d.pgm", i, j), 0);
            if (!face.data) {
                LOGE("不能读取文件");
                continue;
            }
            // 确保大小一致
            resize(face, face, Size(128, 128));
            faces_c.push_back(face);
            labels.push_back(i);
        }
    }
}



/**
 * 从摄像机采集 脸部数据
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_facedetection_BitmapUtils_nCollection(JNIEnv *env, jclass type,
                                                         jlong nativeMat, jint labe) {
    Mat *src = reinterpret_cast<Mat *>(nativeMat);
    m_label = labe;
    Rect rect;
    if (frame_face(src, rect, false)) {
        //克隆出脸部数据 其余的丢弃
        Mat face = (*src)(rect).clone();
        
        cvtColor(face,face,COLOR_BGRA2GRAY);
        //保证图片大小 
        resize(face, face, Size(128, 128));
        faces_c.push_back(face);
        labels.push_back(labe);

    }

}

/**
 * 采集完毕  训练模板
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_facedetection_BitmapUtils_collectionComplete(JNIEnv *env, jclass type) {

    LOGE("%d---%d", faces_c.size(), labels.size());
    model->train(faces_c, labels);
    char file_name[100];
    sprintf(file_name, "/storage/emulated/0/face_pattern%d.xml", m_label);
    LOGE("%s", file_name);

    model->save(file_name);

    labels.clear();
    faces_c.clear();
    LOGE("人脸采集完毕");

}

上面的数据采集,样本的训练包括下面识别过程最好放在子线程,这个步骤比较耗时。

样本数据的加载,人脸的识别:

在识别的时候,要去加载我们刚才采集的数据,并将摄像机的帧传入ndk层去时时识别

//人脸识别
BitmapUtils.loadPattern("/storage/emulated/0/face_pattern"+faceId+".xml");
BitmapUtils.faceDetection(inputFrame);
/**
 * 加载模板数据
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_facedetection_BitmapUtils_loadPattern(JNIEnv *env, jclass type,
                                                         jstring pattern_) {
    const char *pattern = env->GetStringUTFChars(pattern_, 0);

    FileStorage fs(pattern, FileStorage::READ);

    const FileNode fn = fs.getFirstTopLevelNode();

    model->read(fn);

    env->ReleaseStringUTFChars(pattern_, pattern);
}

/**
 * 人脸识别
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_facedetection_BitmapUtils_nFaceDetection(JNIEnv *env, jclass type,
                                                            jlong nativeMat) {

    Mat *src = reinterpret_cast<Mat *>(nativeMat);
    Rect face_rect;
    if (frame_face(src, face_rect, false)) {

        Mat face = (*src)(face_rect).clone();
        resize(*src, face, Size(128, 128));
        cvtColor(face, face, COLOR_BGRA2GRAY);
        //返回的 label 对应你存储特征时的label  标签  一个label标签就对应一个人  
        int label = model->predict(face);
        LOGE("label = %d",label);
   
    }

}

在网上拿angelababy 图片,利用摄像头采集到她的样本数据,进行训练,下面是识别的效果。


识别.gif

angelababy几乎都能够识别出来,换到其他人就不认识了(没有采集他们的样本数据所以不认识),但是有时会把其他人识别成angelababy ,从识别的效果来看识别率不是太准高,这个跟训练样本的数据量有关,程序的效果还是能够达到人脸的识别的。

上一篇下一篇

猜你喜欢

热点阅读