ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

乒乓球比赛视频分析系统实战:检测、追踪、姿态估计与API封装

乒乓球比赛视频分析系统实战:检测、追踪、姿态估计与API封装 横滨冠军赛打完张本智和夺冠采访里那句“这是我爸妈的胜利”在各平台刷屏语言表达和家庭故事成为讨论点。讨论归讨论换个角度看一场国际乒乓球比赛从现场转播、实时比分、回放集锦到赛后采访的跨语言分发背后是一条完整的音视频和 AI 技术链路。这篇文章不追热点而是把这条链路里最核心、也最容易自己动手验证的一个环节拿出来做一次完整实战搭建一个乒乓球比赛视频分析系统覆盖乒乓球检测追踪、运动员姿态识别、击球回合统计、结果导出再用 FastAPI 封装成接口跑通批量任务。文末会补一个赛后采访的语音转写与字幕导出扩展把“技术含量”落到赛事内容生产场景。整个 Demo 不依赖专业摄影棚和昂贵传感器一台普通电脑就能起步。CPU 也能跑离线分析实时多路或大批量处理时再考虑上 NVIDIA GPU。本文会给出从环境准备、代码实现、功能验证到 API 封装的完整流程所有代码都按模块拆分可以直接套用到自己的训练视频或比赛录像上。如果你正准备做体育视频分析、乒乓球训练辅助工具、赛事数据统计或者只是想把一段采访视频做成多语言字幕这篇内容可以直接收藏按章节往下走即可。1. 核心能力速览能力项说明系统类型乒乓球比赛视频分析与赛事内容生产辅助系统自建 Demo核心功能乒乓球目标检测、球轨迹跟踪、运动员姿态估计、击球/过网回合粗略统计、赛后采访语音转写与字幕导出技术栈Python、OpenCV、MediaPipe、FastAPI、Uvicorn可选 Whisper硬件要求CPU 可运行离线分析实时多路或大规模批量推荐 NVIDIA GPU需安装 CUDA 环境显存占用取决于检测模型与输入分辨率实际占用需以本机运行 nvidia-smi 或任务管理器观察为准支持平台Windows / Linux / macOS启动方式命令行脚本运行、Python 函数调用、FastAPI HTTP 接口是否支持 API支持可上传视频并返回结构化 JSON是否支持批量任务支持遍历输入目录批量分析并输出结果文件适合场景乒乓球训练复盘、比赛战术统计、体育内容生产、教学视频辅助标注需要提前说明本文不是某个现成开源仓库的完整复制而是按通用技术流程给出一个可落地的骨架。实际使用中需要根据自己的视频素材、机位角度和灯光条件调整参数。2. 适用场景与使用边界先说适合谁。教练和运动员是最直接的使用方。训练时用一台手机或摄像机固定机位拍摄课后用这套流程把整场训练视频过一遍可以快速看到每一次球的轨迹片段、击球节奏和回合持续时间比手动剪辑看录像高效很多。赛事运营和体育媒体也能用上。比赛结束后系统可以把整场比赛视频拆成多个片段并给出回合起止帧再结合语音转写把赛后采访音频直接生成字幕文件分发到短视频平台前只做简单校对生产速度会快很多。教学和科研场景同样适用。体育课程里用视频分析讲解动作和战术实验室里把轨迹数据导出为 JSON后续做更细的旋转、速度、落点建模。但也有明显的边界。这套方案的核心定位是训练辅助和内容辅助不能替代正式比赛中的鹰眼判罚。乒乓球高速旋转、擦边球、擦网球普通摄像头帧率不够时根本抓不清这类场景需要专用高速相机和光学标定系统。本文的回合统计是“原型精度”做训练复盘够用做裁判决策并不够。另外视频里出现人脸、声音、赛事画面时要确认素材来源是否合法、是否取得必要授权涉及未成年人或他人隐私的内容尤其要注意脱敏处理。3. 环境准备与前置条件开始之前先把运行环境理清。3.1 语言与依赖推荐使用 Python 3.10 版本。OpenCV、MediaPipe、FastAPI 这些库在 3.10 上的兼容性比较稳定。核心依赖如下opencv-python mediapipe numpy fastapi uvicorn pandas python-multipart如果要做赛后采访转写再额外装openai-whisperWhisper 是一个开源语音识别工具安装时会自动拉取 PyTorch。如果你只跑比赛分析可以先不装避免环境过重。3.2 硬件与系统系统支持 Windows、Linux、macOS三个平台都能跑。纯 CPU 推理可以跑通全部功能但视频解析速度和帧率会受限于 CPU 性能。处理 1080p 视频时建议先把画面降到 1280 宽度再分析。NVIDIA GPU 推理有独显时建议直接走 GPU。安装显卡驱动后再用 pip 安装对应版本的 PyTorch接着跑python -c import torch; print(torch.cuda.is_available())输出 True 表示 GPU 可用。显存不够时不需要慌离线单视频分析时把分辨率降到 960 或 1280 宽度大部分中端显卡都能顺利跑完。多路并发或高分辨率实时推理才需要更高显存。3.3 视频素材准备准备一段横屏拍摄的乒乓球比赛或训练视频固定机位、光线稳定、画面里包含完整球台效果最好。测试阶段建议先用 1 分钟以内的小片段跑通后再处理长视频。建议项目目录结构如下pingpong_analyzer/ ├── main.py ├── detector.py ├── tracker.py ├── pose_estimator.py ├── analyzer.py ├── batch_process.py ├── api_service.py ├── interview_asr.py ├── requirements.txt └── data/ ├── input/ # 比赛视频 └── output/ # 结果 JSON / 标注视频4. 安装部署与启动方式4.1 创建虚拟环境并安装依赖Windows 使用 PowerShellLinux/macOS 使用终端mkdir pingpong_analyzer cd pingpong_analyzer python -m venv venv # Windows venv\Scripts\activate # Linux / macOS source venv/bin/activate把 requirements.txt 放到项目目录后执行pip install -r requirements.txt如果安装 mediapipe 或 opencv 的速度很慢可以用国内镜像源加速例如pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple4.2 编写乒乓球检测模块新建 detector.py实现最基本的乒乓球检测。这里用 HSV 颜色阈值来筛选白色或橙色球体再通过轮廓检测取半径和圆心。# detector.py import cv2 import numpy as np def find_ball(frame): 输入 BGR 帧返回 (x, y, radius) 或 None。 hsv cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # 白色球 lower_white np.array([0, 0, 180]) upper_white np.array([179, 40, 255]) mask_white cv2.inRange(hsv, lower_white, upper_white) # 橙色球 lower_orange np.array([5, 100, 100]) upper_orange np.array([20, 255, 255]) mask_orange cv2.inRange(hsv, lower_orange, upper_orange) mask cv2.bitwise_or(mask_white, mask_orange) mask cv2.medianBlur(mask, 5) contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None best max(contours, keycv2.contourArea) area cv2.contourArea(best) if area 20: return None (x, y), radius cv2.minEnclosingCircle(best) return int(x), int(y), int(radius)这段代码最关键的是 HSV 阈值。现场灯光偏暖或偏冷时白色球可能会被过滤掉需要把lower_white的 V 值调低或者把H范围放宽。没有一套阈值能适配所有场地第一次跑新素材时先调整这里。4.3 编写轨迹跟踪模块只检测单帧还不够球在高速移动时会有大量断帧。这里用一个简单的轨迹缓冲类把连续的球位置串起来超过一定帧数没检测到球就清空轨迹。# tracker.py class BallTracker: def __init__(self, max_miss5, max_track90): self.max_miss max_miss self.max_track max_track self.miss 0 self.trajectory [] def update(self, ball): if ball is None: self.miss 1 if self.miss self.max_miss: self.trajectory.clear() return None self.miss 0 self.trajectory.append(ball) if len(self.trajectory) self.max_track: self.trajectory.pop(0) return self.trajectorymax_miss控制轨迹中断的容忍度。乒乓球速度极快普通 30fps 视频里一帧跨度很大建议设置为 3 到 8。max_track控制单条轨迹最多保留多少帧避免内存膨胀。4.4 启动基础分析脚本新建 main.py先跑一个最小可用的命令行程式# main.py import argparse from analyzer import process_video from batch_process import batch_process if __name__ __main__: parser argparse.ArgumentParser(descriptionPingPong video analyzer) parser.add_argument(--video, typestr) parser.add_argument(--input-dir, typestr) parser.add_argument(--output-dir, typestr, default./data/output) parser.add_argument(--json, typestr) parser.add_argument(--show, actionstore_true) args parser.parse_args() if args.video: stats process_video(args.video, output_jsonargs.json, showargs.show) print(ftotal_frames{stats[total_frames]}, ball_segments{stats[ball_segments]}) elif args.input_dir: batch_process(args.input_dir, args.output_dir) else: parser.print_help()启动方式python main.py --video ./data/input/train01.mp4 --json ./data/output/train01.json --show加上--show可以在弹窗里实时看到检测结果调试阈值时非常方便。第一次跑如果发现球框不住先停下来调 HSV 阈值不要急着跑长视频。5. 功能测试与效果验证这里拆成几个独立功能测试每一块都能单独验证。5.1 乒乓球检测与轨迹片段在 analyzer.py 里实现process_video把单帧检测和轨迹缓冲串起来同时切出“球出现到球消失”的片段。# analyzer.py import json import cv2 from detector import find_ball from tracker import BallTracker FIXED_WIDTH 1280 def process_video(video_path, output_jsonNone, showFalse): cap cv2.VideoCapture(video_path) if not cap.isOpened(): raise RuntimeError(fcan not open video: {video_path}) tracker BallTracker(max_miss5, max_track90) segments [] current None frame_index 0 while True: ok, frame cap.read() if not ok: break h, w frame.shape[:2] frame cv2.resize(frame, (FIXED_WIDTH, int(h * FIXED_WIDTH / w))) ball find_ball(frame) tracker.update(ball) if ball is None: if current is not None and tracker.miss 5: current[end_frame] frame_index current[duration_frames] current[end_frame] - current[start_frame] segments.append(current) current None else: if current is None: current { start_frame: frame_index, end_frame: frame_index, points: [], } current[points].append((ball[0], ball[1])) if show: show_frame frame.copy() if ball: cv2.circle(show_frame, (ball[0], ball[1]), ball[2], (0, 0, 255), 2) cv2.imshow(analysis, show_frame) if cv2.waitKey(1) 0xFF ord(q): break frame_index 1 cap.release() if current is not None: current[end_frame] frame_index segments.append(current) stats { total_frames: frame_index, ball_segments: len(segments), segments: segments, } if output_json: with open(output_json, w, encodingutf-8) as f: json.dump(stats, f, ensure_asciiFalse, indent2) return stats测试方法用 30 秒短视频运行--show。观察弹窗中的红色圆圈是否稳定套住乒乓球。如果球只出现几帧就丢失先看 HSV 阈值再看max_miss是否太小。跑完后检查 JSON 里的ball_segments数量。一段对拉训练理论上应该能切出多个球片段。判断成功的标准同一个球从发球到落台能被连续追踪超过 5 帧且片段起止位置与实际画面大致对应。常见失败原因问题现象可能原因球完全检测不到灯光反光、球颜色接近背景、HSV 阈值范围太窄轨迹中间断裂球速太快、镜头抖动、max_miss 太小检测到大量无关小物体背景里有白色纸片、广告牌面积阈值太低5.2 击球与过网回合统计有了轨迹后可以做一个粗略的“击球事件”识别。原理是检测球运动方向的反转当球被球拍击回时运动向量会突然反转。这个思路不完美但作为训练复盘原型够用。# analyzer.py 增加一个辅助函数 import math def detect_hits(trajectory, reverse_threshold0.35): 根据运动方向反转粗略统计击球点。 threshold 越小越容易识别为一次击球。 hits [] if len(trajectory) 3: return hits last_vec None for i in range(1, len(trajectory)): vx trajectory[i][0] - trajectory[i - 1][0] vy trajectory[i][1] - trajectory[i - 1][1] norm math.hypot(vx, vy) if norm 1e-6: continue vec (vx / norm, vy / norm) if last_vec is not None: dot vec[0] * last_vec[0] vec[1] * last_vec[1] if dot -reverse_threshold: hits.append(i) last_vec vec return hits调用时把segments里的points传进来返回的hits就是候选击球点索引。要进一步提升准确率可以加两个约束击球点必须靠近球台中线区域。相邻击球点之间的间隔不能太短因为同一侧连续触球不符合规则。这个模块只能在固定机位、画面完整覆盖球台的情况下使用。如果是跟拍镜头或者俯拍角度需要重新设计过网判断逻辑。5.3 运动员姿态估计用 MediaPipe 可以快速画出运动员骨架帮助复盘动作和站位。# pose_estimator.py import cv2 import mediapipe as mp mp_pose mp.solutions.pose pose mp_pose.Pose( static_image_modeFalse, model_complexity1, min_detection_confidence0.5, min_tracking_confidence0.5, ) def draw_pose(frame): rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results pose.process(rgb) if results.pose_landmarks: mp.solutions.drawing_utils.draw_landmarks( frame, results.pose_landmarks, mp_pose.POSE_CONNECTIONS, ) return frame在process_video的只读循环里把画好骨架的帧写到一个新视频文件就能得到一份带动作标注的训练视频。MediaPipe 后续版本对 POSE 接口可能会有废弃提示不影响 Demo 运行算法思路是一致的。用 CPU 跑姿态估计时1080p 视频建议先降到 1280 宽度再每隔一帧检测一次速度会明显提升。如果要做实时姿态反馈才需要 GPU 加速。5.4 赛后采访语音转写与字幕导出这一节直接回应开头提到的“夺冠采访”。把采访音频或视频丢给 Whisper可以得到带时间戳的文本再导出成 SRT 字幕。# interview_asr.py import whisper def format_timestamp(seconds): ms int(round(seconds * 1000)) h, ms divmod(ms, 3600000) m,
返回列表