【案例共创】基于华为云开发环境桌面版的人脸颜值打分

本案例由开发者:华为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_specconnection_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 算法优化

开发者可以根据自己人脸与网上数字人的人脸颜值对比,看看是否合理,以及调整计算颜值指标的算法。

三、反馈改进建议

如您在案例实操过程中遇到问题或有改进建议,可以到论坛帖评论区反馈即可,我们会及时响应处理,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值