利用mediapipe+yolov5实现多人驱动unity“火柴人”

本文介绍了如何利用YOLOv5目标检测模型和Mediapipe库进行人体关键点检测,结合Python和Unity实现多人游戏中的火柴人动作同步。作者选择了Top-down方法以保证准确性,通过Python代码处理视频流,检测人体姿态并发送数据到Unity端。Unity中使用多线程避免主线程阻塞,以保持游戏流畅。目前游戏仍处于初步阶段,存在一些待优化问题,如火柴人朝向和后续的UI设计等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1 背景介绍

2 技术介绍

3 思路介绍

4 代码实现

4.1 环境

4.2 python代码

4.3 unity代码

4.4 游戏其他可玩性设定

5 结果展示

 6 后续优化

7 文件分享


1 背景介绍

在上一篇博客中,我们已经介绍了如何单人驱动火柴人。详情可见(1条消息) mediapipe单人动捕驱动unity“火柴人”_痴生的博客-优快云博客

2 技术介绍

YOLOv5(You Only Look Once version 5)是一种目标检测模型,用于在视频或图像中识别和定位物体。它在YOLOv4的基础上进行了改进。

YOLOv5是一种端到端的深度学习模型,可以直接从原始图像中检测和定位目标。它使用卷积神经网络(CNN)来学习图像中物体的特征,并使用多尺度预测和网格分割来检测和定位目标。YOLOv5的优势在于它可以在高速运行,并且可以在不同的图像分辨率上很好地工作。

总的来说,YOLOv5是一种高效的目标检测模型,可以应用于许多不同的场景,包括自动驾驶,机器人感知,图像分析等。其具有合适于移动端部署,模型小,速度快的特点。

YOLOv5有YOLOv5s。YOLOv5m,YOLOV51、YOLOV5x四个版本,文件中,这几个模型的结构基本一样,不同的是depth multiple模型深度和width multioler模型宽度这两个参数。就和我们买衣服的尺码大小排席一样,YOLOV5S网络是YOLOV5系列中深度最小,特征图的宽度最小的网络。其他的三种都是在此基础上不断加深,不断加宽。

3 思路介绍

人体姿态估计(Human Pose Estimation)也称为人体关键点检测(Human Keypoints Detection)。对于人体姿态估计的研究,主要有两种思路:Top-down 和Bottom-up。

Top-down首先利用目标检测算法检测出单个人,得到边界框,然后在每一个边界框中检测人体关键点,连接成一个人形,缺点就是受检测框的影响太大,漏检,误检,IOU大小等都会对结果有影响,算法包括RMPE、Mask-RCNN 等,这种方法一般具有较高的准确率但是处理速度较低。

Bottom-up方法首先对整个图片进行每个人体关键点部件的检测,再将检测到的部件拼接成一个人形,这种方法一般准确率较差,会将不同人的不同部位按一个人进行拼接,但处理速度较快,代表方法就是OpenPose、DeepCut 、PAF。

在本项目中,我使用Top-down思路原因有三,如下所示:

  1. 我在上一节中已经实现了单人驱动“火柴人”,使用Top-down思路符合我之前所做工作的脉络并且省时省力。
  2. 对于体感游戏来说,准确性尤为重要,在我看来,即使损失部分性能也要确保识别动作的准确性。因此,准确性更高的Top-down思路是我的首选。
  3. 我选择通过一个端口传输一个人体姿态识别的数据,使用Bottom-up方法会增加将一个人的坐标信息拼凑起来再通过端口发送的工作,使工作变得更加复杂。

4 代码实现

4.1 环境

python3.9 安装mediapipe和opencv-python包
python和Unity通信使用socket
Unity2021.3

4.2 python代码

import os
import cv2
import mediapipe as mp
from mediapipe.python.solutions import pose as mp_pose
import socket
# PyTorch Hub
import torch
import cv2
import mediapipe as mp
import torch.nn as nn

import time
import onnxruntime as ort


