ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于MediaPipe的舞蹈视频自动拆分与姿态评估流水线

基于MediaPipe的舞蹈视频自动拆分与姿态评估流水线 从视频切分到姿态序列标注我把「split dance」做成了一条自动化流水线。很多编舞作品在后期流传时会被二创拆成“卡点片段”比如『ch/维粹』这类组合标题里的 split往往指的是舞蹈中的“劈叉/大跳”动作也指后期将完整编舞按音乐节拍拆成多个独立镜头的处理方式。我先把这两个概念合到一套方案里用音频节拍检测自动拆出舞蹈高潮片段再借助姿态估计技术提取骨架关键点判断每一位舞者在每个拆分动作里是否真正完成了“一字马”级别的开腿角度。整个过程包含数据准备、Python 脚本、ffmpeg 分割、MediaPipe 关键点提取和角度可视化适合对视频处理、动作分析感兴趣的同学也适合想批量整理舞蹈二创素材的 UP 主和编舞老师。1. 背景与核心概念1.1 什么是 split dance为什么要做动作拆分在舞蹈视频领域split dance 至少有三种常见理解第一种是字面意义的“劈叉舞”也就是编舞中出现大跳、地面一字马、站姿搬腿等大幅开腿动作第二种是“拆分舞蹈片段”指把一首几分钟的完整作品切成多个短镜头方便在短视频平台逐段发布或二次创作第三种是舞蹈训练中按动作轨迹拆解把一个复合动作分成准备、发力、滞空、落地四段进行分析。本文更关注的是“如何把一段舞蹈视频拆成可以被程序理解的动作序列”。核心原因是舞蹈是时间维度上的连续运动一个跳跃动作可能只持续 0.5 秒中间还混着旋转、腿部摆动和上肢变化。如果直接把整段视频丢给模型去学习不仅计算量大而且动作标签的边界模糊。先做时序拆分再针对每一帧做姿态关键点分析后续无论做动作检索、相似度匹配还是自动化评分都会容易很多。换到实际业务场景这套处理逻辑还能做成不少工具比如舞蹈教学时自动打点每个八拍二创视频自动切出最炸的动作瞬间或者利用姿态关键点辅助判断舞者的跨腿幅度是否达标。把音乐信息和视觉信息组合使用是多数动作拆分方案的基础。1.2 一个方案包含哪些部分一条完整的舞蹈动作拆分流水线通常包含六个环节第一数据采集。准备一段分辨率清晰、镜头尽量固定的舞蹈视频。如果是运镜复杂的直拍背景虚化和人物遮挡会影响姿态估计因此建议先用固定机位素材测试。第二音频节拍检测。从视频中抽取音轨检测节拍位置、重音位置或音乐能量变化。舞蹈通常踩着八拍编排重音往往对应招牌动作。第三时间切分。根据节拍时间点把原始视频切成若干 clip。这一步主要需要 ffmpeg也可以直接使用 librosa 算好时间后再用 Python 调用 subprocess 完成批量切割。第四姿态估计。对每个 clip 逐帧提取人体关键点。比较常见的开源工具是 MediaPipe Pose它可以输出 33 个身体关键点在图像中的归一化坐标。第五动作判定。根据关键点坐标计算关节角度。比如要判断劈叉就计算两条大腿之间的夹角、髋关节到踝关节的距离比或者踝关节相对地面的高度。第六结果序列化。把每一帧的关键点坐标、判定结果、时间戳输出为 JSON 或 CSV供后续训练或可视化使用。这个过程本质上是对舞蹈数据的结构化处理让原本不可检索的视频变成每一帧都“可回答问题”的数据集。2. 环境准备与项目结构说明2.1 运行环境与依赖本文的完整示例在 Windows 11 和 Ubuntu 22.04 上都验证过思路核心依赖如下软件用途备注Python 3.9运行脚本与依赖管理建议使用虚拟环境ffmpeg 4.4视频转码、抽取音频、切割视频需要加入 PATHlibrosa音频节拍检测有 numpy 依赖opencv-python视频帧读取与绘制结果需要额外安装mediapipe人体姿态关键点提取不同版本 API 有差异matplotlib可选绘制角度曲线仅在调试时使用下面创建虚拟环境并安装依赖python -m venv dance_split_env source dance_split_env/bin/activate pip install numpy librosa opencv-python mediapipe在 Windows 上创建虚拟环境和激活命令略有不同python -m venv dance_split_env dance_split_env\Scripts\activate这里要特别提醒一点mediapipe 的 API 变化比较快。早期版本常用mp.solutions.pose新版则逐渐迁移到 Tasks API。本文以稳定的solutions.pose为例演示处理逻辑如果你使用的是新版本需要根据官方文档调整关键点获取方式。逻辑本身是通用的。2.2 项目目录规划为了让代码不混乱我先把所有文件按下面的结构组织好dance_splitter/ ├── input/ │ └── original_video.mp4 ├── output/ │ ├── audio/ │ ├── cuts/ │ ├── pose_videos/ │ └── pose_data/ ├── beat_splitter.py ├── pose_analyzer.py └── run_pipeline.pyinput放置原始视频。output/audio存放从视频中抽取出的音频文件。output/cuts存放按节拍切分后的舞蹈片段。output/pose_videos存放标注了骨架和角度的视频。output/pose_data存放每段切片提取出的 JSON 数据。实际使用中不建议把中间结果和源代码混在一起舞蹈视频通常会比较大中间产物可以单独放到另一个磁盘分区避免反复拷贝大文件。3. 关键技术原理拆解3.1 音频节拍检测的原理与输出音频节拍检测其实就是估计“音乐中的强时刻”。librosa 的节拍跟踪算法会先计算一个称为 onset strength 的序列然后在该序列中搜索周期性的峰值最终输出拍子对应的帧索引再结合采样率换算成秒。下面这个函数可以输出一个音频文件里的拍子时间点import librosa def extract_beats(audio_path: str, hop_length: int 512): y, sr librosa.load(audio_path, sr22050, monoTrue) tempo, beat_frames librosa.beat.beat_track( yy, srsr, hop_lengthhop_length, trimFalse ) if hasattr(tempo, __len__): tempo float(tempo[0]) else: tempo float(tempo) beat_times librosa.frames_to_time(beat_frames, srsr, hop_lengthhop_length) return tempo, beat_times if __name__ __main__: tempo, beats extract_beats(output/audio/audio.wav) print(f估计BPM: {tempo:.2f}) print(前10个节拍时间点:, beats[:10]) }需要注意早期 librosa 的beat_track返回的 tempo 是一个标量较新版本可能返回一维数组所以我在代码里做了一个兼容判断。另外拍子时间只是基础信息舞蹈拆分通常需要结合八拍也就是每 4 拍或 8 拍切一次。例如一首 128 BPM 的歌每个四分音符间隔约 0.46875 秒一个八拍大约 3.75 秒。如果只用单个拍子切片段会太碎不利于动作分析如果整段都切成八拍有些爆发动作又可能跨拍因此还要结合画面变化做二次清洗。3.2 切分时间点的粒度选择自动拆舞不能“见拍就切”。以快速街舞为例一拍内可能包含两个甚至三个动作切太碎会导致后续姿态估计不稳定反过来如果只在重音处切普通连接动作会被算进高潮片段里影响标注精确度。实际工程中我会对节拍做去重和间隔过滤先计算相邻拍子的间隔剔除间隔极短的异常点。如果两段高潮动作间隔小于 0.8 秒就把它们合并成一个片段。设置最小片段长度和最大片段长度例如 1.2 秒到 8 秒。这些规则能明显减少脏数据。比如一个舞蹈视频中音乐在副歌处有连续重音切分时间点非常密集如果照单全收会切出几十段不到一秒的空镜头。用规则过滤后可以保留真正值得分析的编舞短语。3.3 人体姿态关键点与关节角度MediaPipe Pose 可以输出 33 个关键点。常用的人体部位索引如下索引部位索引部位11左肩12右肩23左髋24右髋25左膝26右膝27左踝28右踝每个关键点包含 x、y、z 三个方向的信息。x 和 y 是图像坐标归一化后的值z 是深度估计值以髋部中心为原点。判断一个舞者是否在做劈叉动作不能只看膝盖高不高本质要看两条大腿之间的夹角是否接近 180 度。我们用一个简单的三点夹角公式import math def calculate_angle(a, b, c): 计算向量 ba 与 bc 之间的夹角a、b、c 为三个关键点 ba [a.x - b.x, a.y - b.y, a.z - b.z] bc [c.x - b.x, c.y - b.y, c.z - b.z] dot ba[0] * bc[0] ba[1] * bc[1] ba[2] * bc[2] norm_ba math.sqrt(ba[0] ** 2 ba[1] ** 2 ba[2] ** 2) norm_bc math.sqrt(bc[0] ** 2 bc[1] ** 2 bc[2] ** 2) if norm_ba 0 or norm_bc 0: return 0.0 cos_theta max(-1.0, min(1.0, dot / (norm_ba * norm_bc))) return math.degrees(math.acos(cos_theta))计算每个动作片段的关键点后就能得到一条角度变化曲线。一个明显的 split dance 慢动作大腿夹角曲线会从 60 度左右持续增长到 160 度以上然后再恢复站立状态。这种曲线比单帧判断鲁棒得多也是自动化评分的核心依据。4. 完整实战从视频到拆分动作序列4.1 步骤一抽取音频并检测节拍点先创建脚本beat_splitter.py下面这段代码会实现音频抽取、节拍检测、切分点生成和视频分割。import subprocess import os import json import librosa import numpy as np class BeatSplitter: def __init__(self, video_path: str, output_dir: str output): self.video_path video_path self.output_dir output_dir self.audio_dir os.path.join(output_dir, audio) self.cut_dir os.path.join(output_dir, cuts) os.makedirs(self.audio_dir, exist_okTrue) os.makedirs(self.cut_dir, exist_okTrue) def extract_audio(self): base_name os.path.splitext(os.path.basename(self.video_path))[0] self.audio_path os.path.join(self.audio_dir, base_name .wav) cmd [ ffmpeg, -y, -i, self.video_path, -vn, -acodec, pcm_s16le, -ar, 22050, -ac, 1, self.audio_path ] subprocess.run(cmd, checkTrue) print(f音频已抽取到: {self.audio_path}) return self.audio_path def detect_sections(self, min_gap: float 1.2, section_range(1.5, 12.0)): y, sr librosa.load(self.audio_path, sr22050, monoTrue) onset_env librosa.onset.onset_strength(yy, srsr) # 在 onset strength 曲线中找局部峰值 peak_frames librosa.util.peak_pick( onset_env, pre_max5, post_max5, pre_avg5, post_avg5, delta0.3, wait10 ) peak_times librosa.frames_to_time(peak_frames, srsr) # 按最小间隔过滤再合并过近的时间点 filtered [] for t in peak_times: if not filtered or t - filtered[-1] min_gap: filtered.append(t) # 构造片段区间 sections [] for i, start in enumerate(filtered[:-1]): end filtered[i 1] duration end - start if section_range[0] duration section_range[1]: sections.append((round(start, 3), round(end, 3))) self.sections sections print(f切出 {len(sections)} 个待分析片段) return sections def export_cuts(self): cut_paths [] for idx, (start, end) in enumerate(self.sections): out_path os.path.join(self.cut_dir, fsegment_{idx:03d}.mp4) cmd [ ffmpeg, -y, -i, self.video_path, -ss, str(start), -to, str(end), -c:v, libx264, -c:a, aac, out_path ] subprocess.run(cmd, checkTrue, stdoutsubprocess.DEVNULL, stderrsubprocess.DEVNULL) cut_paths.append(out_path) print(f已输出: {out_path} ({start}s - {end}s)) return cut_paths这里我选择了“峰值能量”而非单纯的节拍位置原因在于舞蹈视频中的炸点通常伴随音乐能量增强。librosa.util.peak_pick的作用是在 onset strength 序列中找出明显的峰值。wait10表示峰值附近至少间隔多少帧这个值可以减少连续噪声产生的伪峰值。需要注意的是delta0.3是一个经验值实际音乐动态不同需要做调整。如果切出的片段太多就增大 delta如果片段太碎就增大 wait。4.2 步骤二运行切分脚本在终端中执行python beat_splitter.py为了让脚本能被直接调用可以在文件末尾补上入口。当然更推荐把它封装成模块用下面命令运行python -m beat_splitter --video input/original_video.mp4这里为了篇幅不再展示命令行参数解析完整代码但建议使用argparse传入video_path、min_gap等参数方便批量处理多个视频。如果运行时出现FileNotFoundError优先检查 ffmpeg 是否正确安装以及是否能在终端直接执行ffmpeg -version。4.3 步骤三对每个片段进行姿态估计现在进入视频动作分析。新建pose_analyzer.py它的任务不是一次性处理整个长视频而是对切出来的每个小片段逐帧检测这样能避免由于视频过长导致的累计误差也方便定位是哪一段动作引发了异常判定。import cv2 import mediapipe as mp import json import os mp_pose mp.solutions.pose class PoseAnalyzer: def __init__(self, cut_dir: str output/cuts, out_video_dir: str output/pose_videos, out_json_dir: str output/pose_data): self.cut_dir cut_dir self.out_video_dir out_video_dir self.out_json_dir out_json_dir os.makedirs(self.out_video_dir, exist_okTrue) os.makedirs(self.out_json_dir, exist_okTrue) def analyze_single_video(self, video_path: str): cap cv2.VideoCapture(video_path) fps cap.get(cv2.CAP_PROP_FPS) width int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) base_name os.path.splitext(os.path.basename(video_path))[0] out_video_path os.path.join(self.out_video_dir, base_name _pose.mp4) writer cv2.VideoWriter( out_video_path, cv2.VideoWriter_fourcc(*mp4v), fps, (width, height) ) frame_results [] frames_saved 0 with mp_pose.Pose( static_image_modeFalse, model_complexity1, min_detection_confidence0.5, min_tracking_confidence0.5 ) as pose: while True: ret, frame cap.read() if not ret: break rgb_frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) result pose.process(rgb_frame) if result.pose_landmarks: landmarks result.pose_landmarks.landmark leg_angle self.compute_leg_angle(landmarks) height_ratio self.compute_ankle_hip_height_ratio(landmarks) visibility self.compute_visibility(landmarks) frame_results.append({ frame_index: frames_saved, timestamp: round(frames_saved / fps, 3), leg_angle: round(leg_angle, 2), ankle_hip_height_ratio: round(height_ratio, 4), visibility: round(visibility, 4) }) annotated self.draw_landmarks(frame, result.pose_landmarks) cv2.putText( annotated, fleg_angle: {leg_angle:.1f}, (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2 ) writer.write(annotated) else: frame_results.append({ frame_index: frames_saved, timestamp: round(frames_saved / fps, 3), leg_angle: None, ankle_hip_height_ratio: None, visibility: 0.0 }) writer.write(frame) frames_saved 1 cap.release() writer.release() json_path os.path.join(self.out_json_dir, base_name _pose.json) with open(json_path, w, encodingutf-8) as f: json.dump({ video: base_name, fps: fps, frame_count: frames_saved, results: frame_results }, f, ensure_asciiFalse, indent2) print(f完成: {base_name}, 总帧数 {frames_saved}) return json_path def compute_leg_angle(self, landmarks): left_knee landmarks[mp_pose.PoseLandmark.LEFT_KNEE.value] right_knee landmarks[mp_pose.PoseLandmark.RIGHT_KNEE.value] left_hip landmarks[mp_pose.PoseLandmark.LEFT_HIP.value] right_hip landmarks[mp_pose.PoseLandmark.RIGHT_HIP.value] import math def vec_angle(p1, p2, p3, p4): v1 [p2.x - p1.x, p2.y - p1.y, p2.z - p1.z] v2 [p4.x - p3.x, p4.y - p3.y, p4.z - p3.z] dot v1[0] * v2[0] v1[1] * v2[1] v1[2] * v2[2] n1 math.sqrt(sum([c * c for c in v1])) n2 math.sqrt(sum([c * c for c in v2])) if n1 0 or n2 0: return 0.0 cos_a max(-1.0, min(1.0, dot / (n1 * n2))) return math.degrees(math.acos(cos_a)) # 左大腿方向与右大腿方向之间的夹角 leg_angle vec_angle(left_hip, left_knee, right_hip, right_knee) return min(leg_angle, 360 - leg_angle) def compute_ankle_hip_height_ratio(self, landmarks): left_hip_y landmarks[mp_pose.PoseLandmark.LEFT_HIP.value].y right_hip_y landmarks[mp_pose.PoseLandmark.RIGHT_HIP.value].y left_ankle_y landmarks[mp_pose.PoseLandmark.LEFT_ANKLE.value].y right_ankle_y landmarks[mp_pose.PoseLandmark.RIGHT_ANKLE.value].y hip_y (left_hip_y right_hip_y) / 2 ankle_y (left_ankle_y right_ankle_y) / 2 # 归一化坐标中y 越大越靠近图像下方这个比例越高说明越接近一字马贴合地面 return max(left_ankle_y, right_ankle_y) - hip_y def compute_visibility(self, landmarks): visible_points [lm.visibility for lm in landmarks if lm.visibility 0.5] if not visible_points: return 0.0 return sum(visible_points) / len(visible_points) def draw_landmarks(self, frame, landmarks): mp_drawing mp.solutions.drawing_utils annotated frame.copy() mp_drawing.draw_landmarks( annotated, landmarks, mp_pose.POSE_CONNECTIONS, mp_drawing.DrawingSpec(color(0, 255, 0), thickness2, circle_radius2), mp_drawing.DrawingSpec(color(0, 0, 255), thickness2) ) return annotatedcompute_leg_angle计算的是左大腿向量和右大腿向量之间的夹角。为什么不是直接看膝盖到脚踝因为劈叉的幅度主要由大腿劈开程度决定小腿弯曲会干扰判断。在真正完成一字马时两条大腿差不多在一条直线上这个角度会接近 180 度。compute_ankle_hip_height_ratio则是用踝关节相对髋关节的位置判断身体是否贴地。站姿时这个值较小地面一字马时会接近 1因为脚踝位置和髋关节之间的归一化距离变大了。两个指标组合可以有效区分高抬腿、大跳劈叉和地面一字马。但要清楚一点单目摄像机无法准确还原深度所以这个指标只能作为辅助想要精确的地面高度还需要双目相机或深度相机。4.4 步骤四整合解析流程把前面的类串成一个完整流水线新建run_pipeline.pyimport os from beat_splitter import BeatSplitter from pose_analyzer import PoseAnalyzer def main(video_path: str): splitter BeatSplitter(video_pathvideo_path) splitter.extract_audio() sections splitter.detect_sections() cut_paths splitter.export_cuts() analyzer PoseAnalyzer() json_paths [] for cut_path in cut_paths: json_paths.append(analyzer.analyze_single_video(cut_path)) return sections, json_paths if __name__ __main__: main(input/original_video.mp4)执行时建议先只处理 1 到 2 个片段确认参数没问题后再全量运行。因为 MediaPipe 的 CPU 推理速度通常在每秒 20 帧到 50 帧之间取决于机器算力和视频分辨率。如果视频是 4K 的建议先统一缩放至 1280 甚至 720否则单条视频会跑很久。4.5 步骤五检查结果与可视化运行完成后打开输出目录中的segment_000_pose.mp4会看到类似下面的效果画面中人物被绘制了绿色骨架连线。左上角显示了当前帧的leg_angle数值。视频会逐帧变化如果画面中舞者双腿打开角度数值迅速上升。对应的 JSON 文件里记录了每个片段每一帧的数据类似{ video: segment_000, fps: 30.0, frame_count: 125, results: [ { frame_index: 0, timestamp: 0.0, leg_angle: 42.16, ankle_hip_height_ratio: 0.231, visibility: 0.85 } ] }观察到 leg_angle 从 40 度上升到 160 度以上基本可以确认该片段包含一次明显的分腿动作。如果同一片段内 leg_angle 始终在 20 度左右波动说明这段并不是 split 动作可能是连接步或走位镜头。5. 常见问题与排查思路下面总结我在实际调试中遇到最多的问题对应给出解决路径。问题现象常见原因解决思路ffmpeg 报No such file or directoryffmpeg 未安装或未加入 PATH在终端运行ffmpeg -version检查Windows 可重新下载并配置环境变量librosa 加载音频太慢音频采样率太高或文件为无损格式统一用 22050 Hz、单声道 WAV也可先压缩为 AAC 再分析切分片段数量过多onset 峰值太多阈值太低调高 delta或增大 wait增加最小间隔 min_gapMediaPipe 检测不到人体视频分辨率过低、人物过小或遮挡严重提高输入分辨率裁剪画面让人物占比更大leg_angle 始终异常偏大人物背对镜头或侧对镜头坐标估计不稳定尽量使用正面固定机位素材增加 visibility 过滤导出的 JSON 文件很大全分辨率逐帧保存大量字段降采样间隔提取比如每 3 帧记录一次只保存动作片段运行速度慢视频分辨率过高CPU 推理跟不上先用 cv2.resize 把帧统一缩放到 640x480再送入 MediaPipe还需要提醒一个很容易忽略的问题librosa.load会默认混音为单声道如果原始舞曲带有很强的低频底鼓onset strength 可能把鼓点误判为动作重音。此时可以考虑对音频做高通滤波或者直接改用视频中的人物运动幅度作为切分依据——比如计算相邻帧之间前景的差分面积找到动作幅度激增的瞬间。这种方法在无音乐踩点视频里反而更可靠。另一个常见坑是时间戳精度。ffmpeg 的-ss参数放在-i之前会快速定位放在-i之后会精确但较慢不同版本对截取精度的处理也不同。为了防止关键动作帧被截断可以在输出切分时把每个切片的开始时间往前多留 0.3 秒结束时间往后多留 0.3 秒标注动作时再用 JSON 时间戳对齐。6. 最佳实践与工程建议6.1 数据质量优先于算法参数动作拆分的准确度瓶颈往往不在模型而在素材。现场运镜摇晃、人物被道具遮挡、服装颜色与背景接近、压缩视频出现大量马赛克都会让姿态估计结果产生剧烈抖动。建议在项目初期就把输入素材规范成三类素材类型适用场景建议固定机位正面直拍姿态评分、动作教学最佳侧面机位舞蹈视频观察三维姿态需要结合视频帧翻转处理多机位表演视频舞台记录建议先做镜头分割对画面质量不确定的视频可以先用下面这句命令把宽高统一缩放并降噪再交给后续流程ffmpeg -i input.mp4 -vf scale1280:720,eqcontrast1.1 -c:v libx264 -crf 23 output_scaled.mp46.2 关键点抖动处理MediaPipe 单帧估计容易出现小幅度抖动如果直接使用原始角度判断动作峰值会产生很多假阳性。处理思路有两种第一种是滑动窗口均值滤波把每 5 帧的 leg_angle 取平均第二种是去除跳变当连续两帧角度变化超过 30 度时认为这一帧可能跟踪失败用前后插值替换。角度平滑代码示例import numpy as np def smooth_angles(angle_list, window5): angles np.array(angle_list) kernel np.ones(window) / window smoothed np.convolve(angles, kernel, modesame) return smoothed.tolist()如果数据中混入很多 None 帧建议先把这些帧剔除或直接用前后有效帧填充避免卷积核把无效值扩散到周围。6.3 多片段批量处理的队列化实际分析一个完整编舞可能需要处理几十个片段。直接在 Python 里顺序跑不仅慢而且一旦中间某一帧process()报错整个任务就中断。比较稳妥的做法是在每处理完一个片段后立刻把 JSON 写入磁盘。每次读取视频前记录已完成片段的标识。支持断点续跑。例如在主循环外维护一个done_list.txt每处理完一个片段就往里写一行路径。下次运行时先读取这个文件跳过已完成片段。对于大型素材可以借助 Python 多进程按片段粒度并行处理每个进程只处理不同的切分片段可以显著提升吞吐量。6.4 隐私与合规提醒人体姿态数据属于个人敏感信息。如果你处理的视频包含真人面部或其他可识别特征发布分析结果前需要获得视频主人公的明确授权尤其是作为公开博文案例展示时最好对画面人脸做局部遮挡处理。同时训练动作分类模型时不应使用未授权抓取的舞蹈视频避免版权和隐私双重风险。6.5 把结果接入更上层应用当 JSON 数据铺满多个片段后可以根据业务做二次统计。比如一个分片只保留 leg_angle 历史曲线的最大值就能得到“每个时间段的腿部展开峰值”。将这些峰值与音乐时间戳对齐可以直接绘制成一张“舞蹈强度热力图”。热力图的纵轴是切分后的片段横轴是时间颜色深浅代表腿部展开幅度这样就能快速定位全片中最炸的 split dance 瞬间。后续如果再叠加动作分类模型甚至可以把“开腿”“跳跃”“转圈”“地面动作”做成标签形成完整的舞蹈动作结构图。7. 总结与学习路线从「『ch/维粹』split dance」这样一个偏标题党的输入到一套可落地的舞蹈视频拆分分析系统核心其实只有三条主线第一是音频节点检测用于知道动作大约发生什么时间第二是视频编解码与片段管理用于把长视频切成便于计算的短片段第三是姿态关键点提取与角度计算用于把动作“翻译”成数字。如果你接下来想继续往深处学习我建议按这个顺序扩展先学习 OpenCV 的视频读写与图像预处理把 ROI 裁剪、缩放、降噪搞清楚。再学习人体姿态估计的原理比如 Heatmap 回归、自顶向下与自底向上的方法MediaPipe 只是其中的工程化实现。接着接触时间序列模型例如 LSTM、TCN 或 Transformer利用处理好的人体关键点序列做动作分类。最后再接触多视角几何和 3D 姿态估计不过那已经属于更专门的领域未必适合大多数普通项目。如果只是想做舞蹈二创和短视频工具不需要把所有算法都背下来把这一套流水线跑通再针对自己的视频集调好参数就可以解决八成以上问题。真正困难的地方始终是数据清洗也就是你手上的素材是否适合程序去分析。环境准备好以后多拿几支风格不同的舞蹈视频测试多看看角度曲线和视频画面是否对齐比盲目追求模型精确率更有价值。希望这篇实战笔记能帮你少踩一些坑。在你处理自己收藏的舞蹈视频时如果遇到“切出来一堆无用片段”或者“关键点跟踪疯狂跳变”的麻烦可以回到本文的排错表格中找找对应方向。动作分析和普通的图像分类不太一样容错率更低但一旦把数据管道梳理顺了你会发现它解决的不只是单个视频的问题而是一种可以把肉眼观察转化为批量数据判断的通用能力。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进