基于Mediapipe的换脸

基于Mediapipe的换脸



前言

Mediapipe是一个开源跨平台框架,用于构建多媒体处理管道。支持各种任务,如面部识别、手势识别、姿态估计等。下面是基于Mediapipe的换脸方法与过程的叙述与总结。


一、方法

  1. 面部检测与跟踪:首先使用Mediapipe的人脸检测模型来检测图像或视频中的人脸,并跟踪面部关键点。
  2. 面部特征提取:基于检测到的面部关键点,提取面部的特征,如眼睛、鼻子、嘴巴等。
  3. 面部融合:将源人脸的面部特征与目标人脸的面部特征进行融合。这个过程通常涉及图像变形、颜色校正等。
  4. 渲染输出:将融合后的面部特征贴回目标人脸,生成最终的换脸图像或视频。

二、过程

  1. 环境搭建:
    ○ 准备输入数据:选择需要换脸的源图像和目标图像。
  2. 面部检测与跟踪:
    ○ 使用Mediapipe的FaceMesh或FaceDetection模型来检测和跟踪源图像和目标图像中的人脸。
  3. 面部特征提取:
    ○ 根据检测到的面部关键点,提取源人脸和目标人脸的面部特征。
  4. 面部融合:
    ○ 使用图像处理技术,如三角剖分和变形,将源人脸的面部特征映射到目标人脸的相应位置。
    ○ 对融合区域进行颜色校正,使源人脸和目标人脸的颜色更加一致。
  5. 渲染输出:
    ○ 将融合后的面部特征合并到目标人脸图像中,生成最终的换脸结果。
    ○ 如果是视频,需要对每一帧图像进行上述处理,并合成视频输出。

1.引入库

代码如下:

import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
import cv2
import numpy as np

2.算法流程

流程图:
在这里插入图片描述

流程图解释:

  1. 输入图像(Input Image)
    用户提供源图像和目标图像。
  2. 面部检测与特征提取(Face Detection & Landmark Extraction)
    使用 MediaPipe 的 Face Detection 模块检测图像中的面部。
    通过 Face Mesh 模块,提取面部关键点(通常是 468 个点)。
  3. 关键点对齐与匹配(Landmark Matching & Alignment)
    将源图像的面部关键点与目标图像的面部关键点进行匹配。
    对关键点进行对齐,以确保两个面部的比例、方向等一致。
  4. 面部区域提取(Face Region Extraction)
    根据检测到的关键点提取源图像中的面部区域。
    采用 凸包(Convex Hull) 或 Delaunay 三角化(Delaunay Triangulation) 来描述面部轮廓,确保换脸时的过渡更加自然。
  5. 换脸(Face Swap)
    将目标面部区域与源图像中的面部区域进行融合。
    通过纹理映射(Texture Mapping)将源面部图像贴到目标面部区域。
  6. 颜色和光照调整(Color and Lighting Adjustment)
    调整源面部图像的颜色、光照等参数,使其与目标面部环境更加一致。
    可以使用图像处理方法,如直方图匹配(Histogram Matching)等。
  7. 图像融合(Image Blending)
    使用 Poisson Image Editing 或其他融合技术平滑过渡,消除接缝,使得换脸效果更加自然。
  8. 输出结果(Output Image)
    最终输出换脸后的图像。

3.检测人脸关键点

def ladmask(img):
    """
    使用 MediaPipe 检测人脸关键点。
    """
    # 初始化 MediaPipe 的人脸网格检测器,设置静态模式为 False,最多检测 5 张人脸
    face_mesh = mp_face.FaceMesh(static_image_mode=False, max_num_faces=5)
    # 将输入图像从 BGR 转为 RGB
    rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    # 处理图像以获取人脸关键点
    results = face_mesh.process(rgb_img)
    # 释放资源
    face_mesh.close()

    # 用于存储所有检测到的人脸关键点
    faces_points = []
    if results.multi_face_landmarks:
        # 遍历每张检测到的人脸
        for landmarks in results.multi_face_landmarks:
            # 将每个关键点的坐标转换为图像上的像素位置
            points = np.array([
                (int(landmark.x * img.shape[1]), int(landmark.y * img.shape[0]))
                for landmark in landmarks.landmark
            ])
            faces_points.append(points)  # 添加到结果列表
    return faces_points  # 返回所有检测到的关键点

#这段代码通过 MediaPipe 的 FaceLandmarker 模型检测人脸的关键点,获取其归一化坐标后将其转换为像素坐标。
#返回值是一个 NumPy 数组,包含图像中检测到的人脸关键点的位置,用于后续处理,如特征提取或姿态估计。

注意:以上代码用于检测单个采取到的人脸,如果想用于进行多人人脸换脸,则需改动代码,示例如下:

