【SOD】数据集分析——目标框的位置以及大小的分布

目标框的位置以及大小的分布

方法步骤:

数据集统一化:
和yolo训练和推理的预处理一样,等比例缩放 + 边缘填充,换算标签值。

中心点分布:

  • 按 8×8 网格(每格 80×80像素)统计每个网格的目标数及占比(占比 = 网格数 / 总目标数);
  • 分别打印出两个8乘8的矩阵,目标数的和目标数占比的。
  • 用占比的值绘制热力图,热力图可视化,标注占比(<1% 时特殊显示),保留两位小数。

框大小分布:

  • 计算 640×640 中的归一化面积((w_640/640)×(h_640/640)),绘制直方图(等频分箱,10~20 箱);
  • 计算 640×640 中的归一化对角线长度(√((w_640/640)² + (h_640/640)² )),绘制直方图(同前分箱方式)。
  • 两个图都是横坐标从0到最大值等分为10份(变量记录方便修改)
  • 打印出两个的各个统计量(均值,方差,中位数等等)

分析结果说明

两数据集参与统计的样本情况

testB-3000,DUT-Anti-UAV-train
参数相同:

    sampling_step=1,       # 步长
    grid_size=8,           # 8×8网格
    bin_num=10,            # 直方图分箱数
    save_plots='./DUT_plot',   # 保存图片到plot目录,False表示不保存仅显示
    hist_xlim=[0, 0.03]     # 直方图横坐标范围,这里设置为0到0.2
testB的数据集信息:
  原始样本数:3000
  原始样本中的目标数量:3719
  抽样后样本数:3000
  抽样后的目标数量:3719
  抽样步长:1
  图像文件夹:C:\baidunetdiskdownload\testB3000-new\images
  标签文件夹:C:\baidunetdiskdownload\testB3000-new\labels
处理完成:
  总目标数:3719

DUT的数据集信息:
  原始样本数:5197
  原始样本中的目标数量:5243
  抽样后样本数:5197
  抽样后的目标数量:5243
  抽样步长:1
  图像文件夹:/mnt/Virgil/YOLO/drone_dataset/DUT-Anti-UAV-train/images
  标签文件夹:/mnt/Virgil/YOLO/drone_dataset/DUT-Anti-UAV-train/labels
处理完成:
  总目标数:5243

目标位置分布对比

testB:
在这里插入图片描述
DUT
在这里插入图片描述

都集中在中心,并且上下边缘都没有目标。testB的可能因为数据量少中心有些偏左上角。

具体目标数矩阵:

中心点分布矩阵(目标数):
testB:
[[  0   0   0   0   0   0   0   0]
 [  0   1   3   4   2   0   2   1]
 [ 10  35  79 140 117  46  14   3]
 [ 22 110 347 636 419 108  22  11]
 [ 35 115 267 414 311  95  28  17]
 [ 14  37  60  78  63  31   4   0]
 [  1   4   2   5   5   1   0   0]
 [  0   0   0   0   0   0   0   0]]


DUT:
[[  0   0   0   0   0   0   0   0]
 [  3   1   7   8   6   5   1   1]
 [ 24  46 101 134 160  97  47  23]
 [ 42 145 268 527 583 359 136  47]
 [ 52 142 279 489 565 349 137  45]
 [  8  31  60  85 105  70  27  14]
 [  2   0   3   2   3   1   2   1]
 [  0   0   0   0   0   0   0   0]]



目标框大小分布对比[0, 0.03]

testB
在这里插入图片描述
DUT
在这里插入图片描述

详细统计信息

testB
框面积统计量:
  均值:0.003754
  方差:0.000057
  中位数:0.001302
  最小值:0.000027
  最大值:0.194579

框对角线统计量:
  均值:0.079778
  方差:0.003986
  中位数:0.059560
  最小值:0.008240
  最大值:0.730294


