基于Opencv3和QT实现人脸识别

前言:

    什么是OpenCV?可能还有人不清楚吧,简单地说,OpenCV——Open Source Computer Vision Library,即开源计算机视觉库,它是基于C语言和部分C++语言来开发,可用于计算机视觉、图像处理以及模式识别和跟踪。

  由于网上有很多Opencv源码编译的教程,这里我就直接拿编译好的Opencv在QT Create使用,首先先强调一下我使用的环境,针对的不同环境选择的版本也不同

我的电脑配置是Win10、qt-opensource-windows-x86-5.12.0,选择的opencv是64位的opencv3.4.4

一、Opencv的安装及使用

 1、将编译好的Opecv库中的bin添加到环境变量中

  将opencv的bin目录找到 :D:\opencv64\x64\mingw\bin

【P.S以上内容根据安装的Opencv库的路径,自行修改】

点开电脑的属性——>高级系统变量——>环境变量——>系统变量的Path——>点击新建——>将bin的目录添加进去

记住一定要点击确定,完成后,建议重启电脑

2、在QT项目中配置OPencv

创建一个Qt桌面项目;

在.pro文件的底部添加

 在main.cpp的代码

如果图片正常显示出来,那么恭喜你,opencv库在QT配置成功

 二、利用Opencv实现人脸识别

Opecv在QT中配置成功,接下来我们就来实现人脸识别,人脸识别分三个步骤:1、图片采集和预处理   2、训练模型  3、将训练好的模型进行人脸识别

1、图片的采集和预处理

(1)下载数据集

    本次用的数据集是opencv给出的教程里面的第一个数据集:The AT&T Facedatabase。又称ORL人脸数据库,40个人,每人10张照片。照片在不同时间、不同光照、不同表情(睁眼闭眼、笑或者不笑)、不同人脸细节(戴眼镜或者不戴眼镜)下采集。所有的图像都在一个黑暗均匀的背景下采集的,正面竖直人脸(有些有有轻微旋转)。

下载下来的文件名是 att_faces  ,里面一共有40个文件夹,每个文件夹里面有10张图片。且每张图片都进行了预处理。

(2)采集自己的图片,对图片进行预处理

   想要识别自己,单有别人的数据集还是不行的,还需要自己人脸的照片才行。这就需要我们收采集自己的照片,对采集的照片进行预处理,然后和上面的那个数据集一起来训练模型

使用Opencv官方训练好的人脸检测分类器(haarcascade_frontalface_alt2.xml)对采集的图片进行预处理。这个人脸识别分类器的路径是:D:\opencv64\etc\haarcascades\haarcascade_frontalface_alt2.xml,将此文件拷贝到工程目录文件下。

源代码:

#include <iostream>
#include <opencv2\opencv.hpp>
#include <vector>
#include<stdio.h>
using namespace std;
using namespace cv;
int main()
{
    CascadeClassifier cascada;
    //将opencv官方训练好的人脸识别分类器拷贝到自己的工程目录中
    cascada.load("F:\\video\\pic\\haarcascade_frontalface_alt2.xml");
    VideoCapture cap(0);  //0表示电脑自带的,如果用一个外接摄像头,将0变成1
    Mat frame, myFace;
    int pic_num = 1;
    while (1) {
        //摄像头读图像
        cap >> frame;
        vector<Rect> faces;//vector容器存检测到的faces
        Mat frame_gray;
        
        cvtColor(frame, frame_gray, COLOR_BGR2GRAY);//转灰度化,减少运算
        
        cascada.detectMultiScale(frame_gray, faces, 1.1, 4, CV_HAAR_DO_ROUGH_SEARCH, Size(70, 70), Size(1000, 1000));
        printf("检测到人脸个数:%d\n", faces.size());
       
        //识别到的脸用矩形圈出
        for (int i = 0; i < faces.size(); i++)
        {
            rectangle(frame, faces[i], Scalar(255, 0, 0), 2, 8, 0);
        }
        //当只有一个人脸时,开始拍照
        if (faces.size() == 1)
        {
            Mat faceROI = frame_gray(faces[0]);//在灰度图中将圈出的脸所在区域裁剪出
            //cout << faces[0].x << endl;//测试下face[0].x
            resize(faceROI, myFace, Size(92, 112));//将兴趣域size为92*112
            putText(frame, to_string(pic_num), faces[0].tl(), 3, 1.2, (0, 0, 225), 2, 0);//在 faces[0].tl()的左上角上面写序号
            string filename = format("F:\\video\\%d.jpg", pic_num); //图片的存放位置,frmat的用法跟QString差不对
            imwrite(filename, myFace);//存在当前目录下
            imshow(filename, myFace);//显示下size后的脸
            waitKey(500);//等待500us
            destroyWindow(filename);//:销毁指定的窗口
            pic_num++;//序号加1
            if (pic_num == 11)
            {
                return 0;//当序号为11时退出循环,一共拍10张照片
            }
        }
        int c = waitKey(10);
        if ((char)c == 27) { break; } //10us内输入esc则退出循环
        imshow("frame", frame);//显示视频流
        waitKey(100);//等待100us
    }
    return 0;

}

