4.2-基于opencv的人脸检测与重识别

  1. 实训目的

基于opencv实现小车在gazebo环境中的人脸识别任务。首先,学习使用官方训练的分类器进行小车摄像头画面中人脸的检测。其次,自建人脸图像训练集完成人脸识别模型的训练。最后,学习加载训练得到的分类器文件,结合人脸检测任务的输出,实现自定义人脸的识别。

  1. 实训操作步骤

  • 实现基于Haar特征的人脸检测,其中包括:

    • 获取opencv官方训练的基于Haar特征的人脸检测器文件,将其保存在自己的工作空间下面。

    • 在脚本中调用该检测器完成人脸的检测。

  • 训练人脸识别模型,其中包括:

    • 准备人脸识别训练集。

    • 编写脚本,遍历训练集目录,生成训练好的分类器文件及人名列表,用于下一步人脸识别脚本调用。

  • 加载模型训练结果文件,实现基于EigenFaces的人脸识别

  1. 基于Haar特征人脸检测

首先,我们利用opencv中的CascadeClassifier级联分类器实现人脸检测,并标注出人脸在一帧图像中的位置和大小。

级联分类器是通过不同的特征进行一步步筛选,得出这些特征组合所属的最终分类,它将一个复杂的分类问题(如人脸检测)拆解为一个个简单的分类问题(如眼睛、鼻子、嘴巴等的检测),再通过级联条件的判断得出复杂问题的分类结果,有助于提升分类的速度。

在人脸检测问题中,人脸图像中包含的模式特征十分丰富,如直方图特征、颜色特征、模板特征、结构特征及Haar特征等。其中,Haar特征是将像素划分为很多矩形模块,求相邻模块之间的差值以反映该处图像的灰度变化,以此特征作为图像的分类依据。例如:眼睛比脸颊颜色要深鼻梁两侧比鼻梁颜色要深,嘴巴比周围颜色要深等。满足以上所有灰度变化特征,则判断该图像极有可能是一张人脸。

这里我们使用基于Haar特征的CascadeClassifier级联分类器实现人脸检测。

在工作空间src目录下打开终端输入以下代码创建人脸检测功能包:

$  catkin_create_pkg proj04_face_detector std_msgs rospy sensor_msgs cv_bridge

在功能包下,建立/cascades目录,用于存放opencv官方基于Haar特征的人脸检测级联分类器文件。文件的获取可访问opencv在github上的官方仓库,所在的目录地址如下:https://github.com/opencv/opencv/tree/master/data/haarcascades,其中包含了十余个不同部位的分类器文件,可以根据需要选择合适的文件,如下图:

1 opencv官方级联分类器

这里我们将haarcascade_frontalface_default.xml与haarcascade_profileface.xml拷贝至/cascades,其中前者为正面人脸检测文件,后者为侧面人脸检测文件。

建立/scripts目录,在其中添加基类脚本ros_opencv.py。我们的人脸检测脚本与上一节蓝色圆形识别脚本相同,都需要基于这个基类,通过重写图像处理方法process_image()的方式实现功能。代码如下:

