参考连接:
https://github.com/informramiz/opencv-face-recognition-python
项目背景
对“待分类”中的每一个图像,根据模式识别算法判断它与“已知类别”中哪一个图像最相似。
如果最相似的图像与它的文件名前四个字符相同,则说明识别正确,否则是识别错误。
计算出识别率:“待分类”中识别正确的图像个数 除以 “待分类”中的图像总数。
项目中的主要算法:
局部二值模式直方图(LBPH)人脸识别器 :
Eigenfaces和Fisherfaces人脸识别算法都受光线影响,在现实生活中,我们无法保证完美的光照条件。 LBPH人脸识别器是克服这个缺点的一种改进。
LBPH,Local Binary Patterns Histograms,即LBP特征的统计直方图,LBPH将LBP特征与图像的空间信息结合在一起。这种表示方法由Ahonen等人在论文中提出,他们将LBP特征图像分成m个局部块,并提取每个局部块的直方图,然后将这些直方图依次连接在一起形成LBP特征的统计直方图,即LBPH。
该算法不看整个图像,而是查找图像的局部特征。 LBPH算法试图找出图像的局部结构,并通过比较每个像素与其相邻像素来实现。
LBP原理介绍以及算法实现:
LBP(Local Binary Pattern,局部二值模式)是一种用来描述图像局部纹理特征的算子;它具有旋转不变性和灰度不变性等显著的优点。它用于纹理特征提取。而且,提取的特征是图像的局部的纹理特征。
1、LBP特征的描述
原始的LBP算子定义为在33的窗口内,以窗口中心像素为阈值,将相邻的8个像素的灰度值与其进行比较,若周围像素值大于中心像素值,则该像素点的位置被标记为1,否则为0。这样,33邻域内的8个点经比较可产生8位二进制数(通常转换为十进制数即LBP码,共256种),即得到该窗口中心像素点的LBP值,并用这个值来反映该区域的纹理信息。如下图所示:
图2.1 LBP特征的描述图
2、LBP特征用于检测的原理
提取的LBP算子在每个像素点都可以得到一个LBP“编码”,那么,对一幅图像(记录的是每个像素点的灰度值)提取其原始的LBP算子之后,得到的原始LBP特征依然是“一幅图片”(记录的是每个像素点的LBP值)。
(a)原始图像 (b)LBP图谱
图2.2 不同光照程度下的LBP图像
从上图可以看出LBP对光照具有很强的鲁棒性。
LBP的应用中,如纹理分类、人脸分析等,一般都不将LBP图谱作为特征向量用于分类识别,而是采用LBP特征谱的统计直方图作为特征向量用于分类识别。
因为,从上面的分析我们可以看出,这个“特征”跟位置信息是紧密相关的。直接对两幅图片提取这种“特征”,并进行判别分析的话,会因为“位置没有对准”而产生很大的误差。后来,研究人员发现,可以将一幅图片划分为若干的子区域,对每个子区域内的每个像素点都提取LBP特征,然后,在每个子区域内建立LBP特征的统计直方图。如此一来,每个子区域,就可以用一个统计直方图来进行描述;整个图片就由若干个统计直方图组成;
例如:一幅100100像素大小的图片,划分为1010=100个子区域(可以通过多种方式来划分区域),每个子区域的大小为1010像素;在每个子区域内的每个像素点,提取其LBP特征,然后,建立统计直方图;这样,这幅图片就有1010个子区域,也就有了1010个统计直方图,利用这1010个统计直方图,就可以描述这幅图片了。之后,我们利用各种相似性度量函数,就可以判断两幅图像之间的相似性了。
3、对LBP特征向量进行提取的步骤
(1)首先将检测窗口划分为16×16的小区域(cell);
(2)对于每个cell中的一个像素,将相邻的8个像素的灰度值与其进行比较,若周围像素值大于中心像素值,则该像素点的位置被标记为1,否则为0。这样,3*3邻域内的8个点经比较可产生8位二进制数,即得到该窗口中心像素点的LBP值;
图2.3
(3)然后计算每个cell的直方图,即每个数字(假定是十进制数LBP值)出现的频率;然后对该直方图进行归一化处理。
图2.4:样本直方图
(4)最后将得到的每个cell的统计直方图进行连接成为一个特征向量,也就是整幅图的LBP纹理特征向量;然后便可利用SVM或者其他机器学习算法进行分类了。
(2)程序的实现步骤和思路:
1、准备训练数据:在这一步中,我们将遍历“已知分类”的文件夹,读取每个人/主体的训练图像及其标签,从每个图像中检测人脸并为每个检测到的人脸分配其所属人员的整数标签。
2、训练人脸识别器:在这一步中,我们将训练OpenCV的LBPH人脸识别器,为其提供我们在步骤1中准备的数据。
3、测试:在这一步中,我们会将遍历“待分类”的文件夹,将图像传递给人脸识别器,通过返回值在图片的人脸中绘制出来识别出来的名字,并判断它是否能够正确预测它们,最后通过识别正确的数量与图片数量之比获得识别率。
# 导入OpenCV模块
import cv2
# 导入os模块用于读取训练数据目录和路径
import os
# 导入numpy将python列表转换为numpy数组
#OpenCV人脸识别器需要它
import numpy as np
subjects = [""]
#使用OpenCV用来检测脸部的函数
def detect_face(img):
# 将测试图像转换为灰度图像,因为opencv人脸检测器需要灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 加载OpenCV人脸检测器
face_cascade = cv2.CascadeClassifier('opencv-files/lbpcascade_frontalface.xml')
# 结果是一张脸的列表
faces = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5);
# 如果未检测到面部,则返回原始图像
if (len(faces) == 0):
return None, None
# 只有一张脸,
# 提取面部区域
(x, y, w, h) = faces[0]
# 只返回图像的正面部分
return gray[y:y+w, x:x+h], faces[0]
#该功能将读取所有人的训练图像,从每个图像检测人脸
#并将返回两个完全相同大小的列表,一个列表
# 每张脸的脸部和另一列标签
# 参数为目录的path
def prepare_training_data(data_folder_path):
# 列表来保存所有主题的面孔
faces = []
# 列表以保存所有主题的标签
labels = []
# 获取给定主题目录内的图像名称
subject_images_names = os.listdir(data_folder_path)
label = 0
# 浏览每个图片的名称,阅读图片,
# 检测脸部并将脸部添加到脸部列表
for image_name in subject_images_names:
# 忽略.DS_Store之类的系统文件
if image_name.startswith("."):
continue;
# 建立图像路径
# sample image path = training-data/s1/1.pgm
image_path = data_folder_path + "/" + image_name
# 阅读图像
image = cv2.imread(image_path)
# print("image_name"+image_name)
# 显示图像窗口以显示图像
cv2.imshow("Training on image...图片"+image_name, cv2.resize(image, (400, 500)))
cv2.moveWindow("Training on image...图片"+image_name,200,200)
cv2.waitKey(100)
# 侦测脸部
face, rect = detect_face(image)
# 我们将忽略未检测到的脸部
if face is not None:
subjects.append(image_name)
label = label + 1;
# 将脸添加到脸部列表
faces.append(face)
# 为这张脸添加标签
labels.append(label)
else:
print("已知分类中没有检测到人脸=="+image_name)
cv2.destroyAllWindows()
cv2.waitKey(1)
cv2.destroyAllWindows()
# print("labels" + str(len(labels)))
return faces, labels
#准备好我们的训练数据
#数据将在两个相同大小的列表中
#一个列表将包含所有的面孔
#数据将在两个相同大小的列表中
print("Preparing data...")
#faces, labels = prepare_training_data("./training-data2")
faces, labels = prepare_training_data("./yizhi-fen-lei")
print("Data prepared")
# 打印总面和标签
print("Total faces: ", len(faces))
print("Total labels: ", len(labels))
# 训练人脸识别器
#
# OpenCV配备了三个人脸识别器。
#
# 1、EigenFaces人脸识别器识别器 - cv2.face.createEigenFaceRecognizer()
#
# 2、FisherFaces人脸识别器识别器 - cv2.face.createFisherFaceRecognizer()
#
# 3、局部二值模式直方图(LBPH)人脸识别器 - cv2.face.createLBPHFaceRecognizer()
#
# 使用LBPH人脸识别器
#创建LBPH人脸识别器
face_recognizer = cv2.face.LBPHFaceRecognizer_create()
#或者使用EigenFaceRecognizer替换上面的行
#face_recognizer = cv2.face.createEigenFaceRecognizer()
#或者使用FisherFaceRecognizer替换上面的行
#face_recognizer = cv2.face.createFisherFaceRecognizer()
# 初始化了我们的人脸识别器,
# 准备了训练数据,现在是时候训练人脸识别器了。
# 通过调用人脸识别器的序列(面向量,标签向量)方法来实现这一点。
#训练面部识别器
face_recognizer.train(faces, np.array(labels))
#函数在图像上绘制矩形
#根据给定的(x,y)坐标和
#给定的宽度和高度
def draw_rectangle(img, rect):
(x, y, w, h) = rect
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
# 函数在从图像开始绘制文本
# 通过(x,y)坐标。
def draw_text(img, text, x, y):
cv2.putText(img, text, (x, y), cv2.FONT_HERSHEY_PLAIN, 1.5, (0, 255, 0), 2)
# 他的功能可以识别通过图像的人
#并在检测到的面周围绘制一个矩形,
def predict(test_img):
# 制作图像的副本,不更改原始图像
img = test_img.copy()
# 从图像中检测脸部
face, rect = detect_face(img)
if face is not None:
# 使用我们的脸部识别器预测图像
label, confidence = face_recognizer.predict(face)
#label= face_recognizer.predict(face)
# 获取由人脸识别器返回的相应标签的名称
label_text = subjects[label]
# print("label_text"+label_text)
# print("label"+str(label))
# 在检测到的脸部周围画一个矩形
draw_rectangle(img, rect)
# 画预计人的名字
draw_text(img, label_text, rect[0], rect[1]-5)
return img, label_text
else:
return img, None
print("Predicting images...")
# 获取给定主题目录内的图像名称
subject_images_names = os.listdir("./dai-fen-lei")
#subject_images_names = os.listdir("./test-data")
# 浏览每个图片的名称,阅读图片,
# 检测脸部并将脸部添加到脸部列表
correct_num = 0
all_um = 0
for image_name in subject_images_names:
#test_img_path = "./test-data/"+image_name
test_img_path = "./dai-fen-lei/"+image_name
test_img = cv2.imread(test_img_path)
# 执行预测
predicted_img, text = predict(test_img)
# 显示两个图像
if text is not None:
all_um = all_um+1
cv2.imshow(image_name, cv2.resize(predicted_img, (400, 500)))
print("image_name==="+image_name)
print("text==="+text)
if image_name[0:4] == text[0:4]:
correct_num = correct_num + 1
cv2.waitKey(1000)
cv2.destroyAllWindows()
else:
print("人脸匹配错误image_name=="+image_name+"text=="+text)
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print("待分类中没有检测到人脸"+image_name)
cv2.waitKey(1)
cv2.destroyAllWindows()
#print(str(num/len(subject_images_names)*100)+"%")
print(str(correct_num/all_um*100)+"%")