本案例由开发者:华为2024年第三批次协同育人项目-南航金城学院-孙福清教师提供
一、概述
1. 案例介绍
本案例是基于华为云主机的python开发环境,引导开发者学习和实践人工智能计算机视觉的一个应用案例—基于计算机视觉python开源模块opencv以及mediapipe实现人脸颜值的打分量化。
华为开发者空间,是为全球开发者打造的专属开发者空间,致力于为每位开发者提供一台云主机、一套开发工具和云上存储空间,汇聚昇腾、鸿蒙、鲲鹏、GaussDB、欧拉等华为各项根技术的开发工具资源,并提供配套案例指导开发者 从开发编码到应用调测,基于华为根技术生态高效便捷的知识学习、技术体验、应用创新。
2. 适用对象
- 企业
- 个人开发者
- 高校学生
3. 案例时间
本案例总时长预计90分钟。
4. 案例流程

说明: 1. 安装验证python模块opencv; 2. opencv基础函数介绍; 3. 安装验证python模块mediapipe; 4. mediapipe获取脸部地标点; 5. 人脸颜值量化打分。
按照上述步骤逐步进行学习和实验。
5. 资源总览
本案例预计花费0元。
| 资源名称 | 规格 | 单价(元) | 时长(分钟) |
|---|---|---|---|
| 华为开发者空间 - 云主机 | 鲲鹏通用计算增强型 kc2 | 4vCPUs | 8G | Ubuntu | 免费 | 90 |
二、开发者空间配置
1. 开发者空间配置
面向广大开发者群体,华为开发者空间提供一个随时访问的“开发桌面云主机”、丰富的“预配置工具集合”和灵活使用的“场景化资源池”,开发者开箱即用,快速体验华为根技术和资源。
进入华为开发者空间工作台界面,点击打开云主机 > 进入桌面连接云主机。 如果还没有领取云主机进入工作台界面后点击配置云主机,选择Ubuntu操作系统。


2 安装验证python模块opencv
安装验证python模块opencv,请参考空间案例《【案例共创】基于华为云开发环境桌面版的剪刀手手势识别》中的“二、安装验证 Python 模块 opencv”。
3 opencv基础函数介绍
opencv基础函数介绍,请参考空间案例《【案例共创】基于华为云开发环境桌面版的剪刀手手势识别》中的“三、opencv 基础函数介绍”。
4 安装验证python模块mediapipe
安装验证python模块mediapipe,请参考空间案例《【案例共创】基于华为云开发环境桌面版的剪刀手手势识别》中的“四、安装验证Python模块mediapipe”。
5 Mediapipe获取脸部的关键地标点
5.1 Mediapipe的脸部地标点face landmarks
"landmark"在医学和生物学领域常被用来指代身体或面部的重要标志或特征,这些标志或特征在定位、测量或分析时具有重要作用。 Face Landmark,面部标志,是指面部关键点检测技术,它利用深度学习等技术,在图像中精确地识别并定位出人脸的关键特征点,如眼睛、鼻子和嘴巴的位置,在人脸识别、表情识别、美颜应用以及虚拟主播等领域具有广泛的应用前景和巨大的潜力。 MediaPipe的face_mesh模块能够检测人脸并提取468个3D面部地标点。这些地标点覆盖了面部的各个部位,包括眼睛、眉毛、鼻子、嘴巴、下巴等: 眼睛:包括眼睑、瞳孔等部位。 眉毛:覆盖了眉毛的轮廓。 鼻子:包括鼻梁、鼻尖等部位。 嘴巴:包括嘴唇的轮廓和内部。 下巴:覆盖了下巴的轮廓。 脸部轮廓:包括脸颊、颧骨等部位。
每个地标点都有一个唯一的索引编号和对应的 3D 坐标(x, y, z)。

https://github.com/google-ai-edge/mediapipe/blob/master/mediapipe/modules/face_geometry/data/canonical_face_model_uv_visualization.png
这些地标点可以用于以下应用:
-
面部表情识别 通过分析地标点之间的相对位置和变化,可以识别出各种面部表情,例如微笑、皱眉、眨眼等。
-
虚拟现实和增强现实 利用地标点的三维坐标信息,可以在虚拟环境中实时跟踪和渲染人脸,实现更加自然的交互效果。
-
人机交互 通过检测特定的面部动作(如点头、摇头等),可以实现非接触式的人机交互
5.2 完成项目环境搭建
参照前面的案例,在华为云主机上完成项目环境搭建: - 完成《华为云主机Linux Python开发环境配置》; - 在华为云主机上搭建本案例项目环境(此案例前半部分已介绍): 创建项目文件夹; 创建虚拟环境; 在虚拟环境中完成python开源模块opencv和mediapipe安装和验证。

5.3 从脸部图片中获取地标点face landmarks
步骤1:准备脸部图片(高清、大图),可从网上下载无版权的数字人图片,复制到项目文件夹。

步骤2:新建空白py代码文件,准备编写复制获取脸部地标点的py示例代码。
gedit facelandmarks.py

