MMDetection v2 目标检测(2):数据准备

本文主要介绍如何标注、存放和划分 VOC 格式的自定义数据集。

目录:

  • MMDetection v2 目标检测(1):环境搭建
  • MMDetection v2 目标检测(2):数据准备
  • MMDetection v2 目标检测(3):配置修改
  • MMDetection v2 目标检测(4):模型训练和测试

服务器的环境配置:

  • Ubuntu:18.04.5
  • CUDA:10.1.243
  • Python:3.7.9
  • PyTorch:1.5.1
  • MMDetection:2.16.0

1 标注数据

MMDetection 目前支持 Pascal VOCMS COCOCityscapesLVISWider FaceDeep Fashion 等多个公开数据集。

而其他公开数据集,例如:KITTI 等,可在网上搜索脚本,将标注文件转化成 VOCCOCO 格式。

如果是自定义数据集,则可使用数据标注工具 LabelImg,来生成 VOC 格式的标注文件。

下面介绍如何在 Win 10 上安装和使用 LabelImg

1.1 安装 LabelImg

pip 安装:

pip install labelimg

1.2 使用 LabelImg

  1. 将数据集按照以下格式存放:
data
├─ Annotations
├─ JPEGImages
└─ predefined_classes.txt
  • Annotations:存放标注文件
  • JPEGImages:存放图像文件
  • predefined_classes.txt:定义标签类别
  1. predefined_classes.txt 中,定义标签类别,例如:
Car
Pedestrian
Cyclist
  1. 打开 data 目录:
cd data
  1. 运行 LabelImg
labelimg JPEGImages predefined_classes.txt
  1. 选择图像文件夹 JPEGImages

  2. 进入 LabelImg 界面:

  • Open Dir:打开存放图像文件的目录路径,选择 JPEGImages
  • Change Save Dir:更换存放标注文件的目录路径,选择 Annotations
  • PascalVOC / YOLO:切换标注保存的格式
  • Create\nRectBox:创建标注
  • Save:保存标注
  1. 其他设置:
  • Auto Save mode:自动保存模式,不用每标注一张图像,都要点击一次保存标注
  • Display Labels:显示标签,标注好的标签会自动显示出来
  • Advanced Mode:高级模式,不用每标注一个目标,都要点击一次创建标注

2 存放数据

  1. 将数据集上传到服务器,并将图像和标注文件按照 VOC 格式存放:
data
└─ VOCdevkit
   └─ MyDataset
      ├─ Annotations
      ├─ ImageSets
      │  └─ Main
      │     ├─ test.txt
      │     ├─ train.txt
      │     ├─ trainval.txt
      │     └─ val.txt
      └─ JPEGImages
  • Annotations:存放标注文件
  • ImageSets:存放划分文件
  • JPEGImages:存放图像文件

Tips:
目录中的 MyDataset 可改为任意自定义数据集的名字。

  1. 推荐使用符号链接,将数据集放到 mmdetection 目录下:
ln -s ~/data ~/mmdetection

mmdetection 的目录结构:

mmdetection
├─ mmdet
├─ tools
├─ configs
├─ checkpoints
├─ data

3 划分数据

打开 MyDataset 目录:

cd data/VOCdevkit/MyDataset

3.1 划分数据集

运行 split_dataset.py

python split_dataset.py

具体代码如下:

import os
import random

trainval_percent = 0.9
train_percent = 0.9
total_xml = os.listdir('./Annotations')

num = len(total_xml)
nums = range(num)
tv = int(num * trainval_percent)
tr = int(tv * train_percent)
trainval = random.sample(nums, tv)
train = random.sample(trainval, tr)

ftrainval = open('./ImageSets/Main/trainval.txt', 'w')
ftrain = open('./ImageSets/Main/train.txt', 'w')
fval = open('./ImageSets/Main/val.txt', 'w')
ftest = open('./ImageSets/Main/test.txt', 'w')

for i in nums:
    name = total_xml[i][:-4] + '\n'
        if i in trainval:
            ftrainval.write(name)
            if i in train:
                ftrain.write(name)
            else:
                fval.write(name)
        else:
            ftest.write(name)

ftrainval.close()
ftrain.close()
fval.close()
ftest.close()

./ImageSets/Main 目录下,生成四个 txt 文件:

  • test.txt:测试集
  • train.txt:训练集
  • trainval.txt:训练和验证集
  • val.txt:验证集

每个文件分别记录了对应划分数据集包含的图像文件名(不含后缀名)。

3.2 统计数据个数

运行 cal_data_num.py

python cal_data_num.py

具体代码如下:

import os

names_txt = os.listdir('./ImageSets/Main')
print(f'共有文件:{len(names_txt)} 个')

for name_txt in names_txt:
    with open(os.path.join('./ImageSets/Main', name_txt)) as f:
        lines = f.readlines()
        print(f'{name_txt} 共有数据:{len(lines)} 个')

