当前位置: 首页 > news >正文

基于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))

面部特征点检测

面部特征点检测,利用预训练模型 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")

姿势估计

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

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

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 libimport 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 = 0for index in range(1, len(dets)):if face_areas[index] > largest_area:largest_index = indexlargest_area = face_areas[index]# 打印最大人脸框的索引和总人脸数print("largest_face index is {} in {} faces".format(largest_index, len(dets)))return largest_indexdef 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 vectorimage_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_pointsdef 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, Nonelargest_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 cornercv2.circle(draw, (landmark_shape.part(21).x, landmark_shape.part(21).y), 2, (0, 165, 255),-1)  # 21 left brow right cornercv2.circle(draw, (landmark_shape.part(22).x, landmark_shape.part(22).y), 2, (0, 165, 255),-1)  # 22 right brow left cornercv2.circle(draw, (landmark_shape.part(26).x, landmark_shape.part(26).y), 2, (0, 165, 255),-1)  # 26 right brow right cornercv2.circle(draw, (landmark_shape.part(36).x, landmark_shape.part(36).y), 2, (0, 165, 255),-1)  # 36 left eye left cornercv2.circle(draw, (landmark_shape.part(39).x, landmark_shape.part(39).y), 2, (0, 165, 255),-1)  # 39 left eye right cornercv2.circle(draw, (landmark_shape.part(42).x, landmark_shape.part(42).y), 2, (0, 165, 255),-1)  # 42 right eye left cornercv2.circle(draw, (landmark_shape.part(45).x, landmark_shape.part(45).y), 2, (0, 165, 255),-1)  # 45 right eye right cornercv2.circle(draw, (landmark_shape.part(31).x, landmark_shape.part(31).y), 2, (0, 165, 255),-1)  # 31 nose left cornercv2.circle(draw, (landmark_shape.part(35).x, landmark_shape.part(35).y), 2, (0, 165, 255),-1)  # 35 nose right cornercv2.circle(draw, (landmark_shape.part(48).x, landmark_shape.part(48).y), 2, (0, 165, 255),-1)  # 48 mouth left cornercv2.circle(draw, (landmark_shape.part(54).x, landmark_shape.part(54).y), 2, (0, 165, 255),-1)  # 54 mouth right cornercv2.circle(draw, (landmark_shape.part(57).x, landmark_shape.part(57).y), 2, (0, 165, 255),-1)  # 57 mouth central bottom cornercv2.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 internalsfocal_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_coeffsdef 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 = 10rear_depth = 0point_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 = 10point_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 linescv2.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 anglestheta = cv2.norm(rotation_vector, cv2.NORM_L2)# transformed to quaterniondw = math.cos(theta / 2)x = math.sin(theta / 2) * rotation_vector[0][0] / thetay = math.sin(theta / 2) * rotation_vector[1][0] / thetaz = math.sin(theta / 2) * rotation_vector[2][0] / thetaysqr = 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.0if t2 < -1.0:t2 = -1.0yaw = 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_degreedef 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, Noneret, 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, Noneret, pitch, yaw, roll = get_euler_angle(rotation_vector)if ret != 0:print('get_euler_angle failed')return -1, None, None, Noneeuler_angle_str = 'Pitch:{}, Yaw:{}, Roll:{}'.format(pitch, yaw, roll)print(euler_angle_str)return 0, pitch, yaw, rollexcept Exception as e:print('get_pose_estimation_in_euler_angle exception:{}'.format(e))return -1, None, None, Nonedef 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 cv2from PIL import Image, ImageDraw, ImageFont# 定义图片大小和背景颜色width = img_.shape[1]background_color = (255, 255, 255)# 定义字体、字号和颜色font_path = 'arial.ttf'font_size = 26font_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) // 2text_y = (height - text_height) // 2draw.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 imutilsmontages = 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 Imageim = cv2.imread("image.jpg")size = im.shape# 对图像进行缩放的操作if size[0] > 700:h = size[0] / 3w = 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')continueret, rotation_vector, translation_vector, camera_matrix, dist_coeffs = get_pose_estimation(size, image_points)if ret != True:print('get_pose_estimation failed')continuedraw_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)

博文部分内容参考

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


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)

相关文章:

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

写在前面 工作中遇到&#xff0c;简单整理博文内容涉及基于 OpenCV 和 Dlib头部姿态评估的简单Demo理解不足小伙伴帮忙指正 庐山烟雨浙江潮&#xff0c;未到千般恨不消。到得还来别无事&#xff0c;庐山烟雨浙江潮。 ----《庐山烟雨浙江潮》苏轼 https://github.com/LIRUILONGS…...

24个Jvm面试题总结及答案

