最近开始接触机器学习,虽然还在入门阶段,但还是想实际动手操作一下,于是就着手一个比较简单的项目:基于opencv的人脸识别。它不仅能找到人脸,还可以显示是否是之前录入过的人脸,以达到真正的识别效果。
实现的过程如下:
- 摄像头获取实时画面(可以换成视频)
- 捕捉人脸,保存人脸信息,打上标签(相当于对样本做标记,有了标记的样本又称为样例)
- 使用上述样例的训练集训练,得到自己训练的yml文件(?)
- 之后就可以使用自己的分类器对视频内的人脸进行对比识别,如果是则输出名字,不是则显示未知。
首先要做一个自己的模型,录入人脸后进行训练。
首先是录入若干人脸图像(faceImgWrite.py):
import cv2 as cv
import os
# 人脸图像保存处,文件夹的路径
myFacePath = os.path.join(os.path.dirname(__file__), 'src/imwrite/me')
def writePic():
"""
写入人脸数据,并做标记
:return: None 不返回任何值
"""
num = 1 # 计数变量
cap = cv.VideoCapture(0) # 打开摄像头
while cap.isOpened(): # 摄像头打开时始终循环
flag, frame = cap.read() # flag: 有没有截取到图片(bool); frame: 截取到的每一帧图片
cv.imshow('figure', frame) # 在名为figure的窗口里显示视频
k = cv.waitKey(1) & 0xFF # 取按键的ASCII值后8位,降干扰(?)
if k == ord('s'): # 按 s 保存当前图片帧,并给个提示
cv.imwrite(os.path.join(myFacePath, f'{num}.me.jpg'), frame)
print(f'成功保存{num}.me.jpg')
num += 1
elif k == ord('q'): # 按 q 退出
break
# 释放内存
cap.release()
cv.destroyAllWindows()
if __name__ == '__main__':
writePic() # 执行当前文件,测试的时候用,后面省略
接下来使用保存的人脸图片集做训练(dataTraining.py)
import cv2 as cv
import os
from PIL import Image
import numpy as np
def getImgAndLabels(path):
"""
对已保存的人脸图片样本集进行标记
:param path: 图片样本集文件夹路径
:return:
"""
# 创建空列表
facesSamples = []
ids = []
imagePaths = []
# 将文件夹里的所有图片的路径整理进一个列表里
for f in os.listdir(path):
imagePaths.append(os.path.join(path, f))
# 创建分类器(opencv自带)
faceDetector = cv.CascadeClassifier('C:\\Users\\28264\\AppData\\Local\\Programs\\Python\\Python38\\Lib\\site-packages\\cv2\\data\\haarcascade_frontalface_default.xml')
# 对每张图片都做处理
for imgPath in imagePaths:
PILImg = Image.open(imgPath).convert('L')
imgNumpy = np.array(PILImg, 'uint8')
print(imgNumpy)
faces = faceDetector.detectMultiScale(imgNumpy) # 检测人脸,使用默认参数,返回人脸选框的左上角坐标及长宽
id = int(os.path.split(imgPath)[1].split('.')[0]) # 从图片名字中分离出图片索引
# 对每张图片都抽出识别到的脸的部分
for x, y, w, h in faces:
ids.append(id)
facesSamples.append(imgNumpy[y:y+h, x:x+w])
print(id)
return facesSamples, ids
def dataTraining():
"""
将采集到的人脸数据集用作训练
:return: None 不返回任何值
"""
path = os.path.join(os.path.dirname(__file__), 'src/imwrite/me')
faces, ids = getImgAndLabels(path)
# 创建LBPH识别器,使用LBPH做训练,并保存
recognizer = cv.face.LBPHFaceRecognizer_create()
recognizer.train(faces, np.array(ids))
recognizer.write('trainer.yml')
这里使用已有的LBPH算法 cv.face.LBPHFaceRecognizer_create() 直接进行训练,关于该算法的文章可以参考基于LBPH的人脸识别系统 – 知乎 (zhihu.com)。
训练出了自己的识别器(trainer.yml)后,就可以用于人脸识别了。
再单独新建一份python脚本文件存放识别功能(faceRecognize.py):
import os.path
import cv2
# 模型路径、分类器路径、创建列表
trainingData = os.path.join(os.path.dirname(__file__), 'trainer.yml')
classifierPath = "C:\\Users\\28264\\AppData\\Local\\Programs\\Python\\Python38\\Lib\\site-packages\\cv2\\data\\haarcascade_frontalface_alt2.xml"
# 标签列表
names = []
def name():
path = os.path.join(os.path.dirname(__file__), 'src/imwrite/me')
imagePaths=[os.path.join(path, f) for f in os.listdir(path)]
# 从文件名中分离出标签
for imagePath in imagePaths:
name = str(os.path.split(imagePath)[1].split('.', 2)[1])
names.append(name)
# 创建识别器对象(自己训练的)
recognizer = cv2.face.LBPHFaceRecognizer_create()
recognizer.read(trainingData)
def faceRecognizer(cv_img):
"""
传入opencv图片,识别人脸并标记
:param cv_img:
:return:
"""
# 转换为灰度图,同样为了加快后续识别速度,RGB图会有三大组数据,而灰度图仅有一组
grayImg = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
# 创建人脸检测对象,仍然是使用的默认分类器 haarcascade_frontalface_alt2.xml
faceDetector = cv2.CascadeClassifier(classifierPath)
# 检测人脸的一行代码,返回所有的选框左上角坐标与长宽,装在一个列表里
faceData = faceDetector.detectMultiScale(grayImg, 1.01, 5, cv2.CASCADE_SCALE_IMAGE, (100, 100), (300, 300))
for x, y, w, h in faceData:
# 画方框,thickness为实数
cv2.rectangle(cv_img, (x, y), (x+w, y+h), (255, 0, 0), 2)
# 画实心矩形,thickness为-1
cv2.rectangle(cv_img, (x, y-20), (x+w, y), (255, 0, 0), -1)
# 给出图像标签ID和置信度(距离?)
ids, confidence = recognizer.predict(grayImg[y: y+h, x:x + w])
if confidence > 80: # 置信评分大于80则不合格,相当于离标准太远
# 标为未知
cv2.putText(cv_img, f'未知 ({round(confidence, 2)})', (x + 5, y - 5), cv2.FONT_ITALIC, 0.5, (255, 255, 255))
else:
# 标出对应标签
cv2.putText(cv_img, f'{names[ids-1]} ({round(confidence, 2)})', (x + 5, y - 5), cv2.FONT_ITALIC, 0.5, (255, 255, 255))
# 显示带有识别结果的视频
cv2.imshow('figure', cv_img)
def faceRecognizeOnVideo():
"""
人脸识别——视频模式
:return: None 不返回任何值
"""
# 打开摄像头
cap = cv2.VideoCapture(0)
# 实时显示识别结果
while True:
flag, frame = cap.read()
if not flag:
break
# 调用上面刚码好的人脸识别函数
faceRecognizer(frame)
# 等待按键,按下q就终止视频
if ord('q') == cv2.waitKey(5):
break
def faceRecognizeOnPic(picPath):
"""
人脸识别 图片版
:param picPath: 图片路径
:return: None 不返回任何值
"""
img = cv2.imread(picPath)
grayImg = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faceDetecter = cv2.CascadeClassifier(classifierPath) # 装载分类器
faceData = faceDetecter.detectMultiScale(grayImg, 1.1, 5, cv2.CASCADE_SCALE_IMAGE, (100, 100), (300, 300)) # 进行人脸检测
for x, y, w, h in faceData:
# 画方框,thickness为实数
cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)
# 画实心矩形,thickness为-1
cv2.rectangle(img, (x, y-20), (x+w, y), (255, 0, 0), -1)
# 给出图像标签ID和置信度(距离?)
ids, confidence = recognizer.predict(grayImg[y: y+h, x:x + w])
if confidence > 80: # 置信评分大于80则不合格,相当于离标准太远
# 标为未知
cv2.putText(img, f'other ({confidence})', (x + 5, y - 5), cv2.FONT_ITALIC, 0.5, (255, 255, 255))
else:
# 标出对应标签
cv2.putText(img, f'{names[ids-1]} ({confidence})', (x + 5, y - 5), cv2.FONT_ITALIC, 0.5, (255, 255, 255))
# 显示带有识别结果的视频
cv2.imshow('figure', img)
# 等待按键,如果没有此行将无法正常显示图像
cv2.waitKey(0)
# 释放内存
cv2.destroyAllWindows()
if __name__ == '__main__':
name()
picPath = os.path.join(os.path.dirname(__file__), 'src/photo1.jpg')
faceRecognizeOnPic(picPath)
测试的时候总共录入了100张人脸,在同样的环境下得到的效果还是不错的,能正确框住人脸,返回的置信度均小于30,但是换成其他不同环境的照片时(比如室外时拍摄的照片,光照等其他因素有所变化时),虽然也能分辨出是不是录过的人脸,但是置信度普遍偏高,一般都在75左右,而设置的阈值为80,只能勉强合格。针对这种情况,个人的改进想法有以下几点:
- 1.增加训练时的样本数量,在不同的环境下录入人脸;
- 2.进一步调节detectMultiScale的参数,如步长与检测次数;
- 3.寻找其他更好的分类器,这里用的是opencv自带的分类器。
到这里只是做了初步的实现,还有很多地方值得改进。实际也不简单
其实之后想在卡通图片上做进一步的练习,收集某一个角色的画,用作样本以进行训练,然后用视频测试训练结果,看能否正确识别出画面里出现的角色是否学习过。到时候将开一篇新的文章进行记录。