至此,我们就得到和ORL人脸数据库人脸大小一致的自己的人脸数据集(10张图片)。然后我们把自己的作为第41个人,在我们下载的人脸文件夹(att_faces)下建立一个s41的子文件夹,把自己的人脸数据放进去。就成了这样下面这样,最后一个文件夹里面是我自己的头像照片

2、训练模型 

opecv3人脸识别的坑

opencv3的人脸识别库等contrib模块已经不再内置了。所以要想使用这个功能的话就必须下载contrib模块https://github.com/opencv/opencv_contrib

下载完成后,解压找到\modules\face   然后include里面的就是opencv2,打开后就是face和face.hpp头文件,src里面是源文件

将include下的opencv2里面的face文件夹face.hpp,以及src 导入到训练模型工程中(训练模型项目源码在下面),这时候运行的话会出现一系列问题,主要是文件中的包含关系的问题,改一下就行了,下面是我训练模型的源码,源码中已经改好了文件中的包含关系

                  链接:https://pan.baidu.com/s/1sR_voL2cD05y_1vdNObVug 
                  提取码:v01i 

有了源码后还是需要往下看的,不然里面有很多文件不明白是怎么生成的

(1)csv文件的生成

数据集现在准备好了,我们写人脸模型训练程序的时候需要读取人脸和人脸对应的标签,直接读取显然是低效的,所以我们用csv文件读取。csv文件中包含两方面的内容,一是每一张图片的路径,二是每一个人脸对应的标签,幸好opencv教程里面为我们提供了自动生成csv文件的脚本(create_csv.py)。不然的话自己制作一个有410数据对的csv格式的数据还是挺费事的。脚本就是刚刚在github的下载的文件中,路径为:opencv_contrib-4.1.1\opencv_contrib-4.1.1\modules\face\samples\etc\create_csv.py

打开后是我们修改一下这个代码,BASE_PATH手动的改成自己的数据集路径,改完后代码如下

#!/usr/bin/env python

import sys
import os.path

# This is a tiny script to help you creating a CSV file from a face
# database with a similar hierarchie:
#
#  philipp@mango:~/facerec/data/at$ tree
#  .
#  |-- README
#  |-- s1
#  |   |-- 1.pgm
#  |   |-- ...
#  |   |-- 10.pgm
#  |-- s2
#  |   |-- 1.pgm
#  |   |-- ...
#  |   |-- 10.pgm
#  ...
#  |-- s40
#  |   |-- 1.pgm
#  |   |-- ...
#  |   |-- 10.pgm
#

if __name__ == "__main__":

    #if len(sys.argv) != 2:
    #   print("usage: create_csv <base_path>")
    #   sys.exit(1)

    BASE_PATH = "F:/video/att_faces"   //数据集的路径
    SEPARATOR = ";"
    fh = open("F:/video/att_faces/at.txt",'w')   //生成csv格式数据的路径
    label = 0
    for dirname, dirnames, filenames in os.walk(BASE_PATH):
        for subdirname in dirnames:
            subject_path = os.path.join(dirname, subdirname)
            for filename in os.listdir(subject_path):
                abs_path = "%s/%s" % (subject_path, filename)
                print("%s%s%d" % (abs_path, SEPARATOR, label))
                fh.write(abs_path)
                fh.write(SEPARATOR)
                fh.write(str(label))
                fh.write("\n")
            label = label + 1
    fh.close()

(2)训练模型

至此,前期准备都做好了,接下来就是训练模型了,训练模型用到了opencv的Facerecognizer类。opencv中所有的人脸识别模型都是来源于这个类

FaceRecognizer这个类目前包含三种人脸识别方法:基于PCA变换的人脸识别(EigenFaceRecognizer)、基于Fisher变换的人脸识别(FisherFaceRecognizer)、基于局部二值模式的人脸识别(LBPHFaceRecognizer)。对于像我这样的人脸识别初学者,对人脸识别理论了解得不是很透彻,但并不影响对函数的使用,接下来就分别训练这三种人脸模型。这个时候就能体现Facerecognizer类的强大了。因为每一种模型的训练只需要三行代码

 Ptr<face::BasicFaceRecognizer> model = face::EigenFaceRecognizer::create();
 model->train(images, labels);
 model->save("MyFacePCAModel.xml");//保存路径可自己设置,但注意用“\\”

 Ptr<face::BasicFaceRecognizer> model1 = face::FisherFaceRecognizer::create();
 model1->train(images, labels);
 model1->save("MyFaceFisherModel.xml");

 Ptr<face::LBPHFaceRecognizer> model2 = face::LBPHFaceRecognizer::create();
 model2->train(images, labels);
 model2->save("MyFaceLBPHModel.xml");


