• 基于OpenCV 和 Dlib 进行头部姿态估计


    写在前面


    • 工作中遇到,简单整理
    • 博文内容涉及基于 OpenCV 和 Dlib头部姿态评估的简单Demo
    • 理解不足小伙伴帮忙指正

    庐山烟雨浙江潮,未到千般恨不消。到得还来别无事,庐山烟雨浙江潮。 ----《庐山烟雨浙江潮》苏轼


    https://github.com/LIRUILONGS/Head-posture-detection-dlib-opencv-.git

    实验项目以上传,只需 git 克隆,安装需要的 pytohn 包,就可以开始使用了,但是需要说明的是 Dlib 的基于 HOG特征和SVM分类器的人脸检测器很一般,很多脸都检测不到,实际情况中可以考虑使用深度学习模型来做关键点检测,然后评估姿态。可以查看文章末尾大佬的开源项目

    实现效果

    Demo
    原图
    原图
    原图
    特征点标记后
    特征点标记后
    姿态标记
    姿态标记
    姿态对应的Yaw,Pitch,Roll 度数
    姿态对应的Yaw,Pitch,Roll
    姿态对应的Yaw,Pitch,Roll

    步骤

    三个主要步骤

    人脸检测

    人脸检测:引入人脸检测器 dlib.get_frontal_face_detector() 以检测包含人脸的图片,多个人脸会选择面积最大的人脸。

    dlib.get_frontal_face_detector()dlib 库中的一个函数,用于获取一个基于HOG特征和SVM分类器的人脸检测器。该函数返回一个可以用于检测图像中人脸的对象。

    具体来说,HOG(Histogram of Oriented Gradients,梯度方向直方图)是一种常用于图像识别中的特征描述子,SVM(Support Vector Machine,支持向量机)是一种常用的分类器。将HOG特征与SVM分类器结合起来,可以得到一个有效的人脸检测器。

    在使用 dlib.get_frontal_face_detector()函数时,只需将待检测的图像作为参数传入,即可得到一个用于检测人脸的对象。一个Demo

    import dlib
    import cv2
    
    # 读取图像
    img = cv2.imread('image.jpg')
    
    # 获取人脸检测器
    detector = dlib.get_frontal_face_detector()
    
    # 在图像中检测人脸
    faces = detector(img)
    
    # 输出检测到的人脸数
    print("检测到的人脸数为:", len(faces))
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    面部特征点检测

    面部特征点检测,利用预训练模型 shape_predictor_68_face_landmarks.dat 以人脸图像为输入,输出68个人脸特征点

    shape_predictor_68_face_landmarks.dat 是基于 dlib 库中的人脸特征点检测模型,该模型使用了基于 HOG 特征和 SVM 分类器的人脸检测器来检测图像中的人脸,并使用回归算法来预测人脸的 68 个关键点位置。这些关键点包括眼睛、鼻子、嘴巴等部位,可以用于进行人脸识别、表情识别、姿态估计等应用。

    这个模型文件可以在dlib的官方网站上下载。在使用它之前,需要安装dlib库并将模型文件加载到程序中。

    predictor = dlib.shape_predictor(r".\shape_predictor_68_face_landmarks.dat")
    
    • 1
    姿势估计

    姿势估计。在获得 68 个面部特征点后,选择部分特征点,通过 PnP算法计算姿势 Yaw、Pitch、Roll 度数

        (success, rotation_vector, translation_vector) = cv2.solvePnP(model_points, image_points, camera_matrix,
                                                                      dist_coeffs, flags=cv2.SOLVEPNP_ITERATIVE)
    
    • 1
    • 2

    Yaw、Pitch、Roll 是用于描述物体或相机在三维空间中的旋转角度的术语,常用于姿态估计和姿态控制中。

    • Yaw(左右):绕垂直于物体或相机的轴旋转的角度,也称为偏航角。通常以 z 轴为轴进行旋转,正值表示逆时针旋转,负值表示顺时针旋转。
    • Pitch(上下):绕物体或相机的横轴旋转的角度,也称为俯仰角。通常以 x 轴为轴进行旋转,正值表示向上旋转,负值表示向下旋转。
    • Roll(弯曲):绕物体或相机的纵轴旋转的角度,也称为翻滚角。通常以 y 轴为轴进行旋转,正值表示向右旋转,负值表示向左旋转。

    这三个角度通常以欧拉角的形式表示,可以用于描述物体或相机的姿态信息。在计算机视觉中,常用于人脸识别、动作捕捉、机器人控制等应用场景。

    完整 Demo 代码

    #!/usr/bin/env python
    # -*- encoding: utf-8 -*-
    """
    @File    :   face_ypr_demo.py
    @Time    :   2023/06/05 21:32:45
    @Author  :   Li Ruilong
    @Version :   1.0
    @Contact :   liruilonger@gmail.com
    @Desc    :   根据68个人脸关键点,获取人头部姿态评估
    """
    
    # here put the import lib
    
    import cv2
    import numpy as np
    import dlib
    import math
    import uuid
    
    # 头部姿态检测(dlib+opencv)
    
    detector = dlib.get_frontal_face_detector()
    predictor = dlib.shape_predictor(r".\shape_predictor_68_face_landmarks.dat")
    POINTS_NUM_LANDMARK = 68
    
    
    # shape_predictor_68_face_landmarks.dat 是一个预训练的人脸关键点检测模型,可以用于识别人脸的68个关键点,如眼睛、鼻子、嘴巴等。这个模型可以被用于人脸识别、人脸表情分析、面部姿势估计等领域。
    # 它是由dlib库提供的,可以在Python中使用。如果你想使用它,可以在dlib的官方网站上下载。
    
    # 获取最大的人脸
    def _largest_face(dets):
        """
        @Time    :   2023/06/05 21:30:37
        @Author  :   liruilonger@gmail.com
        @Version :   1.0
        @Desc    :   从一个由 dlib 库检测到的人脸框列表中,找到最大的人脸框,并返回该框在列表中的索
                    如果只有一个人脸,直接返回
                     Args:
                       dets: 一个由 `dlib.rectangle` 类型的对象组成的列表,每个对象表示一个人脸框
                     Returns:
                       人脸索引
        """
        # 如果列表长度为1,则直接返回
        if len(dets) == 1:
            return 0
        # 计算每个人脸框的面积
        face_areas = [(det.right() - det.left()) * (det.bottom() - det.top()) for det in dets]
        import heapq
        # 找到面积最大的人脸框的索引
        largest_area = face_areas[0]
        largest_index = 0
        for index in range(1, len(dets)):
            if face_areas[index] > largest_area:
                largest_index = index
                largest_area = face_areas[index]
        # 打印最大人脸框的索引和总人脸数
        print("largest_face index is {} in {} faces".format(largest_index, len(dets)))
    
        return largest_index
    
    
    def get_image_points_from_landmark_shape(landmark_shape):
        """
        @Time    :   2023/06/05 22:30:02
        @Author  :   liruilonger@gmail.com
        @Version :   1.0
        @Desc    :   从dlib的检测结果抽取姿态估计需要的点坐标
                     Args:
                       landmark_shape:  所有的位置点
                     Returns:
                       void
        """
    
        if landmark_shape.num_parts != POINTS_NUM_LANDMARK:
            print("ERROR:landmark_shape.num_parts-{}".format(landmark_shape.num_parts))
            return -1, None
    
        # 2D image points. If you change the image, you need to change vector
    
        image_points = np.array([
            (landmark_shape.part(17).x, landmark_shape.part(17).y),  # 17 left brow left corner
            (landmark_shape.part(21).x, landmark_shape.part(21).y),  # 21 left brow right corner
            (landmark_shape.part(22).x, landmark_shape.part(22).y),  # 22 right brow left corner
            (landmark_shape.part(26).x, landmark_shape.part(26).y),  # 26 right brow right corner
            (landmark_shape.part(36).x, landmark_shape.part(36).y),  # 36 left eye left corner
            (landmark_shape.part(39).x, landmark_shape.part(39).y),  # 39 left eye right corner
            (landmark_shape.part(42).x, landmark_shape.part(42).y),  # 42 right eye left corner
            (landmark_shape.part(45).x, landmark_shape.part(45).y),  # 45 right eye right corner
            (landmark_shape.part(31).x, landmark_shape.part(31).y),  # 31 nose left corner
            (landmark_shape.part(35).x, landmark_shape.part(35).y),  # 35 nose right corner
            (landmark_shape.part(48).x, landmark_shape.part(48).y),  # 48 mouth left corner
            (landmark_shape.part(54).x, landmark_shape.part(54).y),  # 54 mouth right corner
            (landmark_shape.part(57).x, landmark_shape.part(57).y),  # 57 mouth central bottom corner
            (landmark_shape.part(8).x, landmark_shape.part(8).y),  # 8 chin corner
        ], dtype="double")
        return 0, image_points
    
    
    def get_image_points(img):
        """
        @Time    :   2023/06/05 22:30:43
        @Author  :   liruilonger@gmail.com
        @Version :   1.0
        @Desc    :   用dlib检测关键点,返回姿态估计需要的几个点坐标
                     Args:
                       
                     Returns:
                       void
        """
    
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 图片调整为灰色
    
        dets = detector(img, 0)
    
        if 0 == len(dets):
            print("ERROR: found no face")
            return -1, None
        largest_index = _largest_face(dets)
        face_rectangle = dets[largest_index]
    
        landmark_shape = predictor(img, face_rectangle)
        draw = im.copy()
        cv2.circle(draw, (landmark_shape.part(0).x, landmark_shape.part(0).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(1).x, landmark_shape.part(1).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(2).x, landmark_shape.part(2).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(3).x, landmark_shape.part(3).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(4).x, landmark_shape.part(4).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(5).x, landmark_shape.part(5).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(6).x, landmark_shape.part(6).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(7).x, landmark_shape.part(7).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(8).x, landmark_shape.part(8).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(9).x, landmark_shape.part(9).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(10).x, landmark_shape.part(10).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(11).x, landmark_shape.part(11).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(12).x, landmark_shape.part(12).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(13).x, landmark_shape.part(13).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(14).x, landmark_shape.part(14).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(15).x, landmark_shape.part(15).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(16).x, landmark_shape.part(16).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(17).x, landmark_shape.part(17).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(18).x, landmark_shape.part(18).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(19).x, landmark_shape.part(19).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(20).x, landmark_shape.part(20).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(21).x, landmark_shape.part(21).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(22).x, landmark_shape.part(22).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(23).x, landmark_shape.part(23).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(24).x, landmark_shape.part(24).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(25).x, landmark_shape.part(25).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(26).x, landmark_shape.part(26).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(27).x, landmark_shape.part(27).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(28).x, landmark_shape.part(28).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(29).x, landmark_shape.part(29).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(30).x, landmark_shape.part(30).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(31).x, landmark_shape.part(31).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(32).x, landmark_shape.part(32).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(33).x, landmark_shape.part(33).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(34).x, landmark_shape.part(34).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(35).x, landmark_shape.part(35).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(36).x, landmark_shape.part(36).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(37).x, landmark_shape.part(37).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(38).x, landmark_shape.part(38).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(39).x, landmark_shape.part(39).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(40).x, landmark_shape.part(40).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(41).x, landmark_shape.part(41).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(42).x, landmark_shape.part(42).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(43).x, landmark_shape.part(43).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(44).x, landmark_shape.part(44).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(45).x, landmark_shape.part(45).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(46).x, landmark_shape.part(46).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(47).x, landmark_shape.part(47).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(48).x, landmark_shape.part(48).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(49).x, landmark_shape.part(49).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(50).x, landmark_shape.part(50).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(51).x, landmark_shape.part(51).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(52).x, landmark_shape.part(52).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(53).x, landmark_shape.part(53).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(54).x, landmark_shape.part(54).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(55).x, landmark_shape.part(55).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(56).x, landmark_shape.part(56).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(57).x, landmark_shape.part(57).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(58).x, landmark_shape.part(58).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(59).x, landmark_shape.part(59).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(60).x, landmark_shape.part(60).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(61).x, landmark_shape.part(61).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(62).x, landmark_shape.part(62).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(63).x, landmark_shape.part(63).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(64).x, landmark_shape.part(64).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(65).x, landmark_shape.part(65).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(66).x, landmark_shape.part(66).y), 2, (0, 255, 0), -1)
        cv2.circle(draw, (landmark_shape.part(67).x, landmark_shape.part(67).y), 2, (0, 255, 0), -1)
    
        # 部分关键点特殊标记
        cv2.circle(draw, (landmark_shape.part(17).x, landmark_shape.part(17).y), 2, (0, 165, 255),
                   -1)  # 17 left brow left corner
        cv2.circle(draw, (landmark_shape.part(21).x, landmark_shape.part(21).y), 2, (0, 165, 255),
                   -1)  # 21 left brow right corner
        cv2.circle(draw, (landmark_shape.part(22).x, landmark_shape.part(22).y), 2, (0, 165, 255),
                   -1)  # 22 right brow left corner
        cv2.circle(draw, (landmark_shape.part(26).x, landmark_shape.part(26).y), 2, (0, 165, 255),
                   -1)  # 26 right brow right corner
        cv2.circle(draw, (landmark_shape.part(36).x, landmark_shape.part(36).y), 2, (0, 165, 255),
                   -1)  # 36 left eye left corner
        cv2.circle(draw, (landmark_shape.part(39).x, landmark_shape.part(39).y), 2, (0, 165, 255),
                   -1)  # 39 left eye right corner
        cv2.circle(draw, (landmark_shape.part(42).x, landmark_shape.part(42).y), 2, (0, 165, 255),
                   -1)  # 42 right eye left corner
        cv2.circle(draw, (landmark_shape.part(45).x, landmark_shape.part(45).y), 2, (0, 165, 255),
                   -1)  # 45 right eye right corner
        cv2.circle(draw, (landmark_shape.part(31).x, landmark_shape.part(31).y), 2, (0, 165, 255),
                   -1)  # 31 nose left corner
        cv2.circle(draw, (landmark_shape.part(35).x, landmark_shape.part(35).y), 2, (0, 165, 255),
                   -1)  # 35 nose right corner
        cv2.circle(draw, (landmark_shape.part(48).x, landmark_shape.part(48).y), 2, (0, 165, 255),
                   -1)  # 48 mouth left corner
        cv2.circle(draw, (landmark_shape.part(54).x, landmark_shape.part(54).y), 2, (0, 165, 255),
                   -1)  # 54 mouth right corner
        cv2.circle(draw, (landmark_shape.part(57).x, landmark_shape.part(57).y), 2, (0, 165, 255),
                   -1)  # 57 mouth central bottom corner
        cv2.circle(draw, (landmark_shape.part(8).x, landmark_shape.part(8).y), 2, (0, 165, 255), -1)
    
        # 保存关键点标记后的图片
        cv2.imwrite('new_' + "KeyPointDetection.jpg", draw)
    
        return get_image_points_from_landmark_shape(landmark_shape)
    
    
    def get_pose_estimation(img_size, image_points):
        """
        @Time    :   2023/06/05 22:31:31
        @Author  :   liruilonger@gmail.com
        @Version :   1.0
        @Desc    :   获取旋转向量和平移向量
                     Args:
                       
                     Returns:
                       void
        """
    
        # 3D model points.
        model_points = np.array([
            (6.825897, 6.760612, 4.402142),  # 33 left brow left corner
            (1.330353, 7.122144, 6.903745),  # 29 left brow right corner
            (-1.330353, 7.122144, 6.903745),  # 34 right brow left corner
            (-6.825897, 6.760612, 4.402142),  # 38 right brow right corner
            (5.311432, 5.485328, 3.987654),  # 13 left eye left corner
            (1.789930, 5.393625, 4.413414),  # 17 left eye right corner
            (-1.789930, 5.393625, 4.413414),  # 25 right eye left corner
            (-5.311432, 5.485328, 3.987654),  # 21 right eye right corner
            (2.005628, 1.409845, 6.165652),  # 55 nose left corner
            (-2.005628, 1.409845, 6.165652),  # 49 nose right corner
            (2.774015, -2.080775, 5.048531),  # 43 mouth left corner
            (-2.774015, -2.080775, 5.048531),  # 39 mouth right corner
            (0.000000, -3.116408, 6.097667),  # 45 mouth central bottom corner
            (0.000000, -7.415691, 4.070434)  # 6 chin corner
        ])
        # Camera internals
    
        focal_length = img_size[1]
        center = (img_size[1] / 2, img_size[0] / 2)
        camera_matrix = np.array(
            [[focal_length, 0, center[0]],
             [0, focal_length, center[1]],
             [0, 0, 1]], dtype="double"
        )
    
        dist_coeffs = np.array([7.0834633684407095e-002, 6.9140193737175351e-002, 0.0, 0.0, -1.3073460323689292e+000],
                               dtype="double")  # Assuming no lens distortion
    
        (success, rotation_vector, translation_vector) = cv2.solvePnP(model_points, image_points, camera_matrix,
                                                                      dist_coeffs, flags=cv2.SOLVEPNP_ITERATIVE)
    
        # print("Rotation Vector:\n {}".format(rotation_vector))
        # print("Translation Vector:\n {}".format(translation_vector))
        return success, rotation_vector, translation_vector, camera_matrix, dist_coeffs
    
    
    def draw_annotation_box(image, rotation_vector, translation_vector, camera_matrix, dist_coeefs, color=(0, 255, 0),
                            line_width=2):
        """
        @Time    :   2023/06/05 22:09:14
        @Author  :   liruilonger@gmail.com
        @Version :   1.0
        @Desc    :   标记一个人脸朝向的3D框
                     Args:
                       
                     Returns:
                       void
        """
    
        """Draw a 3D box as annotation of pose"""
        point_3d = []
        rear_size = 10
        rear_depth = 0
        point_3d.append((-rear_size, -rear_size, rear_depth))
        point_3d.append((-rear_size, rear_size, rear_depth))
        point_3d.append((rear_size, rear_size, rear_depth))
        point_3d.append((rear_size, -rear_size, rear_depth))
        point_3d.append((-rear_size, -rear_size, rear_depth))
    
        front_size = 10
        # 高度
        front_depth = 10
        point_3d.append((-front_size, -front_size, front_depth))
        point_3d.append((-front_size, front_size, front_depth))
        point_3d.append((front_size, front_size, front_depth))
        point_3d.append((front_size, -front_size, front_depth))
        point_3d.append((-front_size, -front_size, front_depth))
        point_3d = np.array(point_3d, dtype=np.float32).reshape(-1, 3)
    
        # Map to 2d image points
        (point_2d, _) = cv2.projectPoints(point_3d,
                                          rotation_vector,
                                          translation_vector,
                                          camera_matrix,
                                          dist_coeefs)
        point_2d = np.int32(point_2d.reshape(-1, 2))
    
        # Draw all the lines
        cv2.polylines(image, [point_2d], True, color, line_width, cv2.LINE_AA)
        cv2.line(image, tuple(point_2d[1]), tuple(
            point_2d[6]), color, line_width, cv2.LINE_AA)
        cv2.line(image, tuple(point_2d[2]), tuple(
            point_2d[7]), color, line_width, cv2.LINE_AA)
        cv2.line(image, tuple(point_2d[3]), tuple(
            point_2d[8]), color, line_width, cv2.LINE_AA)
    
    
    # 从旋转向量转换为欧拉角
    def get_euler_angle(rotation_vector):
        """
        @Time    :   2023/06/05 22:31:52
        @Author  :   liruilonger@gmail.com
        @Version :   1.0
        @Desc    :   从旋转向量转换为欧拉角
                     Args:
                       
                     Returns:
                       void
        """
    
        # calculate rotation angles
        theta = cv2.norm(rotation_vector, cv2.NORM_L2)
    
        # transformed to quaterniond
        w = math.cos(theta / 2)
        x = math.sin(theta / 2) * rotation_vector[0][0] / theta
        y = math.sin(theta / 2) * rotation_vector[1][0] / theta
        z = math.sin(theta / 2) * rotation_vector[2][0] / theta
    
        ysqr = y * y
        # pitch (x-axis rotation)
        t0 = 2.0 * (w * x + y * z)
        t1 = 1.0 - 2.0 * (x * x + ysqr)
    
        # print('t0:{}, t1:{}'.format(t0, t1))
        pitch = math.atan2(t0, t1)
    
        # yaw (y-axis rotation)
        t2 = 2.0 * (w * y - z * x)
        if t2 > 1.0:
            t2 = 1.0
        if t2 < -1.0:
            t2 = -1.0
        yaw = math.asin(t2)
    
        # roll (z-axis rotation)
        t3 = 2.0 * (w * z + x * y)
        t4 = 1.0 - 2.0 * (ysqr + z * z)
        roll = math.atan2(t3, t4)
    
        print('pitch:{}, yaw:{}, roll:{}'.format(pitch, yaw, roll))
    
        # 单位转换:将弧度转换为度
        pitch_degree = int((pitch / math.pi) * 180)
        yaw_degree = int((yaw / math.pi) * 180)
        roll_degree = int((roll / math.pi) * 180)
    
        return 0, pitch, yaw, roll, pitch_degree, yaw_degree, roll_degree
    
    
    def get_pose_estimation_in_euler_angle(landmark_shape, im_szie):
        try:
            ret, image_points = get_image_points_from_landmark_shape(landmark_shape)
            if ret != 0:
                print('get_image_points failed')
                return -1, None, None, None
    
            ret, rotation_vector, translation_vector, camera_matrix, dist_coeffs = get_pose_estimation(im_szie,
                                                                                                       image_points)
            if ret != True:
                print('get_pose_estimation failed')
                return -1, None, None, None
    
            ret, pitch, yaw, roll = get_euler_angle(rotation_vector)
            if ret != 0:
                print('get_euler_angle failed')
                return -1, None, None, None
    
            euler_angle_str = 'Pitch:{}, Yaw:{}, Roll:{}'.format(pitch, yaw, roll)
            print(euler_angle_str)
            return 0, pitch, yaw, roll
    
        except Exception as e:
            print('get_pose_estimation_in_euler_angle exception:{}'.format(e))
            return -1, None, None, None
    
    
    def build_img_text_marge(img_, text, height):
        """
        @Time    :   2023/06/01 05:29:09
        @Author  :   liruilonger@gmail.com
        @Version :   1.0
        @Desc    :   生成文字图片拼接到 img 对象
                     Args:
    
                     Returns:
                       void
        """
        import cv2
        from PIL import Image, ImageDraw, ImageFont
    
        # 定义图片大小和背景颜色
        width = img_.shape[1]
        background_color = (255, 255, 255)
    
        # 定义字体、字号和颜色
        font_path = 'arial.ttf'
        font_size = 26
        font_color = (0, 0, 0)
    
        # 创建空白图片
        image = Image.new('RGB', (width, height), background_color)
    
        # 创建画笔
        draw = ImageDraw.Draw(image)
    
        # 加载字体
        font = ImageFont.truetype(font_path, font_size)
    
        # 写入文字
        text_width, text_height = draw.textsize(text, font)
        text_x = (width - text_width) // 2
        text_y = (height - text_height) // 2
        draw.text((text_x, text_y), text, font=font, fill=font_color)
    
        # 将Pillow图片转换为OpenCV图片
        image_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
    
        montage_size = (width, img_.shape[0])
        import imutils
        montages = imutils.build_montages([img_, image_cv], montage_size, (1, 2))
    
        # 保存图片
        return montages[0]
    
    
    if __name__ == '__main__':
        from imutils import paths
    
        # for imagePath in paths.list_images("W:\\python_code\\deepface\\huge_1.jpg"):
        for imagePath in range(1):
            print(f"处理的图片路径为: {imagePath}")
            # Read Image
            im = cv2.imread("image.jpg")
            size = im.shape
            # 对图像进行缩放的操作
            if size[0] > 700:
                h = size[0] / 3
                w = size[1] / 3
                # 如果图像的高度大于700,就将其高度和宽度分别缩小为原来的1/3,然后使用双三次插值的方法进行缩放。最后返回缩放后的图像的大小。
                im = cv2.resize(im, (int(w), int(h)), interpolation=cv2.INTER_CUBIC)
                size = im.shape
            # 获取坐标点    
            ret, image_points = get_image_points(im)
            if ret != 0:
                print('get_image_points failed')
                continue
    
            ret, rotation_vector, translation_vector, camera_matrix, dist_coeffs = get_pose_estimation(size, image_points)
    
            if ret != True:
                print('get_pose_estimation failed')
                continue
            draw_annotation_box(im, rotation_vector, translation_vector, camera_matrix, dist_coeffs)
            cv2.imwrite('new_' + "draw_annotation_box.jpg", im)
    
            ret, pitch, yaw, roll, pitch_degree, yaw_degree, roll_degree = get_euler_angle(rotation_vector)
    
            draw = im.copy()
            # Yaw:
    
            if yaw_degree < 0:
                output_yaw = "left : " + str(abs(yaw_degree)) + " degrees"
            elif yaw_degree > 0:
                output_yaw = "right :" + str(abs(yaw_degree)) + " degrees"
            else:
                output_yaw = "No left or right"
            print(output_yaw)
    
            # Pitch:
            if pitch_degree > 0:
                output_pitch = "dow :" + str(abs(pitch_degree)) + " degrees"
            elif pitch_degree < 0:
                output_pitch = "up :" + str(abs(pitch_degree)) + " degrees"
            else:
                output_pitch = "No downwards or upwards"
            print(output_pitch)
    
            # Roll:
            if roll_degree < 0:
                output_roll = "bends to the right: " + str(abs(roll_degree)) + " degrees"
            elif roll_degree > 0:
                output_roll = "bends to the left: " + str(abs(roll_degree)) + " degrees"
            else:
                output_roll = "No bend  right or left."
            print(output_roll)
    
            # Initial status:
            if abs(yaw) < 0.00001 and abs(pitch) < 0.00001 and abs(roll) < 0.00001:
                cv2.putText(draw, "Initial ststus", (20, 40), cv2.FONT_HERSHEY_SIMPLEX, .5, (0, 255, 0))
                print("Initial ststus")
    
            # 姿态检测完的数据写在对应的照片
            imgss = build_img_text_marge(im, output_yaw + "\n" + output_pitch + "\n" + output_roll, 200)
            cv2.imwrite('new_' + str(uuid.uuid4()).replace('-', '') + ".jpg", imgss)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    • 449
    • 450
    • 451
    • 452
    • 453
    • 454
    • 455
    • 456
    • 457
    • 458
    • 459
    • 460
    • 461
    • 462
    • 463
    • 464
    • 465
    • 466
    • 467
    • 468
    • 469
    • 470
    • 471
    • 472
    • 473
    • 474
    • 475
    • 476
    • 477
    • 478
    • 479
    • 480
    • 481
    • 482
    • 483
    • 484
    • 485
    • 486
    • 487
    • 488
    • 489
    • 490
    • 491
    • 492
    • 493
    • 494
    • 495
    • 496
    • 497
    • 498
    • 499
    • 500
    • 501
    • 502
    • 503
    • 504
    • 505
    • 506
    • 507
    • 508
    • 509
    • 510
    • 511
    • 512
    • 513
    • 514
    • 515
    • 516
    • 517
    • 518
    • 519
    • 520
    • 521
    • 522
    • 523
    • 524
    • 525
    • 526

    博文部分内容参考

    © 文中涉及参考链接内容版权归原作者所有,如有侵权请告知,这是一个开源项目,如果你认可它,不要吝啬星星哦 😃


    https://blog.csdn.net/zhang2gongzi/article/details/124520896

    https://github.com/JuneoXIE/

    https://github.com/yinguobing/head-pose-estimation


    © 2018-2023 liruilonger@gmail.com, All rights reserved. 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)

  • 相关阅读:
    commons-lang3
    Ceph入门到精通-centos8 install brctl
    A Philosophy of Software Design读书笔记——模块的接口要通用,实现要深入
    一周内快速上线!菊风联手浙银金租打造轻量化智能单向视频见证解决方案
    周志华机器学习——聚类算法。
    【Java 基础篇】自如应对文本数据:Java缓冲字符流详解
    【2022最新核心面试资料 】最强Java面试八股文秋招offer召唤术!入职薪资53k
    白话理解和使用DOCKER VOLUME
    C语言 数组
    论文笔记系列:经典主干网络(一)-- VGG
  • 原文地址:https://blog.csdn.net/sanhewuyang/article/details/131063583