ros_opencv.py:

  1. #!/usr/bin/env python  
    
    # coding:utf-8  
    
      
    
    import rospy  
    
    import cv2  
    
    from cv_bridge import CvBridge,CvBridgeError  
    
    from sensor_msgs.msg import Image  
    
    from geometry_msgs.msg import  Twist  
    
    import numpy as np  
    
    import time  
    
    import os, sys  
    
      
    
    class ROS2OPENCV(object):  
    
        # 基类的初始化方法,实例化时自动调用  
    
        def __init__(self,node_name):  
    
            self.node_name = node_name  
    
            os.chdir(sys.path[0])  
    
            rospy.init_node(node_name)  
    
            rospy.on_shutdown(self.shutdown)  
    
            ### 定义ros图像消息的订阅者和处理后图像的发布者  
    
            # 发布/display_image话题,类型为Image,队列大小设置为1  
    
            self.rgb_image_pub = rospy.Publisher("/display_image",Image,queue_size=1)  
    
            # 订阅/camera/rgb/image_raw图像话题,回调函数rgb_image_callback(self, data)  
    
            # 该回调函数实现了图像处理、帧率输出  
    
            self.rgb_topic = rospy.get_param("~rgb_image_topic", "/camera/rgb/image_raw")  
    
            self.rgb_image_sub = rospy.Subscriber(self.rgb_topic,Image,self.rgb_image_callback)  
    
            # 订阅深度图像  
    
            self.depth_topic = rospy.get_param("~depth_image_topic", "/camera/depth/image_raw")  
    
            self.depth_image_sub = rospy.Subscriber(self.depth_topic,Image,self.depth_image_callback)  
    
            self.move = rospy.get_param("~if_move", False)   
    
            # 定义机器人速度消息的发布者,用于机器人运动驱动   
    
            self.cmd_pub = rospy.Publisher('/cmd_vel', Twist, queue_size=1)  
    
            self.bridge = CvBridge()  
    
            self.frame = None  
    
            self.frame_width = None  
    
            self.frame_height = None  
    
            self.frame_size = None  
    
            self.track_box = None  
    
            self.detect_box = None  
    
            self.display_box = None  
    
            self.marker_image = None  
    
            self.processed_image = None   
    
            self.display_image = None  
    
            self.target_center_x = None  
    
            self.cascade_frontal = None  
    
            self.cascade_profile = None  
    
            self.cps = 0  
    
            self.cps_values = list()  
    
            self.cps_n_values = 20  
    
            self.linear_speed = 0.0  
    
            self.angular_speed = 0.0  
    
              
    
      
    
        def rgb_image_callback(self, data):  
    
            start = time.time()  
    
            frame = self.convert_image(data)  
    
            if self.frame is None:  
    
                self.frame = frame.copy()  
    
                self.marker_image = np.zeros_like(frame)  
    
                self.frame_size = (frame.shape[1], frame.shape[0])  
    
                self.frame_width, self.frame_height = self.frame_size  
    
            else:  
    
                self.frame = frame.copy()  
    
                self.marker_image = np.zeros_like(frame)  
    
                # 调用图像处理方法,获取处理后图像  
    
                processed_image = self.process_image(frame)  
    
                self.processed_image = processed_image.copy()  
    
                self.display_image = cv2.bitwise_or(self.processed_image, self.marker_image)  
    
                # 绘制跟踪框  
    
                if self.track_box is not None and self.is_rect_nonzero(self.track_box):  
    
                    tx, ty, tw, th = self.track_box  
    
                    cv2.rectangle(self.display_image,(tx,ty),(tx+tw,ty+th),(255,255,0),2)  
    
      
    
                    ### 计算机器人移动速度  
    
                    # 计算跟踪目标中心x坐标  
    
                    self.target_center_x = int(tx+tw/2)  
    
                    # 计算目标中心与视野中心x坐标的偏移量  
    
                    offset_x = (self.target_center_x - self.frame_width/2)  
    
                    # 计算目标区域的面积  
    
                    target_area = tw*th  
    
                    # 若允许机器人跟踪目标  
    
                    if self.move:  
    
                        # 目标距离较远时速度大  
    
                        if target_area < 25000:  
    
                            linear_vel = 0.3  
    
                        # 到达目标时停止前进  
    
                        elif target_area > 45000:  
    
                            linear_vel = 0.0  
    
                        # 接近目标时速度减慢  
    
                        else:  
    
                            linear_vel = 0.1  
    
                        # 根据偏移量计算角速度  
    
                        angular_vel = float(-0.01 * (offset_x))  
    
                        # 设置角速度极大极小值  
    
                        if angular_vel > 0.1:  
    
                            angular_vel = 0.1  
    
                        if angular_vel < -0.1:  
    
                            angular_vel = -0.1  
    
                        # 调用update_cmd方法发布速度消息  
    
                        self.update_cmd(linear_vel,angular_vel)  
    
                # 绘制检测框  
    
                elif self.detect_box is not None and self.is_rect_nonzero(self.detect_box):  
    
                    dx, dy, dw, dh = self.detect_box  
    
                    cv2.rectangle(self.display_image, (dx, dy), (dx+dw, dy+dh), (255, 50, 50), 2)  
    
                ### 计算时间与帧率,并打印至图像  
    
                # self.cps_values包含若干条最新fps数据,最多为20条,对其求和取平均得到cps值  
    
                end = time.time()  
    
                duration = end - start  
    
                fps = int(1.0/duration)  
    
                self.cps_values.append(fps)  
    
                if len(self.cps_values)>self.cps_n_values:  
    
                    self.cps_values.pop(0)  
    
                self.cps = int(sum(self.cps_values)/len(self.cps_values))  
    
                # 根据不同的窗体大小设置字体位置  
    
                font_face = cv2.FONT_HERSHEY_SIMPLEX  
    
                font_scale = 0.5  
    
                if self.frame_size[0] >= 640:  
    
                    vstart = 25  
    
                    voffset = int(50+self.frame_size[1]/120.)  
    
                elif self.frame_size[0] == 320:  
    
                    vstart = 15  
    
                    voffset = int(35+self.frame_size[1]/120.)  
    
                else:  
    
                    vstart = 10  
    
                    voffset = int(20 + self.frame_size[1] / 120.)  
    
                # cv2.putText函数实现在画面上输出文字信息  
    
                cv2.putText(self.display_image, "CPS: " + str(self.cps), (10, vstart),font_face, font_scale,(255, 255, 0))  
    
                cv2.putText(self.display_image, "RES: " + str(self.frame_size[0]) + "X"+ str(self.frame_size[1]), (10, voffset), font_face, font_scale, (255, 255, 0))  
    
      
    
                # 发布处理后的图像  
    
                try:  
    
                    self.rgb_image_pub.publish(self.bridge.cv2_to_imgmsg(self.display_image,"bgr8"))  
    
                except CvBridgeError as e:  
    
                    print(e)  
    
      
    
      
    
        # 深度图像回调函数  
    
        def depth_image_callback(self,data):  
    
            depth_frame = self.convert_depth_image(data)  
    
          
    
        # 转换ros图像为cv2图像  
    
        def convert_image(self,ros_image):  
    
            try:  
    
                cv_image = self.bridge.imgmsg_to_cv2(ros_image,"bgr8")  
    
                cv_image = np.array(cv_image,dtype= np.uint8)  
    
                return cv_image  
    
            except CvBridgeError  as e:  
    
                print (e)  
    
      
    
        # 转换ros深度图为cv2图像  
    
        def convert_depth_image(self,ros_image):  
    
            try:  
    
                depth_image = self.bridge.imgmsg_to_cv2(ros_image,"passthrough")  
    
                depth_image =np.array(depth_image,dtype=np.float32)  
    
                return depth_image  
    
            except CvBridgeError as e:  
    
                print (e)  
    
      
    
        # 图像处理方法  
    
        def process_image(self,frame):  
    
            return frame  
    
      
    
        # 深度图处理方法  
    
        def process_depth_image(self,frame):  
    
            return frame  
    
      
    
        # 判断矩形框是否存在  
    
        def is_rect_nonzero(self,rect):  
    
            try:  
    
                (_,_,w,h) = rect  
    
                return ((w>0)and(h>0))  
    
            except :  
    
                try:  
    
                    ((_,_),(w,h),a) = rect  
    
                    return (w > 0)and (h > 0)  
    
                except:  
    
                    return False  
    
      
    
        # shut down  
    
        def shutdown(self):  
    
            rospy.loginfo("Shutting down node")  
    
      
    
        # 构造速度消息并发布  
    
        def update_cmd(self, linear_speed, angular_speed):  
    
            self.linear_speed = linear_speed  
    
            self.angular_speed = angular_speed  
    
            move_cmd = Twist()  
    
            move_cmd.linear.x = linear_speed  
    
            move_cmd.angular.z = angular_speed  
    
            self.cmd_pub.publish(move_cmd)  
    
      
    
      
    
    if __name__ == "__main__":  
    
        try:  
    
            ROS2OPENCV("ros_opencv")  
    
            rospy.loginfo("Please subscribe the ROS image.")  
    
            rospy.spin()  
    
        except KeyboardInterrupt:  
    
            print ("Shutting down cv_bridge_test node")  

