使用OpenCV和MediaPipe库——增强现实特效(在手腕添加虚拟手表)

发布于:2025-03-10 ⋅ 阅读:(21) ⋅ 点赞:(0)

目录

代码实现思路

整体代码

配套资源说明

关键实现细节

效果优化

效果展示


代码实现思路

  1. 初始化

    • 使用MediaPipe库来处理人体姿势估计,特别是手部位置。
    • 加载了一张带被抠图的手表图像(watch.png)作为要叠加到视频帧上的对象。
  2. 打开摄像头并开始捕捉视频流

    • 使用OpenCV的VideoCapture类打开默认摄像头(设备ID为0),然后进入一个循环不断读取视频帧。
  3. 姿势检测

    • 每一帧首先被转换为RGB格式,因为MediaPipe需要RGB输入。
    • 利用mp_pose.Pose进行人体姿势预测,并将结果保存在results变量中。
  4. 坐标变换和手表图像处理

    • 从姿势预测结果中获取左手腕的关键点坐标,并将其归一化坐标转换为实际像素坐标。
    • 根据手腕的位置调整手表图像的大小,并计算出它应该放置的具体位置,确保手表中心与手腕关键点对齐。
    • 分离手表图像的Alpha通道(即透明度信息),用于后续的图像混合操作。
  5. 图像混合

    • 将手表图像按照计算好的位置叠加到视频帧上。这个过程涉及到利用Alpha通道对手表图像和原始视频帧进行加权混合,以保证手表图像能够正确显示其透明部分。
  6. 绘制关键点(可选)

    • 在视频帧上绘制出所有检测到的人体关键点和它们之间的连线,帮助可视化检测结果。
  7. 展示结果

    • 显示最终合成后的图像。如果用户按下键盘上的“q”键,则退出循环并关闭所有窗口,停止视频捕捉。
  8. 资源释放

    • 循环结束后,释放摄像头资源并关闭所有OpenCV创建的窗口。


整体代码

import cv2
import mediapipe as mp
import numpy as np  #  numpy 用于图像混合

# 初始化 MediaPipe 组件
mp_drawing = mp.solutions.drawing_utils
mp_pose = mp.solutions.pose

# 加载带透明通道的手表图片 (PNG格式)
watch_img = cv2.imread("watch.png", cv2.IMREAD_UNCHANGED)  # 确保图片路径正确
if watch_img is None:
    raise FileNotFoundError("手表图片未找到,请检查 watch.png 文件路径")

# 打开摄像头
cap = cv2.VideoCapture(0)

# 初始化姿势检测
with mp_pose.Pose(
        min_detection_confidence=0.7,
        min_tracking_confidence=0.7
) as pose:
    while cap.isOpened():
        success, frame = cap.read()
        if not success:
            break

        # 转换为 RGB 格式并进行姿势检测
        image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        results = pose.process(image)

        # 转换回 BGR 用于显示
        image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

        if results.pose_landmarks:
            # ============== 关键点获取 ==============
            # 获取左手腕关键点
            LEFT_WRIST = mp_pose.PoseLandmark.LEFT_WRIST
            wrist_landmark = results.pose_landmarks.landmark[LEFT_WRIST]

            # 获取图像尺寸
            h, w, _ = image.shape

            # 将归一化坐标转换为像素坐标
            wrist_x = int(wrist_landmark.x * w)
            wrist_y = int(wrist_landmark.y * h)

            # ============== 手表处理 ==============
            # 调整手表大小(原图尺寸的 1/4)
            scale_factor = 0.25
            watch_h, watch_w = int(watch_img.shape[0] * scale_factor), int(watch_img.shape[1] * scale_factor)
            resized_watch = cv2.resize(watch_img, (watch_w, watch_h))

            # 计算叠加位置(使手表中心对准手腕)
            y_start = wrist_y - watch_h // 2
            y_end = y_start + watch_h
            x_start = wrist_x - watch_w // 2
            x_end = x_start + watch_w

            # 确保不会超出画面边界
            if y_start >= 0 and y_end <= h and x_start >= 0 and x_end <= w:
                # 分离 Alpha 通道
                alpha = resized_watch[:, :, 3] / 255.0
                inverse_alpha = 1 - alpha

                # 按通道混合图像
                for c in range(3):
                    image[y_start:y_end, x_start:x_end, c] = \
                        resized_watch[:, :, c] * alpha + \
                        image[y_start:y_end, x_start:x_end, c] * inverse_alpha

            # ============== 绘制关键点(可选) ==============
            mp_drawing.draw_landmarks(
                image,
                results.pose_landmarks,
                mp_pose.POSE_CONNECTIONS,
                landmark_drawing_spec=mp_drawing.DrawingSpec(color=(120, 220, 160), thickness=2),
                connection_drawing_spec=mp_drawing.DrawingSpec(color=(120, 160, 220), thickness=2)
            )
            # 显示画面
            cv2.imshow('AR Watch Demo', image)
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break

cap.release()
cv2.destroyAllWindows()

配套资源说明

  1. 手表图片要求

    • 格式:PNG(带透明通道)

    • 推荐尺寸:500x500 像素左右

    • 示例图片(可通过搜索引擎查找 "watch png transparent")

  2. 文件结构

    your_project_folder/
    ├── ar_watch_demo.py  # 代码文件
    └── watch.png         # 手表素材

关键实现细节

  1. 坐标转换

    wrist_x = int(wrist_landmark.x * w)  # 将归一化坐标转换为实际像素坐标
    wrist_y = int(wrist_landmark.y * h)

  2. 透明图像混合

    alpha = resized_watch[:, :, 3] / 255.0  # 提取 Alpha 通道并归一化
    for c in range(3):  # 对 RGB 通道分别混合
        image[y1:y2, x1:x2, c] = \
            resized_watch[:, :, c] * alpha + \
            image[y1:y2, x1:x2, c] * (1 - alpha)

  3. 边界保护

    if y_start >= 0 and y_end <= h and x_start >=0 and x_end <= w:
        # 仅当手表完全在画面内时才进行叠加

    效果优化

  4. 动态大小调整

    # 根据手腕到摄像头的距离动态调整大小(需计算深度信息)
    scale_factor = 0.2 + 0.1 * (wrist_landmark.z * 10)  # z 值为估计的深度

  5. 旋转适配

    # 获取前臂方向(手腕-手肘向量)
    LEFT_ELBOW = mp_pose.PoseLandmark.LEFT_ELBOW
    elbow_landmark = results.pose_landmarks.landmark[LEFT_ELBOW]
    angle = np.arctan2(wrist_y - elbow_y, wrist_x - elbow_x)
    rotated_watch = cv2.warpAffine(resized_watch, ...)  # 应用旋转矩阵

  6. 性能优化

    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)  # 降低分辨率到 640x480
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

效果展示