数据的标注

在介绍mmdetection之前,先来讲讲如何制作一个coco格式的数据集

via标注工具

标注数据集用的是via标注工具

VGG Image Annotator (VIA)是一款开源的图像标注工具, 由Visual Geometry Group开发

这里我上传了一份到gitpage方便在线使用

via标注工具

打开via标注工具,首先导入图片,可以单张导入也可以批量导入

在左侧选择标注框来进行标注,因为我做的是目标检测,所以选择的是bbox

在标注的过程中,如果我们觉得数字labels影响精细标注了,则可以在view这里暂时隐藏掉label

ctrl+鼠标滚轮可以放大缩小图片,可以更细致地标注内容

接下来我们给标注的内容添加类别,因为我制作的数据集只有一个类也就是tree,所以后期直接用python处理统一添加即可,如果有好几个类,则建议直接在via标注工具中添加类别

添加方式如下图 (Snipaste真好用)

最后讲一下保存和载入,因为我们是打了类别标注的,所以要整个工程一起存储方便下次导入继续标注,region/file attributes的导入与导出是把创建的类别导入进来,免得下次标注要重新创建,不过类别少的导师无所谓

因为可能是多人共同完成的标注,所以标注文件有时候会需要合并,则需要每个工程导出为一个json文件,然后将导出的内容一起导入,再统一导出即可 (工程文件不能同时导入多个)

这样子标注就完成了 (最后导出为coco格式)

COCO数据集格式介绍

coco数据集本身是一个字典,里面包含五块内容,info,licenses,images,annotations以及categories

我们在训练时需要的部分是图片images,标注annotations以及类别categories

annotations的格式如下

1
2
3
4
5
6
7
8
9
annotation{
"id": int,
"image_id": int,
"category_id": int,
"segmentation": RLE or [polygon],
"area": float,
"bbox": [x,y,width,height],
"iscrowd": 0 or 1,
}

id是标注的编号,要求同一份数据集里面不能重复,所以在做数据集合并的时候需要注意

image_id是图片在数据集中的编号,这里的image_id要求使用int,但是不知道为什么via标注导出的是个字符串,所以一会儿得要处理

catagory_id对应的是类别的id

image的格式没有特别需要注意的地方,同样也是id不能重复

catagory因为我只有一个类别所以处理的时候最后直接加上

数据集处理

首先是标注文件的合并,因为有一堆信息 (比如image_id得是int) 要处理,所以我合并文件直接用python处理的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import os
import json

json_list = os.listdir("annotations")

# 记录json文件
annotations_list = []

dict = {}
image = []
annotations = []

# 将所有的json文件读入
for file in json_list:
if (file.endswith(".json")):
annotations_list.append(file)

# 编号统计,防止图片编号重复
total_img = 0

for anno_name in annotations_list:
img_cnt = 0
anno = json.loads(open("annotations/"+anno_name,encoding='utf-8').read())
for img in anno["images"]:
img["id"] += total_img
# 处理完的img存入image列表
image.append(img)
img_cnt += 1
for a in anno['annotations']:
# 计算对应的图片编号
a['image_id'] = int(a['image_id'])+total_img
a['category_id'] = 1
# 处理完的annotation存入annotations列表
annotations.append(a)
total_img += img_cnt

# 将字典对应的部分放入
dict['images']=image
dict['annotations']=annotations
# 类别直接写
dict['categories']=[{
"id": 1,
"name": "tree",
"supercategory": "category"
}]
json.dump(dict, open('total.json', 'w'), indent=4, ensure_ascii=True)

然后划分训练集和验证集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import os
import random
import json
import shutil

anno = json.loads(open("total.json").read())

# print(anno.keys())

# 训练集测试集比例为3:1
train_percent=0.75

# 产生一个随机的排列
len = len(anno['images'])
list = range(len)

img_name = []

train_img = []
val_img = []

train_anno = []
val_anno = []

train_file = {}
val_file = {}

# 图片名字列表
for img in anno['images']:
img_name.append(img["file_name"])

# 计算训练集大小
tr = int(len * train_percent)

# 从排列中随机
train = random.sample(list, tr)

dic = {}

# 初始化字典,给分配到训练集中的图片名打上标记
for i in range(0,len):
dic[img_name[i]] = 0
for i in train:
dic[img_name[i]] = 1

# 给图片id打上标记
id_dic = {}
for i in range(0,len):
id_dic[i] = 0

# 根据标记分配图片
for img in anno["images"]:
if(dic[img["file_name"]] == 1):
train_img.append(img)
id_dic[img["id"]] = 1
else:
val_img.append(img)