在/scripts目录下新建一个python脚本face_detector.py,代码如下。

face_detector.py

  1. #!/usr/bin/env python  
    
    # -*- coding: utf-8 -*-  
    
    import rospy  
    
    import cv2  
    
    from ros_opencv import ROS2OPENCV  
    
    import os  
    
    import sys  
    
      
    
    class FaceDetector(ROS2OPENCV):  
    
        def __init__(self, node_name):  
    
            super(FaceDetector, self).__init__(node_name)  
    
            self.detect_box = None  
    
            self.result = None  
    
            self.count = 0  
    
            # 设置级联表的参数,优化人脸识别  
    
            self.haar_scaleFactor = 1.2  
    
            self.haar_minNeighbors = 3  
    
            self.haar_minSize = 40  
    
            self.haar_maxSize = 60  
    
            # 获取haar特征的级联表的XML文件, 其中存放的是训练好的分类器
    
            cascade_frontal = "../cascades/haarcascade_frontalface_default.xml"  
    
            cascade_profile = "../cascades/haarcascade_profileface.xml"  
    
            # 使用级联表初始化haar特征检测器  
    
            self.cascade_frontal = cv2.CascadeClassifier(cascade_frontal)  
    
            self.cascade_profile = cv2.CascadeClassifier(cascade_profile)  
    
      
    
        def process_image(self, frame):  
    
            src = frame.copy()  
    
            # 创建灰度图像  
    
            gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)  
    
            # 对图像进行直方图均衡化操作,减少光线影响,增加对比度  
    
            gray = cv2.equalizeHist(gray)  
    
        # 调用人脸检测方法,获取人脸检测结果
    
            faces = self.detect_face(gray)  
    
            result = src.copy()  
    
            self.result = result  
    
            # 绘制人脸位置  
    
            # (x,y,w,h)分别代表人脸框的左上角点的坐标,人脸框的宽度和长度  
    
            for (x, y, w, h) in faces:  
    
                result = cv2.rectangle(result, (x, y), (x + w, y + h), (255, 0, 0), 2)  
    
            return result  
    
        
    
        def detect_face(self, input_image):  
    
            faces = []  
    
            # 首先匹配正面人脸的模型  
    
            if self.cascade_frontal:  
    
          # detectMultiScale为人脸检测的核心函数
    
                faces = self.cascade_frontal.detectMultiScale(input_image, self.haar_scaleFactor ,  
    
                                    self.haar_minNeighbors,cv2.CASCADE_SCALE_IMAGE,(self.haar_minSize,self.haar_maxSize))  
    
            # 如果正面人脸匹配失败,那么就尝试匹配侧面人脸的模型  
    
                if len(faces) == 0 and self.cascade_profile:  
    
                    faces = self.cascade_profile.detectMultiScale(input_image, self.haar_scaleFactor ,  
    
                                        self.haar_minNeighbors,cv2.CASCADE_SCALE_IMAGE,(self.haar_minSize,self.haar_maxSize))  
    
            return faces  
    
      
    
      
    
    if __name__ == '__main__':  
    
        try:  
    
            node_name = "face_detector"  
    
            FaceDetector(node_name)  
    
            rospy.loginfo("Please subscribe the ROS image.")  
    
            rospy.spin()  
    
        except KeyboardInterrupt:  
    
            print("shutting down face detector node.")  

