最近更新于 2024-05-05 14:19
本文主要是为了介绍使用 face_recognition(后简称 fr),该项目提供的 API 可以快速实现人脸识别的功能。
fr 本身是一个开源项目,地址位于:https://github.com/ageitgey/face_recognition,中译文档:https://github.com/ageitgey/face_recognition/blob/master/README_Simplified_Chinese.md
安装 fr 后,可以通过命令行使用,也可以通过编写 Python 程序使用接口。
测试环境
Ubuntu 20.04 x86_64
Python 3.9.10
face_recognition 1.3.0
pip3 install face_recognition==1.3.0
OpenCV 4.5.5
pip3 install opencv-python==4.5.5.64
命令行使用
检测人脸
# 单张图片 face_detection [图片名] # 多张图片 face_detection [图片所在路径]
我在网上下载的几张拜登的图片进行测试

检测到人脸后会分别显示 上、右、下、左的坐标

识别人脸
face_recognition [已知的人脸图片的文件夹] [待识别的人脸图片的文件夹]
我先创建两个文件夹 known 和 unknow,known 中存放已知的人脸,unknow 中存放待识别的人脸。


测试时发现出了问题,图片2明明只有川普,却有识别出拜登

那么此时可以调节容错率,默认容错率是 0.6,那么这里设置为 0.54 试试
--tolerance=0.54
这次识别就没问题了

如果要查看识别的人脸匹配程度,可以加上参数
--show-distance true
数值越小匹配度越高

当图片量比较大时,也可以支持并行,通过参数指定并行数量
--cpus
如果传 -1 则对应所有 CPU 核心

我这里测试出现了意外,居然并行数越多越慢,我猜测可能是图片数量比较少,就两张,本来很简单的工作,强行拆分到几个核心去,时间就耗在了分配上。
Python 开发
检测人脸
素材图片
from PIL import Image
import face_recognition
# 导入图片,需要自己指定图片
image = face_recognition.load_image_file("faces.png")
# 寻找图片中所有的人脸
# 基于 HOG-based 模型
# 该模型的准确度很高,不过相对 CNN 低,也没有 GPU 加速
face_locations = face_recognition.face_locations(image)
print("我在图片中发现了 {} 张脸。".format(len(face_locations)))
for face_location in face_locations:
# 显示每张人脸的位置信息
top, right, bottom, left = face_location
print("人脸位置: 上: {}, 左: {}, 下: {}, 右: {}".format(top, left, bottom, right))
# 单独截取人脸出来截取
face_image = image[top:bottom, left:right]
pil_image = Image.fromarray(face_image)
pil_image.show()

上面的人脸检测是基于 HOG-based 模型,如果需要更高的准确度,可以使用 CNN。
但是相应的速度会很慢,有 GPU 加速支持的话会好一些。face_recognition 的底层依赖的是 Dlib 库,如果需要 GPU 加速支持,那么需要先配置 CUDA,然后再编译安装 Dlib,参阅:https://blog.iyatt.com/?p=1237

这里我是用的自己的笔记本电脑测试,有 NVIDIA 的独显。如果是用树梅派,那就没法用 GPU 加速了,NVIDIA 的 Jetson 开发板有 GPU,目前还没测试,等 fr 整体介绍写完后,我再看看。
from PIL import Image
import face_recognition
image = face_recognition.load_image_file("faces.png")
# CNN 模型
# number_of_times_to_upsample 多次采样次数,值越大,可以发现更小的人脸
face_locations = face_recognition.face_locations(image, number_of_times_to_upsample=0, model="cnn")
print("I found {} face(s) in this photograph.".format(len(face_locations)))
print("我在图片中发现了 {} 张脸。".format(len(face_locations)))
for face_location in face_locations:
# 显示每张人脸的位置信息
top, right, bottom, left = face_location
print("人脸位置: 上: {}, 左: {}, 下: {}, 右: {}".format(top, left, bottom, right))
# 单独截取人脸出来截取
face_image = image[top:bottom, left:right]
pil_image = Image.fromarray(face_image)
pil_image.show()