class poseDetector():
    def __init__(self, mode=False, upBody=False, smooth=True, detectionCon=0.5, trackCon=0.5):
        self.mode = mode
        self.upBody = upBody
        self.smooth = smooth
        self.detectionCon = detectionCon
        self.trackCon = trackCon

        self.mpDraw = mp.solutions.drawing_utils
        self.mpPose = mp.solutions.pose
        self.pose = self.mpPose.Pose(self.mode, self.upBody, self.smooth, False, True, # 这里的False 和True为默认
                                     self.detectionCon, self.trackCon)  # pose对象 1、是否检测静态图片,2、姿态模型的复杂度,3、结果看起来平滑(用于video有效),4、是否分割,5、减少抖动,6、检测阈值,7、跟踪阈值
        '''
        STATIC_IMAGE_MODE:如果设置为 false,该解决方案会将输入图像视为视频流。它将尝试在第一张图像中检测最突出的人,并在成功检测后进一步定位姿势地标。在随后的图像中,它只是简单地跟踪那些地标,而不会调用另一个检测,直到它失去跟踪,以减少计算和延迟。如果设置为 true,则人员检测会运行每个输入图像,非常适合处理一批静态的、可能不相关的图像。默认为false。
        MODEL_COMPLEXITY:姿势地标模型的复杂度:0、1 或 2。地标准确度和推理延迟通常随着模型复杂度的增加而增加。默认为 1。
        SMOOTH_LANDMARKS:如果设置为true,解决方案过滤不同的输入图像上的姿势地标以减少抖动,但如果static_image_mode也设置为true则忽略。默认为true。
        UPPER_BODY_ONLY:是要追踪33个地标的全部姿势地标还是只有25个上半身的姿势地标。
        ENABLE_SEGMENTATION:如果设置为 true,除了姿势地标之外,该解决方案还会生成分割掩码。默认为false。
        SMOOTH_SEGMENTATION:如果设置为true,解决方案过滤不同的输入图像上的分割掩码以减少抖动,但如果 enable_segmentation设置为false或者static_image_mode设置为true则忽略。默认为true。
        MIN_DETECTION_CONFIDENCE:来自人员检测模型的最小置信值 ([0.0, 1.0]),用于将检测视为成功。默认为 0.5。
        MIN_TRACKING_CONFIDENCE:来自地标跟踪模型的最小置信值 ([0.0, 1.0]),用于将被视为成功跟踪的姿势地标,否则将在下一个输入图像上自动调用人物检测。将其设置为更高的值可以提高解决方案的稳健性,但代价是更高的延迟。如果 static_image_mode 为 true,则忽略,人员检测在每个图像上运行。默认为 0.5。
        '''
    def findPose(self, img, draw=True):
        imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 将BGR格式转换成灰度图片
        self.results = self.pose.process(imgRGB)  # 处理 RGB 图像并返回检测到的最突出人物的姿势特征点。
        if self.results.pose_landmarks:
            if draw:
                self.mpDraw.draw_landmarks(img, self.results.pose_landmarks, self.mpPose.POSE_CONNECTIONS)
                # results.pose_landmarks画点 mpPose.POSE_CONNECTIONS连线
        return img


    def findPosition(self, img, draw = True):
    #print(results.pose_landmarks)
        lmList = []
        if self.results.pose_landmarks:
            for id, lm in enumerate(self.results.pose_landmarks.landmark):  # enumerate()函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标
                h, w, c = img.shape  # 返回图片的(高,宽,位深)
                cx, cy, cz = int(lm.x * w), int(lm.y * h), int(lm.z * w)  # lm.x  lm.y是比例  乘上总长度就是像素点位置
                lmList.append([id, cx, cy, cz])
                if draw:
                    cv2.circle(img, (cx, cy), 5, (255, 0, 0), cv2.FILLED)  # 画蓝色圆圈
        return lmList





detector = poseDetector()
lmLists = []
strdata = ""
y0min=100000


