美文网首页我爱编程
5. 小项目之Face_detection

5. 小项目之Face_detection

作者: 和蔼的zhxing | 来源:发表于2018-05-28 17:30 被阅读55次

    git地址:face_detection

    功能和框架

    想做的是这么一个东西:识别视频(或者摄像头获得的实时视频)中的人脸,并判断是谁(因为数据采集的原因,找了身边的5个朋友采集了一些数据),如果不是这几个人,标记为其他人。
    功能上其实比较简单,主要是想体会一下这整个过程,做下来还是有很多值得注意的地方的。大致框架也比较简单:

    框架

    下面就分别说一下。

    1.视频采集

    这次的整个编程语言选择的是python,之所以选择python主要是因为要遇到CNN来进行分类,所以还是在python下容易实现一些。linux还是用的不熟,所以还是用的windows10+pycharm。
    视频采集用的是opencv的模块,应该说是很简单易用的模块。c++版本的videocapture原来写过,在这里,python版本的大同小异。典型的简单使用:

    #需要调用摄像头的话只需要参数设置为0就可以打开笔记本自带的摄像头
    camera = cv2.VideoCapture('liuyang.mp4')
    success, img = camera.read()
    

    这样就没有问题了。

    2. 人脸定位

    人脸定位采用的harr特征来做的,使用的是opencv训练好的harr特征分类器,这个在opencv的源文件里有:haarcascade_frontalface_default.xml,这个文件保存的就是harr特征正脸检测的模型,是已经训练好的。
    harr特征是基础图像处理领域三个基本特征之一(还有hog和Lbp)
    加载特征使用的是CascadeClassifier这个分类器,是一个级联分类器。在官方文档中有定义。

    Use the cv::CascadeClassifier class to detect objects in a video stream. Particularly, we will use the functions:

    定义很简单,这个是c++版本的解释,python载入模型的时候:
    haar = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

    检测的时候一般使用的是:detectMultiScale这个函数。使用起来也比较简单,python的结果是返回在函数外边,c++版本是将Vector<Rect>作为参数传入。

    CV_WRAP virtual void detectMultiScale( const Mat& image,  
                                       CV_OUT vector<Rect>& objects,  
                                       double scaleFactor=1.1,  
                                       int minNeighbors=3, int flags=0,  
                                       Size minSize=Size(),  
                                       Size maxSize=Size() ); 
    const Mat& image: 需要被检测的图像(灰度图)
    vector<Rect>& objects: 保存被检测出的人脸位置坐标序列
    double scaleFactor: 每次图片缩放的比例
    int minNeighbors: 每一个人脸至少要检测到多少次才算是真的人脸
    int flags: 决定是缩放分类器来检测,还是缩放图像
    Size(): 表示人脸的最大最小尺寸
    

    python版本的使用就更加简单:

    haar = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
    faces = haar.detectMultiScale(gray_img, 1.3, 5)
    #返回的face就是人脸的位置以及大小(左上角的位置及宽和高),相当于c++版本中的vector<Rect>
    

    所以这样下来进行人脸定位就比较简单了。下面的这段程序不仅仅是实现了一个人脸的定位,这个实际上是我获取训练样本使用的一个程序,也是先从摄像头获取视频,然后定位人脸,把人脸图像取下来resize到64的尺寸,然后随机变换进行存储(增加样本多样性)。

    import cv2
    import os
    import sys
    import random
    import time
    
    
    #新建存储文件夹
    out_dir = './my_faces'
    if not os.path.exists(out_dir):
        os.makedirs(out_dir)
    
    
    # 改变图片的对比度和亮度,增加图像多样性
    def relight(img, alpha=1, bias=0):
        w = img.shape[1]
        h = img.shape[0]
        #image = []
        for i in range(0,w):
            for j in range(0,h):
                for c in range(3):
                    tmp = int(img[j,i,c]*alpha + bias)
                    if tmp > 255:
                        tmp = 255
                    elif tmp < 0:
                        tmp = 0
                    img[j,i,c] = tmp
        return img
    
    
    # 获取分类器,用的是opencv的harr默认特征
    haar = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
    
    # 打开摄像头 参数为输入流,可以为摄像头或视频文件
    camera = cv2.VideoCapture('liuyang.mp4')
    
    n = 1
    
    while 1:
        if (n <= 10000):
            print('It`s processing %s image.' % n)
            # 读帧
            start=time.time()
            success, img = camera.read()
            cv2.imshow('face',img)
            gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            faces = haar.detectMultiScale(gray_img, 1.3, 5)       #多尺度检测人脸
    
            for f_x, f_y, f_w, f_h in faces:
                face = img[f_y:f_y+f_h, f_x:f_x+f_w]
                face = cv2.resize(face, (64,64))
                '''
                if n % 3 == 1:
                    face = relight(face, 1, 50)
                elif n % 3 == 2:
                    face = relight(face, 0.5, 0)
                '''
                face = relight(face, random.uniform(0.5, 1.5), random.randint(-50, 50))
                cv2.imshow('img', face)
                cv2.imwrite(out_dir+'/'+str(n)+'.jpg', face)
                #time_cost
                end=time.time()
                time_cost=end-start
                print('time_cost:\t'+str(time_cost))
    
                n+=1
            key = cv2.waitKey(30) & 0xff
            if key == 27:
                break
        else:
            break
    

    3. 人脸识别

    人脸识别也是先定位再识别,没有想要做的太复杂,所以定位还是使用的上面说的利用harr来定位,识别用的是CNN,用CNN进行识别之后然后再返回结果到主程序进行显示,这其中最重要的两个部分是训练数据制作和模型训练。

    3.1训练数据制作。

    我找了5个人采集数据,同一个人不同表情的人脸图片大概都是5000张左右,然后下载了一个数据集(LFW)当做“其他类”,下载下来大概有13000多张人脸,而且是在不同的文件夹下的,所以全部读出来放在同一个文件夹下并删掉了一部分只剩下60000多张,与其他类的数量来匹配。所以一共是6类,分别放在6个文件夹里:


    然后用matlab读入图片数据,顺便打上标签,所有的结果存放在一个2维矩阵中,为了达到这个目的,每张图片读进来先拉长成为1行:64_64_3的图片,拉长成为长度12288的向量,然后在其最后一列打上其所属类的标签,标签就先用1,2,3,4,5,6这样的数字打上,所以每张图片和其标签就是一个行向量,维度为12289。在这个过程中,每5张照片,选择一张放入测试集,这样的话就相当于分掉1/5的数据去做训练集。下面是这么一个处理过程,完成之后我把这样的数据就存成mat形式了,因为这样的一次处理耗时还是挺可观的,大概需要半个小时的时间才处理完3万多张图片。
    % 所有的图片拉成一行,放入矩阵之中,矩阵的最后一列是其每行对应的标签,标签的对应关系在上面的注释中已说明。
    % 这个程序所有的图片均是64*64*3的大小。
    
    
    % train_img_and_label=[];
    % test_img_and_label=[];
    % 
    % %创建进度条显示处理进程
    % h=waitbar(0,'processing img...');
    % 
    % for i=1:6 
    % img_path='C:\Users\zhxing\Desktop\data';
    % img_path_i=[img_path '\' num2str(i) '\'];
    % img_dir=dir([img_path_i '*.jpg']);          %获取所有文件夹下的所有图像
    % num_img=length(img_dir);
    % display(['正在处理第 ' num2str(i) ' 类数据:  共' num2str(num_img) '张图片' ])
    % tic;
    % for j=1:num_img
    %     img=imread([img_path_i img_dir(j).name]);
    %     if mod(j,5)==0   %每5张把一个扔进测试集
    %         img_reshape=reshape(img,[1,4096*3]);
    %         test_img_and_label_tmp=[img_reshape i];
    %         test_img_and_label=[test_img_and_label;test_img_and_label_tmp];
    %     else
    %         img_reshape=reshape(img,[1,4096*3]);
    %         train_img_and_label_tmp=[img_reshape i];
    %         train_img_and_label=[train_img_and_label;train_img_and_label_tmp];
    %     end
    %     waitbar(j/num_img);
    % end
    % toc;
    % end
    % display('finished!!')
    

    得到这样的一个图像矩阵之后,下面的任务就是把矩阵按照行打乱顺序,来防止训练的时候一个batch传入的都是同一类的数据,matlab提供了这么一个函数。先获得一个乱序表,然后按照这个乱序表把矩阵打乱。
    rowrank=randperm(num)这个函数可以获得一个乱序表,然后利用B = A(rowrank, :)把矩阵打乱,前面的把图像拉成一行的原因也是为了在此同步打乱数据和标签。再打乱之后就可以把数据和标签分开了,以后的使用中,依据索引就可以完全对应了,一般在制作训练数据到这一步的时候,最好随机选择一些数据输出来看标签和数据是否对应。

    
    train_img_and_label=load('C:\Users\zhxing\Desktop\data\train.mat');
    test_img_and_label=load('C:\Users\zhxing\Desktop\data\test.mat');
    train_img_and_label=train_img_and_label.train_img_and_label;
    test_img_and_label=test_img_and_label.test_img_and_label;
    
    rowrank_test = randperm(size(test_img_and_label, 1));
    test_img_and_label=test_img_and_label(rowrank_test,:);
    
    rowrank_train = randperm(size(train_img_and_label, 1));
    train_img_and_label=train_img_and_label(rowrank_train,:);
    
    
    test=test_img_and_label;
    train=train_img_and_label;
    
    %把数据和标签分开
    train_img=train(:,1:end-1);       
    train_label=train(:,end);
    test_img=test(:,1:end-1);
    test_label=test(:,end);
    

    接着,可以把数据恢复成原始尺寸来进行保存,和python要求的格式相同[batch,width,height,channel]。这里也是一个技巧,如果不这样保存的话读入python里然后再想恢复其实是不容易的,因为python中numpy通道的存储方式和matlab是不同的,matlab中一个通道存储完然后接着存储另一个通道,但是python里是一个位置的三个通道连续存储,所以如果直接把一行向量存入mat里再读入python直接用reshape是恢复不出来三通道的图像的。
    另外,标签也转换成码表形式的。(这个是叫码表么?我不太确定)

    %把数据和标签分开
    train_img=train(:,1:end-1);       
    train_label=train(:,end);
    test_img=test(:,1:end-1);
    test_label=test(:,end);
    
    %把数据重置成四维矩阵并保存为mat形式,然后读入python进行处理
    train_sz=size(train_img);
    train_img_rz=reshape(train_img,[train_sz(1),64,64,3]);
    save('train_img.mat','train_img_rz');
    
    
    test_sz=size(test_img);
    test_img_rz=reshape(test_img,[test_sz(1),64,64,3]);
    save('test_img.mat','test_img_rz');
    
    %把数字标签转换为码表形式的。
    train_label_sz=size(train_label);          %大小
    train_labels=zeros(train_label_sz(1),6);   %建立空label表
    for i=1:train_label_sz(1)
        train_labels(i,train_label(i))=1;
    end
    save('train_label.mat','train_labels');
    
    test_label_sz=size(test_label);          %大小
    test_labels=zeros(test_label_sz(1),6);   %建立空label表
    for i=1:test_label_sz(1)
        test_labels(i,test_label(i))=1;
    end
    save('test_label.mat','test_labels');
    

    3.2CNN框架及其训练。

    经过上面的训练数据制作,我们一共得到了4个mat,分别是:
    train_img.mat, train_label.mat, test_img.mat,test_label.mat
    存储的分别是训练数据,训练标签,测试数据,测试标签。


    我这里是两份,一份是乱序的,一份是没有乱序的。训练的时候直接使用的是乱序的,这个数据量大小一般,4个文件在400m左右。
    CNN框架采用的比较简单,因为只是做一个6分类,框架如下:
    框架
    所有激活层的激活函数采用的都是relu(),权重初始化使用的是截断的高斯分布,偏置初始化卷积层为0,全连接层为0.1,训练是,全连接层采用0.5的dropout。
    损失函数采用的是交叉熵损失:交叉熵损失函数,主要是要避免采用平方损失带来的神经元(以sigmoid为例)参数更新过慢的问题。
    优化器采用tf.train.AdamOptimizer()
    训练时batch_size为100,只训练了一个epoch就达到了比较好的准确率(0.99-1),所以就没有训练过长时间。
    值得注意的是,训练在加载训练数据之前,对数据进行归一化和中心化是必要的,我这里采用的比较简单:
    train_img, train_label, test_img, test_label = get_date_and_label()
    
    # 数据归一化,统一/255然后减掉0.5
    train_img =np.float32(train_img) / 255 - 0.5
    test_img = np.float32(test_img) / 255 - 0.5
    

    训练过程就不说了,GTX1060 6G i57500训练是非常快的,总耗时20s左右,下午用笔记本试了一下则特别慢,每50个batch耗时1秒多,但是可能加载数据比较慢,实际耗时远大于这个。不过上个厕所也差不多训练好了。
    训练好的模型保存起来待用就可以了,关于模型保存和使用这里有一篇博客我觉得讲的很清楚,贴在这里

    四.识别。

    先定位,再识别,于获取人脸那里是差不多的,先定位人脸,然后取出来,resize成64_64的,作为输入输入placeholder,drop设置为1,predictions作为输出,对应可得到分类的信息,虽然图像是三维的,但是在输入的时候要把batch的这一维加上。然后根据分类信息画框图显示就可以了,进行了简单的测试,基本可以实现识别功能,偶有识别不出人脸(其实这个主要是因为harr人脸定位的原因),还有一个就是偶尔会把没出现过的人脸误认为是已知的5类中的。主要原因还是在采集图像的过程中大家的表情还是都单一了一些。

    相关文章

      网友评论

        本文标题:5. 小项目之Face_detection

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