以上代码生成了一个继承类FaceDetector,继承了ROS 与 Opencv 接口类ROS2OPENCV该脚本通过如下代码引入了同目录下类。

from ros_opencv import ROS2OPENCV

FaceDetector中包含了如下方法:

  1. 类的构造方法,用于类的初始化

  1.     def __init__(self, node_name):  
    
            super(FaceDetector, self).__init__(node_name)  
    
            self.detect_box = None  
    
            self.result = None  
    
            self.count = 0  
    
            # 设置级联表的参数,优化人脸识别  
    
            self.haar_scaleFactor = 1.2  
    
            self.haar_minNeighbors = 3  
    
            self.haar_minSize = 40  
    
            self.haar_maxSize = 60  
    
            # 获取haar特征的级联表的XML文件, 其中存放的是训练好的分类器
    
            cascade_frontal = "../cascades/haarcascade_frontalface_default.xml"  
    
            cascade_profile = "../cascades/haarcascade_profileface.xml"  
    
            # 使用级联表初始化haar特征检测器  
    
            self.cascade_frontal = cv2.CascadeClassifier(cascade_frontal)  
    
            self.cascade_profile = cv2.CascadeClassifier(cascade_profile)  

  1. 重写了父类的process_image 方法,用于图像处理。

process_image方法中,程序在进行了基本的图像处理操作后,调用了人脸检测方法detect_face,获取人脸检测结果,并将结果绘制到图像上。

  1.     def process_image(self, frame):  
    
            src = frame.copy()  
    
            # 创建灰度图像  
    
            gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)  
    
            # 对图像进行直方图均衡化操作,减少光线影响,增加对比度  
    
            gray = cv2.equalizeHist(gray)  
    
        # 调用人脸检测方法,获取人脸检测结果
    
            faces = self.detect_face(gray)  
    
            result = src.copy()  
    
            self.result = result  
    
            # 绘制人脸位置  
    
            # (x,y,w,h)分别代表人脸框的左上角点的坐标,人脸框的宽度和长度  
    
            for (x, y, w, h) in faces:  
    
                result = cv2.rectangle(result, (x, y), (x + w, y + h), (255, 0, 0), 2)  
    
            return result  

  1. detect_face方法,用于人脸检测的具体实现。

detect_face方法中,实现人脸识别的核心函数为detectMultiScale,这个函数将一帧图像中的全部人脸提取出来,将这些人脸在当前帧图像中的位置组成的列表放入返回值faces中。其元素为(x,y,w,h),分别代表人脸框的左上角点的x、y坐标,人脸框的宽度w和长度h。

  1.     def detect_face(self, input_image):  
    
            faces = []  
    
            # 首先匹配正面人脸的模型  
    
            if self.cascade_frontal:  
    
          # detectMultiScale为人脸检测的核心函数
    
                faces = self.cascade_frontal.detectMultiScale(input_image, self.haar_scaleFactor ,  
    
                                    self.haar_minNeighbors,cv2.CASCADE_SCALE_IMAGE,(self.haar_minSize,self.haar_maxSize))  
    
            # 如果正面人脸匹配失败,那么就尝试匹配侧面人脸的模型  
    
                if len(faces) == 0 and self.cascade_profile:  
    
                    faces = self.cascade_profile.detectMultiScale(input_image, self.haar_scaleFactor ,  
    
                                        self.haar_minNeighbors,cv2.CASCADE_SCALE_IMAGE,(self.haar_minSize,self.haar_maxSize))  
    
            return faces  

程序运行流程:

  • 程序入口:主函数,实例化扩展类 FaceDetector

  • 实例化时执行类的构造方法,其中包含了超类的构造方法。执行完毕后返回主函数。

  • 主函数中通过rospy.spin()不断触发所有回调函数,当回调函数对应的消息队列中存在消息时,进入回调函数。

  • 由于继承了父类的rgb_image_callback回调函数,因此进入该回调函数,并在其中调用 FaceDetector类重写的图像处理方法process_image。

  • 在图像处理方法中调用detect_face方法,在其中调用opencv提供的detectMultiScale函数,得到返回值后返回图像处理方法继续执行。

  • 图像处理方法执行完毕,返回回调函数继续执行。

接下来我们运行这个节点,在终端中输入如下命令启动人脸识别环境:

$ export TURTLEBOT3_MODEL=waffle_pi

$ source devel/setup.bash

$ roslaunch billboard tb3_house_with_face_boards.launch

环境如图所示:

2 人脸识别环境

给脚本添加可执行权限,并将脚本添加到CmakeList中后,另起终端二,输入如下命令,启动人脸检测节点:

$ source devel/setup.bash

$ rosrun proj04_face_detector face_detector.py