# Model
# 加载模型文件
#yolo_model = torch.hub.load('ultralytics/yolov5', 'yolov5s')
# 加载轻量级的模型文件
yolo_model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True)


# 检查是否有可用的GPU设备
if torch.cuda.is_available():
    # 将yolo_model加载到GPU设备上
    yolo_model = yolo_model.to('cuda')
else:
    print("GPU device not found. Using CPU instead.")



# since we are only interested in detecting a person
yolo_model.classes = [0]

mp_drawing = mp.solutions.drawing_utils
# mp_pose = mp.solutions.pose     detector.findPose

# cap = cv2.VideoCapture(1)  # 使用默认摄像头设备
cap = cv2.VideoCapture('q3.mp4')  # 视频

# 获取摄像头的尺寸
ret, frame = cap.read()
h, w, _ = frame.shape
size = (w, h)

# 创建用于保存视频的 VideoWriter 对象
out = cv2.VideoWriter("output.avi", cv2.VideoWriter_fourcc(*"MJPG"), 20, size)

num_people = 0  # 用于记录检测到的人数

# 构建两个实例,分别用于连接不同的监听端口
client1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client1.connect(('127.0.0.1', 9999))
client2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client2.connect(('127.0.0.1', 8888))

clients = {}  # 使用字典存储多个客户端套接字对象
clients[0] = client1
clients[1] = client2