得到每个划分数据集的数据个数。

4 转换数据

另外,还可以将 VOC 格式转换成 COCO 格式。

  1. 打开 ./tools/convert_datasets/pascal_voc.py

如果标注文件中不存在 difficult 标签,需要将 difficult 设为 None

def parse_xml(args):

    xml_path, img_path = args
    tree = ET.parse(xml_path)
    root = tree.getroot()
    size = root.find('size')
    w = int(size.find('width').text)
    h = int(size.find('height').text)

    for obj in root.findall('object'):
        name = obj.find('name').text
        label = label_ids[name]
        try:
            difficult = int(obj.find('difficult').text)
        except AttributeError:
            difficult = None
        # difficult = int(obj.find('difficult').text)

如果是自定义数据集的名字,需要修改数据集的路径 filelistxml_pathsimg_paths

def cvt_annotations(devkit_path, years, split, out_file):

    if not isinstance(years, list):
        years = [years]
    annotations = []
    for year in years:
        filelist = osp.join(devkit_path,
                            f'{year}/ImageSets/Main/{split}.txt')
        # filelist = osp.join(devkit_path,
        #                     f'VOC{year}/ImageSets/Main/{split}.txt')
        if not osp.isfile(filelist):
            print(f'filelist does not exist: {filelist}, '
                  f'skip {year} {split}')
            # print(f'filelist does not exist: {filelist}, '
            #       f'skip voc{year} {split}')
            return
        img_names = mmcv.list_from_file(filelist)
        xml_paths = [
            osp.join(devkit_path, f'{year}/Annotations/{img_name}.xml')
            for img_name in img_names
        ]
        # xml_paths = [
        #     osp.join(devkit_path, f'VOC{year}/Annotations/{img_name}.xml')
        #     for img_name in img_names
        # ]
        img_paths = [
            f'{year}/JPEGImages/{img_name}.png' for img_name in img_names
        ]
        # img_paths = [
        #     f'VOC{year}/JPEGImages/{img_name}.jpg' for img_name in img_names
        # ]
        part_annotations = mmcv.track_progress(parse_xml,
                                               list(zip(xml_paths, img_paths)))
        annotations.extend(part_annotations)
    mmcv.dump(annotations, out_file)

    return annotations

如果是自定义数据集的名字,需要添加 mydataset

def main():

    args = parse_args()
    devkit_path = args.devkit_path
    out_dir = args.out_dir if args.out_dir else devkit_path
    mmcv.mkdir_or_exist(out_dir)

    years = []
    if osp.isdir(osp.join(devkit_path, 'VOC2007')):
        years.append('VOC2007')
    if osp.isdir(osp.join(devkit_path, 'VOC2012')):
        years.append('VOC2012')
    if 'VOC2007' in years and 'VOC2012' in years:
        years.append(['VOC2007', 'VOC2012'])
    # ------------------------------
    if osp.isdir(osp.join(devkit_path, 'MyDataset')):
        years.append('mydataset')
    # ------------------------------
    if not years:
        raise IOError(f'The devkit path {devkit_path} contains neither '
                      '"VOC2007" nor "VOC2012" subfolder')
    for year in years:
        if year == 'VOC2007':
            prefix = 'voc07'
        elif year == 'VOC2012':
            prefix = 'voc12'
        elif year == ['VOC2007', 'VOC2012']:
            prefix = 'voc0712'
        # ------------------------------
        elif year = 'mydataset':
            prefix = 'mydataset'
        # ------------------------------
        for split in ['train', 'val', 'trainval']:
            dataset_name = prefix + '_' + split
            print(f'processing {dataset_name} ...')
            cvt_annotations(devkit_path, year, split,
                            osp.join(out_dir, dataset_name + '.pkl'))
        if not isinstance(year, list):
            dataset_name = prefix + '_test'
            print(f'processing {dataset_name} ...')
            cvt_annotations(devkit_path, year, 'test',
                            osp.join(out_dir, dataset_name + '.pkl'))
    print('Done!')
  1. 运行 ./tools/convert_datasets/pascal_voc.py

命令格式:

python tools/convert_datasets/pascal_voc.py ${DEVKIT_PATH} [--out-dir ${OUT_DIR}]

命令参数:

  • devkit_pathVOC 格式数据集的目录路径
  • --out-dir:输出 COCO 格式标注的目录路径

示例:

python tools/convert_datasets/pascal_voc.py data/VOCdevkit data/coco

5 结语

有帮助的话,点个赞再走吧,谢谢~

参考:

  1. 最新版本的mmdetection2.0 (v2.0.0版本)环境搭建、训练自己的数据集、测试以及常见错误集合
  2. 目标检测使用LabelImg标注VOC数据格式和YOLO数据格式——LabelImg使用详细教程
  3. Test existing models on standard datasets

你可能感兴趣的:(MMDetection v2 目标检测(2):数据准备)