train_anno_id = 0
val_anno_id = 0

# 根据标记分配标注
for anno_item in anno["annotations"]:
if(id_dic[anno_item["image_id"]]==1):
anno_item["id"] = train_anno_id
train_anno.append(anno_item)
train_anno_id += 1
else:
anno_item["id"] = val_anno_id
val_anno.append(anno_item)
val_anno_id += 1

# 分别保存训练集和验证集
train_file['images'] = train_img
train_file['annotations'] = train_anno
train_file['categories']=[{
"id": 1,
"name": "tree",
"supercategory": "category"
}]
json.dump(train_file, open('train.json', 'w'), indent=4, ensure_ascii=True)

# 将对应的图片pick出来
for img in train_file["images"]:
shutil.copy("img/"+img["file_name"],"train_img/")

val_file['images'] = val_img
val_file['annotations'] = val_anno
val_file['categories']=[{
"id": 1,
"name": "tree",
"supercategory": "category"
}]
json.dump(val_file, open('val.json', 'w'), indent=4, ensure_ascii=True)

for img in val_file["images"]:
shutil.copy("img/"+img["file_name"],"val_img/")

至此,算是彻底完成了一个自定义的coco格式的数据集

mmdetection的使用

先在git上clone一个mmdetection到服务器上,或者直接下载上传到服务器

然后到这个mmdetection文件夹下

我的安装列表如下

1
2
3
4
5
6
7
8
9
10
11
pip install torch==1.6.0

pip install torchvision==0.7.0

pip install -r requirements/build.txt

pip install -v -e .

pip install ninja

pip install mmcv-full -f https://download.openmmlab.com/mmcv/dist/cu101/torch1.6.0/index.html

注意要装mmcv-full,而不是mmcv,同时如果mmcv-full安装不了就看看有没有ninja,没有的话安装一个

这些步骤都执行完之后按照官网的代码测试一下

然后在文件夹下创建一个data文件夹

内容如下

1
2
3
4
5
6
7
data
|---coco
|---annotations
|---instances_train2017.json
|---instances_val2017.json
|---val2017
|---train2017

其中val2017和train2017是验证集和测试集的图片文件夹,instances_train2017.json和instances_val2017.json是训练集和验证集的标注文件 (当然这里可以用自己的名字然后去代码里面改,但是要改的东西更多)

接着我们来整一个模型配置文件

首先看一下configs/_base_/models/目录,这里面是各个模型对应的参数,我们先尝试改一个fasterRCNN,我们打开faster_rcnn_r50_fpn.py

搜索num_classes,将类的数量改为自己数据集类的数量,不需要因为背景+1,因为mmdetection会自动处理

1
2
# num_classes=80,
num_classes=1,

然后修改coco数据集对应的类,在mmdet/core/evaluation/class_names.py中,找到coco数据集,进行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def coco_classes():
return [
'''
'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train',
'truck', 'boat', 'traffic_light', 'fire_hydrant', 'stop_sign',
'parking_meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep',
'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella',
'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard',
'sports_ball', 'kite', 'baseball_bat', 'baseball_glove', 'skateboard',
'surfboard', 'tennis_racket', 'bottle', 'wine_glass', 'cup', 'fork',
'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange',
'broccoli', 'carrot', 'hot_dog', 'pizza', 'donut', 'cake', 'chair',
'couch', 'potted_plant', 'bed', 'dining_table', 'toilet', 'tv',
'laptop', 'mouse', 'remote', 'keyboard', 'cell_phone', 'microwave',
'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase',
'scissors', 'teddy_bear', 'hair_drier', 'toothbrush'
'''
'tree',
]

mmdet/datasets/coco.py中也要做对应的修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
'''
CLASSES = ('person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus',
'train', 'truck', 'boat', 'traffic light', 'fire hydrant',
'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog',
'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe',
'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',
'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat',
'baseball glove', 'skateboard', 'surfboard', 'tennis racket',
'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl',
'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot',
'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',
'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop',
'mouse', 'remote', 'keyboard', 'cell phone', 'microwave',
'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock',
'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush')
'''
CLASSES = ('tree',)

完成,然后我们用tool/train.py来生成对应的配置文件

1
python tools/train.py configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py --work-dir record

--work-dir的意思是指定工作目录,一会儿会在这里生成配置文件,生成配置文件之后,我们可以中断掉,然后在工作目录中找到这个配置文件,做修改之后 (各种微调,比如学习率,训练次数,继续上次的训练文件等) 再运行这个配置文件即可

1
python tools/train.py record/faster_rcnn_r50_fpn_1x_coco.py