美文网首页
OpenCV人脸校验,人脸识别

OpenCV人脸校验,人脸识别

作者: 小小混世魔王 | 来源:发表于2019-04-23 13:01 被阅读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 ,从识别的效果来看识别率不是太准高,这个跟训练样本的数据量有关,程序的效果还是能够达到人脸的识别的。

    相关文章

      网友评论

          本文标题:OpenCV人脸校验,人脸识别

          本文链接:https://www.haomeiwen.com/subject/zgxoiqtx.html