ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python基于录屏的LOLM玩家偏好状态自动统计分析

2026/9/27 4:59:44 拓冰建站 浏览量
Python基于录屏的LOLM玩家偏好状态自动统计分析 基于录屏的LOLM玩家偏好状态自动统计分析一、技术选型与整体架构从大量LOLM录屏视频中自动统计玩家偏好核心任务是从视频帧中识别关键游戏状态信息英雄选择、装备、技能、位置等按时间序列聚合后分析偏好模式。推荐技术栈· 视频处理OpenCV逐帧抽取、ROI裁剪· OCR识别PaddleOCR对中文游戏界面的识别效果优于EasyOCR和Tesseract且支持自定义模型· 数据分析Pandas Matplotlib· 状态判定基于HUD区域检测 规则引擎整体流水线为视频 → 关键帧抽取 → ROI裁剪 → OCR/视觉识别 → 结构化数据 → 时间序列分析 → 偏好报告。二、完整代码实现2.1 依赖安装bashpip install opencv-python paddleocr pandas matplotlib numpy2.2 核心代码pythonLOLM 录屏玩家偏好状态统计分析工具从大量对局录屏中按时间顺序自动提取指定玩家的偏好状态import cv2import osimport jsonimport refrom datetime import datetime, timedeltafrom collections import Counter, defaultdictfrom dataclasses import dataclass, field, asdictfrom typing import Optionalimport numpy as npimport pandas as pdfrom paddleocr import PaddleOCR# 配置 dataclassclass LOLMConfig:LOLM 界面区域配置基于 1920x1080 录屏需根据实际分辨率调整# 玩家信息区域左上角英雄头像名称player_name_roi: tuple (80, 30, 420, 100)# 装备栏区域底部中央items_roi: tuple (620, 900, 1300, 1000)# 技能栏区域底部右侧skills_roi: tuple (1350, 850, 1800, 1000)# 金币/经济区域gold_roi: tuple (100, 850, 400, 950)# 小地图区域右上角或左下角按实际布局调整minimap_roi: tuple (1550, 50, 1900, 400)# 击杀/死亡/助攻区域kda_roi: tuple (80, 100, 350, 180)# 时间戳区域右上角timer_roi: tuple (1650, 10, 1900, 60)dataclassclass PlayerState:单帧中提取的玩家状态timestamp: float # 视频内秒数hero_name: str # 英雄名称items: list field(default_factorylist) # 装备列表skill_order: list field(default_factorylist) # 技能加点顺序gold: int 0kills: int 0deaths: int 0assists: int 0position: tuple (0, 0) # 小地图坐标# 视频关键帧抽取 class VideoFrameExtractor:从录屏中按固定间隔抽取关键帧def __init__(self, video_path: str, sample_interval_sec: float 2.0):self.video_path video_pathself.interval sample_interval_secself.cap cv2.VideoCapture(video_path)if not self.cap.isOpened():raise IOError(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 self.total_frames / self.fps if self.fps 0 else 0def extract_keyframes(self):生成器按间隔逐帧返回 (时间戳秒, 帧图像)step max(1, int(self.fps * self.interval))for frame_idx in range(0, self.total_frames, step):self.cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)ret, frame self.cap.read()if not ret:breaktimestamp frame_idx / self.fpsyield timestamp, framedef release(self):self.cap.release()# OCR 识别模块 class LOLMOCR:封装 PaddleOCR针对 LOLM 界面做预处理和结果后处理def __init__(self, config: LOLMConfig, use_gpu: bool False):self.config config# 使用中英文混合模型self.ocr PaddleOCR(use_angle_clsTrue,langch,use_gpuuse_gpu,show_logFalse,det_db_thresh0.3, # 降低检测阈值适应游戏小字det_db_box_thresh0.5,)staticmethoddef _crop(frame: np.ndarray, roi: tuple) - np.ndarray:从帧中裁剪 ROI 区域x1, y1, x2, y2 roih, w frame.shape[:2]# 边界保护x1, y1 max(0, x1), max(0, y1)x2, y2 min(w, x2), min(h, y2)return frame[y1:y2, x1:x2]staticmethoddef _preprocess(img: np.ndarray) - np.ndarray:增强对比度提升 OCR 对游戏半透明背景的适应性if len(img.shape) 3:gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)else:gray img# 自适应直方图均衡clahe cv2.createCLAHE(clipLimit3.0, tileGridSize(8, 8))enhanced clahe.apply(gray)# 放大以提升小字识别率enhanced cv2.resize(enhanced, None, fx2.0, fy2.0,interpolationcv2.INTER_CUBIC)return enhanceddef recognize_roi(self, frame: np.ndarray, roi: tuple) - list:识别指定 ROI 内的文字返回 [(文本, 置信度), ...]cropped self._crop(frame, roi)if cropped.size 0:return []processed self._preprocess(cropped)try:result self.ocr.ocr(processed, clsTrue)except Exception:return []texts []if result and result[0]:for line in result[0]:text line[1][0].strip()conf line[1][1]if text and conf 0.6: # 过滤低置信度结果texts.append((text, conf))return textsdef extract_player_name(self, frame: np.ndarray) - str:从玩家信息区域提取英雄/玩家名称texts self.recognize_roi(frame, self.config.player_name_roi)# 后处理过滤掉纯数字、UI关键词for text, _ in texts:cleaned re.sub(r[^\u4e00-\u9fa5a-zA-Z], , text)if len(cleaned) 2:return cleanedreturn def extract_items(self, frame: np.ndarray) - list:提取装备栏中已购装备texts self.recognize_roi(frame, self.config.items_roi)items []for text, _ in texts:cleaned text.strip()# 装备名通常为中文或英文if re.match(r^[\u4e00-\u9fa5a-zA-Z]{2,}$, cleaned):items.append(cleaned)return itemsdef extract_gold(self, frame: np.ndarray) - int:提取当前金币数texts self.recognize_roi(frame, self.config.gold_roi)for text, _ in texts:digits re.sub(r[^\d], , text)if digits:return int(digits)return 0def extract_kda(self, frame: np.ndarray) - tuple:提取 K/D/Atexts self.recognize_roi(frame, self.config.kda_roi)nums []for text, _ in texts:found re.findall(r\d, text)nums.extend(int(n) for n in found)if len(nums) 3:return nums[0], nums[1], nums[2]return 0, 0, 0# 对局状态提取器 class MatchStateExtractor:从单个对局录屏中提取按时间排序的玩家状态序列def __init__(self, video_path: str, config: LOLMConfig,ocr: LOLMOCR, sample_interval: float 2.0):self.video_path video_pathself.config configself.ocr ocrself.extractor VideoFrameExtractor(video_path, sample_interval)self.states: list[PlayerState] []def run(self) - list[PlayerState]:逐关键帧提取状态带去重逻辑prev_hero prev_items []for timestamp, frame in self.extractor.extract_keyframes():state PlayerState(timestamptimestamp)# 英雄名只在变化时更新减少OCR调用hero self.ocr.extract_player_name(frame)if hero:prev_hero herostate.hero_name prev_hero# 装备去重items self.ocr.extract_items(frame)if items:prev_items itemsstate.items list(prev_items)# 金币与 KDAstate.gold self.ocr.extract_gold(frame)k, d, a self.ocr.extract_kda(frame)state.kills, state.deaths, state.assists k, d, aself.states.append(state)self.extractor.release()return self.states# 偏好分析引擎 class PreferenceAnalyzer:对多个对局的状态序列进行时间维度的偏好分析def __init__(self, all_match_states: dict[str, list[PlayerState]]):all_match_states: {视频文件名: [PlayerState, ...]}self.match_data all_match_statesdef hero_preference(self) - pd.DataFrame:英雄使用频率与胜率关联分析hero_counter Counter()hero_match_count defaultdict(int)for match_id, states in self.match_data.items():heroes_in_match set()for s in states:if s.hero_name:hero_counter[s.hero_name] 1heroes_in_match.add(s.hero_name)for h in heroes_in_match:hero_match_count[h] 1# 汇总为 DataFramerows []for hero, count in hero_counter.most_common():rows.append({英雄: hero,出现帧数: count,覆盖对局数: hero_match_count[hero],使用占比: round(count / sum(hero_counter.values()) * 100, 1)})return pd.DataFrame(rows)def item_preference(self) - pd.DataFrame:统计装备选择偏好item_counter Counter()first_item_counter Counter() # 首件装备核心出装思路for match_id, states in self.match_data.items():first_items_recorded Falsefor s in states:for item in s.items:item_counter[item] 1# 记录该局第一次出现装备的时刻if s.items and not first_items_recorded:for item in s.items:first_item_counter[item] 1first_items_recorded Truerows []for item, count in item_counter.most_common(20):rows.append({装备: item,总出现次数: count,作为首件的次数: first_item_counter.get(item, 0),})return pd.DataFrame(rows)def gold_curve(self) - pd.DataFrame:按对局时间归一化后的金币曲线反映经济偏好节奏# 以对局前 15 分钟为窗口按 1 分钟聚合curve_data defaultdict(list)for match_id, states in self.match_data.items():for s in states:minute int(s.timestamp // 60)if minute 15 and s.gold 0:curve_data[minute].append(s.gold)rows []for minute in sorted(curve_data.keys()):golds curve_data[minute]rows.append({分钟: minute,平均金币: round(np.mean(golds), 0),中位金币: round(np.median(golds), 0),样本数: len(golds),})return pd.DataFrame(rows)def item_timing_preference(self) - pd.DataFrame:分析装备成型速度偏好统计关键装备首次出现的时间item_first_seen defaultdict(list)for match_id, states in self.match_data.items():seen_in_match {}for s in states:for item in s.items:if item not in seen_in_match:seen_in_match[item] s.timestampfor item, t in seen_in_match.items():item_first_seen[item].append(t)rows []for item, times in item_first_seen.items():if len(times) 2: # 至少两局出现才统计rows.append({装备: item,平均首次出现时间(秒): round(np.mean(times), 1),标准差: round(np.std(times), 1),样本对局数: len(times),})df pd.DataFrame(rows)if not df.empty:df df.sort_values(平均首次出现时间(秒))return dfdef generate_report(self, output_dir: str ./analysis_output):生成完整分析报告os.makedirs(output_dir, exist_okTrue)hero_df self.hero_preference()item_df self.item_preference()gold_df self.gold_curve()timing_df self.item_timing_preference()hero_df.to_csv(f{output_dir}/hero_preference.csv,indexFalse, encodingutf-8-sig)item_df.to_csv(f{output_dir}/item_preference.csv,indexFalse, encodingutf-8-sig)gold_df.to_csv(f{output_dir}/gold_curve.csv,indexFalse, encodingutf-8-sig)timing_df.to_csv(f{output_dir}/item_timing_preference.csv,indexFalse, encodingutf-8-sig)print(\n * 60)print( LOLM 玩家偏好状态分析报告)print( * 60)print(\n【英雄使用偏好 TOP10】)print(hero_df.head(10).to_string(indexFalse) if not hero_df.emptyelse 无数据)print(\n【装备选择偏好 TOP10】)print(item_df.head(10).to_string(indexFalse) if not item_df.emptyelse 无数据)print(\n【经济曲线前15分钟平均金币】)print(gold_df.to_string(indexFalse) if not gold_df.emptyelse 无数据)print(\n【装备成型速度首次出现时间排序】)print(timing_df.head(15).to_string(indexFalse) if not timing_df.emptyelse 无数据)return {hero: hero_df,items: item_df,gold: gold_df,timing: timing_df,}# 主流程 def analyze_videos(video_dir: str, output_dir: str ./analysis_output):批量分析目录下的所有 LOLM 录屏参数:video_dir: 录屏文件所在目录output_dir: 分析结果输出目录config LOLMConfig()ocr LOLMOCR(config, use_gpuFalse)video_extensions (.mp4, .avi, .mkv, .mov, .flv, .webm)video_files sorted([f for f in os.listdir(video_dir)if f.lower().endswith(video_extensions)])if not video_files:print(f在 {video_dir} 中未找到视频文件)returnprint(f发现 {len(video_files)} 个视频文件开始分析...\n)all_match_states {}for i, vf in enumerate(video_files, 1):path os.path.join(video_dir, vf)print(f[{i}/{len(video_files)}] 处理: {vf})try:extractor MatchStateExtractor(path, config, ocr, sample_interval3.0)states extractor.run()if states:all_match_states[vf] statesprint(f → 提取到 {len(states)} 个状态帧)else:print(f → 未提取到有效状态)except Exception as e:print(f → 错误: {e})if all_match_states:analyzer PreferenceAnalyzer(all_match_states)report analyzer.generate_report(output_dir)print(f\n完整报告已保存至: {output_dir})return reportelse:print(没有成功提取到任何对局数据)if __name__ __main__:import sysif len(sys.argv) 2:analyze_videos(sys.argv[1], sys.argv[2] if len(sys.argv) 2else ./analysis_output)else:print(用法: python lolm_analyzer.py 录屏目录 [输出目录])## 三、关键实现细节说明**ROI 坐标校准**LOLMConfig 中的区域坐标基于 1920×1080 横屏录屏估算实际使用时必须先截取一帧画面用 cv2.selectROI 交互式确定各区域坐标再写入配置。不同手机比例16:9 / 20:9和录屏分辨率720p / 1080p的坐标差异很大。**OCR 精度优化**游戏 HUD 文字通常有描边和半透明背景直接 OCR 容易误读。代码中的 _preprocess 采用 CLAHE 增强 2倍放大能显著提升小字识别率。如果效果仍不理想可以用 Umi-OCR 的思路先配置“忽略区域”排除非目标文本如队友信息、击杀播报只保留玩家自身数据区域的识别结果。**去重与时间序列**装备和英雄名变化频率低代码在 MatchStateExtractor.run 中做了“只在变化时更新”的去重避免每帧都调用 OCR。KDA 和金币则逐帧更新因为它们是连续变化的数值。**偏好分析维度**目前实现了四个维度——英雄偏好频率、装备偏好频率首件、经济曲线时间序列均值、装备成型速度首件出现时间。如果需要分析“对线期偏好 vs 团战期偏好”可以在 PlayerState 中增加小地图坐标字段用 k-means 聚类位置热区。## 四、局限与改进方向这套方案依赖屏幕 OCR精度受录屏质量影响较大。更可靠的路线是直接解析 LOLM 的录像文件类似 PC 端 .rofl 格式从中提取结构化的游戏事件数据但 LOLM 的录像格式目前没有公开的解析工具需要逆向工程[reference:2]。另一个改进方向是引入轻量目标检测模型如 YOLO-nano识别小地图上的英雄图标位置结合 OCR 结果做更精确的位置偏好分析这在 PC 端 LoL 的 league-of-legends-replay-extractor 项目中已有成熟实践。禁止未经允许的一切转载商用。