另起终端三,输入如下命令打开RViz,并点击add订阅/display_image。

$ rosrun rviz rviz

另起终端四,输入如下命令打开键盘控制节点,并控制小车朝向人脸标示牌。

$ rosrun teleop_twist_keyboard teleop_twist_keyboard.py

得到以下结果:

3 人脸检测结果

  1. 训练人脸识别模型

在得到人脸检测结果后,要进一步得到人脸所对应的姓名标签,就要选用合适的人脸识别模型,使用数据集对其进行训练,得到训练完成的分类器文件,在后续工作中加载此文件以识别人脸,进行分类。在这里,我们选用EigenFaces方法来进行人脸识别。

EigenFaces通常也被称为特征脸,它使用主成分分析(Principal Component Analysis,PCA)方法将高维的人脸数据处理为低维数据后,在进行数据分析和处理,获取识别结果。

我们使用opencv提供的函数cv2.face.createEigenFaceRecognizer()生成特征脸识别器,然后应用该识别器的train()函数完成训练。在此之前,我们需要进行相应的数据准备。

2.1 准备人脸识别模型训练集

在功能包的/cascades目录下建立/data文件夹,在/data中按人名建立子目录,其中保存多张不同角度的脸部图片。需要注意的是,图片的大小应尽量维持在200*200像素以内,以确保程序运算的速度不会受太大影响。

2.2 编写模型训练脚本

接下来,在/scripts文件夹下建立face_trainer.py文件,用于读取/data目录下的训练集,并生成训练好的分类器文件recognize_face.xml与姓名列表文件name_list.txt。脚本内容如下。

face_trainer.py

  1. #!/usr/bin/env python  
    
    # -*- coding: utf-8 -*-  
    
      
    
    import rospy  
    
    import os  
    
    import sys  
    
    import cv2  
    
    import numpy as np  
    
    from ros_opencv import ROS2OPENCV  
    
      
    
    class FaceTrainer(ROS2OPENCV):  
    
        def __init__(self, node_name):  
    
            super(FaceTrainer, self).__init__(node_name)  
    
            self.images = None  
    
            self.labels = None  
    
            # 指定获取训练集的目录  
    
            self.read_dir = "../cascades"  
    
            # 调用read_images方法获取训练集图像及标签列表  
    
            [images, labels] = self.read_images(self.read_dir)  
    
            self.images = images  
    
            self.labels = labels  
    
            # 初始化Eigen人脸识别模型  
    
            self.model = cv2.face.createEigenFaceRecognizer()  
    
            # 将图像与标签列表转换为数组,输入模型中进行训练  
    
            self.model.train(np.asarray(images), np.asarray(labels))  
    
            # 将训练得到的分类器文件保存至指定目录  
    
            self.model.save(self.read_dir+"/recognize_face.xml")  
    
            print ("Train Succeed !")  
    
      
    
        # 读取训练集图像文件,构建图像及标签列表  
    
        def read_images(self, path, sz=(100,100)):  
    
            images, labels = [], []  
    
            names = []  
    
            ids = 0  
    
            # 在入参目录后附加data目录  
    
            path = os.path.join(path,"data")  
    
            # 遍历data目录  
    
            for dirname, dirnames, filenames, in os.walk(path):  
    
                # 遍历data的子目录  
    
                for subdirname in dirnames:  
    
                    subject_path = os.path.join(dirname, subdirname)  
    
                    # 遍历子目录中的文件  
    
                    for filename in os.listdir(subject_path):  
    
                        try:  
    
                            # 跳过其中包含的文件夹  
    
                            if filename == ".directory":  
    
                                continue  
    
                            # 构造文件路径,将图像读取为灰度图  
    
                            filepath = os.path.join(subject_path, filename)  
    
                            im = cv2.imread(filepath, cv2.IMREAD_GRAYSCALE)  
    
                            # 处理空图像  
    
                            if im is None:  
    
                                print("image" + filepath + "is None")  
    
                            # 若指定了图像尺寸,重设读入图像的尺寸  
    
                            if sz is not None:  
    
                                im = cv2.resize(im, sz)  
    
                            # 将读取到的图像转换为数组,追加到图像列表尾  
    
                            images.append(np.asarray(im, dtype=np.uint8))  
    
                            # 将标签追加到标签列表尾  
    
                            labels.append(ids)  
    
                        except:  
    
                            print("unexpected error")  
    
                            raise  
    
                    # 遍历完一个子目录的文件后标签自增,用于标识人脸属于同一个人  
    
                    ids = ids + 1  
    
                    # 将子目录名追加到names列表  
    
                    names.append(subdirname)  
    
            # 遍历data目录结束后,将names列表转换为字符串,输出到txt文件中,保存在data目录下  
    
            txtpath = os.path.join(path, 'name_list.txt')  
    
            f=open(txtpath,'w+')  
    
            res=f.write(' '.join(names))  
    
            return [images, labels]  
    
      
    
    if __name__ == "__main__":  
    
        try:  
    
            node_name = "face_trainer"  
    
            FaceTrainer(node_name)  
    
            rospy.spin()  
    
        except KeyboardInterrupt:  
    
            print ("Shutting down face trainer node.")   