当运行这个程序进行检测人脸,可以在左边(nvidia-smi -l 命令持续查看 GPU 资源使用信息)看到 GPU 使用率瞬间上升到 67%,并多出了一个 Python3 进程,代表 GPU 参与了加速计算。
有了 GPU 加速,检测速率会提高很多。前面是单张图像的,现在引入多张图像,使用一段小视频演示。
import face_recognition
import cv2
# 读视频
video_capture = cv2.VideoCapture("short_hamilton_clip.mp4")
frames = []
frame_count = 0
while video_capture.isOpened():
# 从视频中获取一帧图像
ret, frame = video_capture.read()
# 视频结尾退出循环
if not ret:
break
# 将图像从 OpenCV 的 BGR 转为 RGB
frame = frame[:, :, ::-1]
# 将视频中每一帧图像都存入列表中
frame_count += 1
frames.append(frame)
# batch 默认大小为 128,如果显存足够大,可以改为 128
# 我笔记本电脑独显就 2GB,同时处理的图像数再大一点就会超过显存容量,运行出错
if len(frames) == 28:
batch_of_face_locations = face_recognition.batch_face_locations(frames, number_of_times_to_upsample=0)
# 列出找到的所有人脸
for frame_number_in_batch, face_locations in enumerate(batch_of_face_locations):
number_of_faces_in_frame = len(face_locations)
frame_number = frame_count - 128 + frame_number_in_batch
print("在 {} 帧图像中找到了 {} 张人脸".format(frame_number, number_of_faces_in_frame))
for face_location in face_locations:
# 显示出人脸在对应所在图片中的位置
top, right, bottom, left = face_location
print("人脸位于 上:{} 左: {} 下: {} 右: {}".format(top, left, bottom, right))
# 清空 batch
frames = []

人脸关键点提取
素材图片
from PIL import Image, ImageDraw
import face_recognition
# 导入图片
image = face_recognition.load_image_file("two_people.jpg")
# 寻找图像中所有人脸的面部特征
face_landmarks_list = face_recognition.face_landmarks(image)
print("在图像中找到 {} 张人脸".format(len(face_landmarks_list)))
# 创建一个 PIL 绘图对象,以便在图像中绘画
pil_image = Image.fromarray(image)
d = ImageDraw.Draw(pil_image)
for face_landmarks in face_landmarks_list:
# 显示人脸面部特征在图像中的位置
for facial_feature in face_landmarks.keys():
print("特征 {} 位于 {}".format(facial_feature, face_landmarks[facial_feature]))
# 用线条勾勒出面部特征
for facial_feature in face_landmarks.keys():
d.line(face_landmarks[facial_feature], width=5)
# 显示图片
pil_image.show()

人脸识别
图像素材
import face_recognition
# 导入图像
biden_image = face_recognition.load_image_file("biden.jpg")
obama_image = face_recognition.load_image_file("obama.jpg")
unknown_image = face_recognition.load_image_file("obama2.jpg")
# Get the face encodings for each face in each image file
# Since there could be more than one face in each image, it returns a list of encodings.
# But since I know each image only has one face, I only care about the first encoding in each image, so I grab index 0.
# 检测图像中人脸
# 一张图像可能含有多张人脸,返回结果是一个列表
# 但是根据这里准备的素材,我自己是知道一张图片只有一张人脸,所以检测到的人脸肯定位于下标 0 的位置
try:
biden_face_encoding = face_recognition.face_encodings(biden_image)[0]
obama_face_encoding = face_recognition.face_encodings(obama_image)[0]
unknown_face_encoding = face_recognition.face_encodings(unknown_image)[0]
except IndexError:
print("我在其中一张图像中未发现任何人脸,请准备含有人脸的图片文件!")
quit()
# 已知的人脸
known_faces = [biden_face_encoding, obama_face_encoding]
# 结果返回 True 或 False,告诉我们待识别的人脸是否有匹配的已知人脸
results = face_recognition.compare_faces(known_faces, unknown_face_encoding)
print("识别到的人脸是 Biden? {}".format(results[0]))
print("识别到的人脸是 Obama? {}".format(results[1]))
print("识别到的人脸是新面孔?没有匹配的已知人脸? {}".format(not True in results))

