【人工智能学习之人脸识别】
人脸识别思路
因为人脸数据很不好找,训练也比较麻烦,所以我们可以直接使用现成的模型权重。
思路:人脸检测->特征提取->注册/识别
人脸检测可以选择又快又小效果还很棒的DBface克隆链接:DBFace
特征提取我选择的DeepFace中的Facenet512,读者也可以换成其它的模型或者自己训练的模型都可以,这里只做特征提取,所以我直接使用训练好的DeepFace中的Facenet512
有了识别网络和特征网络之后就只剩下后处理部分,我们可以设置待检测区域,只有单张人脸进入该区域,识别或注册程序才会启动。注册需要采集上下左右转头的人脸图像,登录则需要将检测区的人脸与特征库进行比对(比对方法可以采取欧氏距离或者余弦相似度)。
人脸识别代码与讲解
因为借用别人以及训练好的模型,直接导入DBFace和DeepFace使用即可。
不过首先你必须明白DBFace侦测网络返回的各个结果是什么意思,温馨提示:可以查看DBface的common.py。
common.py中的drawbbox中对bbox的使用有很强的参考性,它会让你明白返回的x, y, r, b分别是什么,以及你如何按照你的想法调整。
python
def drawbbox(image, bbox, color=None, thickness=2, textcolor=(0, 0, 0), landmarkcolor=(0, 0, 255)):
if color is None:
color = randcolor(bbox.label)
#text = f"{bbox.label} {bbox.score:.2f}"
text = f"{bbox.score:.2f}"
x, y, r, b = intv(bbox.box)
w = r - x + 1
h = b - y + 1
cv2.rectangle(image, (x, y, r-x+1, b-y+1), color, thickness, 16)
border = thickness / 2
pos = (x + 3, y - 5)
cv2.rectangle(image, intv(x - border, y - 21, w + thickness, 21), color, -1, 16)
cv2.putText(image, text, pos, 0, 0.5, textcolor, 1, 16)
if bbox.haslandmark:
for i in range(len(bbox.landmark)):
x, y = bbox.landmark[i][:2]
cv2.circle(image, intv(x, y), 3, landmarkcolor, -1, 16)
以及DeepFace中特征部分存储在哪一个字段(温馨提示:img_representation[0]['embedding'])。
我的代码:
python
import main
import common
import numpy as np
import torch
import torch.nn.functional as F
import torch.nn as nn
from PIL import Image
from torchvision import transforms
import cv2
from model.DBFace import DBFace
from deepface import DeepFace
import ast
import math
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Resize((160, 160), antialias=True),
])
FEATURE_TXT_PATH = 'datas/feature.txt'
models = ["VGG-Face", "Facenet", "Facenet512", "OpenFace", "DeepFace", "DeepID", "ArcFace", "Dlib"]
# 候选区域
Center = [320,240]
x1 = 160
y1 = 80
x2 = 480
y2 = 400
def preprocess_image(image_path):
image = Image.open(image_path).convert('RGB')
image = transform(image)
return image.unsqueeze(0)
class Face_recognition(nn.Module):
def __init__(self):
super().__init__()
self.DeepFace = DeepFace.build_model(model_name="Facenet512")
self.DBFace = DBFace().eval().cuda()
self.DBFace.load("model/dbface.pth")
try:
self.features = self.txt_read()
except:
print('没有找到特征文件!')
def verify_faces(self, img, log_features):
for log_feature in log_features:
img_representation = DeepFace.represent(img, "Facenet512", enforce_detection=False)
target_face_feature = img_representation[0]['embedding']
feature1 = np.array(target_face_feature)
feature2 = np.array(log_feature)
# 计算 L2 距离
distance = np.linalg.norm(feature1 - feature2)
# 计算余弦相似度
# cosine_similarity = np.dot(target_face_feature, log_feature) / (np.linalg.norm(target_face_feature) * np.linalg.norm(log_feature))
if distance < 0.4:
return True
else:
continue
return False
def face_log(self, img):
img_representation = DeepFace.represent(img, "Facenet512", enforce_detection=False)
face_feature = img_representation[0]['embedding']
with open(FEATURE_TXT_PATH, 'a') as file:
file.write(str(face_feature) + '\n')
print('注册完成!')
def txt_read(self):
all_feature = []
with open(FEATURE_TXT_PATH, 'r') as file:
features = file.readlines()
for feature in features:
all_feature.append(ast.literal_eval(feature))
return all_feature
def face_detect_log(self):
left = True
right = True
center = True
up = True
down = True
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
ok, frame = cap.read()
img = frame.copy()
while ok:
objs = main.detect(self.DBFace, frame)
for obj in objs:
common.drawbbox(frame, obj)
if len(objs) > 1:
print("识别到多张人脸,仅支持单人脸识别认证!")
cv2.putText(frame, 'Multiple faces detected, only single face recognition supported!', (x1-160, y1-40), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, color=[0, 0, 255])
else:
for idx, land in enumerate(obj.landmark):
x = int(land[0])
y = int(land[1])
cv2.putText(frame, f'{idx}', (x, y), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, color=[0, 0, 255])
# 后处理
if self.set_head_in_rang(frame,obj):
creen = self.creen_face(img, obj)
if up:
cv2.putText(frame,'Please up',(x1-80,y1-40), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, color=[255, 0, 0])
if self.turn_head_up(obj):
self.face_log(creen)
up = False
if down and up == False:
cv2.putText(frame, 'Please down', (x1-80,y1-40), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, color=[255, 0, 0])
if self.turn_head_down(obj):
self.face_log(creen)
down = False
if left and up == False and down == False:
cv2.putText(frame, 'Please left', (x1-80,y1-40), fontFace=cv2.FONT_HERSHEY_SIMPLEX,
fontScale=1, color=[255, 0, 0])
if self.turn_head_left(obj):
self.face_log(creen)
left = False
if right and up == False and down == False and left == False:
cv2.putText(frame, 'Please right', (x1-80,y1-40), fontFace=cv2.FONT_HERSHEY_SIMPLEX,
fontScale=1, color=[255, 0, 0])
if self.turn_head_right(obj):
self.face_log(creen)
right = False
if center and up == False and down == False and left == False and right == False:
cv2.putText(frame, 'Please place the face in the center', (x1-80,y1-40), fontFace=cv2.FONT_HERSHEY_SIMPLEX,
fontScale=1, color=[255, 0, 0])
if self.turn_head_center(obj):
self.face_log(creen)
center = False
if up == False and down == False and left == False and right == False and center == False:
cv2.putText(frame, 'Registration completed!', (x1-80,y1-40), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1,
color=[0, 0, 255])
return True
cv2.imshow("demo DBFace", frame)
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
ok, frame = cap.read()
img = frame.copy()
cap.release()
cv2.destroyAllWindows()
def set_head_in_rang(self,image,bbox):
top_left = (x1, y1)
bottom_right = (x2, y2)
x, y, r, b = bbox.box
cv2.rectangle(image, top_left, bottom_right, color=(255, 0, 0), thickness=3)
if y >= y1 and b <= y2 and x >= x1 and r <= x2:
return True
else:
cv2.putText(image,'Please place the face in the detection area.',(x1-160,y1-40), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, color=[255, 0, 0])
return False
def turn_head_up(self, obj):
if math.fabs(obj.landmark[2][1] - obj.box[1]) / obj.height < 0.35:
print('人脸已上转')
return True
else:
print('请向上转头')
return False
def turn_head_down(self, obj):
if math.fabs(obj.landmark[2][1] - obj.box[1]) / obj.height > 0.65:
print('人脸已下转')
return True
else:
print('请向下转头')
return False
def turn_head_left(self, obj):
if math.fabs(obj.landmark[2][0] - obj.box[0]) / obj.width < 0.35:
print('人脸已左转')
return True
else:
print('请向左转头')
return False
def turn_head_right(self, obj):
if math.fabs(obj.landmark[2][0] - obj.box[2]) / obj.width < 0.35:
print('人脸已右转')
return True
else:
print('请向右转头')
return False
def turn_head_center(self, obj):
if math.sqrt(math.pow(obj.landmark[2][0] - obj.center[0],2) + math.pow(obj.landmark[2][1] - obj.center[1],2)) < 5:
print('人脸已转正')
return True
else:
print('请转正人脸,并置于检测框中心')
return False
def face_detect_check(self):
config = False
cap = cv2.VideoCapture('datas/test4.jpg') # 摄像头:0
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
ok, frame = cap.read()
img = frame.copy()
while ok:
objs = main.detect(self.DBFace, frame)
for obj in objs:
common.drawbbox(frame, obj)
if len(objs) > 1:
print("识别到多张人脸,仅支持单人脸识别认证!")
else:
creen = self.creen_face(img, obj)
for idx, land in enumerate(obj.landmark):
x = int(land[0])
y = int(land[1])
cv2.putText(frame, f'{idx}', (x, y), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1,
color=[0, 0, 255])
if self.verify_faces(creen, self.features):
config = True
print('人脸识别成功!!!')
else:
print('人脸识别失败!')
cv2.imshow("demo DBFace", frame)
key = cv2.waitKey(0) & 0xFF
if key == ord('q') or config:
break
ok, frame = cap.read()
img = frame.copy()
cap.release()
cv2.destroyAllWindows()
def creen_face(self, img, obj):
x, y, r, b = obj.box
creen = img[int(y):int(b + 1),int(x):int(r + 1), :]
cv2.imshow('creen',creen)
# 计算两眼之间的斜率
dx = obj.landmark[1][0] - obj.landmark[0][0]
dy = obj.landmark[1][1] - obj.landmark[0][1]
angle = np.degrees(np.arctan2(dy, dx))
# 假设 image 是你要摆正的人脸图像
aligned_face = align_face(creen, angle)
cv2.imshow('aligned_face',aligned_face)
return creen
def align_face(image, angle):
# 获取图像的尺寸
(h, w) = image.shape[:2]
# 创建旋转矩阵
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
# 执行仿射变换
aligned = cv2.warpAffine(image, M, (w, h),
flags=cv2.INTER_CUBIC,
borderMode=cv2.BORDER_CONSTANT,
borderValue=(0, 0, 0))
# 创建一个正方形底图
region_mask = np.zeros((h, h, 3), dtype=np.uint8)
# 计算region图片在底图上的起始位置
start_x = (h - w) // 2
start_y = (h - h) // 2
# 使用NumPy索引将region图片放置到底图上
region_mask[start_y:start_y + h, start_x:start_x + w, :] = aligned
return region_mask
def onnx_dbface(output_path="dbface.onnx"):
# 实例化DBFace模型并加载权重
face_detector = DBFace().eval()
face_detector.load("model/dbface.pth")
# 设置模型输入的动态轴,这里我们假设输入图片大小为640x640
dynamic_axes = {'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}}
# 设置一个随机的或真实的输入张量,用于导出模型
dummy_input = torch.randn(1, 3, 640, 640)
# 导出模型到ONNX格式
torch.onnx.export(face_detector,
dummy_input,
output_path,
verbose=False,
opset_version=11,
input_names=['input'],
output_names=['output'],
dynamic_axes=dynamic_axes)
if __name__ == "__main__":
Face = Face_recognition()
if Face.face_detect_log():
print('注册完毕!!!')
# if Face.face_detect_check():
# print('人脸识别成功!!!')
# img = cv2.imread('datas/test4.jpg')
# if Face.verify_faces(img, Face.features):
# print('人脸认证通过')
# else:
# print('人脸认证未通过')
# onnx_dbface()
效果图