1
2
3
上传视频
AI 分析
查看结果
上传比赛视频
系统自动逐帧检测球员姿态,通过卡尔曼滤波 + De Leva 运动学模型 + 圆柱体原则,判断带球撞人 / 阻挡犯规
拖拽视频文件到此处,或点击选择文件
支持 MP4、AVI、MOV、MKV、WebM 格式,最大 200MB
技术原理与算法流程
分析流程图
开始
↓
用户上传比赛视频
↓
YOLO11-Pose 逐帧姿态估计(17个关键点)
↓
卡尔曼滤波平滑关键点轨迹
↓
攻击方 / 防守方球员分配
↓
De Leva 人体节段模型 → 重心 (CoM) 计算
↓
逐帧速度 / 加速度向量计算
↓
加速度尖峰 → 检测到碰撞?
否
回到"YOLO11-Pose 逐帧姿态估计"继续处理下一帧
是
进入判罚逻辑,提取碰撞帧上下文
↓
防守方速度投影 + 圆柱体原则 → 判罚?
v_parallel ≥ 阈值
防守方横向移动 → 阻挡犯规 (Block)
v_parallel < 阈值
防守方正对站定 → 带球撞人 (Charge)
↓
输出标注视频 + 判罚结论 JSON
流程开始时,用户上传篮球比赛视频。系统使用 YOLO11-Pose 模型逐帧提取 17 个 COCO 关键点,经卡尔曼滤波消除逐帧抖动后,通过球员空间位置分配攻击方与防守方。基于 De Leva 1996 人体节段参数模型分别计算两位球员的重心坐标和帧间速度/加速度向量。当检测到加速度尖峰(即碰撞事件)时,提取防守方速度矢量在进攻方向上的投影分量 v_parallel,结合 FIBA 圆柱体原则判断防守方是否处于合法防守位置,最终输出判罚结论并生成可视化标注视频。
技术参数与模型信息
| 姿态估计 | YOLO11-Pose (yolo11n-pose.pt),17 个 COCO 关键点 |
| 滤波算法 | 卡尔曼滤波 (Kalman Filter),逐帧平滑关键点坐标 |
| 人体模型 | De Leva 1996 节段参数模型,重心 (CoM) 计算 |
| 碰撞检测 | 加速度尖峰检测 + 滑动窗口 (默认 15 帧回看) |
| 判罚依据 | 防守方速度矢量投影 v_parallel + FIBA 圆柱体原则 |
| 后端框架 | Python FastAPI + uvicorn (ASGI) |
| 可视化 | OpenCV 骨架绘制 + 重心轨迹 + 判罚结论叠加 |
| 前端 | 原生 HTML5 + CSS3 + JavaScript (Fetch API) |
软件系统设计
- 路径自动管理:系统自动创建 uploads(视频存储)、outputs(标注视频)目录,确保文件结构规范,每次分析以唯一 video_id 隔离,处理完成后自动清理过期文件(超过 2 小时)。
- 姿态估计引擎:基于 ultralytics YOLO11-Pose 实现,支持 GPU 加速推理。模型输出 17 个 COCO 关键点 (x, y, confidence),覆盖头、肩、肘、腕、髋、膝、踝等关键关节位置。
- 卡尔曼滤波平滑:KeypointSmoother 类实现逐帧关键点卡尔曼滤波,消除 YOLO 单帧检测抖动,确保速度/加速度计算稳定。FPS 自适应的过程噪声协方差矩阵。
- 球员分配策略:基于上一帧位置进行匈牙利匹配,自动区分攻击方(持球者)与防守方,支持球员短暂遮挡后的重识别(通过运动预测继续跟踪)。
- 运动学计算:center_of_mass() 基于 De Leva 1996 模型将 17 个关键点加权合并为单一重心坐标。velocity() 计算相邻帧重心位移除以时间间隔。CollisionDetector 通过加速度幅值尖峰判定碰撞帧。
- Charge/Block 判罚:ChargeBlockJudge 在碰撞帧提取防守方速度矢量在进攻方向上的投影 v_parallel。结合圆柱体原则(防守方躯干圆柱体是否被入侵),综合判定带球撞人 (Charge) 或阻挡犯规 (Block)。
- 可视化输出:逐帧叠加攻击方/防守方骨架(不同颜色)、重心运动轨迹线、速度矢量箭头、判罚结论文字标注,输出 MP4 格式标注视频。
参数配置
| 参数 | 说明 |
|---|---|
| 姿态模型 | yolo11n-pose.pt,17 个 COCO 关键点,置信度阈值 0.5 |
| 碰撞检测阈值 | ACCELERATION_THRESHOLD,默认加速度幅值超过阈值触发碰撞 |
| 判罚速度阈值 | VELOCITY_PARALLEL_THRESHOLD,v_parallel 低于阈值判 Charge |
| 回看窗口 | COLLISION_LOOKBACK_FRAMES=15 帧,碰撞前防守方状态参考窗 |
| 轨迹保留 | MAX_TRAIL=60 帧,重心运动轨迹线的最大保留长度 |
| 上传限制 | MAX_CONTENT_LENGTH=200MB,支持 MP4/AVI/MOV/MKV/WebM 格式 |
| 任务管理 | 异步逐帧处理,前端 Fetch API POST 上传,等待完整 JSON 响应 |
| 文件清理 | 自动清理超过 2 小时的 uploads/outputs 临时文件 |
碰撞检测与 Charge/Block 判罚模块
class CollisionDetector:
"""Detect collision frame by monitoring acceleration spikes."""
def __init__(self, threshold=cfg.ACCELERATION_THRESHOLD, lookback=15):
self.threshold = threshold
self.lookback = lookback
self._history = []
def push(self, com, vel):
acc_mag = 0.0
if len(self._history) > 0:
acc = vel - self._history[-1][1]
acc_mag = np.linalg.norm(acc)
self._history.append((com.copy(), vel.copy(), acc_mag))
return acc_mag > self.threshold
class ChargeBlockJudge:
"""Judge charge vs block based on defender velocity vector."""
def judge(self, attacker_com, defender_com, defender_vel,
attacker_vel, attacker_chest, defender_shoulder_l,
defender_shoulder_r):
# v_parallel: defender's velocity projected onto attack direction
attack_dir = attacker_com - defender_com
attack_dir_norm = attack_dir / np.linalg.norm(attack_dir)
v_parallel = np.dot(defender_vel, attack_dir_norm)
# Cylinder principle: is attacker's chest inside defender's torso?
cyl_intrusion = self._check_cylinder(
attacker_chest, defender_shoulder_l, defender_shoulder_r)
if v_parallel < cfg.VELOCITY_PARALLEL_THRESHOLD or cyl_intrusion:
return {"verdict": "charge", ...}
else:
return {"verdict": "block", ...}
运动学分析模块
# De Leva 1996 人体节段参数 — 各节段占体重百分比
SEGMENT_WEIGHTS = {
"head_neck": 0.0694, "trunk": 0.4346,
"upper_arm": 0.0271, "forearm": 0.0162, "hand": 0.0061,
"thigh": 0.1416, "shank": 0.0433, "foot": 0.0137,
}
def center_of_mass(kps):
"""Calculate center of mass from 17 COCO keypoints."""
com = np.zeros(2)
total_weight = 0.0
for seg_name, (i, j, w) in SEGMENT_MAPPING.items():
mid = (kps[i, :2] + kps[j, :2]) / 2
weight = SEGMENT_WEIGHTS[seg_name]
com += mid * weight
total_weight += weight
return com / total_weight
def velocity(com_current, com_prev, fps):
"""Calculate velocity vector between two consecutive frames."""
return (com_current - com_prev) * fps # pixels/second