# 初始化 MediaPipe 的 Face Mesh 模块,用于多人脸关键点检测
face_mesh = mp_face_mesh.FaceMesh(static_image_mode=False, max_num_faces=5)

此处既是更改人脸检测数量的关键,max_num_faces=5,此处为5,意义着可以实现同时进行5个人的换脸

要完成这段代码实现的目标——同时更换多张人脸,需定义和实现以下关键功能模块:

1.掩模生成 (get_face_mask):
根据人脸关键点生成区域掩模。

2.仿射变换 (transformation_from_points):
计算从一个人脸关键点集到另一个人脸关键点集的变换矩阵。

3.仿射变换应用 (get_affine_image):
对图像和掩模执行仿射变换。

4.掩模合并 (get_mask_union):
合并两张掩模,用于泊松融合。

5.泊松融合中心点计算 (get_mask_center_point):
计算掩模的中心点,用于泊松融合

4.换脸仿射

def transformation_from_points(points1, points2):
    # 根据两组关键点计算仿射变换矩阵
    points1 = np.array(points1, dtype=np.float32)  # 转为浮点型数组
    points2 = np.array(points2, dtype=np.float32)  # 转为浮点型数组
    return cv2.estimateAffinePartial2D(points1, points2, method=cv2.LMEDS)[0]  # 计算变换矩阵

def get_face_mask(img, landmarks):
    # 创建一个与输入图像大小相同的空白蒙版
    mask = np.zeros(img.shape[:2], dtype=np.uint8)
    # 根据人脸关键点填充一个凸多边形区域(即人脸区域)
    cv2.fillConvexPoly(mask, cv2.convexHull(landmarks), 255)
    return mask  # 返回人脸区域蒙版

def get_mask_center_point(mask):
    # 获取蒙版中所有非零像素的坐标
    y, x = np.nonzero(mask)
    if len(x) == 0 or len(y) == 0:
        return None  # 如果没有找到非零像素,则返回 None
    return (int(np.mean(x)), int(np.mean(y)))  # 返回蒙版中像素的平均中心点

要进行换脸,对目标人脸进行识别是必要的

人脸仿射变换的原理:
仿射变换是一种线性变换,能够保持直线、比例和夹角特性。换脸仿射变换的作用是将一张脸的特征对齐到目标脸上。核心过程如下:

a. 确定对应点
根据 Mediapipe 提取的关键点,在源图像和目标图像中选取特定区域(通常是三角形网格)作为基础。
对应点可能包括眼睛角点、鼻尖、嘴角等,用于描述脸部特定区域。

b. 三角形网格的细化
为了实现更精细的换脸效果,可以将整个脸部区域划分成三角形网格。具体方法是:

1.使用 Delaunay 三角剖分将关键点划分为多个三角形。
2.对每个三角形计算仿射变换矩阵并对其进行变换。

5.输入源脸

对目标脸进行替换的源脸的载入

img1=cv2.imread("huge.jpg")
#原图像

points1 = ladmask(img1)
# 调用前面定义的 `ladmask` 函数,检测图片 `img1` 中的人脸关键点。
# 返回值 `points1` 是一个 NumPy 数组,包含人脸的关键点像素坐标。

img1_mask = get_face_mask(img1, points1)
# 调用 `get_face_mask` 函数,生成 `img1` 中人脸的掩模图像。
# 参数 `img1` 是原始图像,`points1` 是对应的人脸关键点坐标。
# 返回值 `img1_mask` 是一个灰度图像,其中人脸区域被填充为白色 (255),背景为黑色 (0)。

landmarks1 = ladmask(img1)
# 再次调用 `ladmask` 函数,重新检测图片 `img1` 中的人脸关键点。
# 返回值 `landmarks1` 是包含检测到的关键点坐标的 NumPy 数组。
# 这里重新调用 `ladmask` 是冗余的,因为 `points1` 已经包含这些信息。

6.图像融合并输出结果

"""
该代码实现从摄像头或视频源获取图像,并对每一帧执行以下任务:
1. 检测人脸关键点。
2. 对输入图像执行仿射变换,以对齐到目标帧的人脸。
3. 生成掩模并进行泊松融合,将仿射变换后的图像无缝融合到目标帧。
4. 实时显示融合结果,并支持按键退出。
"""

