import json import numpy as np import subprocess import os from collections import defaultdict import matplotlib.pyplot as plt import seaborn as sns from tqdm import tqdm import scipy.stats jsonl_list = [ "./metadata_wan_fps24.jsonl" ] def get_video_dimensions(video_path): """使用ffmpeg获取视频的宽度和高度""" try: # 构建ffmpeg命令来获取视频信息 cmd = [ 'ffprobe', '-v', 'quiet', '-print_format', 'json', '-show_streams', video_path ] result = subprocess.run(cmd, capture_output=True, text=True, check=True) video_info = json.loads(result.stdout) # 查找视频流 for stream in video_info['streams']: if stream['codec_type'] == 'video': width = int(stream['width']) height = int(stream['height']) return width, height except (subprocess.CalledProcessError, json.JSONDecodeError, KeyError) as e: print(f"获取视频尺寸失败 {video_path}: {e}") return None, None return None, None def read_face_bbox( bboxs_path, h, w, video_length = None, start_idx = None, end_idx = None, bbox_type = "xywh", ): face_mask_start = None face_mask_end = None face_center = None bboxs = None bbox_infos = None if bboxs_path is not None: bboxs = np.load(bboxs_path) if start_idx is not None and end_idx is not None: # 计算视频选取的帧数 video_frames = end_idx - start_idx # 将视频的起点和终点映射到bbox序列 if len(bboxs) == 1: # 如果只有一个bbox,起点和终点都用这个 bbox_start_idx = 0 bbox_end_idx = 0 else: # 均匀映射:将视频起点终点映射到bbox序列 bbox_start_idx = int(start_idx * (len(bboxs) - 1) / (video_length - 1)) if video_length > 1 else 0 bbox_end_idx = int(end_idx * (len(bboxs) - 1) / (video_length - 1)) if video_length > 1 else 0 bbox_start_idx = min(bbox_start_idx, len(bboxs) - 1) bbox_end_idx = min(bbox_end_idx, len(bboxs) - 1) # 获取序列中所有相关帧的bbox relevant_start_idx = 0 relevant_end_idx = len(bboxs) - 1 # 提取相关的bbox序列 relevant_bboxs = bboxs[relevant_start_idx:relevant_end_idx + 1] # 使用高效的方式计算全局边界(并集) global_x_min = relevant_bboxs[:, 0].min() global_y_min = relevant_bboxs[:, 1].min() if bbox_type == "xywh": global_x_max = (relevant_bboxs[:, 2] + relevant_bboxs[:, 0]).max() global_y_max = (relevant_bboxs[:, 3] + relevant_bboxs[:, 1]).max() elif bbox_type == "xxyy": global_x_max = relevant_bboxs[:, 2].max() global_y_max = relevant_bboxs[:, 3].max() # 不对全局bbox进行扩展 global_width = global_x_max - global_x_min global_height = global_y_max - global_y_min global_center_x = (global_x_min + global_x_max) / 2 global_center_y = (global_y_min + global_y_max) / 2 # 计算全局bbox global_x_min = max(0, global_center_x - global_width / 2) global_x_max = min(w, global_center_x + global_width / 2) global_y_min = max(0, global_center_y - global_height / 2) global_y_max = min(h, global_center_y + global_height / 2) # 创建全局bbox信息 global_face_center = [(global_x_min + global_x_max)/2, (global_y_min + global_y_max)/2] global_bbox_info = { 'center': [global_face_center[0] / w, global_face_center[1] / h], # 相对坐标 'width': (global_x_max - global_x_min) / w, # 相对宽度 'height': (global_y_max - global_y_min) / h, # 相对高度 'bbox': [global_x_min/w, global_y_min/h, global_x_max/w, global_y_max/h] # 相对bbox } return bboxs, bbox_infos def plot_probability_density_distributions(all_widths, all_heights, all_areas, all_relative_widths, all_relative_heights, all_relative_areas): """Plot probability density distributions""" # Create figure fig, axes = plt.subplots(2, 3, figsize=(18, 12)) fig.suptitle('BBox Probability Density Distribution Analysis', fontsize=16, fontweight='bold') # 1. Absolute size distributions # Width distribution axes[0, 0].hist(all_widths, bins=50, density=True, alpha=0.7, color='skyblue', edgecolor='black') kde_x = np.linspace(min(all_widths), max(all_widths), 1000) kde = scipy.stats.gaussian_kde(all_widths) axes[0, 0].plot(kde_x, kde(kde_x), 'r-', linewidth=2, label='KDE') axes[0, 0].set_title('Absolute Width Distribution') axes[0, 0].set_xlabel('Width (pixels)') axes[0, 0].set_ylabel('Probability Density') axes[0, 0].legend() axes[0, 0].grid(True, alpha=0.3) # Height distribution axes[0, 1].hist(all_heights, bins=50, density=True, alpha=0.7, color='lightgreen', edgecolor='black') kde_x = np.linspace(min(all_heights), max(all_heights), 1000) kde = scipy.stats.gaussian_kde(all_heights) axes[0, 1].plot(kde_x, kde(kde_x), 'r-', linewidth=2, label='KDE') axes[0, 1].set_title('Absolute Height Distribution') axes[0, 1].set_xlabel('Height (pixels)') axes[0, 1].set_ylabel('Probability Density') axes[0, 1].legend() axes[0, 1].grid(True, alpha=0.3) # Area distribution axes[0, 2].hist(all_areas, bins=50, density=True, alpha=0.7, color='orange', edgecolor='black') kde_x = np.linspace(min(all_areas), max(all_areas), 1000) kde = scipy.stats.gaussian_kde(all_areas) axes[0, 2].plot(kde_x, kde(kde_x), 'r-', linewidth=2, label='KDE') axes[0, 2].set_title('Absolute Area Distribution') axes[0, 2].set_xlabel('Area (pixels²)') axes[0, 2].set_ylabel('Probability Density') axes[0, 2].legend() axes[0, 2].grid(True, alpha=0.3) # 2. Relative size distributions # Relative width distribution axes[1, 0].hist(all_relative_widths, bins=50, density=True, alpha=0.7, color='lightcoral', edgecolor='black') kde_x = np.linspace(min(all_relative_widths), max(all_relative_widths), 1000) kde = scipy.stats.gaussian_kde(all_relative_widths) axes[1, 0].plot(kde_x, kde(kde_x), 'r-', linewidth=2, label='KDE') axes[1, 0].set_title('Relative Width Distribution') axes[1, 0].set_xlabel('Relative Width (ratio)') axes[1, 0].set_ylabel('Probability Density') axes[1, 0].legend() axes[1, 0].grid(True, alpha=0.3) # Relative height distribution axes[1, 1].hist(all_relative_heights, bins=50, density=True, alpha=0.7, color='plum', edgecolor='black') kde_x = np.linspace(min(all_relative_heights), max(all_relative_heights), 1000) kde = scipy.stats.gaussian_kde(all_relative_heights) axes[1, 1].plot(kde_x, kde(kde_x), 'r-', linewidth=2, label='KDE') axes[1, 1].set_title('Relative Height Distribution') axes[1, 1].set_xlabel('Relative Height (ratio)') axes[1, 1].set_ylabel('Probability Density') axes[1, 1].legend() axes[1, 1].grid(True, alpha=0.3) # Relative area distribution axes[1, 2].hist(all_relative_areas, bins=50, density=True, alpha=0.7, color='gold', edgecolor='black') kde_x = np.linspace(min(all_relative_areas), max(all_relative_areas), 1000) kde = scipy.stats.gaussian_kde(all_relative_areas) axes[1, 2].plot(kde_x, kde(kde_x), 'r-', linewidth=2, label='KDE') axes[1, 2].set_title('Relative Area Distribution') axes[1, 2].set_xlabel('Relative Area (ratio)') axes[1, 2].set_ylabel('Probability Density') axes[1, 2].legend() axes[1, 2].grid(True, alpha=0.3) plt.tight_layout() plt.savefig('bbox_probability_density_distributions.png', dpi=300, bbox_inches='tight') plt.show() def analyze_bbox_distribution(): """分析所有jsonl文件中bbox的分布情况""" all_widths = [] all_heights = [] all_areas = [] all_relative_widths = [] all_relative_heights = [] all_relative_areas = [] total_processed = 0 total_errors = 0 for jsonl_path in tqdm(jsonl_list, desc="处理数据集文件"): if not os.path.exists(jsonl_path): print(f"文件不存在: {jsonl_path}") continue # 先计算文件行数 with open(jsonl_path, 'r') as f: total_lines = sum(1 for _ in f) with open(jsonl_path, 'r') as f: for line_num, line in tqdm(enumerate(f, 1), total=total_lines, desc="处理行", leave=False): try: data = json.loads(line.strip()) # 获取视频路径和bbox路径 video_path = data.get('video') bboxs_path = data.get('bboxs') width = data.get('width') height = data.get('height') if not all([video_path, bboxs_path]): continue # 如果jsonl中没有width/height信息,使用ffmpeg获取 if width is None or height is None: full_video_path = os.path.join(os.path.dirname(jsonl_path), video_path) width, height = get_video_dimensions(full_video_path) if width is None or height is None: print(f"无法获取视频尺寸: {full_video_path}") total_errors += 1 continue # 加载bbox数据 full_bbox_path = os.path.join(os.path.dirname(jsonl_path), bboxs_path) if not os.path.exists(full_bbox_path): print(f"bbox文件不存在: {full_bbox_path}") total_errors += 1 continue bboxs = np.load(full_bbox_path) # 计算每个bbox的统计信息 for bbox in bboxs: if len(bbox) >= 4: x, y, w_bbox, h_bbox = bbox[:4] # 绝对尺寸(像素) abs_width = w_bbox abs_height = h_bbox abs_area = abs_width * abs_height # 相对尺寸(占图像的比例) rel_width = abs_width / width rel_height = abs_height / height rel_area = rel_width * rel_height # 添加到全局统计 all_widths.append(abs_width) all_heights.append(abs_height) all_areas.append(abs_area) all_relative_widths.append(rel_width) all_relative_heights.append(rel_height) all_relative_areas.append(rel_area) total_processed += 1 except json.JSONDecodeError as e: print(f"JSON解析错误 {jsonl_path}:{line_num}: {e}") total_errors += 1 except Exception as e: print(f"处理错误 {jsonl_path}:{line_num}: {e}") total_errors += 1 # 打印统计结果 print(f"\n=== 总体统计 ===") print(f"总处理样本数: {total_processed}") print(f"总错误数: {total_errors}") print(f"总bbox数: {len(all_widths)}") if all_widths: print(f"\n=== 绝对尺寸统计(像素) ===") print(f"宽度 - 均值: {np.mean(all_widths):.2f}, 中位数: {np.median(all_widths):.2f}, 标准差: {np.std(all_widths):.2f}") print(f"高度 - 均值: {np.mean(all_heights):.2f}, 中位数: {np.median(all_heights):.2f}, 标准差: {np.std(all_heights):.2f}") print(f"面积 - 均值: {np.mean(all_areas):.2f}, 中位数: {np.median(all_areas):.2f}, 标准差: {np.std(all_areas):.2f}") print(f"\n=== 相对尺寸统计(占图像比例) ===") print(f"相对宽度 - 均值: {np.mean(all_relative_widths):.4f}, 中位数: {np.median(all_relative_widths):.4f}, 标准差: {np.std(all_relative_widths):.4f}") print(f"相对高度 - 均值: {np.mean(all_relative_heights):.4f}, 中位数: {np.median(all_relative_heights):.4f}, 标准差: {np.std(all_relative_heights):.4f}") print(f"相对面积 - 均值: {np.mean(all_relative_areas):.6f}, 中位数: {np.median(all_relative_areas):.6f}, 标准差: {np.std(all_relative_areas):.6f}") # 绘制概率密度分布图 print(f"\n=== 绘制概率密度分布图 ===") if all_widths: plot_probability_density_distributions(all_widths, all_heights, all_areas, all_relative_widths, all_relative_heights, all_relative_areas) # 保存统计结果 results = { 'total_samples': total_processed, 'total_errors': total_errors, 'total_bboxes': len(all_widths), 'absolute_stats': { 'widths': {'mean': float(np.mean(all_widths)), 'median': float(np.median(all_widths)), 'std': float(np.std(all_widths))}, 'heights': {'mean': float(np.mean(all_heights)), 'median': float(np.median(all_heights)), 'std': float(np.std(all_heights))}, 'areas': {'mean': float(np.mean(all_areas)), 'median': float(np.median(all_areas)), 'std': float(np.std(all_areas))} }, 'relative_stats': { 'widths': {'mean': float(np.mean(all_relative_widths)), 'median': float(np.median(all_relative_widths)), 'std': float(np.std(all_relative_widths))}, 'heights': {'mean': float(np.mean(all_relative_heights)), 'median': float(np.median(all_relative_heights)), 'std': float(np.std(all_relative_heights))}, 'areas': {'mean': float(np.mean(all_relative_areas)), 'median': float(np.median(all_relative_areas)), 'std': float(np.std(all_relative_areas))} } } print(f"\n保存统计结果...") with open('bbox_distribution_stats.json', 'w', encoding='utf-8') as f: json.dump(results, f, indent=2, ensure_ascii=False) print(f"统计结果已保存到: bbox_distribution_stats.json") print(f"概率密度分布图已保存到: bbox_probability_density_distributions.png") if __name__ == "__main__": # 运行完整的分析(包括概率密度分布图) analyze_bbox_distribution()