用OpenCv-Python自带的LBPH识别器实现简单人脸识别(上)

发布时间 2023-04-02 16:16:39作者: 天黑星更亮

用OpenCv-Python自带的LBPH识别器实现简单人脸识别(上)

引言:

本文开发环境为: Windows10 + phchram + Anaconda5.2 (Python3.6)+ Opencv4.5.5,用opencv-contrib原生的API完成了人脸识别的功能,其可以任意添加人脸ID数据,但其效果比较差(勉强能用),如果日后有时间的话,给大家出一期(挖坑)利用基于paddle人脸识别教程。

特别注意:上篇只是一个大致流程和核心API的讲解,其含有大量细节未补充,如果有copy需求,请看下篇!

一、环境搭建

此处不做赘述,只提一点,Opencv需要下载opencv_contrib_python这个包,这个contrib是opencv的拓展版,包括了opencv的主要模块和一些拓展的算法,注意:下载一个contrib就可以,不要把两个2个都安装。

二、思路解析

我们完成人脸识别,需要哪些步骤?

(1)数据的收集

毫无疑问,第一步是人脸数据的采集,我们要得到这个人的面部数据和其对应的ID,我们需要写一个函数去采集这些东西,我们需要2个参数,一个是int类型的ID号码,一个是字符串类型的名字,至于为什么不能直接输入名字,我们后面会讲到

def get_data(id,face_name):
  • 1

下面介绍一下核心的API

#这个函数可以帮助我们去找到图像中的人脸部分,返回值是一个包含4个元素的列表,分别是x,y坐标,以及宽度和高度。
face_xy = face_detector.detectMultiScale(src)
#我们得到face的坐标之后,可以直接截取下来,然后resize一下,保存下来,方便我们日后的训练
src = src[X:x+w,y:y+h]
#保存图像,里面填写地址和要保存的图像
cv2.imwrite()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

经过这步,我们会得到size固定的人脸图片,类似这样的文件名字:

1.NAME.1.jpg,1.NAME.2.jpg,1.NAME.3.jpg,1.NAME.4.jpg ... 
  • 1

其中第一个数字,是我们这个人的ID,第二个是我们的姓名,第三个则是代表我们这个ID号的照片序号,如ID为1的第1张照片。

(2)模型的训练

我们得到了ID和图像信息之后,需要构建一个训练器trainer,其中,我们需要的核心API如下:

recognizer.train(images, np.array(ids))
  • 1

他需要我们导入2个数组,一个是图像数组,一个是ID数据,比如说,我们现在要训练2个ID,每个ID包括2张图片,那么输入的参数就应该长这样:

#由于字数原因,我就简写,这个图像矩阵就是uint8_t的灰度矩阵,0-255之间取值
images = [图像1矩阵,图像2矩阵,图像3矩阵,图像4矩阵]
 #这个就是序号的矩阵,需要和前面的图像一一对应!
ids = [1,1,2,2]								   
  • 1
  • 2
  • 3
  • 4

那么问题来了,我们如何让图像与序号对应,一个一个写?当然可以,但如果图像很多呢,显然不太现实,所以我们需要专门写一个函数来帮助我们来,绑定这个ID和图像,还记得我们前面的命名规则吗?我们可以用OS库轻松的将其分离开来,如何利用append插入数组,完成数据的绑定。

下面我们来介绍核心API,使用他需要导入OS库

1.拼接路径,我直接放效果: os.path.join(path,f)

test_path = os.path.join('data/','test.jpg')
print(test_path)
#输出:
$data/test.jpg
  • 1
  • 2
  • 3
  • 4

2.显示路径下所有文件的信息:os.listdir(path)

test_path = os.listdir(path)
print(test_path)
#输出:路径下所有文件
$['1.HQS.1.jpg', '1.HQS.10.jpg', '1.HQS.100.jpg']	
  • 1
  • 2
  • 3
  • 4

3.分割函数: os.path.spilt()

test_path = 'data/1.HQS.1.jpg'
test = os.path.split(test_path)
print(test)
#输出一个数组
$('data', '1.HQS.1.jpg')
#可以[1]取文件的名称,然后spilt('.'),以.为分割符继续分割,然后[0]取我们的ID
test_path = 'data/1.HQS.1.jpg'
test = os.path.split(test_path)[1].split('.')[0]
print(test)
#输出
$1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

之后就简单许多,我们只需要遍历文件夹里的人脸数据,然后把图像和ID append到我们的列表里,便可以完成图像与ID的对应。

我们得到了对应ID列表和图像列表之后,就可以开始训练了,训练很简单,opencv给我们提供了识别器,以下为核心API介绍:

recognizer = cv2.face.LBPHFaceRecognizer_create()	#加载识别器
recognizer.train(faces, np.array(ids))				#导入数据,开始训练
recognizer.write('trainer/trainer.yml')				#保存数据
  • 1
  • 2
  • 3

至此,模型训练完毕,接下来就是部署了。

(3)模型的部署

经过我们的训练,我们可以得到一个权重文件yml,我们只需要建立一个识别器对象,读取yml的路径,然后使用.predict,就可以返回2个参数,一个是置信度,一个是ID编号,置信度越高,可信几率越低,下面我们看看具体的步骤:

1.读取图像,转化灰度图

#这个没啥好说的,你可以单张图片预测,也可以开启摄像头或者视频,这里以单张图片举例
test = cv2.imread('data/1.HQS.1.jpg')
gray = cv2.cvtColor(test, cv2.COLOR_BGR2GRAY)  # 转换为灰度图像
  • 1
  • 2
  • 3

2.加载检测器,裁取人脸图像

读取到图片之后,我们需要把图片中的人脸裁出来,然后resize拉一下,方便预测

#加载分类器,路径填自己的
face_detector = cv2.CascadeClassifier('G:/opencv/build/etc/haarcascades/haarcascade_frontalface_default.xml')
#这个函数之前我们介绍过
face = face_detector.detectMultiScale(gray)
#裁出人脸部分,if是为了防止没有检测到人脸导致程序卡死
if face is not None:
    for x, y, w, h in face:
        cv2.rectangle(img, (x, y), (x+w, y+h), color=(0, 0, 255), thickness=2)
        gray = gray[y:y + h, x:x + w]
        gray = cv2.resize(gray,dsize=(500,500))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3.加载识别器,将图像对比

我们得到人脸图片之后,就可以开始预测了,下面介绍一下核心API

/****************************************************
分类器预测函数.predict,输入参数是图片,输出有2个,一个是ID序号
就是我们模型训练时候的那个ids里的内容,还有一个就是置信度,这个置
信度越高表明可信度越低,没错,是反比的关系,下面我们随便举一个例子
来讲。
****************************************************/
#假设我们以及把人脸照片传了进去,他预测完了之后,返回2个值
#id列表
names = ['name1','name1']
id,ture = recogizer.predict(gray)
#根据置信度判断,并且在图像上画图
if (confidence > 50):
	cv2.putText(test,'unknow', (x + 10, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (0, 255, 0), 1)
else:
	cv2.putText(img,str(names[id-1]), (x + 10, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (0, 255, 0), 1)
cv2.imshow('result', test)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

结语:

上篇至此完结,下篇会有完整的代码,如果觉得有错误的地方,欢迎大家讨论交流。

转载声明:原文链接