1.什么是Java虚拟机&#xff1f;为什么Java被称作是“平台无关的编程语言”&#xff1f; Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。 Java被设计成允许应用程序可以运行在任意的平台&#xff0c;而不需要程序员为每…...

freemarker 生成前端文件

Freemarker是一种模板引擎&#xff0c;它允许我们在Java应用程序中分离视图和业务逻辑。在Freemarker中&#xff0c;List是一种非常有用的数据结构&#xff0c;它允许我们存储一组有序的元素。有时候&#xff0c;我们需要判断一个List是否为空&#xff0c;这在程序设计中有许多…...

Pycharm+pytest+allure打造高逼格的测试报告

目录 前言&#xff1a; 1、安装allure 2、安装allure-pytest 3、一个简单的用例test_simpe.py 4、在pycharm底部打开terminal 5、用allure美化报告 6、查看报告 总结&#xff1a; 前言&#xff1a; 今天分享的内容&#xff1a;在Pycharmpytest基础上使用allure打造高逼格…...

Mybatis-Plus中update更新操作用法

目录 一、前言二、update1、关于修改的4个条件构造器2、UpdateWrapper【用法示例】3、LambdaUpdateWrapper【用法示例】4、UpdateChainWrapper【 用法示例】5、LambdaUpdateChainWrapper【 用法示例】6、updateById 和 updateBatchById7、Mybatis-plus设置某个字段值为null的方…...

16道JVM面试题

1.jvm内存布局 1.程序计数器&#xff1a;当前线程正在执行的字节码的行号指示器&#xff0c;线程私有&#xff0c;唯一一个没有规定任何内存溢出错误的情况的区域。 2.Java虚拟机栈&#xff1a;线程私有&#xff0c;描述Java方法执行的内存模型&#xff0c;每个方法运行时都会…...

HttpRunner 接口自动化测试框架实战,打造高效测试流程

简介 2018年python开发者大会上&#xff0c;了解到HttpRuuner开源自动化测试框架&#xff0c;采用YAML/JSON格式管理用例&#xff0c;能录制和转换生成用例功能&#xff0c;充分做到用例与测试代码分离&#xff0c;相比excel维护测试场景数据更加简洁。在此&#xff0c;利用业…...

手写一个webpack插件(plugin)

熟悉 vue 和 react 的小伙伴们都知道&#xff0c;在执行过程中会有各种生命周期钩子&#xff0c;其实webpack也不例外&#xff0c;在使用webpack的时候&#xff0c;我们有时候需要在 webpack 构建流程中引入自定义的行为&#xff0c;这个时候就可以在 hooks 钩子中添加自己的方…...

jvm常见面试题

0x01. 内存模型以及分区&#xff0c;需要详细到每个区放什么。 栈区&#xff1a; 栈分为java虚拟机栈和本地方法栈 重点是Java虚拟机栈&#xff0c;它是线程私有的&#xff0c;生命周期与线程相同。 每个方法执行都会创建一个栈帧&#xff0c;用于存放局部变量表&#xff0…...

TF-A 项目的长期支持介绍

引流关键词:Armv8-A, Armv9-A, Cortex-A, Cortex-A12, Cortex-A15, Cortex-A17, Cortex-A32, Cortex-A34, Cortex-A35, Cortex-A5, Cortex-A510, Cortex-A53, Cortex-A55, Cortex-A57, Cortex-A65, Cortex-A65AE, Cortex-A7, Cortex-A710, Cortex-A715, Cortex-A72, Cortex-A7…...

企业电子招标采购系统源码java 版本 Spring Cloud + Spring Boot

项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及…...

7.Mysql 事务底层

一、事务的基础知识 mysql中的事务 分为 显式事务 和 隐式事务。 1.1 显式事务 显式事务就是我们手动开启事务,并且提交事务比如: -- 开启事务 begin; -- 执行查询语句 select *from where id = 1 for update ; -- 提交事务 commit;1.2 隐式事务 在 MySQL 中,隐式事务是…...

15.DIY可视化-拖拽设计1天搞定主流小程序-分类联动文章列表实时刷新