人脸识别完整样例程序,支持实时预览
'''
人脸识别样例程序
视频源可以来自于摄像头、视频文件、视频源链接
实时预览识别情况,对识别出的人脸进行标注
使用时需要在代码所在目录下创建一个文件夹名为 known_faces ,将已知人脸图片放在里面,图片格式要求为 .jpg,不能在该文件夹下放置其它文件
如果识别时在已知人脸中有匹配的图像,那么会用已知人脸图片名对识别到的人脸进行标注
该样例程序单纯完整地示例一遍人脸识别的实现,并未写异常处理,如果未按要求放置已知人脸图片文件,可能会出现意想不到的错误。
Copyright (C) 2022 IYATT-yx iyatt@iyatt.com
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
import os
import cv2
from cv2 import batchDistance
import numpy as np
from face_recognition import *
class Face_recognition(object):
def __init__(self, cap):
# 已知人脸的路径
known_face_folder = os.sep.join([os.path.abspath(os.path.dirname(__file__)), 'known_faces'])
# 已知人脸图片的文件名
self.known_face_names = [known_face_name.strip('.jpg') for known_face_name in os.listdir(known_face_folder)]
# 已知人脸图片的绝对路径
known_face_paths = [os.sep.join([known_face_folder, known_face_name + '.jpg']) for known_face_name in self.known_face_names]
# 导入已知人脸的图片
known_face_imgs = [load_image_file(known_face_name) for known_face_name in known_face_paths]
# 提取人脸并编码
self.known_face_encodings = [face_encodings(known_face_img, face_locations(known_face_img))[0] for known_face_img in known_face_imgs]
# 打开视频流
self.cap = cv2.VideoCapture(cap)
# 保存上次的计数,用于 FPS 计算
self.last = 0
def add_fps(self, img, x, y, r, g, b):
now = cv2.getTickCount()
fps = int(cv2.getTickFrequency() / (now - self.last))
cv2.putText(img, 'FPS: {}'.format(fps), (x,y), cv2.FONT_HERSHEY_COMPLEX, 1, (b,g,r))
self.last = now
def run(self):
while True:
ret, bgr_img = self.cap.read()
# 视频播放完就退出
if not ret:
break
# BGR 转 RGB
rgb_img = bgr_img[:, :, ::-1]
# 在图像上添加 FPS
self.add_fps(bgr_img, 50, 50, 255, 0, 0)
# 检测人脸
locations = face_locations(rgb_img)
# 没有检测到人脸继续下一帧
if not locations:
cv2.imshow('face_recognition', bgr_img)
if cv2.waitKey(1) == 27:
break
continue
# 对检测到的人脸编码
unknow_face_encodings = face_encodings(rgb_img, locations)
# 将待检测的人脸数据与已知人脸数据进行对比
face_names = []
for unknow_face_encoding in unknow_face_encodings:
matches = compare_faces(self.known_face_encodings, unknow_face_encoding)
name = 'stranger'
distance = face_distance(self.known_face_encodings, unknow_face_encoding)
best_match_idx = np.argmin(distance)
if matches[best_match_idx]:
name = self.known_face_names[best_match_idx]
print('识别为 {},与 {} 的匹配度分别为:{}'.format(name, self.known_face_names, 1 - distance))
face_names.append(name)
# 根据检测到的人来内位置在图像上框画出来,并标注识别结果
for (top, right, bottom, left), name in zip(locations, face_names):
cv2.rectangle(bgr_img, (left, top), (right, bottom), (0, 0, 255), 2)
cv2.putText(bgr_img, name, (left + 6, bottom - 6), cv2.FONT_HERSHEY_DUPLEX, 1.0, (255, 255, 255), 1)
# 预览图像
cv2.imshow('face_recognition', bgr_img)
if cv2.waitKey(1) == 27: # 按 Esc 退出
break
def __del__(self):
self.cap.release() # 析构释放视频采集对象
if __name__ == '__main__':
fr = Face_recognition(0) # 指定数字就对应系统摄像头编号,另外也可以指定视频路径或者视频源的链接
fr.run() # 运行识别程序
运行效果




