数据集标注格式
前面讲解了数据集文件结构。
这一部分,我们来讲解数据集的标注格式。
在目标检测任务中,主要就是三种常见的标注格式。
- VOC
- COCO
- YOLO
标注格式无非是表示的形式有什么区别,但是本质都是为了标注目标。
比如有的是用绝对值坐标来表示目标的,有的是用相对值坐标来表示目标的。
后面,我们会将其他标注格式,都转换为 YOLO 的标注格式。
这样的话,后面在写不同模型的时候,我们都可以复用这个标注格式。
在遇到其他标注格式的时候,我们只需要进行相应的转换。
VOC 标注格式
这是我们关心的内容。
- 每个标注文件里面的标注内容 对应的是 哪个图像文件的标注
- 图像的基本信息,比如图像的宽,高,通道数
- 图像文件中存在的目标信息(目标的位置,xmin, ymin, xmax, ymax;以及目标的类别)
除此之外的内容,不是我们关心的。大家可以做个大概的了解。
- source 和 owner 是表示图像文件的来源以及作者是谁,
- segmented 表示这个标注文件中是否包含其他任务(图像分割任务)可以用的信息
- pose 表示拍摄图像时候的方位
- truncate 为0,表示 bndobx中的框选位置完全覆盖包含目标。如果为1的话,说明框选位置没有完全覆盖目标。
- difficult 为0,表示这个目标不存在严重的干扰和遮挡,我们人类可以很轻易的分辨。如果为1的话,说明这个目标存在严重干扰和遮挡,即便是我们人类,也很难分辨。
大家做个大概了解就行了。
COCO 标注格式
YOLO 标注格式
在介绍完这三种标注格式后,我们来思考一个问题,YOLO标注格式为什么现在如此流行?
在我看来,YOLO标注格式将 VOC 和 COCO 的标注方式的优点进行了保留,同时又避免了 VOC 和 COCO 的缺点。
- 一个图像对应一个标注文件
- 标注中的坐标采用的是归一化坐标,符合现在主流的模型输出
- 标注文件只是简简单单的txt格式,不需要引入其他库就可以进行处理
- 标注文件的内容简单,不包含无关的信息
VOC to YOLO 转换
在后面的实战中,无论我们采用 COCO 数据集,还是 VOC 数据集,我们都可以将数据集转换为 YOLO 的标注格式。 甚至我们自己在标注数据格式的时候,也可以采用 YOLO 的标注格式。
大家只要了解数据集输出的含义就可以了,没必要在乎是哪种形式。
当我们拿到一个数据集的时候,只要将数据集转换为 YOLO 的标注格式,我们就可以复用后面的代码。
改编自:https://gist.github.com/Amir22010/a99f18ca19112bc7db0872a36a03a1ec
import glob
import os
import pickle
import xml.etree.ElementTree as ET
from os import listdir, getcwd
from os.path import join
dirs = ['train', 'val']
classes = ['person', 'car']
def getImagesInDir(dir_path):
image_list = []
for filename in glob.glob(dir_path + '/*.jpg'):
image_list.append(filename)
return image_list
def convert(size, box):
dw = 1./(size[0])
dh = 1./(size[1])
x = (box[0] + box[1])/2.0 - 1
y = (box[2] + box[3])/2.0 - 1
w = box[1] - box[0]
h = box[3] - box[2]
x = x*dw
w = w*dw
y = y*dh
h = h*dh
return (x,y,w,h)
def convert_annotation(dir_path, output_path, image_path):
basename = os.path.basename(image_path)
basename_no_ext = os.path.splitext(basename)[0]
in_file = open(dir_path + '/' + basename_no_ext + '.xml')
out_file = open(output_path + basename_no_ext + '.txt', 'w')
tree = ET.parse(in_file)
root = tree.getroot()
size = root.find('size')
w = int(size.find('width').text)
h = int(size.find('height').text)
for obj in root.iter('object'):
difficult = obj.find('difficult').text
cls = obj.find('name').text
if cls not in classes or int(difficult)==1:
continue
cls_id = classes.index(cls)
xmlbox = obj.find('bndbox')
b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text), float(xmlbox.find('ymax').text))
bb = convert((w,h), b)
out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')
cwd = getcwd()
for dir_path in dirs:
full_dir_path = cwd + '/' + dir_path
output_path = full_dir_path +'/yolo/'
if not os.path.exists(output_path):
os.makedirs(output_path)
image_paths = getImagesInDir(full_dir_path)
list_file = open(full_dir_path + '.txt', 'w')
for image_path in image_paths:
list_file.write(image_path + '\n')
convert_annotation(full_dir_path, output_path, image_path)
list_file.close()
print("Finished processing: " + dir_path)