以上代码同样实现了一个继承类FaceDetector,其中包含了类的构造方法和read_images方法。

  1. 类的构造方法

在构造方法中,调用了read_images方法,获取了其构建的训练集图片及标签列表,并送入Eigen人脸识别模型中进行训练,后将训练结果文件保存至/ cascades目录下。

  1.     def __init__(self, node_name):  
    
            super(FaceTrainer, self).__init__(node_name)  
    
            self.images = None  
    
            self.labels = None  
    
            # 指定获取训练集的目录  
    
            self.read_dir = "../cascades"  
    
            # 调用read_images方法获取训练集图像及标签列表  
    
            [images, labels] = self.read_images(self.read_dir)  
    
            self.images = images  
    
            self.labels = labels  
    
            # 初始化Eigen人脸识别模型  
    
            self.model = cv2.face.createEigenFaceRecognizer()  
    
            # 将图像与标签列表转换为数组,输入模型中进行训练  
    
            self.model.train(np.asarray(images), np.asarray(labels))  
    
            # 将训练得到的分类器文件保存至指定目录  
    
            self.model.save(self.read_dir+"/recognize_face.xml")  
    
            print ("Train Succeed !")  

  1. read_images方法

read_images方法则通过遍历/data目录,构建图像列表,同时构建标签列表来标识属于同一个人的人脸图像,并将图像列表和标签列表一同作为返回值。此外,该方法将对应于序号的人名(即/data的子目录名)保存到一个txt文件中,存放至data目录下。

  1.  

       def read_images(self, path, sz=(100,100)):  
    
            images, labels = [], []  
    
            names = []  
    
            ids = 0  
    
            # 在入参目录后附加data目录  
    
            path = os.path.join(path,"data")  
    
            # 遍历data目录  
    
            for dirname, dirnames, filenames, in os.walk(path):  
    
                # 遍历data的子目录  
    
                for subdirname in dirnames:  
    
                    subject_path = os.path.join(dirname, subdirname)  
    
                    # 遍历子目录中的文件  
    
                    for filename in os.listdir(subject_path):  
    
                        try:  
    
                            # 跳过其中包含的文件夹  
    
                            if filename == ".directory":  
    
                                continue  
    
                            # 构造文件路径,将图像读取为灰度图  
    
                            filepath = os.path.join(subject_path, filename)  
    
                            im = cv2.imread(filepath, cv2.IMREAD_GRAYSCALE)  
    
                            # 处理空图像  
    
                            if im is None:  
    
                                print("image" + filepath + "is None")  
    
                            # 若指定了图像尺寸,重设读入图像的尺寸  
    
                            if sz is not None:  
    
                                im = cv2.resize(im, sz)  
    
                            # 将读取到的图像转换为数组,追加到图像列表尾  
    
                            images.append(np.asarray(im, dtype=np.uint8))  
    
                            # 将标签追加到标签列表尾  
    
                            labels.append(ids)  
    
                        except:  
    
                            print("unexpected error")  
    
                            raise  
    
                    # 遍历完一个子目录的文件后标签自增,用于标识人脸属于同一个人  
    
                    ids = ids + 1  
    
                    # 将子目录名追加到names列表  
    
                    names.append(subdirname)  
    
            # 遍历data目录结束后,将names列表转换为字符串,输出到txt文件中,保存在data目录下  
    
            txtpath = os.path.join(path, 'name_list.txt')  
    
            f=open(txtpath,'w+')  
    
            res=f.write(' '.join(names))  
    
            return [images, labels]  

该程序的运行流程如下:

  1. 程序入口:主函数,在主函数中实例化FaceTrainer类。

  2. 进入类的构造方法,调用read_images方法。

  3. 进入read_images方法,执行完毕返回训练集数据。

  4. 继续执行构造方法,初始化模型并进行模型训练,保存训练结果。

2.3 执行模型训练脚本

给脚本添加可执行权限,并将脚本添加到CmakeList中后,开启了gazebo的情况下,在终端中输入如下命令启动训练节点

$ source devel/setup.bash

$ rosrun proj04_face_detector face_trainer.py

如未开启gazebo,首先执行roscore命令启动ros节点服务器,并在训练完成后按下ctrl+c关闭该节点服务器

等待终端返回如下信息:

检查是否生成了xml文件:

检查是否生成了人名文件:

至此,模型训练完成。

  1. 基于EigenFaces的人脸识别

得到基于EigenFaces的模型训练结果后,我们需要编写程序,加载上一节保存的分类器文件recognize_face.xml与姓名列表文件name_list.txt,用于提供给EigenFaces分类器判别摄像头中的人脸究竟属于谁。

我们同样使用cv2.face.createEigenFaceRecognizer()生成特征脸识别器,在加载第二节训练得到的分类器文件后,即可应用该识别器的predict()函数完成人脸分类。

EigenFaces人脸识别要求训练的图像与测试的图像尺寸必须一致,因此我们需要在代码中通过resize函数进行相应的处理。

