一、概述
因为课程要求,需要在ros上完成人脸识别,并将识别出来的人脸对应相应的姓名。我们采用python中第三方库face-recognition进行实现。
二、具体实现过程
(一)版本
所使用的系统是ubuntu20.04,ros版本是noetic,在ros上面运行的python解释器是python3.8。程序先经过window系统下进行测试,在window下测试成功后,将其迁移至ubuntu系统下。
face-recognition是别人已经训练好的一个模型,只需要调用其接口就行。
(二)第三方库安装
程序主要使用两个库dlib和face-recognition。在终端中输入如下命令,安装即可。
pip install dlib
pip install face_recognition
需要注意的是,需要首先安装dlib,因为face-recognition需要依赖于dlib,安装顺序出现问题可能会导致之后程序运行出现问题。如果dlib安装不上去,可以考虑使用源码安装,或者进行换源。
可以参考这篇博客,上面也介绍face-recognition的安装和使用。Ros平台下基于face_recognition的人脸检测及识别_ros人脸识别gitee-CSDN博客
(三) 测试版本
1.概述
首先编写测试版本,在ros中进行测试,主要是测试是否可以使用face-recogniton。这部分我用于提取图片特征信息的图片数量比较少。少量图片可以达到检测出人脸并对应姓名的效果,效果不太理想,极其容易误检。但能达到测试效果。
2.代码及分析
#!/usr/bin/env python
import rospy
import cv2
import numpy as np
import face_recognition
import os
from sensor_msgs.msg import Image
from cv_bridge import CvBridge, CvBridgeError
from std_msgs.msg import Header
# 创建图像订阅者
class ImageConverter:
def __init__(self):
self.bridge = CvBridge()
# 训练集文件夹目录
self.train_dir = r"/home/gfc/study_BIT/ros/study/catkin_ws/src/testPublishPhoto/images"
# 准备空的列表来保存人脸编码和对应的人名
# 这个需要提前完成
self.known_face_encodings = []
self.known_face_names = []
# 训练数据
self.trainFunction()
# 计数,用于输出图像
self.num = 0
self.image_raw_sub = rospy.Subscriber('/usb_cam/image_raw', Image, self.subscriberPhotoCallback)
def convert_and_publish(self, img_path):
# 使用OpenCV读取图像
cv_image = cv2.imread(img_path)
# 转换OpenCV图像到ROS图像消息
ros_image = self.bridge.cv2_to_imgmsg(cv_image, "bgr8")
# 创建频率
rate = rospy.Rate(1)
# 发布图像消息
while not rospy.is_shutdown():
self.image_pub.publish(ros_image)
rospy.loginfo("Image converted and published.")
rate.sleep()
# rospy.sp
def subscriberPhotoCallback(self, data):
# 这个函数是用来接受摄像头的数据的
bridge = CvBridge()
image = bridge.imgmsg_to_cv2(data, "bgr8")
face_locations = face_recognition.face_locations(image)
face_encodings = face_recognition.face_encodings(image, face_locations)
for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings):
self.num = self.num + 1
matches = face_recognition.compare_faces(self.known_face_encodings, face_encoding)
name = ""
face_distances = face_recognition.face_distance(self.known_face_encodings, face_encoding)
best_match = np.argmin(face_distances)
if matches[best_match]:
name = self.known_face_names[best_match]
cv2.rectangle(image, (left, top), (right, bottom), (0, 0, 255), 2)
cv2.rectangle(image, (left, bottom - 15), (right, bottom), (0, 0, 255), cv2.FILLED)
font = cv2.FONT_HERSHEY_DUPLEX
cv2.putText(image, name, (left + 6, bottom - 6), font, 3.0, (255, 255, 255), 5)
cv2.imwrite(r"/home/gfc/study_BIT/ros/study/face_detect/src/testPublishPhoto/result/out" + str(self.num) + ".jpg", image)
cv2.waitKey(500)
def trainFunction(self):
for person in os.listdir(self.train_dir):
pix_dir = os.path.join(self.train_dir, person)
# print(pix_dir)
# # 遍历每个人的文件夹中的每一张照片
for item in os.listdir(pix_dir):
if item.endswith("jpg") or item.endswith("png"):
image_path = os.path.join(pix_dir, item)
print(image_path)
# # 加载图片
image = face_recognition.load_image_file(image_path)
# # 尝试提取一张脸的特征编码
face_encoding = face_recognition.face_encodings(image)
if face_encoding:
self.known_face_encodings.append(face_encoding[0])
self.known_face_names.append(person)
def main():
# ROS节点初始化
rospy.init_node('test', anonymous=True)
# 创建ImageConverter实例
image_converter = ImageConverter()
rospy.spin()
if __name__ == "__main__":
main()
这里我创建了一个类用于进行处理(也可以不使用类,更简单一些)。下面对类中函数进行解析。
def trainFunction(self):
for person in os.listdir(self.train_dir):
pix_dir = os.path.join(self.train_dir, person)
# print(pix_dir)
# # 遍历每个人的文件夹中的每一张照片
for item in os.listdir(pix_dir):
if item.endswith("jpg") or item.endswith("png"):
image_path = os.path.join(pix_dir, item)
print(image_path)
# # 加载图片
image = face_recognition.load_image_file(image_path)
# # 尝试提取一张脸的特征编码
face_encoding = face_recognition.face_encodings(image)
if face_encoding:
self.known_face_encodings.append(face_encoding[0])
self.known_face_names.append(person)
trainFunction函数用于使用face-recognition对我们图片进行特征提取,并将提取的特征保存。文件夹结构如下。每一个人创建一个文件夹,对应文件夹下放置这个人的图片。
subscriberPhotoCallback函数用于,当摄像头接收到图片信息后,进入回调函数中进行处理。在处理中将ros中的图像信息先转换为OpenCV可以处理的信息。
在测试中,我们将OpenCV和face-recognition所处理的数据保存到电脑上用于测试。
def subscriberPhotoCallback(self, data):
# 这个函数是用来接受摄像头的数据的
bridge = CvBridge()
image = bridge.imgmsg_to_cv2(data, "bgr8")
face_locations = face_recognition.face_locations(image)
face_encodings = face_recognition.face_encodings(image, face_locations)
for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings):
self.num = self.num + 1
matches = face_recognition.compare_faces(self.known_face_encodings, face_encoding)
name = ""
face_distances = face_recognition.face_distance(self.known_face_encodings, face_encoding)
best_match = np.argmin(face_distances)
if matches[best_match]:
name = self.known_face_names[best_match]
cv2.rectangle(image, (left, top), (right, bottom), (0, 0, 255), 2)
cv2.rectangle(image, (left, bottom - 15), (right, bottom), (0, 0, 255), cv2.FILLED)
font = cv2.FONT_HERSHEY_DUPLEX
cv2.putText(image, name, (left + 6, bottom - 6), font, 3.0, (255, 255, 255), 5)
cv2.imwrite(r"/home/gfc/study_BIT/ros/study/face_detect/src/testPublishPhoto/result/out" + str(self.num) + ".jpg", image)
cv2.waitKey(500)
(四)改进版本
1.概述
上面的测试版本存在一些值得完善的部分,测试时候每次都需要重新对每张图片进行特征提取,只能适用于小数据集的测试,如果增加数据数量则会导致单次运行时候过长。将程序分成特征提取和实时处理两部分。另外,测试版无法在rviz当中显示,需要一点改动。
2.特征提取部分
将特征提取单独分成一个文件,如下所示,将特征提取结果全部写入到一个txt文件中,用于实时处理作为参数进行读取。
代码如下,最后结果写入到txt文件中。
import cv2
import face_recognition
import os
import numpy as np
# 训练集文件夹的目录
train_dir = r"D:\test1\pythonProject1\.venv\faceDetectorTest\images"
# 准备空的列表来保存人脸编码和对应的人名
known_face_encodings = []
known_face_names = []
# 遍历训练目录中的每一个人
for person in os.listdir(train_dir):
pix_dir = os.path.join(train_dir, person)
# print(pix_dir)
# # 遍历每个人的文件夹中的每一张照片
for item in os.listdir(pix_dir):
if item.endswith("jpg") or item.endswith("png"):
image_path = os.path.join(pix_dir, item)
print(image_path)
# # 加载图片
image = face_recognition.load_image_file(image_path)
# # 尝试提取一张脸的特征编码
face_encoding = face_recognition.face_encodings(image)
if face_encoding:
known_face_encodings.append(face_encoding[0])
known_face_names.append(person)
# # 输出测试
# for i in range(len(known_face_encodings)):
# print(known_face_encodings[i])
#
# for i in range(len(known_face_names)):
# print(known_face_names[i])
# 写入txt文件
file = open("example.txt", "w")
# file.write(known_face_encodings[0])
for i in range(len(known_face_encodings)):
for j in range(len(known_face_encodings[i])):
file.write(str(known_face_encodings[i][j]))
file.write(" ")
file.write(known_face_names[i])
file.write("\n")
3.实时监测部分
这个部分基于上面测试版,添加几个功能。
#!/usr/bin/env python
import rospy
import cv2
import numpy as np
import face_recognition
import os
from sensor_msgs.msg import Image
from cv_bridge import CvBridge, CvBridgeError
from std_msgs.msg import Header
# 创建图像订阅者
class ImageConverter:
def __init__(self):
self.bridge = CvBridge()
# 特征信息文件名
self.trainData = r"/home/gfc/study_BIT/ros/study/face_detect/src/testPublishPhoto/data/example.txt"
# 准备空的列表来保存人脸编码和对应的人名
# 这个需要提前完成
self.known_face_encodings = []
self.known_face_names = []
# 训练数据
# self.trainFunction()
# 读取文件中的训练数据
self.readInfo()
# 创建一个图像发布者对象,用来发布处理之后的数据
self.image_pub = rospy.Publisher("/imageDeal", Image, queue_size=5)
self.image_raw_sub = rospy.Subscriber('/usb_cam/image_raw', Image, self.subscriberPhotoCallbackUpdate)
def trainFunction(self):
for person in os.listdir(self.train_dir):
pix_dir = os.path.join(self.train_dir, person)
# print(pix_dir)
# # 遍历每个人的文件夹中的每一张照片
for item in os.listdir(pix_dir):
if item.endswith("jpg") or item.endswith("png"):
image_path = os.path.join(pix_dir, item)
print(image_path)
# # 加载图片
image = face_recognition.load_image_file(image_path)
# # 尝试提取一张脸的特征编码
face_encoding = face_recognition.face_encodings(image)
if face_encoding:
self.known_face_encodings.append(face_encoding[0])
self.known_face_names.append(person)
def readInfo(self):
file = open(self.trainData, "r")
data_raw = file.readlines()
for i in range(len(data_raw)):
tmp = data_raw[i].strip()
tmp = tmp.split(" ")
tmp2 = []
for j in range(len(tmp) - 1):
tmp2.append(float(tmp[j]))
self.known_face_names.append(tmp[len(tmp) - 1])
self.known_face_encodings.append(tmp2)
self.known_face_encodings = np.array(self.known_face_encodings)
def subscriberPhotoCallbackUpdate(self, data):
# 这个函数是用来接受摄像头的数据的
bridge = CvBridge()
image = bridge.imgmsg_to_cv2(data, "bgr8")
# cv2.imshow("hahaha", image_raw)
# cv2.waitKey(100)
face_locations = face_recognition.face_locations(image)
face_encodings = face_recognition.face_encodings(image, face_locations)
for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings):
self.num = self.num + 1
matches = face_recognition.compare_faces(self.known_face_encodings, face_encoding)
name = ""
face_distances = face_recognition.face_distance(self.known_face_encodings, face_encoding)
best_match = np.argmin(face_distances)
if matches[best_match] and face_distances[best_match] < 0.30:
name = self.known_face_names[best_match]
cv2.rectangle(image, (left, top), (right, bottom), (0, 0, 255), 2)
cv2.rectangle(image, (left, bottom - 15), (right, bottom), (0, 0, 255), cv2.FILLED)
font = cv2.FONT_HERSHEY_DUPLEX
cv2.putText(image, name, (left + 6, bottom - 6), font, 3.0, (255, 255, 255), 5)
# cv2.imwrite(r"/home/gfc/study_BIT/ros/study/face_detect/src/testPublishPhoto/result/out" + str(self.num) + ".jpg", image)
# cv2.waitKey(500)
# 将OpenCV的图像信息转换为ROS消息类型
image_message = bridge.cv2_to_imgmsg(image, "bgr8")
self.image_pub.publish(image_message)
cv2.waitKey(3)
def main():
# ROS节点初始化
rospy.init_node('testTran', anonymous=True)
# 创建ImageConverter实例
image_converter = ImageConverter()
# rospy.loginfo("Image converted and published.")
rospy.spin()
if __name__ == "__main__":
main()
其中readInfo用于从txt文件中将数据读取出来。subscriberPhotoCallbackUpdate函数首先将图像消息转化为OpenCV可以处理的图像信息,处理之后,再将其转换为ros发布的图像信息并进行发布。
因为需要特征提取,实时可能稍微有一些延迟。