1. 引言
跳绳是一项高效的有氧运动,但手动计数可能不准确。本文利用Google开源的Mediapipe姿态识别功能,实现精准的跳绳计数,简化了传统的计数方式。
实现原理
(1) 人体姿态估计:
Mediapipe提供33个人体关键点坐标,通过识别人体的姿态变化,追踪跳跃动作。
(2) 人体中心变化:
记录人体下半身关键点(23, 24)和上半身关键点(11, 12)的位置变化,判断跳绳过程中人体中心的最高和最低点。
功能
- 实时检测人体姿态,并提取髋关节和肩膀的位置。
- 计算并可视化中心Y轴,监测身体上下波动。
- 基于波动幅度计数跳绳次数。
- 实时绘制跳绳计数和中心点位置。
技术栈
- Python
- OpenCV
- MediaPipe
- Matplotlib
- NumPy
环境准备
确保你已经安装了以下库:
pip install opencv-python mediapipe matplotlib numpy
代码运行
-
将待处理视频文件命名为
demo.mp4
,并放在代码文件夹内。 -
运行代码:
python rope_skipping_counter.py
-
视频处理完成后,输出结果会保存为
demo_output.mp4
。
参数设置
可以根据需要调整以下参数:
buffer_time
: 缓冲区时间,默认为 50。dy_ratio
: 移动幅度阈值,默认为 0.3。up_ratio
: 上升阈值,默认为 0.55。down_ratio
: 下降阈值,默认为 0.35。flag_low
和flag_high
: 控制翻转标志的阈值。
代码实现
import cv2
import matplotlib.pyplot as plt
import mediapipe as mp
import numpy as np
class BufferList:
def __init__(self, buffer_time, default_value=0):
self.buffer = [default_value] * buffer_time
def push(self, value):
self.buffer.pop(0)
self.buffer.append(value)
def max(self):
return max(self.buffer)
def min(self):
return min(filter(lambda x: x is not None, self.buffer), default=0)
def smooth_update(self, old_value, new_value, alpha=0.5):
return alpha * new_value + (1 - alpha) * old_value
def extract_landmarks(results, landmarks_indices, image_width, image_height):
"""提取指定关节的坐标"""
return [
(lm.x * image_width, lm.y * image_height)
for i, lm in enumerate(results.pose_landmarks.landmark)
if i in landmarks_indices
]
def calculate_center_y(hip_points, shoulder_points):
"""计算中心Y轴及肩-臀垂直距离"""
cy_hip = int(np.mean([point[1] for point in hip_points]))
cy_shoulder = int(np.mean([point[1] for point in shoulder_points]))
return cy_hip, cy_hip - cy_shoulder
def update_counters(cy, cy_shoulder_hip, cy_max, cy_min, flip_flag, thresholds):
"""更新波动计数逻辑"""
dy = cy_max - cy_min
if dy > thresholds["dy_ratio"] * cy_shoulder_hip:
if (
cy > cy_max - thresholds["up_ratio"] * dy
and flip_flag == thresholds["flag_low"]
):
flip_flag = thresholds["flag_high"]
elif (
cy < cy_min + thresholds["down_ratio"] * dy
and flip_flag == thresholds["flag_high"]
):
flip_flag = thresholds["flag_low"]
return flip_flag
def draw_visualizations(image, cx, cy, count, image_width, image_height):
"""绘制中心点、计数等信息"""
cv2.circle(image, (cx, cy), 5, (0, 0, 255), -1)
cv2.putText(
image,
"centroid",
(cx - 25, cy - 25),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
(0, 0, 255),
1,
)
cv2.putText(
image,
f"count = {count}",
(int(image_width * 0.5), int(image_height * 0.4)),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
(0, 255, 0),
1,
)
def plot_buffers(buffers, ax):
"""实时绘制缓冲区数据"""
ax.clear()
for label, buf in buffers.items():
ax.plot(buf.buffer, label=label)
ax.legend(loc="upper right")
plt.pause(0.1)
# 主代码
file_name = "input.mp4"
mp_pose = mp.solutions.pose
hip_landmarks = [23, 24]
shoulder_landmarks = [11, 12]
# 阈值设置
thresholds = {
"buffer_time": 50, # 缓冲区时间
"dy_ratio": 0.3, # 移动幅度阈值
"up_ratio": 0.55, # 上升阈值
"down_ratio": 0.35, # 下降阈值
"flag_low": 150, # 翻转标志低点
"flag_high": 250, # 翻转标志高点
}
buffers = {
"center_y": BufferList(thresholds["buffer_time"]),
"center_y_up": BufferList(thresholds["buffer_time"]),
"center_y_down": BufferList(thresholds["buffer_time"]),
"center_y_flip": BufferList(thresholds["buffer_time"]),
"center_y_pref_flip": BufferList(thresholds["buffer_time"]),
}
cy_max, cy_min = 100, 100
flip_flag = thresholds["flag_high"]
count = 0
cap = cv2.VideoCapture(file_name)
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
fps = cap.get(cv2.CAP_PROP_FPS)
out = cv2.VideoWriter(
file_name.replace(".mp4", "_output.mp4"),
fourcc,
fps,
(int(cap.get(3)), int(cap.get(4))),
)
plt.ion()
fig, ax = plt.subplots()
with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
while cap.isOpened():
success, image = cap.read()
if not success:
break
image_height, image_width, _ = image.shape
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
results = pose.process(image_rgb)
if results.pose_landmarks:
hip_points = extract_landmarks(
results, hip_landmarks, image_width, image_height
)
shoulder_points = extract_landmarks(
results, shoulder_landmarks, image_width, image_height
)
cx = int(np.mean([point[0] for point in hip_points]))
cy, cy_shoulder_hip = calculate_center_y(hip_points, shoulder_points)
else:
cx, cy, cy_shoulder_hip = 0, 0, 0
buffers["center_y"].push(cy)
cy_max = buffers["center_y"].smooth_update(cy_max, buffers["center_y"].max())
buffers["center_y_up"].push(cy_max)
cy_min = buffers["center_y"].smooth_update(cy_min, buffers["center_y"].min())
buffers["center_y_down"].push(cy_min)
prev_flip_flag = flip_flag
flip_flag = update_counters(
cy, cy_shoulder_hip, cy_max, cy_min, flip_flag, thresholds
)
buffers["center_y_flip"].push(flip_flag)
buffers["center_y_pref_flip"].push(prev_flip_flag)
if prev_flip_flag < flip_flag:
count += 1
draw_visualizations(image, cx, cy, count, image_width, image_height)
plot_buffers(buffers, ax)
cv2.imshow("MediaPipe Pose", image)
out.write(image)
if cv2.waitKey(5) & 0xFF == 27:
break
cap.release()
out.release()
cv2.destroyAllWindows()
4. 致谢
- 感谢 Mediapipe 和开源项目 pushup_counter 提供的技术支持。