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

目录

1 背景介绍

2 技术介绍

3 思路介绍

4 代码实现

4.1 环境

4.2 python代码

4.3 unity代码

4.4 游戏其他可玩性设定

5 结果展示

 6 后续优化

7 文件分享


1 背景介绍

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

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 nnimport time
import onnxruntime as ortclass poseDetector():def __init__(self, mode=False, upBody=False, smooth=True, detectionCon=0.5, trackCon=0.5):self.mode = modeself.upBody = upBodyself.smooth = smoothself.detectionCon = detectionConself.trackCon = trackConself.mpDraw = mp.solutions.drawing_utilsself.mpPose = mp.solutions.poseself.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 imgdef 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 lmListdetector = 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] = client2while cap.isOpened():ret, frame = cap.read()if not ret:break# Recolor Feed from BGR to RGBimage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)# making image writeable to False improves predictionimage.flags.writeable = Falseresult = yolo_model(image)# Recolor image back to BGR for renderingimage.flags.writeable = Trueimage = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)# This array will contain crops of images in case we need itimg_list = []# we need some extra margin bounding box for human crops to be properly detectedMARGIN = 10num_people = 0  # 重置人数为0for (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 predictioncropped_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

4.3 unity代码

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

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

在Server类中:

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

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

4.4 游戏其他可玩性设定

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

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

5 结果展示

 6 后续优化

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

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

7 文件分享

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


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部