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
  • TensorFlow提供了必要的框架来处理图像数据,执行模型推理。
  • TensorFlow Hub允许轻松加载和使用预训练的MoveNet模型,而无需从头开始训练模型。
  • OpenCV提供了高效的图像和视频处理功能,适合实时应用。
  • NumPy提供了底层的数值计算支持,常用于数据操作和处理

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'
}
  • 这个字典定义了 17 个人体关键点。
  • 键是从 0 到 16 的整数,代表关键点的索引。
  • 值是对应的关键点名称,如鼻子、眼睛、耳朵、肩膀、肘部、手腕、臀部、膝盖和脚踝等。
  • 这个映射有助于在代码中引用特定的关键点。

在处理模型输出时,这些定义有助于将数值索引映射到有意义的身体部位名称。

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'
}
  • 这个字典定义了关键点之间的连接,形成人体骨架。
  • 键是一个元组,表示两个关键点的索引。
  • 值是一个字符,可能代表连接线的颜色(‘m’ 可能代表 magenta,‘y’ 可能代表 yellow)。
  • 例如,(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] 的数组,其中:

  • 第一个维度是批次大小(通常为1)
  • 第二个维度是检测到的人数(在单人模式下为1)
  • 17 表示每个人的17个关键点
  • 3 表示每个关键点的 y 坐标、x 坐标和置信度分数

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()
  • 14
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值