opencv 中提供的基于 haar 特征级联进行人脸检测的方法效果非常不好, 本文使用 dlib 中提供的人脸检测方法 (使用 HOG 特征或卷积神经网方法), 并使用提供的深度残差网络(ResNet) 实现实时人脸识别, 不过本文的目的不是构建深度残差网络, 而是利用已经训练好的模型进行实时人脸识别, 实时性要求一秒钟达到 10 帧以上的速率, 并且保证不错的精度 opencv 和 dlib 都是非常好用的计算机视觉库, 特别是 dlib, 前面文章提到了其内部封装了一些比较新的深度学习方法, 使用这些算法可以实现很多应用, 比如人脸检测车辆检测目标追踪语义分割等等由于这两个库相应的都包含了 C++ 和 Python 的版本, 而 Python 的配置和相对使用起来更加简单, 因此这篇文章主要通过 Python 来实现
先上测试的识别效果, 第一张识别吴恩达和 Bengio, 后者我没有打标签所以识别的是 other; 另外一张 gif 是识别梁朝伟刘德华和一个女主持的过程, 本地库中没有存储女主持的图片
因为博客园不方便上传本地视频, 所以用的 gif 显示效果图, 源视频要比 gif 清楚, 640X480 像素大小, 总的来说效果识别的效果还不错
一准备
(1)需要安装 opencv 和 dlib 的 Python 库, 之前的一篇文章提到了怎样安装: http://www.cnblogs.com/supersayajin/p/8446685.html; 如果你有 GPU 并且开启了加速, 那么实现的人脸识别程序速度非常快, 可以满足实时性, 以我运行的结果来看, 检测 + 识别 640X480 像素的视频流一秒钟大约十几帧; 如果你没有 GPU 那么速度就会很慢了, 而且在检测阶段不能使用卷积神经网络的方法了, 否则检测一帧数据可能需要几秒甚至几十秒:)
(2)需要一个和 PC 连接的摄像头; 在本文中使用的是串口的摄像头, 笔记本电脑集成的摄像头就是串口的, opencv 中提供了直接获取串口摄像头的接口, 非常方便使用; 如果是网口的摄像头那么就要看摄像头提供方, 比如大华海康他们的摄像头可能会提供官方的 SDK, 如果有接口那是最好; 或者, 如果摄像头支持 RTSP 协议, opencv 也可以通过 RTSP 协议获取摄像头的数据; 否则可能就要写一套 socket 通信来实现数据传输, 这个不在本文范围之内, 默认使用的是串口的摄像头
二策略
人脸识别分为人脸检测和识别两个阶段, 人脸检测会找到人脸区域的矩形窗口, 识别则通过 ResNet 返回人脸特征向量, 并进行匹配
(1)人脸检测阶段人脸检测算法需要用大小位置不同的窗口在图像中进行滑动, 然后判断窗口中是否存在人脸在深度学习之前的主流方法是特征提取 + 集成学习分类器, 比如以前火热的 haar 特征 + adaboost 级联分类器, opencv 中实现的人脸检测方法就采用了这种, 不过实验结果来看, 这种检测方法效果很不好, 经常误检测人脸, 或者检测不到真实的人脸; dlib 中使用的是 HOG(histogram of oriented gradient)+ 回归树的方法, 使用 dlib 训练好的模型进行检测效果要好很多 dlib 也使用了卷积神经网络来进行人脸检测, 效果好于 HOG 的集成学习方法, 不过需要使用 GPU 加速, 不然程序会卡爆了, 一张图片可能几秒甚至几十秒
(2)识别阶段识别也就是我们常说的分类, 摄像头采集到这个人脸时, 让机器判断是张三还是其他人分类分为两个部分:
特征向量抽取本文用到的是 dlib 中已经训练好的 ResNet 模型的接口, 此接口会返回一个 128 维的人脸特征向量
距离匹配在获取特征向量之后可以使用欧式距离和本地的人脸特征向量进行匹配, 使用最近邻分类器返回样本的标签
根据以上, 识别的大致过程如下:
图 1 人脸识别分类过程
对于图 1 中的获取人脸特征向量, 其过程如下:
图 2 获取人脸特征向量过程
用简单的话总结, 整个过程分为两个阶段, 本地存储已标记人脸数据; 识别阶段把从摄像头读取的人脸和本地进行匹配, 得到分类结果
三程序实现
(1)构建本地人脸特征向量库, 并且打标签
首先加载需要的 python 库:
- import dlib
- import numpy as np
- import cv2
- import os
- import json
然后加载模型参数:
- detector = dlib.cnn_face_detection_model_v1('mmod_human_face_detector.dat')
- sp = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')
- facerec = dlib.face_recognition_model_v1('dlib_face_recognition_resnet_model_v1.dat')
上面代码中的模型参数可以到这里下载: http://dlib.net/files/detector 是使用卷积神经网络 (CNN) 进行人脸检测的检测算子, 当然如果你使用 CNN 的话需要使用 GPU 加速, 否则速度会超级慢也可以使用另一种方法, 即 HOG 特征级联分类的检测方法, 效果略差于 CNN 变量 sp, 使用预测算子获取得到的人脸区域中的五官的几何点区域, 这里加载的是 68 特征点的 landmark 模型; 然后 facerec 会得到 ResNet 模型, He Kaiming(2009 年和 2015 的 CVPR best paper 作者)提出的方法的一个实现, 这里训练模型已经给出, 因此不需要自己手动去训练了
最后, 对某个目录中的所有图片进行处理, 处理的方式是一张一张地读取某个目录中的图片, 每读取一张就检测人脸, 如果存在人脸就使用 ResNet 的接口获取人脸特性向量, 保存到事先准备好的矩阵中, 并且按照文件名存取标签, 完了之后把所有的人脸特征向量和标签都存到本地的文本文件中注意这里给图片打标签的方式, 我把每张图片命名为标签名 + 下划线 + 序号 + 点号 + 后缀名的形式, 标签名是手动命名的标记名称, 序号用以区分同一类中的第几张以下是 demo 中存放的部分图片:
也有很多其他的方法打标签, 这里不多举例
- imagePath = 'LocalImage/' #图像的目录
- data = np.zeros((1,128)) #定义一个 128 维的空向量 data
- label = [] #定义空的 list 存放人脸的标签
- for file in os.listdir(imagePath): #开始一张一张索引目录中的图像
- if '.jpg' in file or '.png' in file:
- fileName = file
- labelName = file.split('_')[0] #获取标签名
- print('current image:', file)
- print('current label:', labelName)
- img = cv2.imread(imagePath + file) #使用 opencv 读取图像数据
- if img.shape[0]*img.shape[1] > 500000: #如果图太大的话需要压缩, 这里像素的阈值可以自己设置
- img = cv2.resize(img, (0,0), fx=0.5, fy=0.5)
- dets = detector(img, 1) #使用检测算子检测人脸, 返回的是所有的检测到的人脸区域
- for k, d in enumerate(dets):
- rec = dlib.rectangle(d.rect.left(),d.rect.top(),d.rect.right(),d.rect.bottom())
- shape = sp(img, rec) #获取 landmark
- face_descriptor = facerec.compute_face_descriptor(img, shape) #使用 resNet 获取 128 维的人脸特征向量
- faceArray = np.array(face_descriptor).reshape((1, 128)) #转换成 numpy 中的数据结构
- data = np.concatenate((data, faceArray)) #拼接到事先准备好的 data 当中去
- label.append(labelName) #保存标签
- cv2.rectangle(img, (rec.left(), rec.top()), (rec.right(), rec.bottom()), (0, 255, 0), 2) #显示人脸区域
- cv2.waitKey(2)
- cv2.imshow('image', img)
- data = data[1:, :] #因为 data 的第一行是空的 128 维向量, 所以实际存储的时候从第二行开始
- np.savetxt('faceData.txt', data, fmt='%f') #保存人脸特征向量合成的矩阵到本地
- labelFile=open('label.txt','w')
- json.dump(label, labelFile) #使用 json 保存 list 到本地
- labelFile.close()
- cv2.destroyAllWindows() #关闭所有的窗口
上面的代码中, 会索引 imagePath 这个存放图像的目录; 然后定义一个 128 维的空向量 data, 在后续获取每一张人脸特征向量的时候可以往这个向量后面追加, 即 data 的每一行是一个样本的特征向量; 然后定义一个 list 来存储标签之后开始索引某个目录下所有的图片文件注意我这里用的是 opencv 的接口读取图像, 也可以使用其他的图像读取接口, 比如 dlib 自带的或者 PIL 接口中的, 都可以使用, 不过重要的是接口一定要统一, 因为每个接口读取图片转成矩阵的数值可能会有差异然后使用前面定义的测算子开始检测人脸, 返回的是 dlib 中的一个数据结构, 这个数据结构存储了所有检测到的人脸区域信息, 对每个检测到的人脸区域获取 landmark, 并且调用深度残差模型的接口获取 128 维的人脸特征向量, 之后我们把这个人脸向量存储到 data 中去, 这里使用 numpy 中提供的 concatenate 方法进行拼接, 同时把标签添加到 label 列表中去最后, 因为 data 事先定义的是一个 128 维的空向量, 之后利用 concatenate 方法进行拼接得到, 我们需要抛弃第一行; 最后把得到的人脸特征和标签存储到本地文件
这里使用的是 CNN 进行人脸检测, 如果你没有 GPU, 或者你有 GPU 但没有进行 GPU 的配置, 那么速度巨慢, 此时你可以使用传统的 HOG 特征 + 级联分类的方法, 不过效果没有 CNN 的好这时代码的第 6 行中模型需要替换成:
detector = dlib.get_frontal_face_detector()
其余的基本保持不变
以上的代码可以直接运行, 运行之后会检测所有的图像, 类似于:
- import dlib
- import numpy as np
- import cv2
- import json
- detector = dlib.cnn_face_detection_model_v1('mmod_human_face_detector.dat')
- sp = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')
- facerec = dlib.face_recognition_model_v1('dlib_face_recognition_resnet_model_v1.dat')
- threshold = 0.54
- def findNearestClassForImage(face_descriptor, faceLabel):
- temp = face_descriptor - data
- e = np.linalg.norm(temp,axis=1,keepdims=True)
- min_distance = e.min()
- print('distance:', min_distance)
- if min_distance > threshold:
- return 'other'
- index = np.argmin(e)
- return faceLabel[index]
- def recognition(img):
- dets = detector(img, 1)
- for k, d in enumerate(dets):
- print("Detection {}: Left: {} Top: {} Right: {} Bottom: {}".format(
- k, d.rect.left(), d.rect.top(), d.rect.right(), d.rect.bottom()))
- rec = dlib.rectangle(d.rect.left(),d.rect.top(),d.rect.right(),d.rect.bottom())
- print(rec.left(),rec.top(),rec.right(),rec.bottom())
- shape = sp(img, rec)
- face_descriptor = facerec.compute_face_descriptor(img, shape)
- class_pre = findNearestClassForImage(face_descriptor, label)
- print(class_pre)
- cv2.rectangle(img, (rec.left(), rec.top()+10), (rec.right(), rec.bottom()), (0, 255, 0), 2)
- cv2.putText(img, class_pre , (rec.left(),rec.top()), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,0), 2, cv2.LINE_AA)
- cv2.imshow('image', img)
- labelFile=open('label.txt','r')
- label = json.load(labelFile) #载入本地人脸库的标签
- labelFile.close()
- data = np.loadtxt('faceData.txt',dtype=float) #载入本地人脸特征向量
- cap = cv2.VideoCapture(0)
- fps = 10
- size = (640,480)
- fourcc = cv2.VideoWriter_fourcc(*'XVID')
- videoWriter = cv2.VideoWriter('video.MP4', fourcc, fps, size)
- while(1):
- ret, frame = cap.read()
- #frame = cv2.resize(frame, (0,0), fx=0.5, fy=0.5)
- recognition(frame)
- videoWriter.write(frame)
- if cv2.waitKey(1) & 0xFF == ord('q'):
- break
- cap.release()
- videoWriter.release()
- cv2.destroyAllWindows()
来源: https://www.cnblogs.com/supersayajin/p/8489435.html