基于录屏的LOLM关键数据与优劣势转折点自动分析系统架构整个系统的处理管线可以概括为录屏视频 → 帧提取 → 屏幕区域裁剪 → OCR/目标检测识别 → 数据序列化 → 转折点检测 → 报告输出核心思路是在每一帧或按固定间隔抽帧中从LOLM固定UI区域提取经济、击杀等数值形成随时间变化的数据序列再通过突变检测算法识别优势劣势的转折时刻。这类“视频→YOLO检测→事件跟踪→OCR过滤→时间戳”的管线已被实际项目验证可行。完整代码实现以下代码依赖opencv-python、paddleocr、ultralytics、numpy。安装bashpip install opencv-python paddleocr ultralytics numpypythonLOLM 录屏自动分析工具从录屏中提取关键数据并检测优势/劣势转折点import cv2import numpy as npfrom dataclasses import dataclass, fieldfrom typing import List, Optional, Tuplefrom pathlib import Path# 数据结构定义 dataclassclass GameSnapshot:某一时刻的游戏状态快照timestamp_sec: float # 视频中的时间秒blue_gold: Optional[int] Nonered_gold: Optional[int] Noneblue_kills: Optional[int] Nonered_kills: Optional[int] Noneblue_towers: Optional[int] Nonered_towers: Optional[int] Nonepropertydef gold_diff(self) - Optional[int]:经济差蓝方 - 红方正数表示蓝方优势if self.blue_gold is not None and self.red_gold is not None:return self.blue_gold - self.red_goldreturn Nonepropertydef kill_diff(self) - Optional[int]:if self.blue_kills is not None and self.red_kills is not None:return self.blue_kills - self.red_killsreturn Nonedataclassclass TurningPoint:优势/劣势转折点timestamp_sec: floatevent_type: str # gold_swing / kill_swing / tower_swingmagnitude: float # 变化幅度绝对值direction: str # blue_advantage / red_advantagedescription: str# 屏幕区域配置 class ScreenRegions:LOLM 屏幕各UI区域的坐标配置。坐标系为归一化坐标 (0~1)适配不同分辨率。重要不同手机、不同游戏版本的UI布局有差异以下坐标需要根据实际录屏画面进行校准。校准方法截取一帧画面用画图工具量取各区域的像素位置再除以画面宽高得到归一化坐标。# 顶部计分板击杀数、经济显示区域SCOREBOARD {blue_kills: (0.38, 0.01, 0.47, 0.05),red_kills: (0.53, 0.01, 0.62, 0.05),blue_gold: (0.38, 0.06, 0.47, 0.10),red_gold: (0.53, 0.06, 0.62, 0.10),}# 小地图区域MINIMAP (0.01, 0.60, 0.28, 0.99)# 防御塔计数通常在计分板两侧TOWER_AREA {blue_towers: (0.30, 0.01, 0.38, 0.05),red_towers: (0.62, 0.01, 0.70, 0.05),}classmethoddef get_pixel_region(cls, norm_region: Tuple, frame_w: int, frame_h: int):将归一化坐标转为像素坐标 (x1, y1, x2, y2)x1 int(norm_region[0] * frame_w)y1 int(norm_region[1] * frame_h)x2 int(norm_region[2] * frame_w)y2 int(norm_region[3] * frame_h)return (x1, y1, x2, y2)# 帧提取与OCR识别 class FrameExtractor:从录屏视频中按固定间隔提取帧def __init__(self, video_path: str, interval_sec: float 2.0):Args:video_path: 录屏文件路径interval_sec: 抽帧间隔秒LOLM数据变化不需要逐帧分析self.video_path video_pathself.interval_sec interval_secself.cap cv2.VideoCapture(video_path)if not self.cap.isOpened():raise FileNotFoundError(f无法打开视频: {video_path})self.fps self.cap.get(cv2.CAP_PROP_FPS)self.total_frames int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))self.duration_sec self.total_frames / self.fps if self.fps 0 else 0self.frame_w int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))self.frame_h int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))def extract_at(self, time_sec: float) - Optional[np.ndarray]:提取指定时间点的帧frame_no int(time_sec * self.fps)self.cap.set(cv2.CAP_PROP_POS_FRAMES, frame_no)ret, frame self.cap.read()return frame if ret else Nonedef iter_frames(self):按间隔迭代所有帧t 0.0while t self.duration_sec:frame self.extract_at(t)if frame is not None:yield t, framet self.interval_secdef release(self):self.cap.release()class GameOCR:使用 PaddleOCR 识别屏幕上的数值。PaddleOCR 在游戏界面识别中表现稳定对半透明UI和特效干扰有一定鲁棒性。def __init__(self, lang: str ch):from paddleocr import PaddleOCRself.ocr PaddleOCR(use_angle_clsFalse,langlang,show_logFalse,use_gpuFalse,)def read_number(self, image_region: np.ndarray) - Optional[int]:从图像区域中读取一个整数识别失败返回 Noneif image_region is None or image_region.size 0:return None# 预处理灰度化 放大 二值化提高小文字识别率gray cv2.cvtColor(image_region, cv2.COLOR_BGR2GRAY)gray cv2.resize(gray, None, fx3, fy3,interpolationcv2.INTER_CUBIC)_, binary cv2.threshold(gray, 0, 255,cv2.THRESH_BINARY cv2.THRESH_OTSU)result self.ocr.ocr(binary, clsFalse)if not result or not result[0]:return None# 从识别结果中提取数字for line in result[0]:text line[1][0].strip()# 过滤非数字字符处理 k 后缀如 12.5kcleaned text.replace(,, ).replace( , )if cleaned.endswith(k) or cleaned.endswith(K):try:return int(float(cleaned[:-1]) * 1000)except ValueError:continuetry:return int(cleaned)except ValueError:continuereturn None# 数据采集主逻辑 class LOLMDataCollector:从录屏中逐帧采集游戏数据def __init__(self, video_path: str,interval_sec: float 2.0,use_ocr: bool True):self.extractor FrameExtractor(video_path, interval_sec)self.ocr GameOCR() if use_ocr else Noneself.snapshots: List[GameSnapshot] []def collect(self) - List[GameSnapshot]:执行数据采集返回时间序列快照列表w, h self.extractor.frame_w, self.extractor.frame_hregions ScreenRegions()for t, frame in self.extractor.iter_frames():snap GameSnapshot(timestamp_secround(t, 1))# 读取顶部计分板中的击杀数和经济for key, norm_box in regions.SCOREBOARD.items():px ScreenRegions.get_pixel_region(norm_box, w, h)crop frame[px[1]:px[3], px[0]:px[2]]if self.ocr:val self.ocr.read_number(crop)if val is not None:setattr(snap, key, val)# 读取防御塔计数for key, norm_box in regions.TOWER_AREA.items():px ScreenRegions.get_pixel_region(norm_box, w, h)crop frame[px[1]:px[3], px[0]:px[2]]if self.ocr:val self.ocr.read_number(crop)if val is not None:setattr(snap, key, val)self.snapshots.append(snap)self.extractor.release()return self.snapshots# 转折点检测 class TurningPointDetector:基于数据序列的突变检测来识别优势/劣势转折点。核心算法参考 LoL-MDC 的思路计算相邻时间窗口之间指标变化量 Δ变化量超过阈值的时刻即为关键转折点。def __init__(self,gold_swing_threshold: float 3000,kill_swing_threshold: int 3,window_sec: float 60.0):Args:gold_swing_threshold: 经济差变化超过此值判定为转折kill_swing_threshold: 击杀差变化超过此值判定为转折window_sec: 滑动窗口大小秒self.gold_threshold gold_swing_thresholdself.kill_threshold kill_swing_thresholdself.window_sec window_secdef detect(self, snapshots: List[GameSnapshot]) - List[TurningPoint]:检测所有转折点points []points.extend(self._detect_gold_swings(snapshots))points.extend(self._detect_kill_swings(snapshots))# 按时间排序points.sort(keylambda p: p.timestamp_sec)return pointsdef _detect_gold_swings(self,snapshots: List[GameSnapshot]) - List[TurningPoint]:检测经济差突变points []valid [(s.timestamp_sec, s.gold_diff)for s in snapshots if s.gold_diff is not None]if len(valid) 2:return pointsfor i in range(1, len(valid)):t_prev, diff_prev valid[i - 1]t_curr, diff_curr valid[i]delta diff_curr - diff_previf abs(delta) self.gold_threshold:direction (blue_advantage if delta 0else red_advantage)# 判断是反超还是扩大优势crossed (diff_prev * diff_curr 0) # 符号相反反超label 经济反超 if crossed else 经济差剧变side 蓝方 if delta 0 else 红方points.append(TurningPoint(timestamp_sect_curr,event_typegold_swing,magnitudeabs(delta),directiondirection,description(f第 {int(t_curr // 60)}:{int(t_curr % 60):02d} f{label}{side}经济差变化 {delta:,d} f(当前经济差 {diff_curr:,d}))))return pointsdef _detect_kill_swings(self,snapshots: List[GameSnapshot]) - List[TurningPoint]:检测击杀差突变points []valid [(s.timestamp_sec, s.kill_diff)for s in snapshots if s.kill_diff is not None]if len(valid) 2:return pointsfor i in range(1, len(valid)):t_prev, diff_prev valid[i - 1]t_curr, diff_curr valid[i]delta diff_curr - diff_previf abs(delta) self.kill_threshold:side 蓝方 if delta 0 else 红方points.append(TurningPoint(timestamp_sect_curr,event_typekill_swing,magnitudeabs(delta),direction(blue_advantage if delta 0else red_advantage),description(f第 {int(t_curr // 60)}:{int(t_curr % 60):02d} f击杀差突变{side}连获 {abs(delta)} 个人头)))return points# 报告输出 def print_report(snapshots: List[GameSnapshot],turning_points: List[TurningPoint],video_duration: float):打印分析报告print( * 60)print( LOLM 对局分析报告)print( * 60)# 1. 对局概况print(f\n 对局概况)print(f 视频时长: {int(video_duration // 60)} 分 f{int(video_duration % 60)} 秒)print(f 采集快照数: {len(snapshots)})valid_gold [s for s in snapshots if s.gold_diff is not None]if valid_gold:diffs [s.gold_diff for s in valid_gold]max_blue max(diffs)max_red min(diffs)final diffs[-1]print(f 蓝方最大经济领先: {max_blue:,d})print(f 红方最大经济领先: {max_red:,d})print(f 最终经济差: {final:,d} f({蓝方 if final 0 else 红方 if final 0 else 持平}))# 2. 转折点列表print(f\n⚡ 优势/劣势转折点 ({len(turning_points)} 个))if turning_points:for i, tp in enumerate(turning_points, 1):print(f [{i}] {tp.description})else:print( 本局未检测到明显的优势/劣势转折)# 3. 经济差时间线摘要if valid_gold:print(f\n 经济差变化时间线每30秒采样)sample_interval 30last_t -sample_intervalfor s in valid_gold:if s.timestamp_sec - last_t sample_interval:bar_len min(abs(s.gold_diff) // 500, 30)if s.gold_diff 0:bar * 15 █ * bar_len f 蓝{s.gold_diff}else:bar * max(0, 15 - bar_len) █ * bar_lenbar f 红{s.gold_diff}print(f {int(s.timestamp_sec // 60):02d}:f{int(s.timestamp_sec % 60):02d} {bar})last_t s.timestamp_secprint(\n * 60)# 入口 def analyze_lolm_recording(video_path: str,interval_sec: float 2.0,gold_threshold: float 3000,kill_threshold: int 3):主入口分析LOLM录屏输出关键数据与转折点。Args:video_path: 录屏文件路径interval_sec: 抽帧间隔秒gold_threshold: 经济差突变阈值kill_threshold: 击杀差突变阈值print(f正在加载视频: {video_path})# Step 1: 数据采集collector LOLMDataCollector(video_path, interval_secinterval_sec, use_ocrTrue)snapshots collector.collect()print(f采集完成共 {len(snapshots)} 个时间点)# Step 2: 转折点检测detector TurningPointDetector(gold_swing_thresholdgold_threshold,kill_swing_thresholdkill_threshold,)turning_points detector.detect(snapshots)# Step 3: 输出报告print_report(snapshots, turning_points,collector.extractor.duration_sec)return snapshots, turning_pointsif __name__ __main__:import sysif len(sys.argv) 2:print(用法: python lolm_analyzer.py 录屏文件路径)sys.exit(1)analyze_lolm_recording(video_pathsys.argv[1],interval_sec2.0,gold_threshold3000,kill_threshold3,)关键说明屏幕区域校准最重要的一步ScreenRegions 中的归一化坐标是模板值不同设备、不同游戏版本、不同UI设置下布局会不同。例如LOLM的操作界面本身就比端游更密集四技能加双召唤师技能使布局有较大差异。你需要1. 用 cv2.imread 打开一帧截图在画图工具中量取目标区域的像素坐标2. 将像素坐标除以画面宽高得到 (x1/W, y1/H, x2/W, y2/H)3. 更新 SCOREBOARD 和 TOWER_AREA 中的值OCR精度优化游戏中特效、半透明UI容易导致识别错误实践中常结合YOLO与PaddleOCR提升鲁棒性——YOLO负责定位UI元素位置OCR只对裁剪出的干净区域做文字识别。如果当前OCR精度不够建议· 先用YOLO训练一个小模型检测计分板区域比固定坐标更稳健· 对OCR预处理加入形态学操作去除细小噪点· 对识别结果做时序平滑连续帧取中位数转折点检测算法核心逻辑来自LoL-MDC的关键事件提取算法对每个事件计算其前后的胜率变化 Δ按 Δ 降序取Top-N作为关键事件。本代码中经济差和击杀差的变化量扮演了类似“胜率变化”的角色。你可以进一步用逻辑回归模型基于经济差、击杀差、推塔数训练一个简易的“蓝方胜率预测器”用胜率曲线的拐点来定位转折点会比单纯阈值判定更准确。扩展方向· 小地图目标检测用YOLO检测小地图上的英雄位置可以分析团战发生时的站位优劣。已有研究表明合成数据预训练真实回放数据微调的迁移学习方案在小地图英雄检测上可达到0.588 mAP· 事件过滤层类似NiceShot AI的做法用OCR识别“回放中”“观战中”等状态文字过滤掉非实时对局的帧· 转折点视频片段导出检测到转折点后用FFmpeg截取前后30秒的片段方便复盘禁止商用转载