DUT
框面积统计量:
  均值:0.007510
  方差:0.001106
  中位数:0.000265
  最小值:0.000011
  最大值:0.528570

框对角线统计量:
  均值:0.063983
  方差:0.013848
  中位数:0.025542
  最小值:0.004914
  最大值:1.049200

代码

import os
import random
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from PIL import Image
import seaborn as sns

# 设置中文显示
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

class YOLODatasetAnalyzer:
    def __init__(self, image_dir, label_dir, sampling_step=1, grid_size=8, bin_num=10, 
                 save_plots=False, hist_xlim=None):
        """
        初始化YOLO数据集分析器
        
        Args:
            image_dir: 图像文件夹路径
            label_dir: 标签文件夹路径
            sampling_step: 抽样步长,例如每4个样本取1个
            grid_size: 中心点分布热力图的网格大小
            bin_num: 直方图的分箱数量
            save_plots: 是否保存可视化图片,False表示不保存仅显示,字符串表示保存目录
            hist_xlim: 直方图横坐标范围,格式为[min, max],None表示自动适应
        """
        self.image_dir = image_dir
        self.label_dir = label_dir
        self.sampling_step = sampling_step
        self.grid_size = grid_size
        self.bin_num = bin_num
        self.save_plots = save_plots
        self.hist_xlim = hist_xlim if hist_xlim is not None else [0, None]  # 默认为0到最大值
        self.image_files = []
        self.label_files = []
        self.sample_image_files = []
        self.sample_label_files = []
        self.total_objects = 0
        self.sample_total_objects = 0
        self.center_distribution = np.zeros((grid_size, grid_size), dtype=int)
        self.box_areas = []
        self.box_diagonals = []
        
    def prepare_dataset(self):
        """准备数据集,获取图像和标签文件列表并进行抽样"""
        # 获取所有图像和标签文件
        original_total_objects = 0
        for file in os.listdir(self.image_dir):
            if file.lower().endswith(('.png', '.jpg', '.jpeg')):
                image_path = os.path.join(self.image_dir, file)
                label_file = os.path.splitext(file)[0] + '.txt'
                label_path = os.path.join(self.label_dir, label_file)
                
                # 检查标签文件是否存在
                if os.path.exists(label_path):
                    # 计算该标签文件中的目标数量
                    with open(label_path, 'r') as f:
                        lines = f.readlines()
                        object_count = len(lines)
                        original_total_objects += object_count
                    
                    self.image_files.append(image_path)
                    self.label_files.append(label_path)
        
        # 随机分层抽样
        if self.sampling_step > 1:
            indices = list(range(0, len(self.image_files), self.sampling_step))
            random.shuffle(indices)
            self.sample_image_files = [self.image_files[i] for i in indices]
            self.sample_label_files = [self.label_files[i] for i in indices]
        else:
            self.sample_image_files = self.image_files
            self.sample_label_files = self.label_files
        
        # 计算抽样后的目标数量
        for label_file in self.sample_label_files:
            with open(label_file, 'r') as f:
                self.sample_total_objects += len(f.readlines())
                
        print(f"数据集信息:")
        print(f"  原始样本数:{len(self.image_files)}")
        print(f"  原始样本中的目标数量:{original_total_objects}")
        print(f"  抽样后样本数:{len(self.sample_image_files)}")
        print(f"  抽样后的目标数量:{self.sample_total_objects}")
        print(f"  抽样步长:{self.sampling_step}")
        print(f"  图像文件夹:{self.image_dir}")
        print(f"  标签文件夹:{self.label_dir}")
        
        # 创建保存图片的目录
        if isinstance(self.save_plots, str):
            os.makedirs(self.save_plots, exist_ok=True)
            
    def process_labels(self):
        """处理标签,计算中心点分布和框大小"""
        for image_file, label_file in zip(self.sample_image_files, self.sample_label_files):
            # 获取图像尺寸
            try:
                img = Image.open(image_file)
                img_width, img_height = img.size
            except Exception as e:
                print(f"无法读取图像 {image_file}: {e}")
                continue
                
            # 读取标签
            with open(label_file, 'r') as f:
                lines = f.readlines()
                
            for line in lines:
                parts = line.strip().split()
                if len(parts) < 5:  # 确保标签格式正确
                    continue
                    
                # 解析标签
                class_id = int(parts[0])
                x_center_norm = float(parts[1])
                y_center_norm = float(parts[2])
                width_norm = float(parts[3])
                height_norm = float(parts[4])
                
                # 计算预处理后的坐标(等比例缩放+填充到640×640)
                scale = min(640/img_width, 640/img_height)
                new_width = img_width * scale
                new_height = img_height * scale
                pad_w = (640 - new_width) / 2
                pad_h = (640 - new_height) / 2
                
                # 转换为640×640图像中的像素坐标
                x_center_640 = x_center_norm * img_width * scale + pad_w
                y_center_640 = y_center_norm * img_height * scale + pad_h
                width_640 = width_norm * img_width * scale
                height_640 = height_norm * img_height * scale
                
                # 更新中心点分布
                grid_x = int(x_center_640 // (640 / self.grid_size))
                grid_y = int(y_center_640 // (640 / self.grid_size))
                grid_x = min(grid_x, self.grid_size - 1)  # 防止越界
                grid_y = min(grid_y, self.grid_size - 1)
                self.center_distribution[grid_y, grid_x] += 1  # 注意matplotlib中y轴向下
                self.total_objects += 1
                
                # 计算归一化面积和对角线长度
                norm_width = width_640 / 640
                norm_height = height_640 / 640
                area = norm_width * norm_height
                diagonal = np.sqrt(norm_width**2 + norm_height**2)
                
                self.box_areas.append(area)
                self.box_diagonals.append(diagonal)
                
        print(f"处理完成:")
        print(f"  总目标数:{self.total_objects}")
        
    def analyze_center_distribution(self):
        """分析中心点分布并可视化"""
        if self.total_objects == 0:
            print("没有可分析的目标")
            return
            
        # 计算占比
        center_percentage = self.center_distribution / self.total_objects * 100
        
        # 打印矩阵
        print("\n中心点分布矩阵(目标数):")
        print(self.center_distribution)
        
        print("\n中心点分布矩阵(占比%):")
        print(np.round(center_percentage, 2))
        
        # 创建自定义颜色映射
        colors = [(0.9, 0.9, 1), (0, 0, 0.7)]  # 从浅蓝色到深蓝色
        cmap = LinearSegmentedColormap.from_list('BlueCustom', colors, N=100)
        
        # 绘制热力图
        plt.figure(figsize=(10, 8))
        ax = sns.heatmap(center_percentage, annot=True, fmt='.2f', cmap=cmap, 
                         cbar=True, square=True, linewidths=.5, 
                         xticklabels=range(1, self.grid_size+1), 
                         yticklabels=range(1, self.grid_size+1))
        
        # 处理<1%的情况
        for i in range(self.grid_size):
            for j in range(self.grid_size):
                if center_percentage[i, j] < 1 and center_percentage[i, j] > 0:
                    ax.text(j+0.5, i+0.5, '<1%', ha='center', va='center', color='white')
        
        plt.title('中心点分布热力图(占比%)')
        plt.xlabel('网格X坐标')
        plt.ylabel('网格Y坐标')
        plt.tight_layout()
        
        # 保存图片
        if isinstance(self.save_plots, str):
            save_path = os.path.join(self.save_plots, 'center_distribution_heatmap.png')
            plt.savefig(save_path, dpi=300, bbox_inches='tight')
        
        # 显示图片
        plt.show()
        
    def analyze_box_size(self):
        """分析框大小并可视化"""
        if not self.box_areas:
            print("没有可分析的框")
            return
            
        # 计算统计量
        area_stats = {
            '均值': np.mean(self.box_areas),
            '方差': np.var(self.box_areas),
            '中位数': np.median(self.box_areas),
            '最小值': np.min(self.box_areas),
            '最大值': np.max(self.box_areas)
        }
        
        diagonal_stats = {
            '均值': np.mean(self.box_diagonals),
            '方差': np.var(self.box_diagonals),
            '中位数': np.median(self.box_diagonals),
            '最小值': np.min(self.box_diagonals),
            '最大值': np.max(self.box_diagonals)
        }
        
        # 打印统计量
        print("\n框面积统计量:")
        for key, value in area_stats.items():
            print(f"  {key}{value:.6f}")
            
        print("\n框对角线统计量:")
        for key, value in diagonal_stats.items():
            print(f"  {key}{value:.6f}")
        
        # 确定直方图横坐标范围
        area_min, area_max = self.hist_xlim[0], self.hist_xlim[1] or max(self.box_areas)
        diag_min, diag_max = self.hist_xlim[0], self.hist_xlim[1] or max(self.box_diagonals)
        
        # 绘制直方图
        fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
        
        # 面积直方图
        _, bins, _ = ax1.hist(self.box_areas, bins=self.bin_num, range=(area_min, area_max),
                             color='skyblue', edgecolor='black')
        ax1.set_title('归一化面积分布')
        ax1.set_xlabel('归一化面积')
        ax1.set_ylabel('目标数量')
        ax1.set_xlim(area_min, area_max)
        ax1.grid(axis='y', linestyle='--', alpha=0.7)
        
        # 添加数据标签
        for i in range(len(bins)-1):
            count = sum(1 for x in self.box_areas if bins[i] <= x < bins[i+1])
            if count > 0:
                ax1.text((bins[i] + bins[i+1])/2, count + max(self.box_areas)/50, f'{count}', ha='center')
        
        # 对角线直方图
        _, bins, _ = ax2.hist(self.box_diagonals, bins=self.bin_num, range=(diag_min, diag_max),
                             color='lightgreen', edgecolor='black')
        ax2.set_title('归一化对角线长度分布')
        ax2.set_xlabel('归一化对角线长度')
        ax2.set_ylabel('目标数量')
        ax2.set_xlim(diag_min, diag_max)
        ax2.grid(axis='y', linestyle='--', alpha=0.7)
        
        # 添加数据标签
        for i in range(len(bins)-1):
            count = sum(1 for x in self.box_diagonals if bins[i] <= x < bins[i+1])
            if count > 0:
                ax2.text((bins[i] + bins[i+1])/2, count + max(self.box_diagonals)/50, f'{count}', ha='center')
        
        plt.tight_layout()
        
        # 保存图片
        if isinstance(self.save_plots, str):
            save_path = os.path.join(self.save_plots, 'box_size_distribution.png')
            plt.savefig(save_path, dpi=300, bbox_inches='tight')
        
        # 显示图片
        plt.show()

def main():
    # 数据集路径(注意使用原始字符串或转义反斜杠)
    image_dir = r"C:\baidunetdiskdownload\testB3000-new\images"
    label_dir = r"C:\baidunetdiskdownload\testB3000-new\labels"
    
    # 创建分析器实例
    analyzer = YOLODatasetAnalyzer(
        image_dir=image_dir,
        label_dir=label_dir,
        sampling_step=1,       # 步长
        grid_size=8,           # 8×8网格
        bin_num=10,            # 直方图分箱数
        save_plots='./plot',   # 保存图片到plot目录,False表示不保存仅显示
        hist_xlim=[0, 0.03]     # 直方图横坐标范围,这里设置为0到0.2
    )
    
    # 执行分析
    analyzer.prepare_dataset()
    analyzer.process_labels()
    analyzer.analyze_center_distribution()
    analyzer.analyze_box_size()

if __name__ == "__main__":
    main()
    
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值