while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        break
    # Recolor Feed from BGR to RGB
    image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    # making image writeable to False improves prediction
    image.flags.writeable = False

    result = yolo_model(image)


    # Recolor image back to BGR for rendering
    image.flags.writeable = True
    image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

    # This array will contain crops of images in case we need it
    img_list = []

    # we need some extra margin bounding box for human crops to be properly detected
    MARGIN = 10

    num_people = 0  # 重置人数为0

    for (xmin, ymin, xmax, ymax, confidence, clas) in result.xyxy[0].tolist():
        with mp_pose.Pose(min_detection_confidence=0.3, min_tracking_confidence=0.3) as pose:
            # Media pose prediction
            cropped_image = image[int(ymin) + MARGIN:int(ymax) + MARGIN, int(xmin) + MARGIN:int(xmax) + MARGIN]
            cropped_image = detector.findPose(cropped_image)
            lmLists.append(detector.findPosition(cropped_image))
            img_list.append(cropped_image)

            num_people += 1  # 每检测到一个人,人数加1

            #将关键点坐标映射回原始帧
            if lmLists[num_people-1] is not None:
                for az, lm in enumerate(lmLists[num_people-1]):
                    id, cx, cy, cz = lm  # 解包关键点信息
                    if cy<y0min:
                        y0min=cy

            if lmLists[num_people-1] is not None:
                for i, lm in enumerate(lmLists[num_people-1]):
                    id, cx, cy, cz = lm  # 解包关键点信息
                    cx =  cx
                    cy =  cy
                    cz = cz
                    # 更新lmLists中的关键点坐标
                    lmLists[num_people - 1][i] = [id, cx, cy, cz]

    # Display the resulting image with person count
    cv2.putText(image, f'People: {num_people}', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
    cv2.imshow('Activity Recognition', image)
    # Write the frame to video file
    out.write(image)

    if num_people == 2:
        # 执行你的操作,例如打印一条消息
        print("There are 2 people in the video.")
        i=0

        # 将每个人的坐标点信息发送到不同的端口上
        for i in range(num_people):
            if len(lmLists[i]) != 0:
                for data in lmLists[i]:
                    print(data)  # print(lmList[n]) 可以打印第n个
                    for a in range(1, 4):
                        if a == 2:
                            strdata = strdata + str(frame.shape[0] - data[a]) + ','
                        else:
                            strdata = strdata + str(data[a]) + ','
                print(str(clients[i]) + ':' + strdata)
                clients[i].send(strdata.encode('utf-8'))
                strdata = ""

    lmLists = []

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
out.release()
cv2.destroyAllWindows()

4.3 unity代码

在unity中,常常会因为出现主线程堵塞的问题而导致游戏画面停滞、响应停滞、帧率下降、内存泄漏等问题。这是因为主线程负责处理游戏的逻辑更新、渲染和用户输入响应等任务,如果主线程被堵塞,那么这些任务无法及时完成,导致游戏画面无法更新,玩家的输入无法响应,游戏逻辑无法进行。总的来说,在Unity中,主线程堵塞会导致游戏的停顿或卡顿现象,给玩家带来不流畅的游戏体验。

因此,在项目中,为了避免主进程堵塞,对于接收两个端口信息的server与server1文件,我在两个代码文件中,都使用了多线程来处理客户端的连接和数据接收,以避免主线程的阻塞。

在Server类中:

  • listenThread是一个线程,通过调用ListenClientConnect方法来监听客户端的连接请求。
  • ReceiveMessage方法也是在一个独立的线程中运行,用于接收客户端发送的数据。

这样,服务器可以在主线程中处理接收到的数据,并在更新物体位置之前等待数据的到达,而不会阻塞整个程序的执行。

4.4 游戏其他可玩性设定

因为我想做一款简易格斗游戏,我在火柴人的手部特征点上添加触发器,编写触发器脚本,打到敌方目标得分加一。

 目前还比较简陋,只是得分页面,后续会将其完善成血条UI。

5 结果展示

 6 后续优化

目前来讲,游戏仅仅是跑起来了而已,由于多目标检测的未绑定目标,时常两个火柴人就变成向外(正常格斗游戏都是两人面对面向内而不是背对背向外),这个问题需要解决。

此外还有将火柴人换成一些动漫模型、血条UI界面设置、游戏开始界面设置等工作仍需解决。

7 文件分享

emmm,等我回头有空上传github吧

### 集成YOLOv5MediaPipe实现物体检测与姿态识别 为了实现更全面的视觉理解能力,可以将YOLOv5用于高效的物体检测,并利用MediaPipe进行精准的姿态估计。这种组合不仅提高了系统的功能性,还增强了特定应用场景下的实用性。 #### 安装依赖库 首先安装必要的Python包来支持YOLOv5MediaPipe的功能: ```bash pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu113 pip install opencv-python mediapipe numpy git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt ``` #### 加载预训练模型 加载YOLOv5作为对象检测器并初始化MediaPipe姿态模块: ```python import cv2 from pathlib import Path import torch import mediapipe as mp mp_pose = mp.solutions.pose.Pose(static_image_mode=False, min_detection_confidence=0.5) model = torch.hub.load('ultralytics/yolov5', 'yolov5s') ``` #### 处理视频流中的每一帧图像 对于每一张输入图片执行如下操作流程:先由YOLOv5完成初步的对象分类;再对感兴趣区域(ROI),特别是物类别的部分传递给MediaPipe做进一步分析处理。 ```python def process_frame(frame): results_yolo = model(frame) annotated_image = frame.copy() for detection in results_yolo.xyxy[0]: label = int(detection[-1]) if label == 0: # Assuming class index `0` corresponds to person. bbox = list(map(int, detection[:4])) cropped_person_img = frame[bbox[1]:bbox[3], bbox[0]:bbox[2]] rgb_cropped_person_img = cv2.cvtColor(cropped_person_img, cv2.COLOR_BGR2RGB) result_media_pipe = mp_pose.process(rgb_cropped_person_img).pose_landmarks if not result_media_pipe: continue # Draw pose landmarks on the image. mp_drawing.draw_landmarks( annotated_image, result_media_pipe, mp.solutions.pose.POSE_CONNECTIONS) return annotated_image ``` 此代码片段展示了如何结合两个强大的工具——YOLOv5负责快速准确地识别物实例,而MediaPipe则专注于这些被标记出来的个体身上精确地标记出骨骼节点位置[^2]。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值