分类联动文章列表实时刷新 本教程均在第一节中项目启动下操作 分类联动文章列表实时刷新前言需求一:功能实现:点击首页分类,对应分类内容显示到当前页一、清空原分类界面:二. 设置选项卡三:设定展示内容字段:1.跨页面复制:文章分类组件到分类![在这里插入图片描述](https://img…...

【SpringCloud】二、服务注册发现Eureka与负载均衡Ribbon

文章目录 一、Eureka1、服务提供者与消费者2、Eureka原理分析3、搭建Eureka4、服务注册5、模拟多服务实例启动6、服务的发现 二、Ribbon1、负载均衡的原理2、源码分析3、负载均衡策略4、饥饿加载 一、Eureka 1、服务提供者与消费者 服务提供者&#xff1a;一次业务中&#xf…...

图形学实验(完整文件见上传)

CRect rect; this->GetClientRect(rect); pDC->Ellipse(rect); // DDALineView.cpp : implementation of the CDDALineView class // #include “stdafx.h” #include “DDALine.h” #include “DDALineDoc.h” #include “DDALineView.h” #ifdef _DEBUG #define new…...

Spark大数据处理学习笔记(3.2.1)掌握RDD算子

该文章主要为完成实训任务&#xff0c;详细实现过程及结果见【http://t.csdn.cn/FArNP】 文章目录 一、准备工作1.1 准备文件1. 准备本地系统文件2. 把文件上传到 1.2 启动Spark Shell1. 启动HDFS服务2. 启动Spark服务3. 启动Spark Shell 二、掌握转换算子2.1 映射算子 - map()…...

lammps初级:石墨烯、金属材料、纳米流体、热传导、多成分体系、金属、半导体材料的辐照、自建分子力场、MOFS、H2/CO2混合气体等模拟

1 LAMMPS的基础入门——初识LAMMPS是什么&#xff1f;能干什么&#xff1f;怎么用&#xff1f; 1.1 LAMMPS在win10和ubuntu系统的安装及使用 1.2 in文件结构格式 1.3 in文件基本语法&#xff1a;结合实例&#xff0c;讲解in文件常用命令 1.4 data文件格式 1.5 LAMMPS常见错误解…...

【MarkerDown】CSDN Markdown之时序图sequenceDiagram详解

CSDN Markdown之时序图sequenceDiagram详解 序列图 sequenceDiagram参与者与组参与者 participant拟人符号 actor别名 as组 box 消息(连线)激活/失活 activate/deactivate备注 Note循环 loop备选 Alt并行 par临界区 critical中断 break背景高亮 rect注释 %%转义字符的实体代码序…...

ReentrantLock实现原理-公平锁

在ReentrantLock实现原理(1)一节中&#xff0c;我们了解了ReentrantLock非公平锁的获取流程&#xff0c;在本节中我们来看下ReentrantLock公平锁的创建以及锁管理流程 创建ReentrantLock公平锁 创建公平锁代码如下&#xff1a; ReentrantLock reentrantLock new ReentrantL…...

掌握Scala数据结构(2)MAP、TUPLE、SET

一、映射 (Map) &#xff08;一&#xff09;不可变映射 1、创建不可变映射 创建不可变映射mp&#xff0c;用键->值的形式 创建不可变映射mp&#xff0c;用(键, 值)的形式 注意&#xff1a;Map是特质&#xff08;Scala里的trait&#xff0c;相当于Java里的interface&#…...

flutter:文件系统目录、文件读写

参考 参考&#xff1a;老孟 文件存储和网络请求 数据存储 Dart的 IO 库包含了文件读写的相关类&#xff0c;它属于 Dart 语法标准的一部分&#xff0c;所以通过 Dart IO 库&#xff0c;无论是 Dart VM 下的脚本还是 Flutter&#xff0c;都是通过 Dart IO 库来操作文件的。但…...

计算机提示“找不到vcruntime140.dll,无法继续执行代码可”以这样子修复

首先&#xff0c;对于那些不熟悉的人来说&#xff0c;vcruntime140.dll是一个关键文件&#xff0c;用于在Windows操作系统上运行使用C语言编写的大型应用程序。如果你正在运行或安装这样的应用程序&#xff0c;但找不到vcruntime140.dll文件&#xff0c;那么你的应用程序可能无…...

深度学习pytorch实战五:基于ResNet34迁移学习的方法图像分类篇自建花数据集图像分类(5类)超详细代码

1.数据集简介 2.模型相关知识 3.split_data.py——训练集与测试集划分 4.model.py——定义ResNet34网络模型 5.train.py——加载数据集并训练&#xff0c;训练集计算损失值loss&#xff0c;测试集计算accuracy&#xff0c;保存训练好的网络参数 6.predict.py——利用训练好的网…...

Rust in Action笔记 第五章 深入理解数据

如果希望看到f32类型的数转换成整型数字u32类型&#xff0c;需要在unsafe包裹下调用std::mem::transmute(data)&#xff0c;因为在安全的Rust语法中没有把整型数据按照bit转换成浮点数据的实现&#xff0c;如果想要看到浮点数的二进制输出&#xff08;通过{:b}&#xff09;&…...

Cocos creator实现飞机大战空中大战《战击长空》小游戏资源及代码

Cocos creator实现飞机大战空中大战《战击长空》小游戏资源及代码 最近在学习Cocos Creator&#xff0c;作为新手&#xff0c;刚刚开始学习Cocos Creator&#xff0c;刚刚入门&#xff0c;这里记录一下飞机大战小游戏实现。 https://wxaurl.cn/VEgRy2eTMyi 一 安装CocosDashBo…...

2.4 逻辑代数的基本定理

学习目标&#xff1a; 如果我要学习逻辑代数的基本定理&#xff0c;我会采取以下步骤&#xff1a; 1. 学习基本概念&#xff1a;首先&#xff0c;我会花时间了解逻辑代数的基本概念&#xff0c;如逻辑运算符&#xff08;合取、析取、否定等&#xff09;、真值表、逻辑等价性等…...

适用于 Linux 的 Windows 子系统wsl文档

参考链接&#xff1a;https://learn.microsoft.com/zh-cn/windows/wsl/ 鸟哥的Linux私房菜&#xff1a;http://cn.linux.vbird.org/ http://cn.linux.vbird.org/linux_basic/linux_basic.php http://cn.linux.vbird.org/linux_server/ 目录 安装列出可用的 Linux 发行版列出已…...

C++特殊类的设计与类型转换

特殊类的设计与类型转换 特殊类的设计请设计一个类&#xff0c;只能在堆上创建对象请设计一个类&#xff0c;只能在栈上创建对象请设计一个类&#xff0c;只能创建一个对象(单例模式) C的类型转换 特殊类的设计 请设计一个类&#xff0c;只能在堆上创建对象 通过new创建的类就…...

如何通过关键词搜索API接口

如果你是一位电商运营者或者是想要进行1688平台产品调研的人员&#xff0c;你可能需要借助API接口来获取你所需要的信息。在这篇文章中&#xff0c;我们将会讨论如何通过关键词搜索API接口获取1688的商品详情。 第一步&#xff1a;获取API接口的授权信息 在使用API接口前&…...

智驾域控新战争打响,谁在抢跑?

智能驾驶域控制器赛道&#xff0c;已经成为了时下最为火热的市场焦点之一。 最近&#xff0c;头部Tier1均胜电子公布了全球首批基于高通Snapdragon Ride第二代芯片平台的智能驾驶域控制器产品nDriveH&#xff0c;在这一赛道中显得格外引人注意。 就在不久之前&#xff0c;均胜…...

wordpress邮箱模板/推广互联网推广

2048游戏 ​ 文件规则&#xff1a; ​ main.c 程序主入口 ​ game2048.c game2048.h 游戏的业务逻辑 ​ direction.c direction.h 方向键的处理 ​ tools.c tools.h 工具函数 ​ Makefile 编译脚本文件 main函数 #include "game2048.h"int main(int argc,cha…...

最早做淘宝客的网站/小程序

一、JDK源码的重要性JDK源码的重要性不言而喻&#xff0c;平时的面试、深入学习等都离不开JDK的源码。当然&#xff0c;JDK源码是非常优秀的代码&#xff0c;我们之所以阅读JDK源码&#xff0c;就是为了理解底层原理、学习优秀的设计模式和思想。不过JDK源码也是相当难啃的知识…...

可以做图的网站/网络公关

什么是WebPack&#xff1f; 征战图 看图就知道了&#xff0c;如图左边的是我们平常编码的一些文件&#xff0c;他们种类繁多复杂&#xff0c; 有css,less,cjs,js,sass,还有各种各样的图片资源。webpack需要做的 就是将我们编码的原始文件资源&#xff0c;通过特定的手段将其…...

河南商丘疫情最新政策/seo是什么意思 为什么要做seo

Symantec Endpoint Protection v11.0.5002.333 简体中文企业版 【现象描述】&#xff1a;为了内网安全&#xff0c;内网可以上外网的一台服务器上面部署了网络版symantec杀毒软件&#xff0c;版本是 v11.0.5002.333 简体中文企业版&#xff1b;由它生成的客户端&#xff0c;分发…...

公司注册网站诈骗莫名被起诉/友情链接只有链接

在对一个简单的Hello World工程进行项目结构剖析后&#xff0c;我们接着来学习下一个Android应用程序的生命周期是怎么样的&#xff0c;以便为后面的开发有个垫下良好的基石~ 所谓的应用程序生命周期无非就是应用程序进程从创建到消亡的整个过程。但是&#xff0c;之所有将这一…...

白云网站建设多少钱/网络上哪里可以做推广

https://www.cnblogs.com/zhangzhifeng/category/835602.html 这是个困惑我很长时间的问题&#xff0c;到今天终于得到解决了。 话说Delphi有个很强的窗体设计器&#xff0c;这一点让VC粉丝垂涎三尺而不可得。但是&#xff0c;Delphi里设计的窗体并没有自动适应屏幕分辨率的属性…...