OpenPose实战:用抖音尬舞机同款技术开发体感游戏(附Python接口调用示例)
从算法到游戏:用OpenPose骨骼点数据打造你的专属体感互动
还记得几年前风靡一时的“尬舞机”吗?站在屏幕前,你的每一个动作都会被实时捕捉,与虚拟舞者同步,那种人机交互的沉浸感让无数人着迷。这背后,正是人体姿态估计技术在消费级应用中的一次惊艳亮相。如今,这项技术早已走下神坛,不再是大型实验室或科技巨头的专属。作为一名开发者,你是否想过,自己也能利用开源的OpenPose,快速搭建一个类似的体感互动原型,甚至孵化出新的商业应用点子?本文将带你绕开繁琐的理论推导,直击应用核心,手把手教你如何通过Python调用OpenPose,捕获实时骨骼点数据,并最终结合PyGame,打造一个简易但完整的体感游戏Demo。我们的目标不是复现一个“抖音尬舞机”,而是为你提供一套可复用、可扩展的技术工具箱,让你能快速验证自己的创意。
1. 绕开安装“深水区”:为应用开发铺平道路
对于大多数希望快速上手的开发者而言,OpenPose最令人望而却步的往往不是其算法本身,而是复杂的环境配置与编译过程。传统的从源码编译,涉及Caffe、CMake、CUDA(如有GPU)等一系列依赖,在Windows、macOS等非Linux系统上尤其容易“翻车”。我们的目标是应用开发,而非底层框架研究,因此,选择一条更高效的路径至关重要。
1.1 拥抱预编译包与Docker:效率优先
在项目初期,尤其是在验证想法和开发原型阶段,我强烈建议你避开从零编译的“坑”。有两种更友好的方式可以让你在几分钟内就拥有一个可运行的OpenPose环境。
方案一:使用预编译的Python包 这是最快捷的方式。社区有一些项目将OpenPose的核心功能封装成了Python的wheel包,虽然可能不是官方最新版,但对于获取骨骼点数据这一核心需求来说,完全够用。你可以直接使用pip进行安装。
# 请注意,包名可能随社区维护而变化,安装前请查阅相关文档
pip install opencv-python
pip install openpose-python # 示例包名,实际请搜索可用包
安装后,通常可以通过简单的import来调用关键函数。这种方式省去了所有编译依赖,但功能可能受限,例如自定义模型或高级参数调整可能不支持。
方案二:使用Docker镜像 如果你需要更完整、更接近官方原生的功能,又不想污染本地环境,Docker是最佳选择。OpenPose官方提供了Dockerfile,社区也有维护良好的镜像。
# 拉取一个流行的OpenPose Docker镜像
docker pull cmuopenpose/openpose:latest
# 运行容器,并将当前目录挂载到容器的/data目录,方便交换文件
docker run -it --rm --runtime=nvidia \
-v $(pwd):/data \
cmuopenpose/openpose:latest \
bash
进入容器后,你就拥有了一个完整的、配置好的OpenPose环境,可以直接使用其命令行工具或Python接口。对于macOS或Windows用户,这几乎是最无痛的方案。
注意:使用Docker需要本地已安装Docker Desktop。如果使用GPU版本,还需要配置NVIDIA Container Toolkit。
1.2 验证你的安装:一个简单的测试脚本
无论采用哪种方式安装,第一时间写个简短的测试脚本验证功能是否正常,是个好习惯。下面这个脚本会尝试导入OpenPose的Python模块,并打印其基本信息。
#!/usr/bin/env python3
# test_openpose_env.py
try:
# 尝试导入,具体模块名根据你的安装方式而定
# 如果是预编译包,可能是 `openpose` 或 `pyopenpose`
import sys
sys.path.append('/path/to/openpose/build/python') # 如果是从源码编译,需要添加路径
from openpose import pyopenpose as op
print("[INFO] OpenPose Python模块导入成功!")
print(f"[INFO] OpenPose版本信息: {op.getOpenPoseVersion()}")
# 初始化参数
params = dict()
params["model_folder"] = "/path/to/openpose/models/" # 模型路径
params["number_people_max"] = 1 # 最大检测人数
# 尝试初始化OpenPose对象
op_wrapper = op.WrapperPython()
op_wrapper.configure(params)
op_wrapper.start()
print("[INFO] OpenPose Wrapper 初始化成功!环境就绪。")
except Exception as e:
print(f"[ERROR] 导入或初始化OpenPose时出错: {e}")
print("请检查:1. 模块路径是否正确;2. 模型文件是否存在;3. 依赖库是否完整。")
运行这个脚本,如果看到成功的提示信息,那么恭喜你,最艰难的一步已经跨过。如果报错,请根据错误信息,检查模型文件路径、动态链接库等问题。
2. 核心:用Python接口捕获实时骨骼点数据
环境就绪后,我们直奔主题:如何用Python代码驱动摄像头,并让OpenPose告诉我们画面中人物的关节在哪里。OpenPose的输出是一个包含多个人员、每个人包含25个身体关键点(COCO模型)或70多个点(身体+手+脸)的数据结构。理解这个数据结构,是后续一切应用的基础。
2.1 初始化与参数配置:平衡速度与精度
pyopenpose.WrapperPython 是我们的主要接口对象。在初始化时,通过一个字典来配置参数,这直接决定了程序的性能和效果。
import cv2
import sys
import time
from openpose import pyopenpose as op
def initialize_openpose(model_path='models/', resolution='-1x368', net_resolution='-1x368'):
"""
初始化OpenPose wrapper。
Args:
model_path: OpenPose模型文件所在目录。
resolution: 输入图像的显示分辨率,'-1x368'表示高度为368,宽度按比例缩放。
net_resolution: 网络输入分辨率,影响精度和速度。值越小越快,但精度可能下降。
Returns:
初始化好的op.WrapperPython对象。
"""
params = {
"model_folder": model_path,
"model_pose": "BODY_25", # 使用BODY_25模型,共25个关键点
"net_resolution": net_resolution,
"output_resolution": resolution,
"num_gpu": 1, # 使用的GPU数量,-1为自动检测,0为仅CPU
"num_gpu_start": 0,
"disable_blending": False, # 为True时,输出图像不会绘制骨骼点,只返回数据
"render_threshold": 0.05, # 渲染阈值,置信度低于此值的关键点不绘制
"number_people_max": 2, # 最大检测人数,根据场景调整以提升性能
"scale_number": 1, # 图像缩放尺度数量
"scale_gap": 0.5 # 缩放尺度之间的间隔
}
op_wrapper = op.WrapperPython()
op_wrapper.configure(params)
op_wrapper.start()
print(f"[INFO] OpenPose初始化完成,网络分辨率: {net_resolution}")
return op_wrapper
这里有几个关键参数需要根据你的硬件和应用场景调整:
net_resolution:这是最重要的性能调节旋钮。在1080p视频上使用656x368会比-1x656(高度656)处理得更快,但可能丢失远处小目标的细节。在原型阶段,可以从-1x368开始。number_people_max:如果你确定场景中只有一个人(如体感游戏),将其设为1可以显著减少计算量。disable_blending:当我们需要纯数据用于后续逻辑判断时,可以将其设为True,避免不必要的渲染开销。
2.2 数据解析:从datum对象到可用的坐标
OpenPose处理完一帧图像后,会将结果封装在一个Datum对象中。我们需要从中提取出结构化的关键点数据。
def extract_keypoints(datum):
"""
从Datum对象中提取关键点坐标和置信度。
Args:
datum: OpenPose处理后的数据对象。
Returns:
list: 一个列表,每个元素是一个字典,代表一个人物的关键点信息。
字典格式: {'pose_keypoints': [], 'pose_scores': []}
"""
people = []
if datum.poseKeypoints is not None and len(datum.poseKeypoints.shape) > 0:
num_people = datum.poseKeypoints.shape[0]
for person_id in range(num_people):
keypoints_2d = datum.poseKeypoints[person_id] # 形状为 (25, 3)
# 每一行是一个关键点: [x, y, confidence]
pose_points = keypoints_2d[:, :2].tolist() # 只取x, y坐标
pose_scores = keypoints_2d[:, 2].tolist() # 置信度
person_data = {
'id': person_id,
'pose_keypoints': pose_points, # 25个点的[x, y]列表
'pose_scores': pose_scores, # 25个点的置信度列表
'neck': pose_points[1] if pose_scores[1] > 0.1 else None, # 颈部点索引为1
'left_shoulder': pose_points[2] if pose_scores[2] > 0.1 else None,
'right_shoulder': pose_points[5] if pose_scores[5] > 0.1 else None,
# ... 可以按需提取其他关键点,如左右髋(9,12)、左右膝(10,13)等
}
people.append(person_data)
return people
datum.poseKeypoints是一个NumPy数组,其形状为(人数, 25, 3)。最后一个维度3分别代表x坐标、y坐标和置信度。置信度是一个介于0到1之间的值,表示模型对该关键点预测的把握程度。在实际应用中,我们通常需要设置一个阈值(如0.2或0.3)来过滤掉低置信度的点,避免噪声干扰。
2.3 构建实时数据流:连接摄像头与处理循环
将初始化、帧捕获、处理和数据显示串联起来,就构成了我们的核心数据流。下面的代码展示了一个稳定的实时处理循环,并包含了简单的帧率计算。
def run_real_time_capture(op_wrapper, camera_id=0):
"""
打开摄像头,实时捕获并处理视频流,显示带骨骼点的画面。
"""
cap = cv2.VideoCapture(camera_id)
if not cap.isOpened():
print("[ERROR] 无法打开摄像头。")
return
fps_time = time.time()
frame_count = 0
while True:
ret, frame = cap.read()
if not ret:
print("[WARN] 获取帧失败。")
break
# 创建Datum对象并填入图像数据
datum = op.Datum()
datum.cvInputData = frame
op_wrapper.emplaceAndPop([datum])
# 提取关键点数据
detected_people = extract_keypoints(datum)
# 在这里可以添加你的业务逻辑,例如:
# if detected_people:
# person = detected_people[0]
# if person['neck'] and person['left_hip']:
# # 计算颈部到左髋的距离或角度...
# pass
# 计算并显示FPS
frame_count += 1
if frame_count % 10 == 0:
current_time = time.time()
fps = 10 / (current_time - fps_time)
fps_time = current_time
cv2.putText(datum.cvOutputData, f"FPS: {fps:.1f}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
# 显示结果
cv2.imshow("OpenPose Real-Time Capture", datum.cvOutputData)
# 按'q'退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
# 使用示例
if __name__ == "__main__":
wrapper = initialize_openpose(net_resolution="-1x368")
run_real_time_capture(wrapper)
运行这段代码,你应该能看到一个实时显示摄像头画面并绘制出人体骨骼点的窗口。控制台虽然没有输出,但detected_people变量里已经包含了每一帧的结构化数据,这就是我们与游戏世界交互的“桥梁”。
3. 游戏逻辑设计:将骨骼点转化为互动指令
有了稳定的骨骼点数据流,下一步就是设计游戏规则。体感游戏的核心在于将身体动作映射为游戏内的控制指令。我们设计一个简单的“接水果”游戏Demo:玩家通过移动身体(用颈部关键点代表)控制屏幕底部的篮子左右移动,通过举手(判断手腕关键点高度超过头部)来触发“跳跃”或“特殊技能”。
3.1 动作映射与状态判断
我们需要从连续的骨骼点数据中,抽象出离散的、稳定的“动作状态”。直接使用原始坐标的瞬时值会非常抖动,因此需要引入简单的滤波和状态机。
class PoseActionRecognizer:
"""
一个简单的姿态动作识别器,用于将骨骼点数据转化为游戏指令。
"""
def __init__(self, smoothing_factor=0.7):
self.smoothing_factor = smoothing_factor # 平滑因子,越大越平滑
self.prev_neck_x = None
self.current_action = "IDLE" # 当前动作状态
self.raise_hand_threshold = 0.15 # 举手判断阈值(相对于身高的比例)
def update(self, person_data, frame_height):
"""
根据当前帧的人物数据更新动作状态。
Args:
person_data: 来自extract_keypoints的一个人物字典。
frame_height: 视频帧的高度,用于归一化计算。
Returns:
dict: 包含控制指令的字典,如 {'move': dx, 'action': 'RAISE_HAND'}。
"""
commands = {'move': 0.0, 'action': 'NONE'}
if not person_data.get('neck'):
return commands
# 1. 平滑处理颈部X坐标,用于控制左右移动
current_neck_x = person_data['neck'][0]
if self.prev_neck_x is None:
self.prev_neck_x = current_neck_x
smoothed_x = self.smoothing_factor * self.prev_neck_x + (1 - self.smoothing_factor) * current_neck_x
# 计算归一化的移动速度(-1 到 1之间)
dx = (smoothed_x - self.prev_neck_x) / frame_height * 5 # 缩放因子可调
commands['move'] = max(-1.0, min(1.0, dx)) # 限制在[-1, 1]
self.prev_neck_x = smoothed_x
# 2. 判断举手动作(左手腕高于头顶)
neck = person_data.get('neck')
left_wrist = person_data.get('left_wrist') # 需要你在extract_keypoints中提取索引为7的点
head = person_data.get('nose') # 需要提取索引为0的点
if neck and left_wrist and head and left_wrist[1] is not None and head[1] is not None:
# 计算手腕相对于头部的高度差(归一化到0-1)
relative_height = (head[1] - left_wrist[1]) / frame_height
if relative_height > self.raise_hand_threshold:
commands['action'] = 'RAISE_LEFT_HAND'
# 可以类似地添加举右手判断
# 3. 简单的状态机,防止动作抖动(例如,举手动作需持续至少3帧才被确认)
# ... (此处省略状态机实现细节,可根据需要添加)
return commands
这个识别器做了两件事:一是通过平滑后的颈部水平移动来产生连续的move指令;二是通过比较手腕和鼻子的垂直坐标来判断是否举手,产生离散的action指令。smoothing_factor和raise_hand_threshold是需要根据实际测试调整的超参数。
3.2 游戏状态与碰撞检测
有了控制指令,我们就可以更新游戏状态。这里用一个非常简化的游戏世界为例:
import pygame
import random
class SimpleFruitGame:
def __init__(self, screen_width=800, screen_height=600):
pygame.init()
self.screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("体感接水果")
self.clock = pygame.time.Clock()
self.basket_width = 100
self.basket_height = 30
self.basket_x = screen_width // 2
self.basket_y = screen_height - 50
self.basket_speed = 10
self.fruits = [] # 水果列表,每个元素为 [x, y, radius, speed, color]
self.score = 0
self.font = pygame.font.Font(None, 36)
def update_basket(self, move_command):
"""根据move_command更新篮子位置。"""
self.basket_x += move_command * self.basket_speed
# 限制篮子不超出屏幕
self.basket_x = max(self.basket_width//2, min(self.screen.get_width() - self.basket_width//2, self.basket_x))
def spawn_fruit(self):
"""随机生成一个水果。"""
x = random.randint(20, self.screen.get_width()-20)
radius = random.randint(10, 20)
speed = random.uniform(1.0, 3.0)
color = (random.randint(100, 255), random.randint(100, 255), random.randint(100, 255))
self.fruits.append([x, -radius, radius, speed, color])
def update_fruits(self):
"""更新所有水果的位置,并检测与篮子的碰撞。"""
fruits_to_remove = []
for i, fruit in enumerate(self.fruits):
fruit[1] += fruit[3] # y坐标增加,向下落
# 简单矩形-圆形碰撞检测(篮子 vs 水果)
basket_left = self.basket_x - self.basket_width // 2
basket_right = self.basket_x + self.basket_width // 2
basket_top = self.basket_y - self.basket_height // 2
fruit_x, fruit_y, radius = fruit[0], fruit[1], fruit[2]
# 计算水果中心到篮子矩形最近点的距离
closest_x = max(basket_left, min(fruit_x, basket_right))
closest_y = max(basket_top, min(fruit_y, basket_top + self.basket_height))
distance = ((fruit_x - closest_x) ** 2 + (fruit_y - closest_y) ** 2) ** 0.5
if distance <= radius:
fruits_to_remove.append(i)
self.score += 10
print(f"接住水果!当前分数: {self.score}")
# 如果水果掉出屏幕底部,也移除
if fruit[1] > self.screen.get_height() + fruit[2]:
fruits_to_remove.append(i)
# 反向遍历删除,避免索引错乱
for i in sorted(fruits_to_remove, reverse=True):
self.fruits.pop(i)
def handle_action(self, action_command):
"""处理动作指令,例如举手触发‘清屏’技能。"""
if action_command == 'RAISE_LEFT_HAND':
# 举手时,清除屏幕上所有水果(作为一个简单技能)
if self.fruits:
self.fruits.clear()
print("举手技能发动!清空所有水果。")
def draw(self):
"""绘制游戏所有元素。"""
self.screen.fill((30, 30, 50)) # 深蓝色背景
# 绘制篮子(一个矩形)
pygame.draw.rect(self.screen, (0, 150, 255),
(self.basket_x - self.basket_width//2, self.basket_y - self.basket_height//2,
self.basket_width, self.basket_height))
# 绘制所有水果(圆形)
for fruit in self.fruits:
pygame.draw.circle(self.screen, fruit[4], (int(fruit[0]), int(fruit[1])), fruit[2])
# 绘制分数
score_text = self.font.render(f'Score: {self.score}', True, (255, 255, 255))
self.screen.blit(score_text, (10, 10))
pygame.display.flip()
def run_frame(self, move_command=0.0, action_command='NONE'):
"""运行一帧游戏逻辑。"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
return False
self.update_basket(move_command)
self.handle_action(action_command)
# 随机生成水果
if random.random() < 0.03: # 每帧约3%的概率生成
self.spawn_fruit()
self.update_fruits()
self.draw()
self.clock.tick(60) # 限制60帧
return True
这个游戏类包含了基本的游戏循环、精灵(篮子、水果)管理、碰撞检测和分数系统。它将从PoseActionRecognizer获得的move_command和action_command直接用于更新游戏状态。
4. 系统整合与性能优化实战
最后一步,也是将想法变为可运行产品的关键,就是将OpenPose数据流、动作识别器和游戏引擎无缝整合起来,并解决实际运行中必然遇到的性能与延迟问题。
4.1 多线程架构:解耦图像处理与游戏渲染
一个常见的错误是将OpenPose处理和游戏循环放在同一个线程中。OpenPose处理一帧图像可能需要几十到几百毫秒,这会直接导致游戏画面严重卡顿。标准的解决方案是使用生产者-消费者模型,用两个线程并行工作。
import threading
import queue
import time
class PoseGameSystem:
def __init__(self, camera_id=0):
self.camera_id = camera_id
self.pose_queue = queue.Queue(maxsize=2) # 限制队列大小,防止堆积
self.game = SimpleFruitGame()
self.recognizer = PoseActionRecognizer()
self.running = False
def pose_capture_thread(self):
"""生产者线程:负责捕获摄像头图像并进行OpenPose处理。"""
cap = cv2.VideoCapture(self.camera_id)
op_wrapper = initialize_openpose(net_resolution="-1x368") # 使用较低分辨率以提升速度
while self.running:
ret, frame = cap.read()
if not ret:
continue
# 为了进一步提速,可以缩小处理图像尺寸
process_frame = cv2.resize(frame, (0,0), fx=0.5, fy=0.5)
datum = op.Datum()
datum.cvInputData = process_frame
try:
op_wrapper.emplaceAndPop([datum])
except Exception as e:
print(f"[ERROR] OpenPose处理失败: {e}")
continue
# 提取关键点并放入队列
people = extract_keypoints(datum)
# 将原始帧也放入队列,用于可能的显示(可选)
if not self.pose_queue.full():
self.pose_queue.put((people, frame))
else:
# 如果队列已满,丢弃旧数据,保证实时性
try:
self.pose_queue.get_nowait()
except queue.Empty:
pass
cap.release()
print("[INFO] 姿态捕获线程结束。")
def game_main_loop(self):
"""消费者线程(主线程):运行游戏并消费姿态数据。"""
self.running = True
pose_thread = threading.Thread(target=self.pose_capture_thread, daemon=True)
pose_thread.start()
# 给OpenPose线程一点启动时间
time.sleep(1.0)
last_pose_data = None
while self.running:
# 非阻塞地从队列获取最新的姿态数据
try:
pose_data, camera_frame = self.pose_queue.get_nowait()
last_pose_data = pose_data
except queue.Empty:
pass # 没有新数据,使用上一帧的数据
move_cmd = 0.0
action_cmd = 'NONE'
if last_pose_data and len(last_pose_data) > 0:
# 默认跟踪第一个人
person = last_pose_data[0]
commands = self.recognizer.update(person, self.game.screen.get_height())
move_cmd = commands['move']
action_cmd = commands['action']
# 运行一帧游戏逻辑
if not self.game.run_frame(move_cmd, action_cmd):
self.running = False
break
print("[INFO] 游戏主循环结束。")
pygame.quit()
# 启动系统
if __name__ == "__main__":
system = PoseGameSystem()
system.game_main_loop()
这个架构的核心是pose_queue。姿态捕获线程(生产者)不断处理图像并将结果放入队列;游戏主循环(消费者)以固定的高帧率(如60FPS)运行,每次循环都尝试从队列中获取最新的姿态数据来更新游戏指令。如果队列为空,就使用上一帧的数据,这保证了游戏的流畅性。队列设置了最大长度,防止处理速度跟不上时内存无限增长。
4.2 关键性能调优策略
即使使用了多线程,在资源有限的设备上,体验仍可能不尽如人意。下面是一些行之有效的调优策略:
1. 图像预处理降分辨率 这是提升速度最有效的方法。OpenPose的处理时间与输入图像像素数量大致呈线性关系。将摄像头捕获的1280x720图像缩放到640x360再进行处理,速度可以提升近4倍,而对中近距离的全身姿态估计精度影响在可接受范围内。
# 在pose_capture_thread中
process_height = 360 # 目标处理高度
h, w = frame.shape[:2]
process_width = int(w * (process_height / h))
process_frame = cv2.resize(frame, (process_width, process_height))
2. 调整OpenPose网络分辨率 net_resolution参数控制输入神经网络的图像尺寸。-1x368(宽自适应,高368)是一个不错的平衡点。如果追求极致速度,可以尝试-1x240或256x256,但会损失对小目标或细节的检测能力。
3. 限制检测人数与关键点 如果你的应用场景是单人,务必设置params["number_people_max"] = 1。如果不需要手部和面部关键点,使用BODY_25模型而非BODY_135(身体+手+脸)可以显著减少计算量。
4. 非均匀处理策略 不必对每一帧都进行完整的OpenPose处理。可以尝试:
- 跳帧处理:每2帧或3帧处理一次,中间帧使用运动预测或插值来更新关键点位置。
- 区域兴趣(ROI)处理:只在上一帧检测到人体的区域附近进行检测,而不是全图检测。这需要额外的逻辑来跟踪和更新ROI。
5. 渲染与显示优化
- 如果不需要实时显示OpenPose的骨骼点渲染图,设置
params["disable_blending"] = True可以节省渲染开销。 - PyGame的绘制操作也可能成为瓶颈。确保没有在每帧中创建新的
Surface对象,尽量重用。对于大量水果精灵,可以考虑使用精灵组(pygame.sprite.Group)来优化绘制。
一个实用的性能对比表格如下,帮助你根据硬件条件做决策:
| 优化策略 | 速度提升 | 精度影响 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|
| 图像降分辨率 (720p -> 360p) | 高 (~300%) | 中 (远距离目标可能丢失) | 低 | 所有场景,首选方案 |
| 降低网络分辨率 (-1x656 -> -1x368) | 中 (~50%) | 中低 | 低 | 对精度要求不苛刻时 |
| 限制检测人数 (多人 -> 单人) | 中 (~30-50%) | 无 (单人场景) | 低 | 明确为单人交互的应用 |
| 使用轻量模型 (BODY_135 -> BODY_25) | 中 (~20-30%) | 中 (失去手、脸细节) | 低 | 仅需身体姿态时 |
| 跳帧处理 (1fps -> 0.5fps) | 高 (与跳帧比例相关) | 中高 (动作连续性下降) | 中 | 动作缓慢的场景 |
| ROI跟踪处理 | 高 (取决于ROI大小) | 低 (若跟踪准确) | 高 | 人物在画面中移动范围有限 |
在实际项目中,我通常会从“图像降分辨率”和“限制检测人数”开始,如果帧率仍不达标,再依次尝试其他策略。记住,原型阶段的首要目标是实现功能的流畅运行,极致优化可以放在产品化阶段进行。
当你把所有这些代码块组装起来,并成功运行后,一个由你身体控制的“接水果”游戏就诞生了。虽然它看起来简单,但这个Demo包含了从底层算法调用、数据解析、业务逻辑设计到系统架构和性能优化的完整链条。你可以在此基础上,更换游戏玩法(比如模仿舞蹈动作的节奏游戏)、增加更复杂的手势识别(握拳、挥手)、甚至接入Unity或Unreal Engine打造更炫酷的3D互动体验。OpenPose提供的这25个关键点,就是你连接现实与数字世界的25个锚点,剩下的,只取决于你的想象力。
更多推荐



所有评论(0)