while True:
    # 读取摄像头当前帧
    ret, frame = capture.read()
    if not ret:
        break  # 如果读取失败,退出循环

    # 检测当前帧中的人脸关键点
    frame_landmarks = ladmask(frame)

    if not frame_landmarks:
        # 如果没有检测到人脸,直接显示原始帧
        cv2.imshow('Face Swap', frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break  # 按下 'q' 键退出循环
        continue

    # 遍历当前帧中检测到的每张人脸
    for i, target_landmarks in enumerate(frame_landmarks):
        # 如果源图像中人脸数少于当前帧,循环使用源图像的人脸
        source_index = i % len(source_landmarks)

        # 计算源人脸到目标人脸的仿射变换矩阵
        M = transformation_from_points(source_landmarks[source_index], target_landmarks)

        # 使用仿射变换将源图像人脸和蒙版变换到目标位置
        transformed_face = cv2.warpAffine(source_img, M, (frame.shape[1], frame.shape[0]))
        transformed_mask = cv2.warpAffine(source_masks[source_index], M, (frame.shape[1], frame.shape[0]))

        # 创建当前帧目标人脸的蒙版并获取其中心点
        union_mask = get_face_mask(frame, target_landmarks)
        center = get_mask_center_point(union_mask)

        if center is None or not (0 <= center[0] < frame.shape[1] and 0 <= center[1] < frame.shape[0]):
            # 如果蒙版中心点无效,跳过此人脸
            continue

        # 将蒙版的像素值限制在有效范围内
        union_mask = np.clip(union_mask, 0, 255)

        # 使用 OpenCV 的无缝克隆将源人脸与当前帧融合
        try:
            frame = cv2.seamlessClone(
                transformed_face, frame, union_mask, center, cv2.NORMAL_CLONE
            )
        except cv2.error as e:
            # 如果融合出错,打印错误信息并跳过
            print(f"Skipping blending due to error: {e}")
            continue

    # 显示处理后的视频帧
    cv2.imshow('Face Swap', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break  # 按下 'q' 键退出循环

# 释放摄像头资源并关闭所有窗口
capture.release()
cv2.destroyAllWindows()

#实时摄像头或视频文件输入:支持从摄像头捕获实时视频,或者通过修改 cv.VideoCapture 参数读取视频文件。
#人脸处理与仿射变换:检测人脸关键点,生成仿射变换矩阵,实现人脸对齐。
#泊松融合:使用联合掩模和融合点将图像自然地嵌入到目标帧中。
#实时显示:将处理结果通过 OpenCV 窗口实时显示。

三、总结

基于Mediapipe的换脸方法大致涉及以下步骤:
● 面部检测与跟踪:使用Mediapipe模型检测并跟踪人脸。
● 面部特征提取:提取源人脸和目标人脸的特征。
● 面部融合:将源人脸特征融合到目标人脸。
● 渲染输出:生成换脸后的图像或视频。

### Spring Framework ApplicationEventPublisher Example and Usage In the context of the Spring framework, `ApplicationEventPublisher` is an interface that allows beans to publish events to the application context. This mechanism facilitates a loosely coupled architecture where components can notify each other about significant occurrences without being directly dependent on one another. The core classes involved in this event-driven model include: - **ApplicationEvent**: A class extending from which all custom events should derive. - **ApplicationListener<E extends ApplicationEvent>**: An interface implemented by any bean wishing to listen for specific types of events. - **ApplicationEventMulticaster**: The component responsible for broadcasting events to registered listeners within the ApplicationContext[^1]. To demonstrate how these pieces work together using `ApplicationEventPublisher`, consider the following code snippets illustrating both publishing and listening capabilities. #### Publishing Events with ApplicationEventPublisher A service or repository layer might want to inform others when certain actions occur. For instance, after saving data into storage, it could broadcast such activity as shown below: ```java @Service public class MyService { private final ApplicationEventPublisher publisher; @Autowired public MyService(ApplicationEventPublisher publisher) { this.publisher = publisher; } void performAction() { // Action logic here... CustomEvent event = new CustomEvent(this); publisher.publishEvent(event); // Publishes the event through the context } } ``` Here, upon executing some action inside `performAction()`, a new `CustomEvent` gets created and published via injection of `ApplicationEventPublisher`. #### Listening for Specific Events Using ApplicationListener On the receiving end, interested parties implement `ApplicationListener<SpecificEventType>` to react accordingly whenever their targeted type occurs: ```java @Component public class EventConsumer implements ApplicationListener<MyCustomEvent> { @Override public void onApplicationEvent(MyCustomEvent event) { System.out.println("Received my custom event : " + event.getMessage()); } } ``` This listener will automatically receive notifications every time a matching event (`MyCustomEvent`) happens anywhere across different parts of your application[^2]. Additionally, annotations like `@EventListener` provide even more concise syntax while offering flexibility regarding method signatures and parameters used during handling processes. By leveraging these constructs effectively, developers gain powerful tools enabling robust communication patterns throughout complex systems built atop Spring's foundation.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值