ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Python视频逐帧分析实战:从OpenCV到OCR的预告片智能解析

Python视频逐帧分析实战:从OpenCV到OCR的预告片智能解析 1. 项目背景与需求分析最近在开发一个电影预告片分析系统时遇到了一个很有意思的技术需求如何通过程序化方式对《复仇者联盟5》这类热门电影的预告片进行逐帧解析。传统的视频分析往往停留在整体内容层面但实际业务中经常需要深入到每一帧的画面细节、文字信息、人物出场等关键元素。本文将以《复联5》预告片为例完整展示一套从视频下载、帧提取、图像分析到结果可视化的技术方案。无论你是对计算机视觉感兴趣的初学者还是需要在实际项目中应用视频分析技术的开发者都能通过本文掌握完整的实现流程。学完本文后你将能够理解视频逐帧分析的基本原理和技术栈使用Python完成预告片的自动化下载和帧提取通过OCR技术识别帧中的文字信息如角色名称、场景提示利用图像处理技术检测关键画面变化构建完整的分析流水线并输出结构化报告2. 技术选型与环境准备2.1 核心工具与库选择在开始实战之前我们需要选择合适的技术工具。基于Python的生态成熟度和社区支持我们选择以下核心库OpenCV用于视频读取、帧提取和基础图像处理PytesseractOCR文字识别提取帧中的文本信息MoviePy视频下载和预处理Pillow图像处理辅助库Matplotlib结果可视化展示2.2 环境配置详细步骤首先确保你的Python版本在3.7以上然后通过pip安装所需依赖# 创建虚拟环境推荐 python -m venv frame_analysis source frame_analysis/bin/activate # Linux/Mac # frame_analysis\Scripts\activate # Windows # 安装核心依赖 pip install opencv-python pytesseract moviepy pillow matplotlib # 安装Tesseract OCR引擎系统级安装 # Ubuntu/Debian: sudo apt-get install tesseract-ocr # Windows: 下载安装包从GitHub releases页面 # Mac: brew install tesseract2.3 项目目录结构规划建立清晰的项目结构有助于后续的代码管理和扩展avengers_frame_analysis/ ├── src/ │ ├── video_downloader.py # 视频下载模块 │ ├── frame_extractor.py # 帧提取模块 │ ├── text_recognizer.py # 文字识别模块 │ └── analysis_pipeline.py # 分析流水线 ├── data/ │ ├── raw_videos/ # 原始视频存储 │ ├── extracted_frames/ # 提取的帧图像 │ └── analysis_results/ # 分析结果 ├── config/ │ └── settings.py # 配置文件 └── requirements.txt # 依赖列表3. 视频下载与预处理实战3.1 预告片资源获取方案在实际项目中预告片来源可能有多种渠道。我们需要设计一个灵活的下载方案# src/video_downloader.py import os import requests from moviepy.editor import VideoFileClip import urllib.parse class TrailerDownloader: def __init__(self, output_dirdata/raw_videos): self.output_dir output_dir os.makedirs(output_dir, exist_okTrue) def download_from_url(self, video_url, filenameNone): 从URL下载视频文件 if filename is None: # 从URL提取文件名 parsed_url urllib.parse.urlparse(video_url) filename os.path.basename(parsed_url.path) or trailer.mp4 filepath os.path.join(self.output_dir, filename) try: # 设置合理的请求头模拟浏览器行为 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } response requests.get(video_url, headersheaders, streamTrue) response.raise_for_status() with open(filepath, wb) as f: for chunk in response.iter_content(chunk_size8192): if chunk: f.write(chunk) print(f视频下载完成: {filepath}) return filepath except Exception as e: print(f下载失败: {e}) return None def validate_video(self, filepath): 验证视频文件完整性 try: video VideoFileClip(filepath) duration video.duration video.close() print(f视频验证通过: 时长 {duration:.2f} 秒) return True except Exception as e: print(f视频文件损坏: {e}) return False # 使用示例 if __name__ __main__: downloader TrailerDownloader() # 替换为实际的预告片URL video_url https://example.com/avengers5_trailer.mp4 local_path downloader.download_from_url(video_url, avengers5_trailer.mp4) if local_path and downloader.validate_video(local_path): print(视频准备就绪可以开始帧提取)3.2 视频质量检查与格式统一不同来源的视频可能存在格式、编码不一致的问题我们需要进行标准化处理# src/video_preprocessor.py import cv2 import os from moviepy.editor import VideoFileClip class VideoPreprocessor: def __init__(self): self.supported_formats [.mp4, .avi, .mov, .mkv] def get_video_info(self, video_path): 获取视频基本信息 cap cv2.VideoCapture(video_path) if not cap.isOpened(): raise ValueError(f无法打开视频文件: {video_path}) fps cap.get(cv2.CAP_PROP_FPS) frame_count int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) width int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) duration frame_count / fps if fps 0 else 0 cap.release() return { fps: fps, frame_count: frame_count, width: width, height: height, duration: duration } def convert_format(self, input_path, output_path, target_formatmp4): 转换视频格式为标准化格式 try: video VideoFileClip(input_path) video.write_videofile(output_path, codeclibx264, audio_codecaac) video.close() print(f格式转换完成: {output_path}) return True except Exception as e: print(f格式转换失败: {e}) return False # 使用示例 preprocessor VideoPreprocessor() video_info preprocessor.get_video_info(data/raw_videos/avengers5_trailer.mp4) print(f视频信息: {video_info})4. 核心帧提取技术详解4.1 基于关键帧的智能提取方案简单的等间隔抽帧会错过重要画面我们需要更智能的提取策略# src/frame_extractor.py import cv2 import os import numpy as np from datetime import datetime class SmartFrameExtractor: def __init__(self, output_dirdata/extracted_frames): self.output_dir output_dir os.makedirs(output_dir, exist_okTrue) def extract_by_scene_change(self, video_path, threshold30.0, min_interval10): 基于场景变化检测的帧提取 cap cv2.VideoCapture(video_path) if not cap.isOpened(): raise ValueError(视频文件打开失败) prev_frame None frame_count 0 extracted_count 0 last_extracted_frame -min_interval # 获取视频fps计算时间戳 fps cap.get(cv2.CAP_PROP_FPS) extracted_frames [] while True: ret, frame cap.read() if not ret: break current_time frame_count / fps if fps 0 else frame_count # 转换为灰度图进行比较 gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) gray cv2.GaussianBlur(gray, (21, 21), 0) if prev_frame is not None: # 计算帧间差异 frame_diff cv2.absdiff(prev_frame, gray) diff_score np.mean(frame_diff) # 如果差异超过阈值且距离上次提取足够远 if diff_score threshold and (frame_count - last_extracted_frame) min_interval: timestamp f{int(current_time//60):02d}:{int(current_time%60):02d} filename fframe_{timestamp}_{frame_count:06d}.jpg filepath os.path.join(self.output_dir, filename) cv2.imwrite(filepath, frame) extracted_frames.append({ filepath: filepath, frame_number: frame_count, timestamp: current_time, diff_score: diff_score }) extracted_count 1 last_extracted_frame frame_count print(f提取关键帧: {filename} (差异分数: {diff_score:.2f})) prev_frame gray frame_count 1 cap.release() print(f总共提取 {extracted_count} 个关键帧) return extracted_frames def extract_by_time_intervals(self, video_path, interval_seconds5): 按时间间隔提取帧 cap cv2.VideoCapture(video_path) fps cap.get(cv2.CAP_PROP_FPS) interval_frames int(interval_seconds * fps) frame_count 0 extracted_count 0 extracted_frames [] while True: ret, frame cap.read() if not ret: break if frame_count % interval_frames 0: current_time frame_count / fps timestamp f{int(current_time//60):02d}:{int(current_time%60):02d} filename finterval_{timestamp}_{frame_count:06d}.jpg filepath os.path.join(self.output_dir, filename) cv2.imwrite(filepath, frame) extracted_frames.append({ filepath: filepath, frame_number: frame_count, timestamp: current_time, type: interval }) extracted_count 1 frame_count 1 cap.release() print(f按间隔提取 {extracted_count} 个帧) return extracted_frames # 使用示例 extractor SmartFrameExtractor() video_path data/raw_videos/avengers5_trailer.mp4 # 两种提取方式结合使用 key_frames extractor.extract_by_scene_change(video_path, threshold25.0) interval_frames extractor.extract_by_time_intervals(video_path, interval_seconds3) print(f总共获得 {len(key_frames) len(interval_frames)} 个分析帧)4.2 帧质量评估与筛选不是所有提取的帧都适合分析我们需要进行质量筛选# src/frame_quality.py import cv2 import numpy as np from PIL import Image, ImageStat class FrameQualityAssessor: def __init__(self): self.quality_thresholds { blur_threshold: 100.0, # 模糊度阈值 brightness_range: (30, 220), # 亮度范围 contrast_threshold: 30.0 # 对比度阈值 } def calculate_blurriness(self, image_path): 计算图像模糊度 image cv2.imread(image_path) if image is None: return float(inf) gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 使用拉普拉斯方差衡量模糊度 blur_value cv2.Laplacian(gray, cv2.CV_64F).var() return blur_value def assess_brightness_contrast(self, image_path): 评估亮度和对比度 image Image.open(image_path).convert(L) # 转换为灰度 stat ImageStat.Stat(image) brightness stat.mean[0] # 平均亮度 contrast stat.stddev[0] # 标准差作为对比度 return brightness, contrast def is_quality_frame(self, image_path): 综合质量评估 blurriness self.calculate_blurriness(image_path) brightness, contrast self.assess_brightness_contrast(image_path) # 检查各项指标 blur_ok blurriness self.quality_thresholds[blur_threshold] brightness_ok (self.quality_thresholds[brightness_range][0] brightness self.quality_thresholds[brightness_range][1]) contrast_ok contrast self.quality_thresholds[contrast_threshold] return blur_ok and brightness_ok and contrast_ok, { blurriness: blurriness, brightness: brightness, contrast: contrast } # 使用示例 quality_checker FrameQualityAssessor() # 对提取的帧进行质量筛选 quality_frames [] for frame_info in key_frames interval_frames: is_quality, metrics quality_checker.is_quality_frame(frame_info[filepath]) if is_quality: frame_info[quality_metrics] metrics quality_frames.append(frame_info) print(f质量筛选后剩余 {len(quality_frames)} 个合格帧)5. 高级图像分析技术实现5.1 基于OCR的文本信息提取预告片中的文字信息往往包含重要线索我们需要准确提取# src/text_recognizer.py import pytesseract from PIL import Image import cv2 import re class TextExtractor: def __init__(self): # 配置Tesseract参数 self.tesseract_config r--oem 3 --psm 6 -c tessedit_char_whitelistABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\.,!?\\\-\–\— : def preprocess_image_for_ocr(self, image_path): 图像预处理优化OCR识别 # 读取图像 image cv2.imread(image_path) if image is None: return None # 转换为灰度图 gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 噪声去除 denoised cv2.medianBlur(gray, 3) # 对比度增强 clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8,8)) enhanced clahe.apply(denoised) # 二值化 _, binary cv2.threshold(enhanced, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) return binary def extract_text_from_frame(self, image_path): 从单帧提取文本信息 processed_image self.preprocess_image_for_ocr(image_path) if processed_image is None: return [] # 使用PIL打开处理后的图像进行OCR pil_image Image.fromarray(processed_image) try: # 提取文本 text_data pytesseract.image_to_data(pil_image, output_typepytesseract.Output.DICT, configself.tesseract_config) # 过滤有效文本 valid_texts [] for i, text in enumerate(text_data[text]): confidence int(text_data[conf][i]) text text.strip() # 过滤低置信度和空文本 if confidence 60 and len(text) 1: # 提取边界框信息 x text_data[left][i] y text_data[top][i] w text_data[width][i] h text_data[height][i] valid_texts.append({ text: text, confidence: confidence, bbox: (x, y, w, h), position: self._classify_text_position(x, y, w, h, processed_image.shape) }) return valid_texts except Exception as e: print(fOCR处理失败: {e}) return [] def _classify_text_position(self, x, y, w, h, image_shape): 分类文本在画面中的位置 img_height, img_width image_shape center_x x w/2 center_y y h/2 # 判断位置类型 if center_y img_height * 0.2: return top elif center_y img_height * 0.8: return bottom elif center_x img_width * 0.33: return left elif center_x img_width * 0.66: return right else: return center def extract_special_keywords(self, texts): 提取特殊关键词角色名、地点等 # 定义漫威相关关键词模式 marvel_patterns { characters: r\b(iron man|captain america|thor|hulk|black widow|hawkeye|spider-man|doctor strange|black panther|ant-man|avengers)\b, locations: r\b(wakanda|asgard|new york|sokovia|titan|earth)\b, events: r\b(infinity war|endgame|quantum realm|multiverse|time travel)\b } found_keywords {} for text_info in texts: text text_info[text].lower() for category, pattern in marvel_patterns.items(): matches re.findall(pattern, text) if matches: if category not in found_keywords: found_keywords[category] [] found_keywords[category].extend(matches) return found_keywords # 使用示例 text_extractor TextExtractor() # 对每个质量合格的帧进行文本提取 for frame_info in quality_frames: texts text_extractor.extract_text_from_frame(frame_info[filepath]) frame_info[extracted_texts] texts if texts: keywords text_extractor.extract_special_keywords(texts) frame_info[keywords] keywords print(f帧 {frame_info[frame_number]} 发现文本: {[t[text] for t in texts]})5.2 视觉特征分析与场景分类除了文本信息视觉特征也能提供重要分析维度# src/visual_analyzer.py import cv2 import numpy as np from sklearn.cluster import KMeans import matplotlib.pyplot as plt class VisualFeatureAnalyzer: def __init__(self): self.scene_categories { action: {color: warm, movement: high}, dialogue: {color: balanced, movement: low}, landscape: {color: natural, movement: static}, closeup: {color: varies, movement: minimal} } def analyze_color_dominant(self, image_path, k3): 分析主色调 image cv2.imread(image_path) if image is None: return None image_rgb cv2.cvtColor(image, cv2.COLOR_BGR2RGB) pixels image_rgb.reshape(-1, 3) # 使用K-means找主色调 kmeans KMeans(n_clustersk, random_state42) kmeans.fit(pixels) # 获取主要颜色和比例 dominant_colors kmeans.cluster_centers_.astype(int) color_counts np.bincount(kmeans.labels_) color_percentages color_counts / len(pixels) return { dominant_colors: dominant_colors, percentages: color_percentages, color_temperature: self._classify_color_temperature(dominant_colors[0]) } def _classify_color_temperature(self, color): 分类颜色温度 r, g, b color if r g 30 and r b 30: return warm elif b r 30 and b g 30: return cool else: return neutral def detect_faces(self, image_path): 人脸检测识别主要角色 image cv2.imread(image_path) if image is None: return [] gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 使用OpenCV的人脸检测器 face_cascade cv2.CascadeClassifier(cv2.data.haarcascades haarcascade_frontalface_default.xml) faces face_cascade.detectMultiScale(gray, scaleFactor1.1, minNeighbors5, minSize(30, 30)) face_info [] for (x, y, w, h) in faces: # 计算人脸在画面中的比例 face_ratio (w * h) / (image.shape[0] * image.shape[1]) face_info.append({ bbox: (x, y, w, h), size_ratio: face_ratio, position: self._classify_face_position(x, y, w, h, image.shape) }) return face_info def _classify_face_position(self, x, y, w, h, image_shape): 分类人脸位置 img_height, img_width image_shape center_x x w/2 center_y y h/2 if center_x img_width / 3: horizontal left elif center_x 2 * img_width / 3: horizontal right else: horizontal center if center_y img_height / 3: vertical top elif center_y 2 * img_height / 3: vertical bottom else: vertical middle return f{vertical}-{horizontal} def estimate_scene_type(self, frame_info): 估计场景类型 color_info self.analyze_color_dominant(frame_info[filepath]) faces self.detect_faces(frame_info[filepath]) # 基于特征判断场景类型 features { face_count: len(faces), color_temperature: color_info[color_temperature] if color_info else unknown, has_large_face: any(f[size_ratio] 0.1 for f in faces), movement_level: frame_info.get(diff_score, 0) # 使用帧间差异作为运动指标 } # 简单的规则分类 if features[face_count] 2 and features[movement_level] 20: return action elif features[has_large_face] and features[movement_level] 10: return closeup elif features[face_count] 1 and 10 features[movement_level] 20: return dialogue else: return landscape # 使用示例 visual_analyzer VisualFeatureAnalyzer() for frame_info in quality_frames: # 颜色分析 color_analysis visual_analyzer.analyze_color_dominant(frame_info[filepath]) frame_info[color_analysis] color_analysis # 人脸检测 faces visual_analyzer.detect_faces(frame_info[filepath]) frame_info[face_detection] faces # 场景分类 scene_type visual_analyzer.estimate_scene_type(frame_info) frame_info[scene_type] scene_type print(f帧 {frame_info[frame_number]}: 场景类型{scene_type}, 人脸数{len(faces)})6. 完整分析流水线集成6.1 构建端到端分析系统现在我们将各个模块整合成完整的分析流水线# src/analysis_pipeline.py import json import os from datetime import datetime from video_downloader import TrailerDownloader from frame_extractor import SmartFrameExtractor from frame_quality import FrameQualityAssessor from text_recognizer import TextExtractor from visual_analyzer import VisualFeatureAnalyzer class AvengersTrailerAnalyzer: def __init__(self, project_nameavengers5_analysis): self.project_name project_name self.setup_directories() # 初始化各个组件 self.downloader TrailerDownloader() self.extractor SmartFrameExtractor() self.quality_checker FrameQualityAssessor() self.text_extractor TextExtractor() self.visual_analyzer VisualFeatureAnalyzer() self.analysis_results { project_info: { name: project_name, created_at: datetime.now().isoformat(), version: 1.0 }, video_info: {}, frame_analysis: [], summary: {} } def setup_directories(self): 创建项目目录结构 directories [ data/raw_videos, data/extracted_frames, data/analysis_results, logs ] for directory in directories: os.makedirs(directory, exist_okTrue) def run_full_analysis(self, video_urlNone, local_video_pathNone): 运行完整分析流程 print(开始《复联5》预告片分析流程...) # 步骤1: 视频准备 video_path self.prepare_video(video_url, local_video_path) if not video_path: print(视频准备失败分析终止) return False # 步骤2: 帧提取 print(正在进行帧提取...) key_frames self.extractor.extract_by_scene_change(video_path) interval_frames self.extractor.extract_by_time_intervals(video_path) all_frames key_frames interval_frames # 步骤3: 质量筛选 print(进行帧质量评估...) quality_frames [] for frame in all_frames: is_quality, metrics self.quality_checker.is_quality_frame(frame[filepath]) if is_quality: frame[quality_metrics] metrics quality_frames.append(frame) print(f质量筛选后剩余 {len(quality_frames)} 个分析帧) # 步骤4: 详细分析每个帧 print(开始详细帧分析...) for i, frame_info in enumerate(quality_frames): print(f分析进度: {i1}/{len(quality_frames)}) # 文本分析 texts self.text_extractor.extract_text_from_frame(frame_info[filepath]) frame_info[text_analysis] texts frame_info[keywords] self.text_extractor.extract_special_keywords(texts) # 视觉分析 frame_info[color_analysis] self.visual_analyzer.analyze_color_dominant(frame_info[filepath]) frame_info[face_detection] self.visual_analyzer.detect_faces(frame_info[filepath]) frame_info[scene_type] self.visual_analyzer.estimate_scene_type(frame_info) self.analysis_results[frame_analysis].append(frame_info) # 步骤5: 生成总结报告 self.generate_summary() # 步骤6: 保存结果 self.save_results() print(分析完成) return True def prepare_video(self, video_url, local_video_path): 准备视频文件 if local_video_path and os.path.exists(local_video_path): print(f使用本地视频文件: {local_video_path}) return local_video_path elif video_url: print(f从URL下载视频: {video_url}) return self.downloader.download_from_url(video_url) else: print(未提供视频来源) return None def generate_summary(self): 生成分析总结 frames self.analysis_results[frame_analysis] summary { total_frames_analyzed: len(frames), scene_type_distribution: {}, character_mentions: {}, keyword_frequency: {}, color_temperature_distribution: {}, face_detection_stats: { total_faces: 0, frames_with_faces: 0, average_faces_per_frame: 0 } } # 统计场景类型 for frame in frames: scene_type frame.get(scene_type, unknown) summary[scene_type_distribution][scene_type] summary[scene_type_distribution].get(scene_type, 0) 1 # 统计关键词 keywords frame.get(keywords, {}) for category, words in keywords.items(): for word in words: summary[keyword_frequency][word] summary[keyword_frequency].get(word, 0) 1 # 统计颜色温度 color_temp frame.get(color_analysis, {}).get(color_temperature, unknown) summary[color_temperature_distribution][color_temp] summary[color_temperature_distribution].get(color_temp, 0) 1 # 统计人脸检测 faces frame.get(face_detection, []) if faces: summary[face_detection_stats][frames_with_faces] 1 summary[face_detection_stats][total_faces] len(faces) if summary[face_detection_stats][frames_with_faces] 0: summary[face_detection_stats][average_faces_per_frame] ( summary[face_detection_stats][total_faces] / summary[face_detection_stats][frames_with_faces] ) self.analysis_results[summary] summary def save_results(self): 保存分析结果 # 保存JSON格式的详细结果 result_file fdata/analysis_results/{self.project_name}_results.json with open(result_file, w, encodingutf-8) as f: json.dump(self.analysis_results, f, indent2, ensure_asciiFalse) # 生成简明的文本报告 self.generate_text_report() print(f分析结果已保存至: {result_file}) def generate_text_report(self): 生成文本格式的分析报告 report_file fdata/analysis_results/{self.project_name}_report.txt summary self.analysis_results[summary] with open(report_file, w, encodingutf-8) as f: f.write(《复仇者联盟5》预告片逐帧分析报告\n) f.write( * 50 \n\n) f.write(f分析帧总数: {summary[total_frames_analyzed]}\n) f.write(f分析时间: {self.analysis_results[project_info][created_at]}\n\n) f.write(场景类型分布:\n) for scene_type, count in summary[scene_type_distribution].items(): percentage (count / summary[total_frames_analyzed]) * 100 f.write(f {scene_type}: {count}帧 ({percentage:.1f}%)\n) f.write(\n关键词出现频率:\n) sorted_keywords sorted(summary[keyword_frequency].items(), keylambda x: x[1], reverseTrue) for keyword, freq in sorted_keywords[:10]: # 显示前10个 f.write(f {keyword}: {freq}次\n) f.write(f\n人脸检测统计:\n) f.write(f 检测到人脸的总帧数: {summary[face_detection_stats][frames_with_faces]}\n) f.write(f 总人脸数: {summary[face_detection_stats][total_faces]}\n) f.write(f 平均每帧人脸数: {summary[face_detection_stats][average_faces_per_frame]:.2f}\n) # 使用示例 if __name__ __main__: analyzer AvengersTrailerAnalyzer(avengers5_trailer_analysis) # 使用本地视频文件或提供URL success analyzer.run_full_analysis( local_video_pathdata/raw_videos/avengers5_trailer.mp4 # video_urlhttps://example.com/trailer.mp4 # 或者使用URL ) if success: print(分析流程执行成功) # 可以在这里添加结果可视化代码6.2 结果可视化与报告生成为了让分析结果更直观我们添加可视化功能# src/visualization.py import matplotlib.pyplot as plt import seaborn as sns from matplotlib.patches import Rectangle import cv2 import json class ResultsVisualizer: def __init__(self, results_file): with open(results_file, r, encodingutf-8) as f: self.results json.load(f) # 设置中文字体支持 plt.rcParams[font.sans-serif] [SimHei, DejaVu Sans] plt.rcParams[axes.unicode_minus] False def plot_scene_distribution(self): 绘制场景类型分布图 summary self.results[summary] scene_data summary[scene_type_distribution] fig, (ax1, ax2) plt.subplots(1, 2, figsize(12, 5)) # 饼图 ax1.pie(scene_data.values(), labelsscene_data.keys(), autopct%1.1f%%) ax1.set_title(场景类型分布) # 柱状图 ax2.bar(scene_data.keys(), scene_data.values()) ax2.set_title(各场景类型帧数) ax2.set_ylabel(帧数) plt.xticks(rotation45) plt.tight_layout() plt.savefig(data/analysis_results/scene_distribution.png, dpi300, bbox_inchestight) plt.show()
返回列表