步骤3:获取脸部地标点的示例代码,复制编写到py文件中。
import cv2
import mediapipe
#------------------------------------------------------------------------------
def imgHeightResize(img, fixedHeight):
imgResized = img
imgH,imgW,imgChs = imgResized.shape
ratioH = fixedHeight/imgH
imgResized = cv2.resize(img,None,fx=ratioH,fy=ratioH,interpolation=cv2.INTER_CUBIC)
return imgResized
#------------------------------------------------------------------------------
if __name__ == '__main__':
imgOrg = cv2.imread("111.jpeg")
img = imgHeightResize(imgOrg, 640)
imgRGB = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
faceDetector=mediapipe.solutions.face_mesh.FaceMesh(
static_image_mode=False,
refine_landmarks=True,
max_num_faces=5,
min_detection_confidence=0.5,
min_tracking_confidence=0.5)
results = faceDetector.process(imgRGB)
if (results.multi_face_landmarks):
for faceLms in results.multi_face_landmarks:
mediapipe.solutions.drawing_utils.draw_landmarks(
image=img,
landmark_list=faceLms,
connections=mediapipe.solutions.face_mesh.FACEMESH_CONTOURS)
else:
cv2.putText(img,"no hands detected",(100,100),cv2.FONT_HERSHEY_COMPLEX,1,(0,0,255),2,8)
cv2.imshow("img",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
注意:图片文件要真实存在。
步骤3:执行py代码,显示脸部地标点的图片。
python3 facelandmarks.py

mediapipe成功从脸部图片中检测出人脸,获取到脸部关键点,并且在图片上绘制出脸部关键点。
5.4 过程代码解析
过程1- 导入模块
import mediapipe
就可以直接使用模块名字来引用其中的函数或者变量、宏等,例如:
mediapipe.solutions.face_mesh.FaceMesh(),
mediapipe.solutions.face_mesh.FACEMESH_CONTOURS
有些网上的导入模块代码为:
import mediapipe as mp
这其实是给mediapipe起个字符串数量更少的别名,减少代码量,本质一样,引用模块的函数、变量或宏就变为:
mediapipe.solutions.face_mesh.FaceMesh(),
mediapipe.solutions.face_mesh.FACEMESH_CONTOURS
过程2- opencv读入图片并调整尺寸大小适合显示
见前面章节关于opencv基础函数介绍部分
#-----------------------------------------------------------------------
def imgHeightResize(img, fixedHeight):
imgResized = img
imgH,imgW,imgChs = imgResized.shape
ratioH = fixedHeight/imgH
imgResized = cv2.resize(img,None,fx=ratioH,fy=ratioH,interpolation=cv2.INTER_CUBIC)
return imgResized
#-----------------------------------------------------------------------
if __name__ == '__main__':
imgOrg = cv2.imread("face.jpeg")
img = imgHeightResize(imgOrg, 640)
过程3- 图片转换色彩空间
imgRGB = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
使用cv2.imread打开图片会得到一个numpy数组[width,height],数组中每个元素就是一个像素点,像素点使用三维BGR表示[0-blue,1-gree,2-red] MediaPipe的像素点使用 RGB 表示,即[0-red,1-gree,2-blue] cv2.cvtColor 函数可以将图像从一种颜色空间转换为另一种颜色空间 BGR和RGB的区别: - BGR:是 OpenCV 默认的颜色空间格式。它将图像的每个像素表示为一个三通道的值,分别是蓝色(Blue)、绿色(Green)和红色(Red),顺序为 BGR。 - RGB:是大多数图像处理库(如 PIL、Matplotlib 等)以及许多深度学习框架(如 TensorFlow、PyTorch 等)使用的颜色空间格式。它的顺序是红色(Red)、绿色(Green)和蓝色(Blue)。
过程4- 创建手部检测器并初始化
faceDetector=mediapipe.solutions.face_mesh.FaceMesh(
static_image_mode=False,
refine_landmarks=True,
max_num_faces=5,
min_detection_confidence=0.5,
min_tracking_confidence=0.5)
mediapipe.solutions.face_mesh.FaceMesh是MediaPipe提供的一个用于人脸检测和面部地标点提取的类,能够实时从图像或视频中重建出人脸的3D网格,支持多达468个面部地标点。类在初始化时可以接收以下参数:
max_num_faces:最多检测的人脸数量,默认值为1。 refine_landmarks:是否细化地标点(如眼睛和嘴巴)。如果设置为 True,会额外输出针对眼睛虹膜的地标点,地标点总数会从468增加到478。 min_detection_confidence:人脸检测的置信度阈值,范围为[0, 1],默认值为 0.5。 min_tracking_confidence:人脸跟踪的置信度阈值,范围为[0, 1],默认值为 0.5。
过程5- 调用检测器对读入的图片进行检测
results = faceDetector.process(imgRGB)
在使用mediapipe.solutions.face_mesh.FaceMesh的process方法时,返回的results(名字随意)数据结构包含了人脸检测和面部地标点(face landmarks)的相关信息。 以下是results数据结构中各个字段的详细解释: - results.multi_face_landmarks 这是一个列表,包含每个检测到的人脸的地标点信息。每个元素是一个 mediapipe.framework.formats.landmark_pb2.LandmarkList对象,表示一个人脸的468个(或更多,取决于是否启用细化地标点)地标点。 landmark:一个包含 468个(或更多)mediapipe.framework.formats.landmark_pb2.Landmark 对象的列表,每个对象表示一个地标点。 x:地标点的 x 坐标(归一化值,范围为 [0, 1])。 y:地标点的 y 坐标(归一化值,范围为 [0, 1])。 z:地标点的 z 坐标(相对深度值,范围为 [-1, 1],表示地标点相对于摄像头的深度)。
-
results.multi_face_world_landmarks 这是一个列表,包含每个检测到的人脸的地标点在世界坐标系中的三维坐标信息。每个元素是一个 mediapipe.framework.formats.landmark_pb2.LandmarkList 对象,表示一个人脸的468个地标点。 landmark:一个包含468个 mediapipe.framework.formats.landmark_pb2.Landmark对象的列表,每个对象表示一个地标点。 x:地标点的 x 坐标(世界坐标系中的值,单位为米)。 y:地标点的 y 坐标(世界坐标系中的值,单位为米)。 z:地标点的 z 坐标(世界坐标系中的值,单位为米)。
-
results.multi_face_blendshapes 这是一个列表,包含每个检测到的人脸的表情混合形状(blendshapes)信息。每个元素是一个mediapipe.framework.formats.classification_pb2.ClassificationResult对象,表示人脸的表情分类结果。 classification:一个包含 mediapipe.framework.formats.classification_pb2.Classification对象的列表,每个对象表示一个表情分类结果。 index:分类结果的索引。 score:分类的置信度分数(范围为 [0, 1])。 label:分类的标签(字符串,表示表情类型,如 "smile"、"frown" 等)。
过程6:对检测到的脸部地标点进行点位和连线绘图
if (results.multi_face_landmarks):
for faceLms in results.multi_face_landmarks:
mediapipe.solutions.drawing_utils.draw_landmarks(
image=img,
landmark_list=faceLms,
connections=mediapipe.solutions.face_mesh.FACEMESH_CONTOURS)
示例代码中使用mediapipe模块内置的点位和连线绘制函数:
mediapipe.solutions.drawing_utils.draw_landmarks()
函数原型:
mediapipe.python.solutions.drawing_utils.draw_landmarks(
image,
landmark_list,
connections=None,
landmark_drawing_spec=None,
connection_drawing_spec=None
)
函数参数:
-
image: 类型:numpy.ndarray 描述:输入图像,地标点将被绘制在这个图像上。图像应该是BGR格式(OpenCV 默认格式)。
-
landmark_list: 类型:mediapipe.framework.formats.landmark_pb2.LandmarkList 描述:包含地标点信息的对象。每个地标点是一个mediapipe.framework.formats.landmark_pb2.Landmark对象,包含 x、y、z 坐标。
-
connections: 类型:list[tuple[int, int]] 描述:地标点之间的连接关系。每个元组表示两个地标点的索引,用于绘制连接线。例如,[(0, 1), (1, 2)] 表示连接地标点0和1,以及地标点1和2。 默认值:None,如果不指定连接关系,则不会绘制连接线。
-
landmark_drawing_spec: 类型:mediapipe.python.solutions.drawing_utils.DrawingSpec 或list[mediapipe.python.solutions.drawing_utils.DrawingSpec] 描述:用于自定义地标点的绘制样式。可以指定颜色、点的大小等。如果传递一个列表,则每个地标点可以使用不同的样式。 默认值:None,默认使用白色绘制地标点。
-
connection_drawing_spec: 类型:mediapipe.python.solutions.drawing_utils.DrawingSpec 或list[mediapipe.python.solutions.drawing_utils.DrawingSpec] 描述:用于自定义连接线的绘制样式。可以指定颜色、线的宽度等。如果传递一个列表,则每条连接线可以使用不同的样式。 默认值:None,默认使用白色绘制连接线
landmark_drawing_spec和connection_drawing_spec被用来自定义地标点和连接线的绘制样式。例如DrawingSpec 的参数包括: color:颜色,格式为 (B, G, R),例如 (0, 255, 0) 表示绿色。 thickness:线条或点的粗细。 circle_radius:地标点的半径。 通过调整这些参数,可以实现不同的可视化效果。
可以按照自己的需求,自己编写绘制点位和连线的函数,例如只需要某个绘制手指的地标点点位,不需要连线等。
5.5 地标点坐标归一化值转换为图片像素值
每个地标点为(x,y,z),值为(0-1),相对于图像的长宽比例,需将xy的比例坐标转换成图像像素坐标: - 比例坐标x乘以宽度width,得到坐标点的X轴的像素坐标; - 比例坐标y乘以宽度height,得到坐标点的Y轴的像素坐标; - 像素坐标需要取整。
完整代码:
import cv2
import mediapipe
#------------------------------------------------------------------------------
def imgHeightResize(img, fixedHeight):
imgResized = img
imgH,imgW,imgChs = imgResized.shape
ratioH = fixedHeight/imgH
imgResized = cv2.resize(img,None,fx=ratioH,fy=ratioH,interpolation=cv2.INTER_CUBIC)
return imgResized
#------------------------------------------------------------------------------
if __name__ == '__main__':
imgOrg = cv2.imread("111.jpeg")
img = imgHeightResize(imgOrg, 640)
imgRGB = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
faceDetector=mediapipe.solutions.face_mesh.FaceMesh(
static_image_mode=False,
refine_landmarks=True,
max_num_faces=5,
min_detection_confidence=0.5,
min_tracking_confidence=0.5)
results = faceDetector.process(imgRGB)
if (results.multi_face_landmarks):
for faceLms in results.multi_face_landmarks:
h, w , c = img.shape
for idx, coord in enumerate(faceLms.landmark):
cx = int(coord.x * w)
cy = int(coord.y * h)
cv2.circle(img, (cx, cy), 1, (255,0,0), -1)
img = cv2.putText(img,
str(idx), #点位旁边标注序号
(cx, cy),
cv2.FONT_HERSHEY_SIMPLEX,
0.25,(0, 0, 0), 1, 1)
else:
cv2.putText(img,"no hands detected",(100,100),cv2.FONT_HERSHEY_COMPLEX,1,(0,0,255),2,8)
cv2.imshow("img",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
执行代码,地标点坐标从归一化值转换为图片像素值并绘制到图片上:
python3 facelandmarks.py

6 基于脸部地标点的颜值打分
6.1 美术的颜值指标
“三庭五眼”是中国传统美学中用于衡量面部比例和颜值的重要标准,它基于面部的纵向和横向比例关系,帮助判断五官的协调性和整体美感。
三庭: “三庭”是指将面部的长度分为三等分,具体划分如下: 上庭:从发际线到眉骨。 中庭:从眉骨到鼻底。 下庭:从鼻底到下巴尖。 理想情况下,这三部分的长度应大致相等。
五眼: “五眼”是指将面部的宽度分为五等分,每份的宽度大致等于一只眼睛的宽度: 两眼内眦(内眼角)之间的距离为一只眼睛的宽度。 两眼外眦(外眼角)到同侧发际线的距离各为一只眼睛的宽度。 两眼之间的间距也应为一只眼睛的宽度。
美学意义 协调性:符合“三庭五眼”标准的面部,通常被认为五官位置协调,整体美感更佳。 个性化:虽然“三庭五眼”是理想标准,但现实中很少有人完全符合这一比例。个性化的面部特征(如独特的眼型或眉型)也能增加颜值。 文化差异:不同文化对美的定义有所不同。例如,东亚文化中更偏好“丹凤眼”,而欧美文化中则对“方下颌”有更高的评价。
现代应用 在整形医学和美容领域,“三庭五眼”常被用作参考标准,帮助医生和美容师评估和调整面部比例,以达到更协调的视觉效果:

嘴唇和鼻子宽度的达芬奇衡量公式: 脸是人体最奇妙的部位,虽然只有眼、嘴、鼻等几个器官,但大小、组合方式、位置的不同,却能变化出千姿百态的脸。
美国研究人员在达·芬奇的画中,发现了他创造美丽的公式:脸宽是鼻宽的四倍,前额的高度、鼻子的长度以及下颌骨高度都相等。 科学家们发现,较小的嘴是文艺复兴时期的首选标准,达·芬奇认为嘴宽是鼻宽的1.5倍时最吸引人。而现代科学家们研究发现,嘴宽是鼻宽的1.6倍时(黄金分割点0.618),才是最吸引人的面貌。

6.2 基于脸部地标点计算五眼指标
这里给出的简单的算法思想:
步骤1:提取指标相关的地标点的坐标点数据。
脸轮廓的最左侧点-23 脸轮廓的最右侧点-45 左眼的左眼角-33 左眼的右眼角-133 右眼的左眼角-362 右眼的右眼角-263

步骤2:计算指标。
- 计算脸部宽度 = 从最左到最右的距离;
- 计算两眼宽度的平均值 = (右眼宽度 + 左眼宽度)/2;
- 计算 五个部位宽度 分别与 眼宽平均 之间的 差,使用脸宽进行归一化: a、宽度1 : 绝对值(左眼左眼角 - 脸最左 - 平均眼宽)/ 脸宽; b、宽度2 : 绝对值(左眼右眼角 - 左眼左眼角 - 平均眼宽)/ 脸宽; c、宽度3 : 绝对值(右眼左眼角 - 左眼右眼角 - 平均眼宽)/ 脸宽; d、宽度4 : 绝对值(右眼右眼角 - 右眼左眼角 - 平均眼宽)/ 脸宽; e、宽度5 : 绝对值(脸最右 - 右眼右眼角 - 平均眼宽)/ 脸宽。
- L2范数-计算五个宽度值归一化值的平方和,再开平方。
6.3 基于脸部地标点计算三庭指标
算法想法: 提取出指标相关的地标点的坐标: - 脸轮廓的最上侧边缘10 - 脸轮廓的最下侧边缘152 - 眉心9 - 鼻翼下缘 2

计算指标: - 脸长度 = 脸轮廓的最下侧边缘152 - 脸轮廓的最上侧边缘10 - 中庭距离 = 鼻翼下缘02 - 眉心9 - 下庭距离 = 脸轮廓的最下侧边缘152 - 鼻翼下缘02 - 三庭指标 = 绝对值(下庭距离 - 中庭距离)* 100 / 脸长度
这里不计算上庭,是因为mediapipe提取的地标点没有发际线的点,只有额头的点。
6.4 基于脸部地标点计算嘴宽与鼻框的达芬奇衡量公式
算法思想 步骤1-提取指标相关的地标点坐标: - 嘴唇左角 61 - 嘴唇右角 291 - 鼻翼左缘 129 - 鼻翼右缘 358

步骤2-计算指标: - 嘴宽 = 嘴唇右角 291 - 嘴唇左角 61 - 鼻宽 = 鼻翼右缘 358 - 鼻翼左缘 129 - 指标 = 鼻宽 / 嘴宽
6.5 过程介绍
过程1:准备2张脸部图片(高清、大图)
准备2张图片,复制到项目文件夹,实现两张脸部的几个颜值指标的人脸颜值比拼

过程2:新建空白py文件,编写代码,实现两张脸部的人脸颜值比拼
gedit faceBeauty.py

faceBeauty.py完整代码:
import cv2
import mediapipe as mp
import numpy as np
import math
import sys
#-------------------------------------
def imgshow_keypoint(img,idxstr,cx,cy):
cv2.circle(img, (cx, cy), 3, (255,0,0), -1)
cv2.putText(img, idxstr, (cx, cy), cv2.FONT_HERSHEY_SIMPLEX, 0.4,(0, 0, 0), 1, 1)
#-------------------------------------
def imgshow_metric(img,idxstr,n):
#参数1-目标图像
#参数2-文本
#参数3-起始坐标,文本左侧的左上角坐标(默认) 或 左下角,
#参数4-字体
#参数5-字体基本大小的比例因子
#参数6-文本颜色
#参数7-文本线条的粗细
#参数8-文本线条的形状
#参数9-文本在起始坐标的左上角false(default),还是左下角True
imgH,imgW,imgChs = img.shape
textSpace = np.full((40,imgW,3),255,np.uint8)
textImg = np.vstack((textSpace,img))
cv2.putText(textImg,idxstr,(10,20),cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,0),1, 8)
#cv2.imshow(("textImg%d"%n),textImg)
return textImg
#-------------------------------------
def imgshow_line(img,start_cx,start_cy,end_cx,end_cy):
#参数1-img:你想要绘制图形的那幅图像。
#参数2-(x,y):线条的起点坐标点
#参数3-(x,y):线条的终点坐标点.
#参数4-color:形状的颜色。以BGR为例,需要传入一个元组,(255,0,0)代表蓝色。对于灰度图只需要传入灰度值。
#参数5-thickness:线条的粗细。如果给一个闭合图形(如矩形)设置为 -1,那么这个图形就会被填充。默认值是1.
#参数6-linetype:线条的类型。默认8。cv2.LINE_AA为抗锯齿,这样看起来会非常平滑。
#参数7-shift:缩放参数,一般不用
#y坐标稍微下移,给关键点标注的序号留下空位
cv2.line(img, (start_cx, (start_cy+3)), (end_cx, (end_cy+3)), (0,0,0), 1)
#-------------------------------------
def beauty_five_eyes(img,faceLandMarks):
h, w , c = img.shape
metricSpace = np.full((80,w,3),255,np.uint8)
metricImg = np.vstack((img,metricSpace))
# 脸轮廓的最左侧点
faceLeft = faceLandMarks.landmark[234];
faceLeft_cx, faceLeft_cy = int(faceLeft.x * w), int(faceLeft.y * h);
imgshow_keypoint(metricImg,'234',faceLeft_cx,faceLeft_cy)
# 脸轮廓的最右侧点
faceRight = faceLandMarks.landmark[454];
faceRight_cx, faceRight_cy = int(faceRight.x * w), int(faceRight.y * h);
imgshow_keypoint(metricImg,'454',faceRight_cx,faceRight_cy)
# 脸轮廓的最上侧边缘
faceTop = faceLandMarks.landmark[10];
faceTop_cx, faceTop_cy = int(faceTop.x * w), int(faceTop.y * h);
imgshow_keypoint(metricImg,'010',faceTop_cx,faceTop_cy)
# 脸轮廓的最下侧边缘
faceBottom = faceLandMarks.landmark[152];
faceBottom_cx, faceBottom_cy = int(faceBottom.x * w), int(faceBottom.y * h);
imgshow_keypoint(metricImg,'152',faceBottom_cx,faceBottom_cy)
# 左眼的左眼角
leftEyeLeftConer = faceLandMarks.landmark[33];
leftEyeLeftConer_cx, leftEyeLeftConer_cy = int(leftEyeLeftConer.x * w), int(leftEyeLeftConer.y * h);
imgshow_keypoint(metricImg,'033',leftEyeLeftConer_cx,leftEyeLeftConer_cy)
# 左眼的右眼角
leftEyeRightConer = faceLandMarks.landmark[133];
leftEyeRightConer_cx, leftEyeRightConer_cy = int(leftEyeRightConer.x * w), int(leftEyeRightConer.y * h);
imgshow_keypoint(metricImg,'133',leftEyeRightConer_cx,leftEyeRightConer_cy)
# 右眼的左眼角362
rightEyeLeftConer = faceLandMarks.landmark[362];
rightEyeLeftConer_cx, rightEyeLeftConer_cy = int(rightEyeLeftConer.x * w), int(rightEyeLeftConer.y * h);
imgshow_keypoint(metricImg,'362',rightEyeLeftConer_cx,rightEyeLeftConer_cy)
# 右眼的右眼角263
rightEyeRightConer = faceLandMarks.landmark[263];
rightEyeRightConer_cx, rightEyeRightConer_cy = int(rightEyeRightConer.x * w), int(rightEyeRightConer.y * h);
imgshow_keypoint(metricImg,'263',rightEyeRightConer_cx,rightEyeRightConer_cy)
w1 = leftEyeLeftConer_cx - faceLeft_cx
w2 = leftEyeRightConer_cx - leftEyeLeftConer_cx
w3 = rightEyeLeftConer_cx - leftEyeRightConer_cx
w4 = rightEyeRightConer_cx - rightEyeLeftConer_cx
w5 = faceRight_cx - rightEyeRightConer_cx
# 从最左到最右的距离
faceWidth = faceRight_cx - faceLeft_cx
# 两眼宽度的平均值 = (右眼宽度 + 左眼宽度)/2
leftEyeWidth = (leftEyeRightConer_cx - leftEyeLeftConer_cx)
rightEyeWidth = (rightEyeRightConer_cx - rightEyeLeftConer_cx)
eyeWidthMean = (leftEyeWidth + rightEyeWidth) / 2
# 计算 五个部位宽度 分别与 眼宽平均 之间的 差,使用脸宽进行归一化
# 宽度1 : 左眼左眼角 - 脸最左
# 宽度2 : 左眼右眼角 - 左眼左眼角
# 宽度3 : 右眼左眼角 - 左眼右眼角
# 宽度4 : 右眼右眼角 - 右眼左眼角
# 宽度5 : 脸最右 - 右眼右眼角
gap1 = np.abs(w1 - eyeWidthMean) * 100 / faceWidth
gap2 = np.abs(w2 - eyeWidthMean) * 100 / faceWidth
gap3 = np.abs(w3 - eyeWidthMean) * 100 / faceWidth
gap4 = np.abs(w4 - eyeWidthMean) * 100 / faceWidth
gap5 = np.abs(w5 - eyeWidthMean) * 100 / faceWidth
#L2范数-所有元素的平方和,再开平方
Five_Eye_Metrics = math.sqrt( math.pow(gap1,2) +
math.pow(gap2,2) +
math.pow(gap3,2) +
math.pow(gap4,2) +
math.pow(gap5,2) )
imgshow_line(metricImg, faceLeft_cx, faceTop_cy, faceLeft_cx, faceBottom_cy )
imgshow_line(metricImg, faceRight_cx, faceTop_cy, faceRight_cx, faceBottom_cy )
imgshow_line(metricImg, leftEyeLeftConer_cx, faceTop_cy, leftEyeLeftConer_cx, faceBottom_cy )
imgshow_line(metricImg, leftEyeRightConer_cx, faceTop_cy, leftEyeRightConer_cx, faceBottom_cy )
imgshow_line(metricImg, rightEyeLeftConer_cx, faceTop_cy, rightEyeLeftConer_cx, faceBottom_cy )
imgshow_line(metricImg, rightEyeRightConer_cx, faceTop_cy, rightEyeRightConer_cx, faceBottom_cy )
cv2.putText(metricImg,
("w1=%d,w2=%d,w3=%d,w4=%d,w5=%d"%(w1,w2,w3,w4,w5)),
(10,20+h),
cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,0),1)
cv2.putText(metricImg,
('FiveEyeMetricC = {:.2f}'.format(Five_Eye_Metrics)),
(10,40+20+h),
cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,0),1)
return metricImg
#-------------------------------------
### 三庭指标:three courts ###
def beauty_three_courts(img,faceLandMarks):
h, w , c = img.shape
metricSpace = np.full((60,w,3),255,np.uint8)
metricImg = np.vstack((img,metricSpace))
# 脸轮廓的最左侧点
faceLeft = faceLandMarks.landmark[234];
faceLeft_cx, faceLeft_cy = int(faceLeft.x * w), int(faceLeft.y * h);
imgshow_keypoint(metricImg,'234',faceLeft_cx,faceLeft_cy)
# 脸轮廓的最右侧点
faceRight = faceLandMarks.landmark[454];
faceRight_cx, faceRight_cy = int(faceRight.x * w), int(faceRight.y * h);
imgshow_keypoint(metricImg,'454',faceRight_cx,faceRight_cy)
# 脸轮廓的最上侧边缘
faceTop = faceLandMarks.landmark[10];
faceTop_cx, faceTop_cy = int(faceTop.x * w), int(faceTop.y * h);
imgshow_keypoint(metricImg,'010',faceTop_cx,faceTop_cy)
# 脸轮廓的最下侧边缘
faceBottom = faceLandMarks.landmark[152];
faceBottom_cx, faceBottom_cy = int(faceBottom.x * w), int(faceBottom.y * h);
imgshow_keypoint(metricImg,'152',faceBottom_cx,faceBottom_cy)
# 眉心
eyebrowMiddle = faceLandMarks.landmark[9];
eyebrowMiddle_cx, eyebrowMiddle_cy = int(eyebrowMiddle.x * w), int(eyebrowMiddle.y * h);
imgshow_keypoint(metricImg,'009',eyebrowMiddle_cx,eyebrowMiddle_cy)
# 鼻翼下缘 2
noseBottom = faceLandMarks.landmark[2];
noseBottom_cx, noseBottom_cy = int(noseBottom.x * w), int(noseBottom.y * h);
imgshow_keypoint(metricImg,'002',noseBottom_cx,noseBottom_cy)
# 三庭:
# 衡量公式1:三庭高度相等,上庭:距离1(发际线-眉心);中庭:距离2(鼻子底部--眉心);下庭:距离3(下巴尖-鼻子底部)
# 注意,脸部网格,脸部最上端是额头中央,不是发际线,有偏差。只计算中庭和下庭的偏差
# 指标A:下庭 与 中庭 之间距离偏差,在使用 脸长(脸部上方和下方距离) 进行归一化
Top_Down = faceBottom_cy - faceTop_cy
sectionUpper = eyebrowMiddle_cy - faceTop_cy
sectionMiddle = noseBottom_cy - eyebrowMiddle_cy
sectionDown = faceBottom_cy - noseBottom_cy
# (中庭 - 下庭)/脸长
MetricThreeSection_A = np.abs(sectionDown - sectionMiddle) * 100 / Top_Down
imgshow_line(metricImg, faceLeft_cx, eyebrowMiddle_cy, faceRight_cx, eyebrowMiddle_cy )
imgshow_line(metricImg, faceLeft_cx, noseBottom_cy, faceRight_cx, noseBottom_cy )
imgshow_line(metricImg, faceLeft_cx, faceBottom_cy, faceRight_cx, faceBottom_cy )
cv2.putText(metricImg,
("h1=%d,h2=%d,h3=%d,faceHeight=%d"%(sectionUpper,sectionMiddle,sectionDown,Top_Down)),
(10,20+h),
cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,0),1)
cv2.putText(metricImg,
('ThreeSectionMetricA = {:.2f}'.format(MetricThreeSection_A)),
(10,30+20+h),
cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,0),1)
return metricImg
#-------------------------------------
### 达芬奇指标:嘴唇宽度 是 鼻翼宽度的1.6倍(0.618) ###
def beauty_davinci(img,faceLandMarks):
h, w , c = img.shape
metricSpace = np.full((60,w,3),255,np.uint8)
metricImg = np.vstack((img,metricSpace))
# 嘴唇左角 61
lipLeft = faceLandMarks.landmark[61];
lipLeft_cx = int(lipLeft.x * w)
lipLeft_cy = int(lipLeft.y * h)
imgshow_keypoint(metricImg,'061',lipLeft_cx,lipLeft_cy)
# 嘴唇右角 291
lipRight = faceLandMarks.landmark[291];
lipRight_cx = int(lipRight.x * w)
lipRight_cy = int(lipRight.y * h)
imgshow_keypoint(metricImg,'291',lipRight_cx,lipRight_cy)
# 鼻翼左缘 129
noseLeft = faceLandMarks.landmark[129];
noseLeft_cx = int(noseLeft.x * w)
noseLeft_cy = int(noseLeft.y * h)
imgshow_keypoint(metricImg,'129',noseLeft_cx,noseLeft_cy)
# 鼻翼右缘 358
noseRight358 = faceLandMarks.landmark[358];
noseRight358_cx = int(noseRight358.x * w)
noseRight358_cy = int(noseRight358.y * h)
imgshow_keypoint(metricImg,'358',noseRight358_cx,noseRight358_cy)
# 鼻翼右缘 344
#noseRight344 = faceLandMarks.landmark[344];
#noseRight344_cx = int(noseRight344.x * w)
#noseRight344_cy = int(noseRight344.y * h)
#imgshow_keypoint(img,'344',noseRight344_cx,noseRight344_cy)
#metricDavinci344 = (lipRight_cx - lipLeft_cx) / (noseRight344_cx - noseLeft_cx)
#metricDavinci358 = (lipRight_cx - lipLeft_cx) / (noseRight358_cx - noseLeft_cx)
noseWidth = (noseRight358_cx - noseLeft_cx)
lipWidth = lipRight_cx - lipLeft_cx
metricDavinci = noseWidth / lipWidth
imgshow_line(metricImg, lipLeft_cx, lipLeft_cy, lipRight_cx, lipLeft_cy )
imgshow_line(metricImg, noseLeft_cx, noseLeft_cy, noseRight358_cx, noseLeft_cy )
cv2.putText(metricImg,
("noseWidth=%d,lipWidth=%d"%(noseWidth,lipWidth)),
(10,20+h),
cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,0),1)
cv2.putText(metricImg,
('metricDavinci={:.2f}'.format(metricDavinci)),
(10,30+20+h),
cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,0),1)
return metricImg
#-------------------------------------
def faceLandmarksBeauty(selfUrl,digitalUrl,processedImgUrl,text):
model1=mp.solutions.face_mesh.FaceMesh(
static_image_mode=False,
refine_landmarks=True,
max_num_faces=5,
min_detection_confidence=0.5,
min_tracking_confidence=0.5,
)
model2=mp.solutions.face_mesh.FaceMesh(
static_image_mode=False,
refine_landmarks=True,
max_num_faces=5,
min_detection_confidence=0.5,
min_tracking_confidence=0.5,
)
selfImg = cv2.imread(selfUrl)
imgH,imgW,imgChs = selfImg.shape
sizedH = 640
sizedW = int ( (sizedH/imgH)*imgW )
selfImg = cv2.resize(selfImg,(sizedW,sizedH),fx=0,fy=0,interpolation=cv2.INTER_CUBIC)
img_RGB = cv2.cvtColor(selfImg, cv2.COLOR_BGR2RGB)
results = model1.process(img_RGB)
if results.multi_face_landmarks:
for faceLms in results.multi_face_landmarks:
selfFiveeyesImg=beauty_five_eyes(selfImg, faceLms)
selfThreecourtsImg=beauty_three_courts(selfImg, faceLms)
selfDavinciImg=beauty_davinci(selfImg, faceLms)
else:
print("no face detected!")
cv2.putText(selfFiveeyesImg,text,(10,sizedH-30),cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),2,8)
cv2.putText(selfThreecourtsImg,text,(10,sizedH-30),cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),2,8)
cv2.putText(selfDavinciImg,text,(10,sizedH-30),cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),2,8)
digitalImg = cv2.imread(digitalUrl)
imgH,imgW,imgChs = digitalImg.shape
sizedH = 640
sizedW = int ( (sizedH/imgH)*imgW )
digitalImg = cv2.resize(digitalImg,(sizedW,sizedH),fx=0,fy=0,interpolation=cv2.INTER_CUBIC)
img_RGB = cv2.cvtColor(digitalImg, cv2.COLOR_BGR2RGB)
results = model2.process(img_RGB)
if results.multi_face_landmarks:
for faceLms in results.multi_face_landmarks:
digitalFiveeyesImg=beauty_five_eyes(digitalImg, faceLms)
digitalThreecourtsImg=beauty_three_courts(digitalImg, faceLms)
digitalDavinciImg=beauty_davinci(digitalImg, faceLms)
else:
print("no face detected!")
cv2.putText(digitalFiveeyesImg,text,(10,sizedH-30),cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),2,8)
cv2.putText(digitalThreecourtsImg,text,(10,sizedH-30),cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),2,8)
cv2.putText(digitalDavinciImg,text,(10,sizedH-30),cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),2,8)
metricFiveeyesImg = np.hstack((selfFiveeyesImg,digitalFiveeyesImg))
metricThreecourtsImg = np.hstack((selfThreecourtsImg,digitalThreecourtsImg))
metricDavinciImg = np.hstack((selfDavinciImg,digitalDavinciImg))
cv2.imwrite(processedImgUrl+"metric-five-eyes.jpeg", metricFiveeyesImg)
cv2.imwrite(processedImgUrl+"metric-three-courts.jpeg", metricThreecourtsImg)
cv2.imwrite(processedImgUrl+"metric-davinci.jpeg", metricDavinciImg)
cv2.imshow("metric-five-eyes.jpeg", metricFiveeyesImg)
cv2.imshow("metric-three-courts.jpeg", metricThreecourtsImg)
cv2.imshow("metric-davinci.jpeg", metricDavinciImg)
#-------------------------------------
if __name__ == '__main__':
# 检查命令行参数 python your_script.py example.txt
if len(sys.argv) < 3:
print("请提供文件名作为命令行参数!python3 faceBeauty.py 111.jpg 222.jpg")
sys.exit(1)
# selfImg
selfImgUrl = sys.argv[1]
try:
with open(selfImgUrl, 'r', encoding='utf-8') as file:
print(f"文件 '{selfImgUrl}' 找到")
except FileNotFoundError:
print(f"错误:文件 '{selfImgUrl}' 未找到!")
sys.exit(1)
except Exception as e:
print(f"发生错误:{e}")
sys.exit(1)
# selfImg
digitalImgUrl = sys.argv[2]
try:
with open(digitalImgUrl, 'r', encoding='utf-8') as file:
print(f"文件 '{digitalImgUrl}' 找到")
except FileNotFoundError:
print(f"错误:文件 '{digitalImgUrl}' 未找到!")
sys.exit(1)
except Exception as e:
print(f"发生错误:{e}")
sys.exit(1)
#savedImgUrl = "./pictures/saved/2020104701-metric-fiveeyes.jpg"
savedImgUrl = "./pictures/saved/"
faceLandmarksBeauty(selfImgUrl,digitalImgUrl,savedImgUrl,
"test")
#frame = cv2.imread(savedImgUrl)
#cv2.imshow("frame",frame)
cv2.waitKey(0)
cv2.destroyAllWindows()
过程3:带两张人脸图片文件名参数执行代码
python3 faceBeauty.py 111.jpeg 222.jpeg

人脸对比: 


6.6 算法优化
开发者可以根据自己人脸与网上数字人的人脸颜值对比,看看是否合理,以及调整计算颜值指标的算法。
三、反馈改进建议
如您在案例实操过程中遇到问题或有改进建议,可以到论坛帖评论区反馈即可,我们会及时响应处理,谢谢!
6万+

被折叠的 条评论
为什么被折叠?



