最近在麦子学院观看美国犹他州立大学的彭亮博士的《机器学习》的视频的系列。不错,就是跟我名字一字之差的彭亮,哈哈,世界巧合的事还真多。不过视频中他演示的《卧虎藏龙》的视频能够人脸识别周润发和章子怡,还真是激起了我的好奇心。不过好在现在互联网这么发达,什么东西只要想学,很多都能找到答案。

本文所用到的技术:

  • Keras
  • OpenCV

所有用到的技术都已上传到github, 读者可以根据自己的需求下载查阅。下载地址:

https://github.com/huailiang/video-face-recognition

Keras

如果说 Tensorflow 或者 Theano 神经网络方面的巨人. 那 Keras 就是站在巨人肩膀上的人. Keras 是一个兼容 Theano 和 Tensorflow 的神经网络高级包, 用他来组件一个神经网络更加快速, 几条语句就搞定了. 而且广泛的兼容性能使 Keras 在 Windows 和 MacOS 或者 Linux 上运行无阻碍.Keras 是建立在 Tensorflow 和 Theano 之上的更高级的神经网络模块, 所以它可以兼容 Windows, Linux 和 MacOS 系统。

鉴于keras极简的api,本次我们使用keras来搭建一个CNN网络模型,基于tensorflow。之前有一篇文章专门介绍CNN,不懂cnn的同学可以去看看。

OpenCV

OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。

OpenCV的作用以前还是小看了,最近网易游戏在GDC发布的AirTest自动化测试的工具就是基于OpenCV. 有些人还用OpenCV实现了微信小游戏《跳一跳》自动刷分,虽说里面使用了很多随机的算法,但还是腾讯官方不知道使用了什么算法,居然能检测到作弊,然后给屏蔽掉评分。

本次利用openCV获取每一帧的所有脸部位置,然后截取脸部部分,传递给我们的CNN模型。cnn模型其实就是一个图片分类器,根据传递过来的图片,经过大量的训练,得到一个分类值(对应的标记label)。获得对应的标记之后,在通过OpenCV gui特性画一个方框,把名字标记在人脸处。

素材获取

因为需要训练需要大量的人脸素材,此次项目中所有使用的素材都是来源于UMASS(马萨诸塞大学)的一个对外的网站,这里你可以获取大量的预处理好的关于人脸素材,下载地址:http://vis-www.cs.umass.edu/lfw/

由于每个人的对应的素材(图片张数)大小不一,我们这里截取了几位数量较多的名人图片的素材来当本地的训练集,比如说美国前任总统George_W_Bush,大概有四五百张。不过其他人好像还是少了点,比如说选择的Laura Bush( George_W_Bush‘s Wife), 犹如素材较少,出现了训练的时候表现很好,测试的时候出错的情况(过拟合-over fit).

而且我们专门写了一个python脚本用来删除那些图片数量较少的名人文件夹:

import os
import shutil

path="/Users/huailiang.peng/Downloads/lfw_funneled/"
alllist=os.listdir(path)

print len(alllist)

for item in alllist:
	
	fpath=os.path.join(path,item)
	print fpath
	if os.path.isdir(fpath):
		cnt = len(os.listdir(fpath))
		print("{0} len: {1} ".format(str(item),str(cnt)))
		if cnt<18:
			 shutil.rmtree(fpath)

我们的视频素材是从youtube 随便找的一个关于Geoger Bush的演讲视频,貌似清晰度有点问题,不过也不影响我们训练的过程,谁关心呢。

关于训练集和测试集的构建, 我们从UMASS下载的图片集中最终选取了八位名人的图片做八分类,他们分别是:

  • Bill_Clinton
  • George_W_Bush
  • Gerhard_Schroeder
  • Junichiro_Koizumi
  • Laura_Bush
  • Serena_Williams
  • Tony_Blair
  • Winona_Ryder

项目按文件路径加载图片集,并根据文件夹的名称划给相应的标签, 代码实现如下:

images = []
labels = []
def read_path(path_name):    
    for dir_item in os.listdir(path_name):
        #从初始路径开始叠加,合并成可识别的操作路径
        full_path = os.path.abspath(os.path.join(path_name, dir_item))
        
        if os.path.isdir(full_path):    #如果是文件夹,继续递归调用
            read_path(full_path)
        else:   #文件
            if dir_item.endswith('.jpg'):
                image = cv2.imread(full_path)                
                image = resize_image(image, IMAGE_SIZE, IMAGE_SIZE)
                
                #放开这个代码,可以看到resize_image()函数的实际调用效果
                #cv2.imwrite('1.jpg', image)
                
                images.append(image)   
                # print path_name    
                if path_name.endswith("Bill_Clinton"):  
                    labels.append(0)
                elif path_name.endswith("George_W_Bush"):
                    labels.append(1)
                elif path_name.endswith("Laura_Bush"):
                    labels.append(2)
                elif path_name.endswith("Gerhard_Schroeder"):
                    labels.append(3)
                elif path_name.endswith("Junichiro_Koizumi"):
                    labels.append(4)
                elif path_name.endswith("Serena_Williams"):
                    labels.append(5)
                elif path_name.endswith("Tony_Blair"):
                    labels.append(6)
                else:
                    labels.append(7)                            
                    

关于CNN网络的搭建,我们借助Keras的代码一共使用了十七层神经网络,统计一共使用了4次卷积,4次激励层,2次池化层,最后还包含全连接层和Dropout层、分类层。对应的代码如下:

def build_model(self, dataset, nb_classes=8):
        # 构建一个空的网络模型,它是一个线性堆叠模型,各神经网络层会被顺序添加,专业名称为序贯模型或线性堆叠模型
        self.model = Sequential()

        # 以下代码将顺序添加CNN网络需要的各层,一个add就是一个网络层
        self.model.add(Convolution2D(32, 3, 3, border_mode='same', input_shape=dataset.input_shape))  # 1 2维卷积层
        self.model.add(Activation('relu'))  # 2 激活函数层

        self.model.add(Convolution2D(32, 3, 3))  # 3 2维卷积层
        self.model.add(Activation('relu'))  # 4 激活函数层

        self.model.add(MaxPooling2D(pool_size=(2, 2)))  # 5 池化层
        self.model.add(Dropout(0.25))  # 6 Dropout层

        self.model.add(Convolution2D(64, 3, 3, border_mode='same'))  # 7  2维卷积层
        self.model.add(Activation('relu'))  # 8  激活函数层

        self.model.add(Convolution2D(64, 3, 3))  # 9  2维卷积层
        self.model.add(Activation('relu'))  # 10 激活函数层

        self.model.add(MaxPooling2D(pool_size=(2, 2)))  # 11 池化层
        self.model.add(Dropout(0.25))  # 12 Dropout层

        self.model.add(Flatten())  # 13 Flatten层
        self.model.add(Dense(512))  # 14 Dense层,又被称作全连接层
        self.model.add(Activation('relu'))  # 15 激活函数层
        self.model.add(Dropout(0.5))  # 16 Dropout层
        self.model.add(Dense(nb_classes))  # 17 Dense层
        self.model.add(Activation('softmax'))  # 18 分类层,输出最终结果

我们使用 model.summary() 可以明了的看清神经网络的组织方式:

Keras使用fit方法拟合模型,关于keras的API使用我们这里就不多介绍了,现在已经对应的中文网站出现了,学习起来应该是无压力的。对应到我们的代码就是:

    model.fit(dataset.train_images,
                           dataset.train_labels,
                           batch_size = batch_size,
                           nb_epoch = nb_epoch,
                           validation_data = (dataset.valid_images, dataset.valid_labels),
                           shuffle = True)

最终学习出来的准确率, 有点欠缺人意,哈哈,准确率只达到了96.4%,不过我想也够用了。

最后如果我们随即使用一张图片集的图片验证,基本上都是对的。但如果我们从Internet上找一张关于Laura Bush的照片,确实很容易就出错了,毕竟laura的训练图片实在是太少了。

我们还是使用openCv的方式提取图像,传递给模型,基本上bush是可以是别的,但也存在着误差。代码部分这里就不贴出来了,大家可以下载github工程去查看对应的face_predict_use_keras.py脚本。 在场的观众也可能被误认为是Bush,毕竟在训练的时候我们没有这些观众的图片,在经过CNN输出分类的时候就有可能随机是Bush了。可能计算机认为他们长得比较像吧。