Numpy 目标检测数据集 按各类别的边界框数量分割

Posted 荷碧·TongZJ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Numpy 目标检测数据集 按各类别的边界框数量分割相关的知识,希望对你有一定的参考价值。

在对目标检测数据集进行分割的时候,如果只是简单地将图像数据随机地按比例分割,难免需要面对以下问题:

  • 如果数据集中某一类别的边界框均聚集在训练集,神经网络在对该类别的学习可能过拟合,在验证集上的表现可信度较低
  • 如果数据集中某一类别的边界框均聚集在验证集,神经网络在训练集上无法充分学习到该类别的特征,同时在验证集上的表现也将较差

如果要将目标检测的数据集按照 4:1 的比例分割,那么最优的解决方案显然不是暴力地按图像数量分割,而是按各类别的边界框数量分割,使得:

  • 类别 1 的边界框在训练集中的数量 : 在验证集中的数量 ≈ 4:1
  • 类别 2 的边界框在训练集中的数量 : 在验证集中的数量 ≈ 4:1
  • ……

所以我提出了这样一个分割目标检测数据集的算法,定义以下变量:

训练集统计:训练集中各个类别的边界框总数,初始全为 0
验证集统计:验证集中各个类别的边界框总数,初始全为 0
图像统计:第 i 张图像各个类别的边界框总数

例如,对于下面这个数据集,第 i 行第 j 列的数据表示了第 i 张图像中第 j 个类别的边界框总数:

012
train_0.jpg 199
train_1.jpg358
train_2.jpg935
……
train_99.jpg806

初始状态下,;给定比例为 4:1,定义某状态的 SSE 损失值为:

打乱数据集的图像顺序后,依次取出数据集中的图像 id、图像统计 ,将该图像加入训练集 / 验证集,则衍生 2 个子状态:

  • 加入训练集:训练集统计变为 ,验证集统计仍为 ,对应损失值为 
  • 加入验证集:训练集统计仍为 ,验证集统计变为 ,对应损失值为 

执行较小的损失值所对应的动作后,更新 ,最后可使得数据集的分割结果逼近我们想要的“按各类别的边界框数量分割”

加上存储图像 id 的操作,该算法的大致流程如下:

下面这个函数的主要要求 label_count 和 scale 两个参数:

  • label_count 是图像边界框的统计信息,为 pandas 的 DataFrame 类型;其形式与上文示例中的表格一致,列索引为类别,行索引为图像 id (建议设为图像的文件名)
  • scale 表示某类别边界框分布在训练集中的比例,当希望上文中提到的比例为 n:1 时,则该参数的值为 

这个函数的用法已经编写在代码最下方的 if 语句下,兄弟姐妹们别忘了一键三连喔

import logging

import numpy as np
import pandas as pd
from tqdm import tqdm

logging.basicConfig(format='%(message)s', level=logging.INFO)
LOGGER = logging.getLogger(__name__)


def bbox_split(label_count: pd.DataFrame, scale: float,
               verbose=False, seed=0):
    ''' 对目标检测数据集按各类别的边界框数量分割
        label_count: Dataframe[classes, img_id]
        scale: 某类别边界框分布在训练集中的比例
        return: 训练集id列表, 验证集id列表'''
    exp_radio = scale / (1 - scale)
    # 打乱图像的次序
    idx = list(label_count.index)
    np.random.seed(seed)
    np.random.shuffle(idx)
    label_count = label_count.loc[idx]
    # 记录训练集、验证集当前的边界框数量
    data_count = np.zeros([2, len(label_count.columns)])
    # 防止后续运算中分母出现 0
    data_count[1] += 1e-4
    # 存放训练集、验证集数据的 id
    data_pool = [[] for _ in range(2)]
    pbar = tqdm(label_count.iterrows(), total=len(idx))
    for i, cnt in pbar:
        cnt = np.array(cnt)
        # 计算期望比例 与 执行动作后比例的 SSE 损失
        loss = np.zeros(2)
        for j, next_sit in enumerate([data_count.copy() for _ in range(2)]):
            next_sit[j] += cnt
            loss[j] = np.square(next_sit[0] / next_sit[1] - exp_radio).sum()
        # 根据损失值选择下一步动作
        choose = loss.argmin()
        data_count[choose] += cnt
        data_pool[choose].append(i)
        # 输出当前的训练集各类别比例的上下限
        cur_scale = data_count[0] / data_count.sum(axis=0) - scale
        pbar.set_description(f'BBox Scale Error ∈ [cur_scale.min():.3f, cur_scale.max():.3f]')
    if verbose:
        LOGGER.info(f'Train Set Length: len(data_pool[0])\\t\\tEval Set Length: len(data_pool[1])')
    return data_pool


if __name__ == '__main__':
    # 5000 张图像各个类别边界框数目统计结果
    example = (np.random.random([5000, 5]) * 10).astype(np.int32)
    example = pd.DataFrame(example, index=[f'train_i.jpg' for i in range(example.shape[0])])
    print(example)

    train_id, eval_id = bbox_split(example, 0.8, verbose=True)

以上是关于Numpy 目标检测数据集 按各类别的边界框数量分割的主要内容,如果未能解决你的问题,请参考以下文章

夜间(低光照)目标检测数据集整理:人脸检测,行人检测

为啥物体检测 CNN 的边界框必须与图像边界平行?

目标跟踪入门:使用OpenCV实现质心跟踪

Tensorflow 对象检测 api 获取按边界框坐标排序的预测

按标签分隔数据框(将数据框转换为 numpy 数组)

[数据集][VOC][目标检测]西瓜数据集目标检测可用yolo训练-1702张介绍