文章目录
准备工作
实现本工程需要以下基础:
- face_recognition的使用:Face Recognition 人脸识别
- Qt的基本用法:初识QT
- MongoDB基础:MongoDB
- openCV基本用法:OpenCV
软件设计
预期功能
本工具分以几个功能:
- 人脸特征数据录入数据库;
- 识别未知图片中人脸并标记出来;
- 识别摄像头或视频文件画面中人脸,并标记出来。
运行本工具时,先弹出主界面。
主界面包含三个按钮:“录入人脸数据”,“识别图中人脸”,“识别视频画面中人脸”。
点击这三个按钮后,弹出对应的功能窗口,并隐藏主界面窗口。
录入人脸数据窗口主要分三块,可实现如下功能:
groupBox1:用户从本机中选择一张照片打开,原图显示在界面的左侧的groupBox1中,点击“识别图中人脸”按钮,软件识别出图中所有人脸。
groupBox2:在界面中间的groupBox2中显示出识别出的第一张人脸头像,头像下方的选项框可选择数据库中已有数据的人名,也可以添加新人物名,用户写好名字后点击“确认按钮”,提取出该头像的面部特征信息编码,每张脸的特征编码都是一条128维的向量,将该组姓名和特征编码加入缓存中,用户点击下一张按钮,跳到下一张人脸头像,等待用户确认姓名。以此循环,直到用户确认完识别出的全部头像名字后,用户点击“提交数据库”按钮,则将缓存中数据逐条更新到数据库。
groupBox3:包含两个table,第一个table用于显示已确认名字的缓存数据,第二个table主要用于显示数据库中已存入的所有人物名字和对应的面部特征信息条数。用户每提交一次人物信息,更新一次此table。
识别图中人脸窗口有以下功能:
用户从本机中选择一张照片打开,原图显示在界面中,点击“识别人物”按钮,软件先定位出照片中所有人脸并用方框标出,然后依次将识别出的人脸与数据库中的所有特征编码比对,匹配成功则在显示的方框下方显示人物名字(绿色),匹配失败则提示“unknown”(红色)。
识别视频画面中人脸窗口有以下功能:
用户点击“启动摄像头”按钮启动本机摄像头,或点击“打开视频文件”按钮选择视频文件,视频画面显示在界面中间,点击“识别人脸”按钮后,软件识别画面中人脸,与数据库中面部特征比对,找到匹配的人物则在画面中标出绿色人名,否则用红色标记出“unknown”。
模块规划
本工程可划分为以下几个模块:
-
数据库基本操作模块:
db.py
-
人脸识别基本操作模块:
face_recog.py
-
其他基本操作:
myLib.py
-
显示界面模块:
主界面:ui_mainWin.ui
,ui_mainWin.py
;
录入人脸数据窗口:ui_win_inputData.ui
,ui_win_inputData.py
;
识别图中人脸窗口:ui_win_recogFaceInPic.ui
,ui_win_recogFaceInPic.py
;
识别视频中人脸窗口:ui_win_recogFaceInVideo.ui
,ui_win_recogFaceInVideo.py
; -
逻辑函数模块:
mian_win.py
win_inputData.py
win_recogFaceInPic.py
win_recogFaceInVideo.py
数据库基本操作:db.py
本模块定义了数据库中数据的存储格式,并提供主要的数据库操作接口,文件名db.py
。
数据格式
MongoDB数据库中数据以键值对形式表示,因此本例中数据将已字典形式导入,存储每个人物的信息格式如下:
{
"name":<name>,
"facial_feature":[<feature1>,<feature2>,...]
}
其中<name>
为人物名字字符串,<feature1>
为该人物的第一张人脸特征编码向量,<feature2>
为该人物的第二张人脸特征编码向量。由于每个人可能有多张照片数据录入,所以特征数据已list形式存入。当人物只有一个特征向量时,也必须以list形式存储。
由于每条特征信息都是一个128维向量,所以feature1
本身是一个包含128个数字元素的list,因此[<feature1>,<feature2>,...]
是一个二维数组。数组的长度即为该人物的特征向量数。
连接数据库
引用本数据库时将自动连接本地数据库并创建数据集合:
from pymongo import MongoClient
#创建本地连接,localhost指本地连接,若要连接到网络中的数据库,localhost改成ip地址
conn = MongoClient('localhost', 27017)
#连接到face_recog数据库,没有则自动创建,且在本模块中用db表示
db = conn.face_recog
#使用facial_features集合,没有则自动创建,且在本模块中用face_data表示
face_data = db.facial_features
其他数据库操作
- get_features_of(name)
用于获取指定人物的全部特征数据;
输入参数:name
为人物名称字符串;
返回值:列表形式,当数据库中能查找到指定人物的信息时,返回特征列表(二维数组),当数据库中不存在此人物信息时,返回空列表[]
。
代码如下:
def get_features_of(name):
data = [i for i in face_data.find({"name":name})]
if data:
return data[0]['facial_feature']
else:
return []
- insert_data(name,facial_feature)
用于插入一张面部信息;
输入参数:name
为人物名称字符串,facial_feature
为一张人脸的特征编码向量,即包含128元素的一维数组。
本函数先获取数据库中该人物的特征信息,并将待插入的特征向量添加到列表的最后,最后将全部特征信息更新到数据库中,若数据库中不存在指定人物的数据,则插入一条新信息。
def insert_data(name,facial_feature):
features = get_features_of(name)
features += [facial_feature]
face_data.update({"name": name}, {'$set': {"facial_feature": features}},upsert=True)
- delet_data_of(name)
用于删除指定人物的全部信息
def delet_data_of(name):
face_data.remove({'name': name})
- get_all_names()
用于获取数据库中全部人物名称,在数据库中查找全部信息,将所有的人物名称以列表的形式返回。
def get_all_names():
names = []
for i in face_data.find():
names.append(i['name'])
return names
- get_all_namesAndNum()
用于获取数据库中全部人物及数据量,在数据库中查找全部信息,将所有的人物名称以二维列表的形式返回,供显示数据库信息使用。
返回值格式[name,number]
.
def get_all_namesAndNum():
name_nums = []
for i in face_data.find():
name_nums.append([i['name'],len(i['facial_feature'])])
return name_nums
- get_all_features()
用于获取全部面部特征向量;
返回数据库中全部特征向量列表及与之对应的人物列表,供人脸匹配使用。
def get_all_features():
features = []
names = []
for i in face_data.find():
features += i['facial_feature']
names += [i['name']] * len(i['facial_feature'])
return features,names
注意:取name的时候必须先装进列表里再复制,否则字符串会被拆成字符复制。
人脸识别基本操作:face_recog.py
本模块封装了一些本工具中常用的面部识别操作接口,且包含了录入数据时常用的全局变量及其操作:
- find_faces_locations_in(f_name)
用于识别出图中全部人脸,并对每张脸定位。
输入图像文件名,返回列表[top, right, bottom, left].
def find_faces_locations_in(f_name):
# 将jpg文件加载到numpy 数组中
image = face_recognition.load_image_file(f_name)
face_locations = face_recognition.face_locations(image)
# 使用CNN模型, 另请参见: find_faces_in_picture_cnn.py
# face_locations = face_recognition.face_locations(image, number_of_times_to_upsample=0, model="cnn")
return face_locations
- get_img_by_location(img,location)
由上面的函数获取的locations参数截取出单张脸的图片,且图片范围扩大20%;
返回值格式与第一个参数相同,第一个参数是np格式的原始图像,第二个参数为需要截取的图像框位置信息。
def get_img_by_location(img,location):
top, right, bottom, left = location
print(top, bottom, left, right,'raw')
w = right - left
h = bottom - top
top = int(top - h / 5.0)
top = top if top >= 0 else 0
bottom = int(bottom + h / 5.0)
bottom = bottom if bottom <= img.shape[0] else img.shape[0]
right = int(right + w / 5.0)
right = right if right <= img.shape[1] else img.shape[1]
left = int(left - h / 5.0)
left = left if left >= 0 else 0
print(top,bottom,left,right)
return img[top:bottom, left:right]
- find_faces_in(f_name)
找出图像文件中所有的面部特征信息,保存在全局变量中faces_info,face_num和face_now。
输入参数为文件名(含路径);返回值是faces_info,也可通过import此全局变量获取。
此函数主要供录入数据窗口中加载图片后调用。
此函数中实现:加载图片,识别出图片中所有人脸,并分割出人脸图片,每张人脸的图像信息(覆盖)保存到全局变量faces_info中,并初始化全局变量face_num和face_now。
face_num表示本次识别的照片中识别出的人脸总数;
face_now表示当前在界面显示出人脸照片在faces_info中的编号;
faces_info为list型,其元素是字典,包含以下键值对:
face[‘location’] :表示当前人脸在原始图片中的位置,列表型[top, right, bottom, left]
face[‘np_img’] :存储当前人脸图像,为numpyarray型的RGB数据
face[‘Qimg’] :存储当前人脸图像,为QImage型,便于在pt环境中使用和显示,可转换成pix_map型后显示在label中。
代码如下:
def find_faces_in(f_name):
image = face_recognition.load_image_file(f_name)
face_locations = face_recognition.face_locations(image)
global face_num,face_now,faces_info
faces_info = []
face_num = len(face_locations)
face_now = 0
for face_location in face_locations:
face = {
}
np_img = get_img_by_location(image,face_location)
face['location'] = face_location
face['np_img'] = np_img
img2 = cv2.resize(src=np_img, dsize=None, fx=0.2, fy=0.2)
face['Qimg'] = QImage(img2[:], img2.shape[1], img2.shape[0], img2.shape[1] * 3, QImage.Format_RGB888)
faces_info.append(face)
return faces_info
- get_face_encoding_of(face_image)
获取单张脸的list型特征数据。
输入参数是是np的图像数据,输入图像中最好只包含一张脸,因为本函数只返回检测到的第一张脸的特征信息编码。
face_recognition.face_encodings()方法编码出的特征信息是nparray型,而数据库不支持此类型数据的存储,因此通过tolist()转换成list型。
#返回单张脸的list型特征数据
def get_face_encoding_of(face_image):
encode_nparray = face_recognition.face_encodings(face_image)[0]
encode_list = encode_nparray.tolist()
return encode_list
- get_featuresArray_fromDB()
获取数据库中所有面部特征信息列表和与之对应的名字列表,用于面部特征比对用。
由于数据库中存储的特征信息是list型,所以在使用特征信息列表时,需先将list型转换成array型,才能供特征信息比对的接口使用。numpy.array()方法用于list到array的转换。
返回值是包含array型元素的列表和名字列表。
def get_featuresArray_fromDB():
features_li , names = get_all_features()
features_array = []
for i in features_li:
features_array.append(numpy.array(i))
return features_array,names
- update_knownFace_list()
此函数用于更新全局变量known_face_encodings, known_face_names。
在特征信息比对之前先要将数据库中的全部特征信息加载出来,此函数用于将数据库中全部特征信息和对应的名字列表更新到本模块的全局变量known_face_encodings, known_face_names中。在匹配人脸之前调用,识别视频中人脸时不需要处理每一帧时都更新特征信息,只需要在开始播视频之前调用一次即可。
def update_knownFace_list():
global known_face_encodings, known_face_names
known_face_encodings, known_face_names = get_featuresArray_fromDB()
- recog_in( frame , update_features = True ,frame_type = ‘rgb’,scale_rate = 1.0 )
识别一帧图像中的人脸,并将所有识别出的人脸名字标记出来。
输入参数:
frame :一帧图像数据,可以是rgb格式也可以是bgr格式,传入bgr格式数据时还需要通过frame_type指明;
update_features :选择是否更新数据库中数据,默认更新;
frame_type :用于指明第一个参数传入的是rgb数据还是bgr数据;
scale_rate :缩放倍率,当图像帧太大时识别速度会较慢,尤其是在处理视频数据时会影响用户体验,此时适当的缩小图像尺寸可以加快识别速度。默认值为1.0表示不缩放。
以下代码是识别人脸后使用CV2加头像框和标签的过程:
#视频和图片通用:
#video call: recog_in( bgrframe , update_features = False ,frame_type = 'bgr_frame',scale_rate = 4.0 )
#pic call: recog_in( rgbframe , update_features = True , frame_type = 'rgb_frame',scale_rate = 1.0 )
def recog_in( frame , update_features = True ,frame_type = 'rgb',scale_rate = 1.0 ):
#预处理
#是否更新数据库中读出的数据
if update_features :
update_knownFace_list()
#是否需要缩放图像后做识别,scale_rate=2.0时表示画面缩小一倍
if scale_rate != 1.0 :
frame2 = cv2.resize(frame, (0, 0), fx = (1/scale_rate) , fy = ( 1/scale_rate ) )
else:
frame2 = frame
#根据指定图像类型准备好rgb和bgr两种数据
if frame_type == "rgb":
rgb_frame= frame
bgr_frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) #cv画图用
else:
rgb_frame = cv2.cvtColor(frame2, cv2.COLOR_BGR2RGB)
bgr_frame = frame
#编码面部特征
face_locations = face_recognition.face_locations(rgb_frame)
face_encodings = face_recognition.face_encodings(rgb_frame, face_locations)
face_names = []
for face_encoding in face_encodings:
#对图中识别出的人脸一次与数据库中已知人脸信息比对
matches = face_recognition.compare_faces(known_face_encodings, face_encoding)
name = "Unknown"# 默认为unknown
if True in matches:
#若匹配上,则找出对应的name
first_match_index = matches.index(True)
name = known_face_names[first_match_index]
face_names.append(name)
# 将捕捉到的人脸标记出来
for (top, right, bottom, left), name in zip(face_locations, face_names):
# 缩放后的图像放大回原尺寸,在原图上标记
top *= scale_rate #视频 scale_rate = 4
right *= scale_rate
bottom *= scale_rate
left *= scale_rate
#使用CV2标记出人脸框和名字
# 矩形框
cv2.rectangle(bgr_frame, (left, top), (right, bottom), (0, 0, 255), 2)
# 加上标签
cv2.rectangle(bgr_frame, (left, bottom - 35), (right, bottom), (0, 0, 255