MoveNet人体估计详解:视频与摄像头教程实战指南
MoveNet人体估计,视频和摄像头教程(详解)
一、简介
MoveNet 是一个超快且准确的模型,可检测身体的 17 个关键点。该模型在 TF Hub 上提供两种变体,分别为 Lightning 和 Thunder。Lightning 用于延迟关键型应用,而 Thunder 用于需要高准确性的应用。在大多数现代台式机、笔记本电脑和手机上,这两种模型的运行速度都快于实时 (30+ FPS),这对于实时的健身、健康和保健应用至关重要。(来自官网)
二、可视化库安装
pip install -q imageio
pip install -q opencv-python
pip install -q git+https://github.com/tensorflow/docs
三、代码详解(文章最后有完整代码)
1、导入的库
#导入TensorFlow库,并将其别名设置为tf。
#TensorFlow是一个开源的机器学习框架,用于构建和训练各种机器学习模型,特别是深度学习模型。
#在这个脚本中,TensorFlow可能用于处理张量操作、运行模型等。
import tensorflow as tf
#导入TensorFlow Hub库,并将其别名设置为hub。
#TensorFlow Hub是一个库和平台,用于共享、发现和重用机器学习模型的部分。
#在这个脚本中,TensorFlow Hub可能用于加载预训练的MoveNet模型。
import tensorflow_hub as hub
#导入OpenCV库
import cv2
#导入了NumPy库,并将其别名设置为np。
#NumPy是Python中用于科学计算的基础库,提供了强大的多维数组对象和各种工具。
#在本代码示例中主要作用:
#1、处理和操作图像数据(图像本质上是多维数组)
#2、处理模型输出的数值计算
#3、进行各种数学运算
import numpy as np
2、加载MoveNet模型
# 这行代码使用 TensorFlow Hub 加载 MoveNet 模型。
#'https://tfhub.dev/google/movenet/singlepose/lightning/4' 是模型在 TensorFlow Hub 上的 URL。
#这个 URL 指向 MoveNet 的 "Lightning" 版本,这是一个单人姿态估计模型,优化了速度。
#'4' 表示模型的版本号。
#hub.load() 函数下载并加载模型,返回一个可以用于推理的 TensorFlow 模型对象。
model = hub.load('https://tfhub.dev/google/movenet/singlepose/lightning/4')
#这行代码获取模型的默认推理函数。
#在 TensorFlow SavedModel 格式中,模型可以有多个 "签名"(signatures),代表不同的功能或接口。
#'serving_default' 是模型的默认推理签名,通常用于模型部署和推理。
#将这个签名赋值给 movenet 变量,使其成为一个可调用的函数对象
movenet = model.signatures['serving_default']
3、定义关键点
KEYPOINTS = {
0: 'nose', 1: 'left_eye', 2: 'right_eye', 3: 'left_ear', 4: 'right_ear',
5: 'left_shoulder', 6: 'right_shoulder', 7: 'left_elbow', 8: 'right_elbow',
9: 'left_wrist', 10: 'right_wrist', 11: 'left_hip', 12: 'right_hip',
13: 'left_knee', 14: 'right_knee', 15: 'left_ankle', 16: 'right_ankle'
}
在处理模型输出时,这些定义有助于将数值索引映射到有意义的身体部位名称。
4、定义骨架连接
EDGES = {
(0, 1): 'm', (0, 2): 'm', (1, 3): 'm', (2, 4): 'm', (0, 5): 'm', (0, 6): 'm',
(5, 7): 'm', (7, 9): 'm', (6, 8): 'm', (8, 10): 'm', (5, 6): 'y', (5, 11): 'm',
(6, 12): 'm', (11, 12): 'y', (11, 13): 'm', (13, 15): 'm', (12, 14): 'm', (14, 16): 'm'
}
(0, 1): 'm'
表示连接鼻子(索引 0)和左眼(索引 1)的线应该是品红色的。这种结构使得代码更加灵活和可读,允许轻松修改或扩展关键点和连接的定义。
演示:
5、绘制人体骨架连接
主要作用是在给定的图像帧上绘制人体骨架,通过连接置信度足够高的关键点。它使用了预定义的边(edges
)来决定哪些关键点之间应该画线,并使用置信度阈值来过滤掉不可靠的检测结果。
def draw_connections(frame, keypoints, edges, confidence_threshold):
#获取图像尺寸
y, x, c = frame.shape
#调整关键点坐标
#np.multiply(keypoints, [y, x, 1]) 将归一化的关键点坐标缩放到实际图像尺寸。
#np.squeeze() 去除多余的维度。
shaped = np.squeeze(np.multiply(keypoints, [y, x, 1]))
#遍历所有边
#对每个边,获取两个端点的坐标和置信度。
for edge, color in edges.items():
p1, p2 = edge
y1, x1, c1 = shaped[p1]
y2, x2, c2 = shaped[p2]
#绘制连接线
#如果两个端点的置信度都高于阈值,则使用 cv2.line() 函数绘制连接线。
#线的颜色设置为红色 (0, 0, 255),宽度为 2 像素。
if (c1 > confidence_threshold) & (c2 > confidence_threshold):
cv2.line(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 0, 255), 2)
参数解释:
frame
: 要绘制的图像帧。keypoints
: 检测到的关键点坐标和置信度。edges
: 定义骨架连接的字典(就是之前定义的 EDGES
)。confidence_threshold
: 置信度阈值,用于过滤低置信度的关键点6、绘制关键点
主要作用是在给定的图像帧上绘制人体姿态的关键点。它遍历所有检测到的关键点,并为那些置信度高于指定阈值的点绘制一个小圆圈。这样可以直观地显示模型检测到的人体关键点位置,如眼睛、鼻子、肩膀等。
通过调整 confidence_threshold
,可以控制显示的关键点数量,只显示那些模型较为确定的点,从而减少误检。
def draw_keypoints(frame, keypoints, confidence_threshold):
# 获取图像的尺寸
y, x, c = frame.shape
# 将关键点坐标调整到图像尺寸
#np.multiply(keypoints, [y, x, 1]) 将归一化的关键点坐标缩放到实际图像尺寸。
#np.squeeze() 去除多余的维度
shaped = np.squeeze(np.multiply(keypoints, [y, x, 1]))
#遍历所有的点对每个关键点,获取其 y 坐标、x 坐标和置信度。
for kp in shaped:
ky, kx, kp_conf = kp
# 如果关键点的置信度高于阈值,则绘制该点
if kp_conf > confidence_threshold:
#圆点的颜色设置为绿色 (0, 255, 0),半径为 4 像素,-1 表示填充圆。
cv2.circle(frame, (int(kx), int(ky)), 4, (0, 255, 0), -1)
frame
: 要绘制的图像帧。keypoints
: 检测到的关键点坐标和置信度。confidence_threshold
: 置信度阈值,用于过滤低置信度的关键点7、处理监测到的所有人的姿态关键点
函数的作用:
1、便利监测到每个人(通常指一个人)
2、为每个人绘制骨架连接
3、为每个人绘制关键点
这个函数是将姿态估计结果可视化的核心部分,它确保了所有监测到的人(即使只有一个)
的姿态都被正确的绘制在视频帧上。
def loop_through_people(frame, keypoints_with_scores, edges, confidence_threshold):
for person in keypoints_with_scores:
draw_connections(frame, person, edges, confidence_threshold)
draw_keypoints(frame, person, confidence_threshold)
frame
:要绘制结果的视频帧(图像)keypoints_with_scores
:包含所有检测到的人的关键点和置信度分数edges
:定义骨架连接的字典confidence_threshold
:置信度阈值,用于过滤低置信度的关键点for person in keypoints_with_scores:
这行开始一个循环,遍历 keypoints_with_scores
中的每个"人"。在 MoveNet 模型中,通常 keypoints_with_scores
是一个形状为 [1, 1, 17, 3] 的数组,其中:
8、视频处理
def process_video(input_video, output_video):
# 打开输入视频文件
#input_video和output_video都是视频文件的地址
cap = cv2.VideoCapture(input_video)
# 获取视频的属性(宽度、高度、帧率)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = int(cap.get(cv2.CAP_PROP_FPS))
# 设置输出视频的编码器和创建VideoWriter对象
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_video, fourcc, fps, (width, height))
while cap.isOpened():
# 读取一帧
ret, frame = cap.read()
if not ret:
break # 如果无法读取帧(视频结束),则退出循环
# 预处理帧
# 调整图像大小为MoveNet模型的输入尺寸(192x192)
input_image = cv2.resize(frame, (192, 192))
# 将颜色空间从BGR转换为RGB
input_image = cv2.cvtColor(input_image, cv2.COLOR_BGR2RGB)
# 将图像转换为TensorFlow张量,并设置数据类型为int32
input_image = tf.cast(input_image, dtype=tf.int32)
# 添加批次维度
input_image = tf.expand_dims(input_image, axis=0)
# 运行MoveNet模型
results = movenet(input_image)
# 获取关键点预测结果
keypoints_with_scores = results['output_0'].numpy()
# 在帧上绘制关键点和连接
# EDGES是预定义的关键点连接关系
# 0.3是置信度阈值(在0到1之间)
loop_through_people(frame, keypoints_with_scores, EDGES, 0.3)
# 将处理后的帧写入输出视频
out.write(frame)
# 显示处理后的帧(可选)
cv2.imshow('MoveNet Pose Estimation', frame)
# 如果用户按'q'键,则退出循环
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 释放资源
cap.release()
out.release()
cv2.destroyAllWindows()
9、处理摄像头实时视频
def process_webcam():
cap = cv2.VideoCapture(0) # 0 表示默认摄像头
while True:
ret, frame = cap.read()
if not ret:
break
# 预处理帧
input_image = cv2.resize(frame, (192, 192))
input_image = cv2.cvtColor(input_image, cv2.COLOR_BGR2RGB)
input_image = tf.cast(input_image, dtype=tf.int32)
input_image = tf.expand_dims(input_image, axis=0)
# 运行模型
results = movenet(input_image)
keypoints_with_scores = results['output_0'].numpy()
# 绘制关键点和连接
loop_through_people(frame, keypoints_with_scores, EDGES, 0.3)
# 显示处理后的帧
cv2.imshow('MoveNet Pose Estimation (Webcam)', frame)
#如果用户按下 'q' 键,退出循环。
if cv2.waitKey(1) & 0xFF == ord('q'):
break
#循环结束后,释放摄像头资源并关闭所有 OpenCV 窗口。
cap.release()
cv2.destroyAllWindows()
10、主程序
# 主程序
if __name__ == "__main__":
#设置视频输入文件的路径
input_video = r".\image\video1.mp4"
#设置视频输出文件的路径
output_video = r".\image\output\video1_output.mp4"
process_video(input_video, output_video)
print(f"处理完成。输出视频保存在: {output_video}")
#使用摄像头,取消下面这行的注释
# process_webcam()
四、完整代码
import tensorflow as tf
import tensorflow_hub as hub
import cv2
import numpy as np
# 加载 MoveNet 模型
model = hub.load('https://tfhub.dev/google/movenet/singlepose/lightning/4')
movenet = model.signatures['serving_default']
# 定义关键点
KEYPOINTS = {
0: 'nose', 1: 'left_eye', 2: 'right_eye', 3: 'left_ear', 4: 'right_ear',
5: 'left_shoulder', 6: 'right_shoulder', 7: 'left_elbow', 8: 'right_elbow',
9: 'left_wrist', 10: 'right_wrist', 11: 'left_hip', 12: 'right_hip',
13: 'left_knee', 14: 'right_knee', 15: 'left_ankle', 16: 'right_ankle'
}
# 定义骨架连接
EDGES = {
(0, 1): 'm', (0, 2): 'm', (1, 3): 'm', (2, 4): 'm', (0, 5): 'm', (0, 6): 'm',
(5, 7): 'm', (7, 9): 'm', (6, 8): 'm', (8, 10): 'm', (5, 6): 'y', (5, 11): 'm',
(6, 12): 'm', (11, 12): 'y', (11, 13): 'm', (13, 15): 'm', (12, 14): 'm', (14, 16): 'm'
}
def draw_connections(frame, keypoints, edges, confidence_threshold):
y, x, c = frame.shape
shaped = np.squeeze(np.multiply(keypoints, [y, x, 1]))
for edge, color in edges.items():
p1, p2 = edge
y1, x1, c1 = shaped[p1]
y2, x2, c2 = shaped[p2]
if (c1 > confidence_threshold) & (c2 > confidence_threshold):
cv2.line(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 0, 255), 2)
def draw_keypoints(frame, keypoints, confidence_threshold):
y, x, c = frame.shape
shaped = np.squeeze(np.multiply(keypoints, [y, x, 1]))
for kp in shaped:
ky, kx, kp_conf = kp
if kp_conf > confidence_threshold:
cv2.circle(frame, (int(kx), int(ky)), 4, (0, 255, 0), -1)
def loop_through_people(frame, keypoints_with_scores, edges, confidence_threshold):
for person in keypoints_with_scores:
draw_connections(frame, person, edges, confidence_threshold)
draw_keypoints(frame, person, confidence_threshold)
def process_video(input_video, output_video):
# 打开输入视频文件
cap = cv2.VideoCapture(input_video)
# 获取视频的属性(宽度、高度、帧率)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = int(cap.get(cv2.CAP_PROP_FPS))
# 设置输出视频的编码器和创建VideoWriter对象
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_video, fourcc, fps, (width, height))
while cap.isOpened():
# 读取一帧
ret, frame = cap.read()
if not ret:
break # 如果无法读取帧(视频结束),则退出循环
# 预处理帧
# 调整图像大小为MoveNet模型的输入尺寸(192x192)
input_image = cv2.resize(frame, (192, 192))
# 将颜色空间从BGR转换为RGB
input_image = cv2.cvtColor(input_image, cv2.COLOR_BGR2RGB)
# 将图像转换为TensorFlow张量,并设置数据类型为int32
input_image = tf.cast(input_image, dtype=tf.int32)
# 添加批次维度
input_image = tf.expand_dims(input_image, axis=0)
# 运行MoveNet模型
results = movenet(input_image)
# 获取关键点预测结果
keypoints_with_scores = results['output_0'].numpy()
# 在帧上绘制关键点和连接
# EDGES是预定义的关键点连接关系
# 0.3是置信度阈值
loop_through_people(frame, keypoints_with_scores, EDGES, 0.3)
# 将处理后的帧写入输出视频
out.write(frame)
# 显示处理后的帧(可选)
cv2.imshow('MoveNet Pose Estimation', frame)
# 如果用户按'q'键,则退出循环
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 释放资源
cap.release()
out.release()
cv2.destroyAllWindows()
# 如果你想处理摄像头实时视频,可以使用以下函数
def process_webcam():
cap = cv2.VideoCapture(0) # 0 表示默认摄像头
while True:
ret, frame = cap.read()
if not ret:
break
# 预处理帧
input_image = cv2.resize(frame, (192, 192))
input_image = cv2.cvtColor(input_image, cv2.COLOR_BGR2RGB)
input_image = tf.cast(input_image, dtype=tf.int32)
input_image = tf.expand_dims(input_image, axis=0)
# 运行模型
results = movenet(input_image)
keypoints_with_scores = results['output_0'].numpy()
# 绘制关键点和连接
loop_through_people(frame, keypoints_with_scores, EDGES, 0.3)
# 显示处理后的帧
cv2.imshow('MoveNet Pose Estimation (Webcam)', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
# 主程序
if __name__ == "__main__":
#使用时替换文件地址
input_video = r".\image\video1.mp4"
output_video = r".\image\output\video1_output.mp4"
process_video(input_video, output_video)
print(f"处理完成。输出视频保存在: {output_video}")
#使用摄像头,取消下面这行的注释
# process_webcam()
作者:WinterCruel