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

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

目录:

服务器的环境配置:

  • 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358