人脸识别的返回值包含了当前人脸图像的标签以及分类置信度。EigenFaces人脸识别算法的置信度数值在0到20000之间,置信度数值越低,则代表分类结果越可靠。当置信度数值低于5000时,可以认为是可靠的结果。

/scripts文件夹下,创建face_ recognizer.py文件,文件内容如下。

face_recognizer.py

  1. #!/usr/bin/env python  
    
    # -*- coding: utf-8 -*-  
    
      
    
    import rospy  
    
    import os  
    
    import sys  
    
    import cv2  
    
    import numpy as np  
    
    from ros_opencv import ROS2OPENCV  
    
      
    
      
    
    class FaceRecognizer(ROS2OPENCV):  
    
        def __init__(self, node_name):  
    
            super(FaceRecognizer, self).__init__(node_name)  
    
            self.detect_box = None  
    
            self.result = None  
    
            self.read_dir = "../cascades"  
    
            # 调用get_labels方法获取人脸图像所对应的标签列表  
    
            self.labels = self.get_labels("../cascades/data/name_list.txt")  
    
            # 设置级联表的参数,优化人脸检测  
    
            self.haar_scaleFactor = 1.2  
    
            self.haar_minNeighbors = 3  
    
            self.haar_minSize = 40  
    
            self.haar_maxSize = 60  
    
            # 获取haar特征的级联表的XML文件,文件路径在launch文件中传入  
    
            cascade_frontal = "../cascades/haarcascade_frontalface_default.xml"  
    
            cascade_profile = "../cascades/haarcascade_profileface.xml"  
    
            # 使用级联表初始化haar特征检测器  
    
            self.cascade_frontal = cv2.CascadeClassifier(cascade_frontal)  
    
            self.cascade_profile = cv2.CascadeClassifier(cascade_profile)  
    
            # 初始化Eigen人脸识别器  
    
            self.model = cv2.face.createEigenFaceRecognizer()  
    
            self.model.load(self.read_dir+"/recognize_face.xml")  
    
      
    
        # 图像处理函数  
    
        def process_image(self, frame):  
    
            src = frame.copy()  
    
            gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)  
    
            gray = cv2.equalizeHist(gray)  
    
            faces = self.detect_face(gray)  
    
            result = src.copy()  
    
            self.result = result  
    
            # 遍历人脸列表  
    
            for (x, y, w, h) in faces:  
    
                # 绘制检测框  
    
                result = cv2.rectangle(result, (x, y), (x + w, y + h), (255, 0, 0), 2)  
    
                # 获取目标人脸图像  
    
                roi = gray[x:x + w, y:y + h]  
    
                try:  
    
                    # 重置人脸图像尺寸为100*100,与训练集相匹配  
    
                    roi = cv2.resize(roi, (100, 100), interpolation=cv2.INTER_LINEAR)  
    
                    # 获取模型对当前人脸的分类结果及置信度  
    
                    [p_label, p_confidence] = self.model.predict(roi)  
    
                    print("confidence: %s" % p_confidence + "   name: %s" % self.labels[p_label])  
    
                    # 置信度小于5000认为结果可靠,并将标签打印到人脸检测框下方  
    
                    if p_confidence < 5000:  
    
                        cv2.putText(result, self.labels[p_label], (x, y + h + 15  
    
                                                         ), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255))  
    
                        print("This is %s" % self.labels[p_label] + "!")  
    
                except:  
    
                    continue  
    
            return result  
    
      
    
        def detect_face(self, input_image):  
    
            faces = []  
    
            # 首先匹配正面人脸的模型  
    
            if self.cascade_frontal:  
    
                faces = self.cascade_frontal.detectMultiScale(input_image, self.haar_scaleFactor,  
    
                                                        self.haar_minNeighbors, cv2.CASCADE_SCALE_IMAGE,  
    
                                                        (self.haar_minSize, self.haar_maxSize))  
    
                # 如果正面人脸匹配失败,那么就尝试匹配侧面人脸的模型  
    
                if len(faces) == 0 and self.cascade_profile:  
    
                    faces = self.cascade_profile.detectMultiScale(input_image, self.haar_scaleFactor,  
    
                                                            self.haar_minNeighbors, cv2.CASCADE_SCALE_IMAGE,  
    
                                                            (self.haar_minSize, self.haar_maxSize))  
    
                return faces  
    
      
    
        # 从文件中获取人脸对应的name列表  
    
        def get_labels(self,path):  
    
            f=open(path,'r')  
    
            return(f.read().split(' '))  
    
      
    
    if __name__ == "__main__":  
    
        try:  
    
            node_name = "face_recognizer"  
    
            FaceRecognizer(node_name)  
    
            rospy.loginfo("Please subscribe the ROS image.")  
    
            rospy.spin()  
    
        except KeyboardInterrupt:  
    
            print ("Shutting down face recognizer node.")  

代码实现了一个继承类FaceRecognizer,包含了构造方法、process_image方法、detect_face方法与get_labels方法。代码在第一节人脸检测的基础上,增加了对检测到的人脸图像进行识别的功能。

  1. 类的构造方法

