照片解锁手机不能忍?教你用OpenCV做活体检测 | 有代码

量子位关注

圆栗子 编译整理

量子位 出品 | 公众号 QbitAI

照片解锁手机不能忍?教你用OpenCV做活体检测 | 有代码

△ 来自虾米妈咪

小朋友用妈妈的一寸照片通过了人脸识别,打击了小度音箱的家长监督机制。

活体检测没做好。

公交车身广告上的董明珠头像,被宁波交警系统拍了照,判定成“违法闯红灯”。

活体检测没做好。

所以,活体检测要怎么做?

名叫Adrian Rosebrock的程序猿,写了份事无巨细的教程,从构建数据集开始,一步步教大家用AI分辨真人和照片,精细到每行代码的用途。

照片解锁手机不能忍?教你用OpenCV做活体检测 | 有代码

△ 川川是假的,光头是真的

这个识别方法,用到了OpenCV和Keras,打开摄像头就可以实时检测。

重要的是,有源码提供,受到了推特用户的踊跃比心。

活体检测,可以检测些什么?

AI可以用哪些技巧,来区分真人和照片?

一是纹理分析 (Texture Analysis) 。皮肤的纹理特征是重要的依据,给2D照片拍照,比起给3D真人拍照,会损失一些纹理。二是频率分析 (Frequency Analysis) 。照片脸部的频率组成,也不像真人那样丰富。三是可变聚焦分析 (Variable focusing Analysis) 。连拍两张照片,聚焦在不同位置,查看像素值 (Pixel Value) 的变化。四是启发式算法 (Heuristic-Based Algorithms) 。眼动、唇动、眨眼这些动作,照片是不会有的。五是光流算法 (Optical Flow Algorithms) 。在相邻两帧之间,检测物体运动的方向和幅度,查出2D和3D物体之间的差别。……

照片解锁手机不能忍?教你用OpenCV做活体检测 | 有代码

不过这里,就把活体检测看成一个粗暴的二分类问题,这些复杂的分析先抛到一边。

自制数据集

程序猿把问题又简化了一下:这里说的“假脸”,只是“屏幕里的人脸”而已。

现在,可以开始造数据集了。

他用手机的前摄像头拍了一段25秒的视频;又举着手机、对着电脑摄像头,把视频播了一遍。

照片解锁手机不能忍?教你用OpenCV做活体检测 | 有代码

这样,一段真人视频,和一段“假脸”视频,就准备好了。

程序猿拍下的这两条视频,都提供下载。不过,他还是建议大家多收集一些数据,不同的人脸,甚至不同的人种,帮助算法茁壮成长。

下一步,要用OpenCV的人脸检测算法处理两段视频,把有用的区域 (ROI) ,就是人脸部分框出来。

这步用了80多行代码,每一行在做些什么,教程里都写清了。

照片解锁手机不能忍?教你用OpenCV做活体检测 | 有代码

最后,按照真假两个类别,把框好的脸部提取出来,就有了数据集。

温馨提示,需要平衡一下真图和假图的数量。比如,程序猿发现真实视频比假视频长,就用更大的间隔来提取。于是,他收获了真图161张,假图150张。

这数据集还是显得有些贫乏,所以后面需要扩增:旋转、翻转之类的操作,可以增加图片数量。

活体检测模型

数据集做好了,就要喂给做活体检测的神经网络。

程序猿给网络起名LivenessNet,大体长这样:

照片解锁手机不能忍?教你用OpenCV做活体检测 | 有代码

他说,这其实只是一个简单的CNN。而且,已经努力让网络保持在最浅、参数最少的状态。

这样做有两个原因:一是为避免模型在小数据集上发生过拟合,二是为保证模型快到可以实时推理,就算在树莓派上也能运行。

搭个网络

现在,就来实现一下这个网络。打开livenessnet.py,再写这一段代码:

1# import the necessary packages 2from keras.models import Sequential 3from keras.layers.normalization import BatchNormalization 4from keras.layers.convolutional import Conv2D 5from keras.layers.convolutional import MaxPooling2D 6from keras.layers.core import Activation 7from keras.layers.core import Flatten 8from keras.layers.core import Dropout 9from keras.layers.core import Dense10from keras import backend as K1112class LivenessNet:13 @staticmethod14 def build(width, height, depth, classes):15 # initialize the model along with the input shape to be16 # "channels last" and the channels dimension itself17 model = Sequential()18 inputShape = (height, width, depth)19 chanDim = -12021 # if we are using "channels first", update the input shape22 # and channels dimension23 if K.image_data_format() == "channels_first":24 inputShape = (depth, height, width)25 chanDim = 1

照片解锁手机不能忍?教你用OpenCV做活体检测 | 有代码

然后,一层一层加上去:

1 # first CONV => RELU => CONV => RELU => POOL layer set 2 model.add(Conv2D(16, (3, 3), padding="same", 3 input_shape=inputShape)) 4 model.add(Activation("relu")) 5 model.add(BatchNormalization(axis=chanDim)) 6 model.add(Conv2D(16, (3, 3), padding="same")) 7 model.add(Activation("relu")) 8 model.add(BatchNormalization(axis=chanDim)) 9 model.add(MaxPooling2D(pool_size=(2, 2)))10 model.add(Dropout(0.25))1112 # second CONV => RELU => CONV => RELU => POOL layer set13 model.add(Conv2D(32, (3, 3), padding="same"))14 model.add(Activation("relu"))15 model.add(BatchNormalization(axis=chanDim))16 model.add(Conv2D(32, (3, 3), padding="same"))17 model.add(Activation("relu"))18 model.add(BatchNormalization(axis=chanDim))19 model.add(MaxPooling2D(pool_size=(2, 2)))20 model.add(Dropout(0.25))

程序猿说,这个网络有点像VGGNet,很浅,过滤器 (Filter) 很少。只是判断真假,不用深度网络。

最后,再加一个FC层→RELU层的组合。

1 # first (and only) set of FC => RELU layers 2 model.add(Flatten()) 3 model.add(Dense(64)) 4 model.add(Activation("relu")) 5 model.add(BatchNormalization()) 6 model.add(Dropout(0.5)) 7 8 # softmax classifier 9 model.add(Dense(classes))10 model.add(Activation("softmax"))1112 # return the constructed network architecture13 return model

CNN搭好了,该训练了。

训练脚本长这样

大致的训练过程,就像这张图:

照片解锁手机不能忍?教你用OpenCV做活体检测 | 有代码

打开train_liveness.py,写下这段代码:

1# set the matplotlib backend so figures can be saved in the background 2import matplotlib 3matplotlib.use("Agg") 4 5# import the necessary packages 6from pyimagesearch.livenessnet import LivenessNet 7from sklearn.preprocessing import LabelEncoder 8from sklearn.model_selection import train_test_split 9from sklearn.metrics import classification_report10from keras.preprocessing.image import ImageDataGenerator11from keras.optimizers import Adam12from keras.utils import np_utils13from imutils import paths14import matplotlib.pyplot as plt15import numpy as np16import argparse17import pickle18import cv219import os2021# construct the argument parser and parse the arguments22ap = argparse.ArgumentParser()23ap.add_argument("-d", "--dataset", required=True,24 help="path to input dataset")25ap.add_argument("-m", "--model", type=str, required=True,26 help="path to trained model")27ap.add_argument("-l", "--le", type=str, required=True,28 help="path to label encoder")29ap.add_argument("-p", "--plot", type=str, default="plot.png",30 help="path to output loss/accuracy plot")31args = vars(ap.parse_args())

照片解锁手机不能忍?教你用OpenCV做活体检测 | 有代码

△ 导入,不停地导入

里面包含了许多的导入:

有matplotlib,是可视化工具;有LivenessNet,就是刚才搭好的CNN;有train_test_split,这是scikit-learn里的函数,把数据集拆成训练集和测试集;有classification_report,也是scikit-learn里面的工具,用来生成简短统计报告的;有ImageDataGenerator,做数据扩增用的;有Adam,适合这个任务的优化器,当然也可以用SGD、RMSprop等等代替;有paths,这个模块是用来收集图片路径的;有pyplot,也是美丽的可视化工具;有numpy,是Python数学库,也是OpenCV必需品;有argparse,用来处理命令行参数;有pickle,可以把标签编码器序列化到盘上;有cv2,这是一组OpenCV Binding;还有os,这个模块用处很多,但这里只用到了它的操作系统路径分隔符而已。

照片解锁手机不能忍?教你用OpenCV做活体检测 | 有代码

梳理好这些,再看余下的代码,就会清晰很多了。

后面,是一系列的初始化,以及训练前的各种准备活动。此处略去,详见教程原文。

训练正式启动

准备就绪,运行这段命令,就可以训练了:

1$ python train.py --dataset dataset --model liveness.model --le le.pickle 2[INFO] loading images... 3[INFO] compiling model... 4[INFO] training network for 50 epochs... 5Epoch 1/50 629/29 [==============================] - 2s 58ms/step - loss: 1.0113 - acc: 0.5862 - val_loss: 0.4749 - val_acc: 0.7436 7Epoch 2/50 829/29 [==============================] - 1s 21ms/step - loss: 0.9418 - acc: 0.6127 - val_loss: 0.4436 - val_acc: 0.7949 9Epoch 3/501029/29 [==============================] - 1s 21ms/step - loss: 0.8926 - acc: 0.6472 - val_loss: 0.3837 - val_acc: 0.807711...12Epoch 48/501329/29 [==============================] - 1s 21ms/step - loss: 0.2796 - acc: 0.9094 - val_loss: 0.0299 - val_acc: 1.000014Epoch 49/501529/29 [==============================] - 1s 21ms/step - loss: 0.3733 - acc: 0.8792 - val_loss: 0.0346 - val_acc: 0.987216Epoch 50/501729/29 [==============================] - 1s 21ms/step - loss: 0.2660 - acc: 0.9008 - val_loss: 0.0322 - val_acc: 0.987218[INFO] evaluating network...19 precision recall f1-score support2021 fake 0.97 1.00 0.99 3522 real 1.00 0.98 0.99 432324 micro avg 0.99 0.99 0.99 7825 macro avg 0.99 0.99 0.99 7826weighted avg 0.99 0.99 0.99 782728[INFO] serializing network to 'liveness.model'...

成果斐然

训练完结,LivenessNet在验证集上拿到了99%的准确度。

当然,验证集只是热身,后面还要打开摄像头,让AI去看更多没见过的人,和没见过的“假人”。

(这一部分代码,也有详尽的解读,参见教程原文。)

照片解锁手机不能忍?教你用OpenCV做活体检测 | 有代码

就像开头展示的那样,AI能清楚地判断,它眼前的川川不是真人,而程序猿是真人。

那么,你也可以训练一只这样的AI了。

不过,不用局限于简单的二分类,可以用上前面讲到的那些复杂的分析方法,比如频率分析,比如光流法,大有可为。

教程原文传送门:

https://www.pyimagesearch.com/2019/03/11/liveness-detection-with-opencv/

源码下载入口,和无微不至的代码解析,都在里面了。

照片解锁手机不能忍?教你用OpenCV做活体检测 | 有代码

— 完 —

诚挚招聘

量子位正在招募编辑/记者,工作地点在北京中关村。期待有才气、有热情的同学加入我们!相关细节,请在量子位公众号(QbitAI)对话界面,回复“招聘”两个字。

量子位 QbitAI · 头条号签约作者

վ'ᴗ' ի 追踪AI技术和产品新动态​​​​

热门推荐
打开APP看更多推荐
今日热点
  • 火箭灰熊裁判报告

  • 亚马逊Go商店禁令

  • 沪指坚守3100点

  • 新西兰全国默哀

  • 波音取消49架订单

  • 素媛凶手将被释放

打开APP看更多热点