)
RetinaFace实战教程结合dlib进行关键点后处理如欧拉角姿态角计算你是不是遇到过这样的情况用RetinaFace检测出了人脸和关键点但总觉得信息还不够用比如你想知道这个人脸是正对着镜头还是稍微侧了一点或者你想分析一下这个人的头部姿态看看他是在抬头、低头还是转头这就是我们今天要解决的问题。RetinaFace本身是个非常棒的人脸检测和关键点定位工具它能精准地找到人脸的五个关键点——双眼、鼻尖和嘴角。但如果我们想从这五个点里挖掘出更多信息比如计算头部的姿态角也就是常说的欧拉角偏航角、俯仰角、翻滚角就需要引入一个强大的“后处理”伙伴dlib。这篇文章我就带你一步步走通这个流程。从用RetinaFace检测出关键点开始到如何把这些点喂给dlib最终计算出能告诉你“脸朝哪边看”的欧拉角。整个过程我会配上详细的代码保证你跟着做就能出结果。1. 环境准备与工具介绍在开始动手之前我们先快速了解一下今天要用到的两个核心工具并确保你的环境已经就绪。1.1 核心工具RetinaFace 与 dlibRetinaFace是我们今天的主角之一它的任务非常明确在一张图片里找到所有人脸并且精准地标出每张脸上的5个关键点。这5个点就像是人脸的“锚点”为我们后续的姿态分析提供了最基础的数据。dlib则是一位“几何学大师”。它内置了一个非常重要的功能通过几个已知的2D图像点比如我们检测到的关键点和它们在3D世界中的对应位置一个标准的人脸3D模型来估算出相机的位置或者物体的姿态。这个过程在计算机视觉里叫做“求解PnP问题”。我们正是利用dlib的这个能力从2D的关键点反推出头部在3D空间中的旋转角度。简单来说RetinaFace负责“看到并定位”dlib负责“解读和计算”。1.2 快速配置你的环境为了让你能快速复现我们直接使用一个预配置好的环境。这个环境已经集成了RetinaFace所需的一切我们只需要额外安装dlib即可。首先按照提示启动并进入RetinaFace镜像环境cd /root/RetinaFace conda activate torch25接下来安装我们今天的另一位主角——dlib。在终端中执行以下命令pip install dlib安装过程可能会需要几分钟因为它需要编译一些C组件。安装成功后我们就可以进入正题了。2. RetinaFace基础检测与关键点获取在请dlib这位“几何大师”出山之前我们得先准备好“原材料”——也就是人脸关键点。让我们先用RetinaFace把这一步走通。2.1 运行RetinaFace检测脚本镜像里已经提供了一个非常好用的脚本inference_retinaface.py。我们先用它来处理一张图片看看RetinaFace的原始输出是什么样子。你可以使用自带的示例图片或者指定你自己的图片路径# 使用默认示例图片 python inference_retinaface.py # 或者使用你自己的图片 python inference_retinaface.py --input ./path/to/your/photo.jpg运行后脚本会在./face_results目录下生成结果图片。图片上会用框标出人脸并用红点标出5个关键点。这证明了我们的RetinaFace基础功能是正常的。2.2 理解关键点的数据结构为了后续处理我们需要修改一下脚本让它不仅保存图片还把关键点的坐标数据也吐出来。关键点的坐标通常是一个包含5个(x, y)坐标对的列表顺序一般是左眼、右眼、鼻尖、左嘴角、右嘴角。下面是一段简化的代码展示了如何从RetinaFace的检测结果中提取这些坐标# 假设 detections 是RetinaFace模型推理后得到的人脸检测结果列表 for i, det in enumerate(detections): # det 通常包含人脸框坐标 [x1, y1, x2, y2] 置信度 score 关键点 landmarks bbox det[:4].astype(int) # 人脸框 score det[4] # 置信度 landmarks det[5:15].reshape(5, 2) # 将10个值重塑为5个点的(x, y)坐标 print(f人脸 {i1}:) print(f 边框: {bbox}) print(f 置信度: {score:.2f}) for j, (x, y) in enumerate(landmarks): point_name [左眼, 右眼, 鼻尖, 左嘴角, 右嘴角][j] print(f {point_name}: ({x:.1f}, {y:.1f}))运行类似的代码你会在终端看到每个检测到的人脸及其关键点的精确数字坐标。这些数字就是我们通往3D姿态世界的钥匙。3. 引入dlib从2D点到3D姿态现在我们拿到了5个2D图像坐标点。接下来dlib要登场了。它的任务是根据这些2D点和一组预设的、对应的3D人脸模型点计算出头部相对于相机的旋转和平移。3.1 建立2D-3D点对应关系这是整个计算中最关键的一步我们必须明确知道我们检测到的2D图像点对应到标准3D人脸模型的哪个部位。dlib需要一个通用的3D人脸模型。这里我们采用一个被广泛使用的、包含68个点的3D模型。但对于RetinaFace的5点模型我们只需要从中选取对应的5个点。下图展示了这种对应关系关键点对应关系示意RetinaFace 2D点 (图像坐标) 左眼、右眼、鼻尖、左嘴角、右嘴角。dlib 3D模型点 (世界坐标) 我们需要从68点模型中找到与上述5个部位最匹配的3D坐标点索引。经过对照一个常用的映射关系如下索引基于dlib 68点模型左眼 (2D) ←→ 3D模型左眼中心点 (索引36-41的平均值可近似用索引39)右眼 (2D) ←→ 3D模型右眼中心点 (索引42-47的平均值可近似用索引45)鼻尖 (2D) ←→ 3D模型鼻尖点 (索引30)左嘴角 (2D) ←→ 3D模型左嘴角点 (索引48)右嘴角 (2D) ←→ 3D模型右嘴角点 (索引54)在代码中我们会预先定义好这组3D模型点的坐标单位通常是毫米具体数值是相对比例不影响角度计算。3.2 使用solvePnP计算姿态有了2D图像点数组和对应的3D模型点数组我们就可以调用OpenCVdlib的求解器底层也类似的cv2.solvePnP函数。这个函数的核心是解决“透视n点问题”。你可以把它想象成我已知一个标准3D人脸模型比如一个雕塑长什么样现在我在2D照片上看到了这个模型的几个特定部位眼睛、鼻子、嘴角。solvePnP会帮我反推出拍摄这张照片时这个3D模型也就是人的头部到底经历了怎样的旋转脸朝哪和平移离多远。计算完成后我们会得到一个旋转向量。这个向量包含了我们需要的全部旋转信息但它是一种紧凑的数学表示不太直观。3.3 将旋转向量转换为欧拉角旋转向量虽然高效但我们人类更习惯用“偏航角Yaw、俯仰角Pitch、翻滚角Roll”来理解姿态。这也就是常说的“欧拉角”。偏航角 (Yaw) 头部左右转动。0度表示正面正角度表示向左转从观察者视角负角度表示向右转。俯仰角 (Pitch) 头部上下点头。0度表示平视正角度表示抬头负角度表示低头。翻滚角 (Roll) 头部侧倾。0度表示水平正角度表示向左肩倾斜负角度表示向右肩倾斜。我们需要使用cv2.Rodrigues()函数将旋转向量转换为一个3x3的旋转矩阵然后再从这个旋转矩阵中分解出欧拉角。这个过程涉及一些三角函数计算好在有现成的公式可以套用。4. 实战代码完整的后处理流程理论说完了是时候把代码整合起来了。下面这个完整的示例脚本实现了从读取图片、RetinaFace检测、到dlib姿态角计算的全过程。创建一个新的Python文件比如叫做retinaface_with_pose.py并将以下代码复制进去import cv2 import numpy as np import torch from modelscope.pipelines import pipeline from modelscope.utils.constant import Tasks import sys import os # 1. 初始化RetinaFace模型 print(正在加载RetinaFace模型...) model_id damo/cv_resnet50_face-detection_retinaface face_detection pipeline(Tasks.face_detection, modelmodel_id) # 2. 定义3D人脸模型点 (对应dlib 68点模型中的索引左眼中心右眼中心鼻尖左嘴角右嘴角) # 这些是标准3D模型的近似坐标单位是毫米比例正确即可 # 注意这里的坐标顺序必须与RetinaFace输出的5个点顺序严格对应 model_points_3d np.array([ [-165.0, 170.0, -115.0], # 左眼中心 (近似索引39) [ 165.0, 170.0, -115.0], # 右眼中心 (近似索引45) [ 0.0, 0.0, 0.0], # 鼻尖 (索引30) [-150.0, -150.0, -125.0], # 左嘴角 (索引48) [ 150.0, -150.0, -125.0] # 右嘴角 (索引54) ], dtypenp.float64) # 3. 定义相机内参矩阵假设一个近似值如果知道真实相机参数更好 # 这里使用一个假设的焦距并认为图像中心是主点。 image_width 640 # 假设的图像宽度后续会根据实际图片调整 image_height 480 # 假设的图像高度 focal_length image_width center (image_width / 2, image_height / 2) camera_matrix np.array([ [focal_length, 0, center[0]], [0, focal_length, center[1]], [0, 0, 1] ], dtypenp.float64) # 假设没有镜头畸变 dist_coeffs np.zeros((4, 1), dtypenp.float64) def calculate_euler_angles(rotation_vector): 将旋转向量转换为欧拉角偏航、俯仰、翻滚 # 将旋转向量转换为旋转矩阵 rotation_matrix, _ cv2.Rodrigues(rotation_vector) # 从旋转矩阵中提取欧拉角 (使用OpenCV的decomposeProjectionMatrix方法的一种变体) # 注意欧拉角有多种计算顺序这里是一种常见顺序 (Yaw, Pitch, Roll) sy np.sqrt(rotation_matrix[0, 0] ** 2 rotation_matrix[1, 0] ** 2) singular sy 1e-6 if not singular: x np.arctan2(rotation_matrix[2, 1], rotation_matrix[2, 2]) # Roll y np.arctan2(-rotation_matrix[2, 0], sy) # Pitch z np.arctan2(rotation_matrix[1, 0], rotation_matrix[0, 0]) # Yaw else: x np.arctan2(-rotation_matrix[1, 2], rotation_matrix[1, 1]) # Roll y np.arctan2(-rotation_matrix[2, 0], sy) # Pitch z 0 # Yaw # 将弧度转换为角度 x np.degrees(x) y np.degrees(y) z np.degrees(z) return np.array([z, y, x]) # 返回顺序: [Yaw, Pitch, Roll] def process_image(image_path): 处理单张图片检测人脸并计算姿态角 print(f\n处理图片: {image_path}) img cv2.imread(image_path) if img is None: print(f错误无法读取图片 {image_path}) return # 更新相机内参矩阵基于实际图片尺寸 h, w img.shape[:2] camera_matrix[0, 2] w / 2.0 camera_matrix[1, 2] h / 2.0 camera_matrix[0, 0] camera_matrix[1, 1] w # 使用宽度作为近似焦距 # 使用RetinaFace进行检测 result face_detection(img) if boxes not in result: print(未检测到人脸。) return img boxes result[boxes] landmarks result[keypoints] # 形状应为 [人脸数, 5, 2] print(f检测到 {len(boxes)} 张人脸。) for i, (box, face_landmarks) in enumerate(zip(boxes, landmarks)): # box: [x1, y1, x2, y2, score] # face_landmarks: 5个点的 [[x1,y1], [x2,y2], ...] x1, y1, x2, y2, score box.astype(int) print(f\n人脸 {i1} (置信度: {score:.2f}):) # 将关键点坐标转换为NumPy数组并调整顺序如果需要 # RetinaFace输出顺序通常是 [左眼 右眼 鼻尖 左嘴角 右嘴角]与我们定义的3D点顺序一致。 image_points_2d np.array(face_landmarks, dtypenp.float64) # 使用solvePnP计算姿态 # 参数3D模型点2D图像点相机内参畸变系数 success, rotation_vec, translation_vec cv2.solvePnP( model_points_3d, image_points_2d, camera_matrix, dist_coeffs, flagscv2.SOLVEPNP_ITERATIVE # 使用迭代法比较稳定 ) if success: # 计算欧拉角 euler_angles calculate_euler_angles(rotation_vec) yaw, pitch, roll euler_angles print(f 头部姿态角 (度):) print(f 偏航角 (Yaw左右转): {yaw:.1f}) print(f 俯仰角 (Pitch上下看): {pitch:.1f}) print(f 翻滚角 (Roll侧倾): {roll:.1f}) # 在图像上绘制结果 # 绘制人脸框 cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) # 绘制关键点 for (x, y) in face_landmarks: cv2.circle(img, (int(x), int(y)), 3, (0, 0, 255), -1) # 显示姿态角文本 text fY:{yaw:04.1f}, P:{pitch:04.1f}, R:{roll:04.1f} cv2.putText(img, text, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2) # 可选绘制3D坐标轴直观显示姿态 axis_length 50.0 axis_points_3d np.float32([[axis_length, 0, 0], # X轴 (红色) [0, axis_length, 0], # Y轴 (绿色) [0, 0, -axis_length]]) # Z轴 (蓝色负号使其指向观察者) axis_points_2d, _ cv2.projectPoints(axis_points_3d, rotation_vec, translation_vec, camera_matrix, dist_coeffs) axis_points_2d axis_points_2d.reshape(-1, 2).astype(int) nose_point_2d tuple(image_points_2d[2].astype(int)) # 鼻尖作为轴起点 colors [(0, 0, 255), (0, 255, 0), (255, 0, 0)] # BGR: 红绿蓝 for j, color in enumerate(colors): end_point tuple(axis_points_2d[j]) cv2.line(img, nose_point_2d, end_point, color, 2) else: print(f 姿态估计失败。) return img if __name__ __main__: # 使用示例图片或传入参数 if len(sys.argv) 1: input_image sys.argv[1] else: # 默认使用镜像内的示例图片请确保路径正确 input_image test.jpg # 或者使用其他存在的图片 if not os.path.exists(input_image): print(请通过命令行参数指定图片路径例如python retinaface_with_pose.py ./my_face.jpg) sys.exit(1) output_img process_image(input_image) if output_img is not None: output_path output_with_pose.jpg cv2.imwrite(output_path, output_img) print(f\n结果已保存至: {output_path}) # 也可以显示图片如果环境支持 # cv2.imshow(Result, output_img) # cv2.waitKey(0) # cv2.destroyAllWindows()4.1 如何使用这个脚本将上面的代码保存为retinaface_with_pose.py放在/root/RetinaFace目录下。确保你已经安装了dlib和opencv-python镜像环境可能已预装如果没有运行pip install opencv-python。在终端运行脚本# 处理默认图片如果脚本里有默认路径 python retinaface_with_pose.py # 处理你自己的图片 python retinaface_with_pose.py /path/to/your/image.jpg运行后你会在终端看到每个检测到的人脸的姿态角输出同时脚本会生成一张名为output_with_pose.jpg的图片。这张图片上不仅画出了人脸框和关键点还标注了计算出的欧拉角并且从鼻尖处绘制了红、绿、蓝三条坐标轴直观地展示了头部的朝向。5. 结果解读与应用思考运行完代码你可能会得到类似这样的输出偏航角 (Yaw左右转): -12.5俯仰角 (Pitch上下看): 5.3翻滚角 (Roll侧倾): 1.8这表示这张脸稍微向右转了约12.5度负号通常表示向右略微抬起了约5.3度头部几乎水平。5.1 理解姿态角的含义数值范围每个角度的理论范围是-90度到90度或-180到180取决于计算方法。接近0度表示“正向”或“水平”。正负号正负号的定义可能因3D模型坐标系和计算顺序的不同而完全相反。上述代码给出的是一种常见约定。最重要的是理解其相对意义数值的绝对值大小表示转动幅度正负号表示转动方向。在实际应用中你需要通过测试比如让人向左/右转头来确定你自己代码中的正负号具体对应哪个方向。精度基于5个关键点的估计是近似值。它的优势是速度快适用于对精度要求不是极端苛刻的场景如视线估计的粗筛、互动应用、驾驶员状态监测等。对于需要高精度的医疗或科研领域可能需要更多关键点如68点或更密集的点和更复杂的模型。5.2 潜在的应用场景一旦能计算出头部姿态很多有趣的应用就打开了大门驾驶员状态监控通过分析俯仰角Pitch可以判断司机是否在频繁点头困倦通过偏航角Yaw可以判断视线是否长时间偏离前方道路。人机交互用户可以通过转动头部来控制游戏角色视角、翻看电子书、或者与虚拟助手进行非接触式交互。视线估计的基础头部姿态是估计眼球视线方向的一个重要先验信息。知道头朝哪边能大大缩小眼球可能看向的范围。摄影与视频自动判断合影中是否所有人都看向了镜头或者在视频通话中提示用户“请正视摄像头”。虚拟试戴/美颜更准确地将虚拟眼镜、帽子或美颜特效贴合到用户脸上因为知道了脸的3D朝向。6. 总结通过这篇教程我们完成了一次从2D到3D的“升级”。RetinaFace提供了精准的2D人脸关键点而dlib的PnP求解能力则让我们能够解读出这些关键点背后蕴含的3D空间信息——头部的欧拉角姿态。回顾一下核心步骤环境搭建在RetinaFace镜像基础上安装dlib库。数据获取运行或修改RetinaFace脚本获取人脸5个关键点的2D图像坐标。坐标映射建立这5个点与标准3D人脸模型点的对应关系。姿态求解使用cv2.solvePnP函数结合相机参数求解旋转和平移向量。角度转换将旋转向量转换为更直观的偏航角、俯仰角、翻滚角。这个过程最巧妙的地方在于我们无需昂贵的3D传感器仅用普通的RGB摄像头和开源算法就能实现对头部姿态的定量分析。虽然基于5点模型的精度有限但对于众多实际应用来说它已经提供了一个强大、快速且低成本的解决方案。你可以尝试用不同角度的人脸照片来测试脚本观察欧拉角的变化是否符合预期。也可以思考如何将这个功能集成到你的实际项目中比如开发一个简单的疲劳驾驶检测原型或者一个用头部控制鼠标的小程序。技术的乐趣就在于将想法一步步实现。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。