5. 小项目之Face_detection

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类中的。主要原因还是在采集图像的过程中大家的表情还是都单一了一些。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员的知识天地

前端工程师深度学习,就能在浏览器里玩转深度学习

TensorFlow.js 的发布可以说是 JS 社区开发者的福音!但是在浏览器中训练一些模型还是会存在一些问题与不同,如何可以让训练效果更好?本文为大家总结了...

18030
来自专栏AI研习社

实时识别字母:深度学习和 OpenCV 应用搭建实用教程

这是一个关于如何构建深度学习应用程序的教程,该应用程序可以实时识别由感兴趣的对象(在这个案例中为瓶盖)写出的字母。

24410
来自专栏数据派THU

手把手教你用Keras进行多标签分类(附代码)

本文将通过拆解SmallVGGNet的架构及代码实例来讲解如何运用Keras进行多标签分类。

7.8K110
来自专栏ATYUN订阅号

深度学习图像识别项目(中):Keras和卷积神经网络(CNN)

在下篇文章中,我还会演示如何将训练好的Keras模型,通过几行代码将其部署到智能手机上。

3K60
来自专栏IT派

值得探索的 8 个机器学习 JavaScript 框架

JavaScript开发人员倾向于寻找可用于机器学习模型训练的JavaScript框架。下面是一些机器学习算法,基于这些算法可以使用本文中列出的不同JavaSc...

15100
来自专栏奇点大数据

理解LSTM一种递归神经网络(RNN)

1 递归神经网络结构 一个简单的传统神经网络结构如下图所示: ? 给他一些输入x0,x1,x2 … xt, 经过神经元作用之后得到一些对应的输出h0,h1,h2...

289100
来自专栏新智元

【前沿】TensorFlow Pytorch Keras代码实现深度学习大神Hinton NIPS2017 Capsule论文

【新智元导读】10月26日,深度学习元老Hinton的NIPS2017 Capsule论文《Dynamic Routing Between Capsules》终...

43470
来自专栏AI科技大本营的专栏

OpenCV特征提取与图像检索实现(附代码)

翻译 | AI科技大本营 参与 | 张蔚敏 审校 | reason_W “拍立淘”“一键识花”“街景匹配”……不知道大家在使用这些神奇的功能的时候,有没有好奇过...

75860
来自专栏量子位

OpenCV+深度学习预训练模型,简单搞定图像识别 | 教程

李林 编译自 pyimagesearch 作者 Adrian Rosebrock 量子位 报道 | 公众号 QbitAI ? OpenCV是一个2000年发布的...

78660
来自专栏贾志刚-OpenCV学堂

OpenCV3.3 深度学习模块-对象检测演示

OpenCV3.3 深度学习模块-对象检测演示 一:概述 OpenCV3.3 DNN模块功能十分强大,可以基于已经训练好的模型数据,实现对图像的分类与图像中的对...

41280

扫码关注云+社区

领取腾讯云代金券