训练模型的源代码 

每个函数的作用都写再代码上,大家只需要合并这三个函数即可成功训练模型

#include <iostream>
#include "face.hpp"    //添加到工程目录的
#include<opencv2\core.hpp>
#include<opencv2\highgui.hpp>
#include<opencv2\imgproc.hpp>
#include <math.h>
#include <fstream>
#include <sstream>
using namespace std;
using namespace cv;
static Mat norm_0_255(InputArray _src) {
    Mat src = _src.getMat();
    // 创建和返回一个归一化后的图像矩阵:
    Mat dst;
    switch (src.channels()) {
    case 1:
        cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC1);
        break;
    case 3:
        cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC3);
        break;
    default:
        src.copyTo(dst);
        break;
    }
    return dst;
}
//使用CSV文件去读图像和标签,主要使用stringstream和getline方法
static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') {
    ifstream file(filename.c_str(), ifstream::in);
    if (!file)
    {
        string error_message = "No valid input file was given, please check the given filename.";
        CV_Error(CV_StsBadArg, error_message);
    }
    string line, path, classlabel;
    while (getline(file, line)) //从文本文件中读取一行字符,未指定限定符默认限定符为“/n”
    {
        stringstream liness(line);//这里采用stringstream主要作用是做字符串的分割
        getline(liness, path, separator);//读入图片文件路径以分好作为限定符
        getline(liness, classlabel);//读入图片标签,默认限定符
        if (!path.empty() && !classlabel.empty()) //如果读取成功,则将图片和对应标签压入对应容器中
        {
            images.push_back(imread(path, 0));
            labels.push_back(atoi(classlabel.c_str()));
        }
    }
}
int main()
{
    //读取你的CSV文件路径.
    string fn_csv = "F:\\video\\ccc\\at.txt";

    // 2个容器来存放图像数据和对应的标签
    vector<Mat> images;
    vector<int> labels;
    // 读取数据. 如果文件不合法就会出错
    // 输入的文件名已经有了.
    try
    {
        read_csv(fn_csv, images, labels); //从csv文件中批量读取训练数据
    }
    catch (cv::Exception& e)
    {
        cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl;
        // 文件有问题,我们啥也做不了了,退出了
        exit(1);
    }
    // 如果没有读取到足够图片,也退出.
    if (images.size() <= 1) {
        string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!";
        CV_Error(CV_StsError, error_message);
    }

    for (int i = 0; i < images.size(); i++)
    {
        //cout<<images.size();
        if (images[i].size() != Size(92, 112))
        {
            cout << i << endl;
            cout << images[i].size() << endl;
        }

    }
    // 下面的几行代码仅仅是从你的数据集中移除最后一张图片,作为测试图片
    //[gm:自然这里需要根据自己的需要修改,他这里简化了很多问题]
    Mat testSample = images[images.size() - 1];
    int testLabel = labels[labels.size() - 1];
    images.pop_back();//删除最后一张照片,此照片作为测试图片
    labels.pop_back();//删除最有一张照片的labels
   
    Ptr<face::BasicFaceRecognizer> model = face::EigenFaceRecognizer::create();
    model->train(images, labels);
    model->save("MyFacePCAModel.xml");//保存路径可自己设置,但注意用“\\”

    Ptr<face::BasicFaceRecognizer> model1 = face::FisherFaceRecognizer::create();
    model1->train(images, labels);
    model1->save("MyFaceFisherModel.xml");

    Ptr<face::LBPHFaceRecognizer> model2 = face::LBPHFaceRecognizer::create();
    model2->train(images, labels);
    model2->save("MyFaceLBPHModel.xml");

    // 下面对测试图像进行预测,predictedLabel是预测标签结果
    //注意predict()入口参数必须为单通道灰度图像,如果图像类型不符,需要先进行转换
    //predict()函数返回一个整形变量作为识别标签
    int predictedLabel = model->predict(testSample);//加载分类器
    int predictedLabel1 = model1->predict(testSample);
    int predictedLabel2 = model2->predict(testSample);

    // 还有一种调用方式,可以获取结果同时得到阈值:
    // int predictedLabel = -1;
    // double confidence = 0.0;
    //  model->predict(testSample, predictedLabel, confidence);

    string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel);
    string result_message1 = format("Predicted class = %d / Actual class = %d.", predictedLabel1, testLabel);
    string result_message2 = format("Predicted class = %d / Actual class = %d.", predictedLabel2, testLabel);
    cout << result_message << endl;
    cout << result_message1 << endl;
    cout << result_message2 << endl;

    getchar();
    //waitKey(0);
    return 0;
}