构造方法中,增加了基于EigenFace的人脸识别模型的初始化,并加载了第二节生成的recognize_face.xml文件。此外,调用get_labels方法获取了训练时得到的与人脸编号所对应的姓名列表。

  1.   

      def __init__(self, node_name):  
    
            super(FaceRecognizer, self).__init__(node_name)  
    
            self.detect_box = None  
    
            self.result = None  
    
            self.read_dir = "../cascades"  
    
            # 调用get_labels方法获取人脸图像所对应的标签列表  
    
            self.labels = self.get_labels("../cascades/data/name_list.txt")  
    
            # 设置级联表的参数,优化人脸检测  
    
            self.haar_scaleFactor = 1.2  
    
            self.haar_minNeighbors = 3  
    
            self.haar_minSize = 40  
    
            self.haar_maxSize = 60  
    
            # 获取haar特征的级联表的XML文件,文件路径在launch文件中传入  
    
            cascade_frontal = "../cascades/haarcascade_frontalface_default.xml"  
    
            cascade_profile = "../cascades/haarcascade_profileface.xml"  
    
            # 使用级联表初始化haar特征检测器  
    
            self.cascade_frontal = cv2.CascadeClassifier(cascade_frontal)  
    
            self.cascade_profile = cv2.CascadeClassifier(cascade_profile)  
    
            # 初始化Eigen人脸识别器  
    
            self.model = cv2.face.createEigenFaceRecognizer()  
    
            self.model.load(self.read_dir+"/recognize_face.xml")  

  1. process_image方法

process_image方法中,将调用detect_face方法得到的图像区域的尺寸修改为100*100,与训练时的图像尺寸保持一致。并使用predict()函数对获取到的人脸进行判别,得到判别结果的标签及置信度。最后判断置信度小于5000时,认为判别结果可靠,将人脸对应的姓名打印至人脸检测框下方。

  1.    def process_image(self, frame):  
    
            src = frame.copy()  
    
            gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)  
    
            gray = cv2.equalizeHist(gray)  
    
            faces = self.detect_face(gray)  
    
            result = src.copy()  
    
            self.result = result  
    
            # 遍历人脸列表  
    
            for (x, y, w, h) in faces:  
    
                # 绘制检测框  
    
                result = cv2.rectangle(result, (x, y), (x + w, y + h), (255, 0, 0), 2)  
    
                # 获取目标人脸图像  
    
                roi = gray[x:x + w, y:y + h]  
    
                try:  
    
                    # 重置人脸图像尺寸为100*100,与训练集相匹配  
    
                    roi = cv2.resize(roi, (100, 100), interpolation=cv2.INTER_LINEAR)  
    
                    # 获取模型对当前人脸的分类结果及置信度  
    
                    [p_label, p_confidence] = self.model.predict(roi)  
    
                    print("confidence: %s" % p_confidence + "   name: %s" % self.labels[p_label])  
    
                    # 置信度小于5000认为结果可靠,并将标签打印到人脸检测框下方  
    
                    if p_confidence < 5000:  
    
                        cv2.putText(result, self.labels[p_label], (x, y + h + 15  
    
                                                         ), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255))  
    
                        print("This is %s" % self.labels[p_label] + "!")  
    
                except:  
    
                    continue  
    
            return result  

  1. get_labels方法

get_labels方法是从指定路径下读取txt文件内容,生成names列表并返回。

  1.     def get_labels(self,path):  

  2.         f=open(path,'r')  

  3.         return(f.read().split(' '))  

  1. detect_face方法

detect_face方法则与第一节人脸检测程序中的detect_face方法完全一致。

程序运行流程:

  • 程序入口:主函数,实例化扩展类 FaceRecognizer

  • 实例化时执行类的构造方法,其中包含了超类的构造方法。执行完毕后返回主函数。

  • 主函数中通过rospy.spin()不断触发所有回调函数,当回调函数对应的消息队列中存在消息时,进入回调函数。

  • 由于继承了父类的rgb_image_callback回调函数,因此进入该回调函数,并在其中调用  FaceRecognizer类重写的图像处理方法process_image。

  • 在图像处理方法中调用detect_face方法,在其中调用opencv提供的detectMultiScale函数,得到返回值后返回图像处理方法继续执行。

  • 在得到候选人脸列表后,调用初始化时加载好的人脸识别器,对人脸列表进行遍历,并识别,输出识别结果。图像处理方法执行完毕,返回回调函数继续执行。

给脚本添加可执行权限,并将脚本添加到CmakeList中后,我们在开启人脸识别仿真环境的基础上,在终端中输入如下命令打开人脸识别节点:

$ source devel/setup.bash

$ rosrun proj04_face_detector face_ recognizer.py

同样的,打开RViz与键盘控制节点,并在RViz中订阅/display_image后,控制小车朝向环境中的人脸标识牌,可以看到如下结果:

(a) lisa识别结果

(b) julian识别结果

4 人脸识别结果

  1. 实训结果

  • 了解了人脸识别任务的各个环节,包括人脸检测、模型训练及人脸识别。

  • 学习使用opencv提供的人脸识别方法完成人脸识别任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值