成功训练模型后,会生成三个xml文件,这三个xml文件对应的是不同的人脸识别方法
 

3、人脸识别

现在我们有训练好的模型,只需要把模型拿出来用就可以进行人脸识别。这里我拿出的是基于Fisher变换的人脸识别训练的模来进行人脸识别。  人脸识别的过程:1、打开摄像头   2、加载人脸检测分类器(haarcascade_frontalface_alt2.xml)和人脸模型  MyFaceFisherModel.xml   3、检测人脸    4、把检测到的人脸与人脸模型里面的对比,找出这是谁的脸。  5、如果人脸是自己拍照的人脸,显示自己的名字。

利用训练好的模型进行人脸识别代码

#include <iostream>
#include<opencv2\opencv.hpp>
#include<opencv2\core\core.hpp>
#include <fstream>
#include <sstream>
#include<math.h>
#include "face.hpp"
using namespace std;
using namespace cv;

RNG g_rng(12345);
Ptr<face::FaceRecognizer> model;

int Predict(Mat src_image)  //识别图片
{
    Mat face_test;
    int predict = 0;
    //截取的ROI人脸尺寸调整
    if (src_image.rows >= 120)
    {
        //改变图像大小,使用双线性差值
        resize(src_image, face_test, Size(92, 112));

    }
    //判断是否正确检测ROI
    if (!face_test.empty())
    {
        //测试图像应该是灰度图
        predict = model->predict(face_test);
    }
    cout << predict << endl;
    return predict;
}

int main()
{
    VideoCapture cap(0);    //打开默认摄像头
    if (!cap.isOpened())
    {
        return -1;
    }
    Mat frame;
    Mat gray;
    //这个分类器是人脸检测所用
    CascadeClassifier cascade;
    bool stop = false;
    //训练好的文件名称,放置在可执行文件同目录下
    cascade.load("haarcascade_frontalface_alt2.xml");
    model = face::FisherFaceRecognizer::create();
    //1.加载训练好的模型
    model->read("MyFaceFisherModel.xml");// opencv2用load
    while (1)
    {
        cap >> frame;

        vector<Rect> faces(0);//建立用于存放人脸的向量容器

        cvtColor(frame, gray, CV_RGB2GRAY);//测试图像必须为灰度图

        equalizeHist(gray, gray); //变换后的图像进行直方图均值化处理
        //检测人脸
        cascade.detectMultiScale(gray, faces,
            1.1, 4, 0
            //|CV_HAAR_FIND_BIGGEST_OBJECT
            | CV_HAAR_DO_ROUGH_SEARCH,
            //| CV_HAAR_SCALE_IMAGE,
            Size(30, 30), Size(500, 500));
        Mat* pImage_roi = new Mat[faces.size()];    //定以数组
        Mat face;
        Point text_lb;//文本写在的位置
        //框出人脸
        string str;
        for (int i = 0; i < faces.size(); i++)
        {
            pImage_roi[i] = gray(faces[i]); //将所有的脸部保存起来
            text_lb = Point(faces[i].x, faces[i].y);
            if (pImage_roi[i].empty())
                continue;
            cout<<Predict(pImage_roi[i]);
            switch (Predict(pImage_roi[i])) //对每张脸都识别
            {
            case 35:str = "fangxin"; break;
            case 36:str = "shenjun"; break;
            default: str = "Error"; break;
            }
            Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));//所取的颜色任意值
            rectangle(frame, Point(faces[i].x, faces[i].y), Point(faces[i].x + faces[i].width, faces[i].y + faces[i].height), color, 1, 8);//放入缓存
            putText(frame, str, text_lb, FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255));//添加文字
        }

        delete[]pImage_roi;
        imshow("face", frame);
        waitKey(200);
    }

        return 0;

    cout << "Hello World!" << endl;
    return 0;
}

结果:(大功告成)

能成功完成人脸识别,特别感谢

https://blog.csdn.net/qq_37791134/article/details/81385848   ——基于OpenCV3实现人脸识别(实践篇)

https://blog.csdn.net/sinat_37185987/article/details/82956367  ——opencv3人脸识别问题解决

https://blog.csdn.net/xingchenbingbuyu/article/details/51386949 ——OpenCV实践之路——人脸识别之一数据收集和预处理

问题咨询及项目源码请加群:

QQ群

名称:IT项目交流群

群号:245022761

  • 53
    点赞
  • 318
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值