数据集介绍
VOC 格式
voc 形式的数据集一般包含以下几个文件夹
- Annotations
- ImageSets
- JPEGImages
- SegmentationClass
- SegmentationObject
对于目标检测的任务而言,一般只需要用到 Annotations 文件夹和 JPEGImages 文件夹,其中测试图片和训练图片都保存在一个目录下面,通过 ImageSets 文件夹中 Main 子文件夹下面的 txt 文件来对测试图片和验证图片进行解析。
JPEGImages 主要提供的是PASCAL VOC所提供的所有的图片信息,包括训练图片,测试图片,这些图像就是用来进行训练和测试验证的图像数据。
Annotations 主要存放 xml 格式的标签文件,每个 xml 对应 JPEGImage 中的一张图片
ImageSetsMain 图像物体识别的数据,总共 20 类, 需要保证 train、val 没有交集
SegmentationObject & SegmentationClass?保存的是物体分割后的数据,在物体识别中没有用到
<annotation>
<folder>VOC2012</folder> // 图像所在文件夹
<filename>2007_000392.jpg</filename> // 文件名
<source> // 图像来源(不重要)
<database>The VOC2007 Database</database>
<annotation>PASCAL VOC2007</annotation>
<image>flickr</image>
</source>
<size> // 图像尺寸(长宽以及通道数)
<width>500</width>
<height>332</height>
<depth>3</depth>
</size>
<segmented>1</segmented> // 是否用于分割(在图像物体识别中01无所谓)
<object> // 目标对象的信息
<name>horse</name> // 物体类别
<pose>Right</pose> // 拍摄角度
<truncated>0</truncated> // 是否被截断(0表示完整)
<difficult>0</difficult> // 目标是否难以识别(0表示容易识别)
<bndbox> // bounding-box 边界框信息(包含左下角和右上角xy坐标)
<xmin>100</xmin>
<ymin>96</ymin>
<xmax>355</xmax>
<ymax>324</ymax>
</bndbox>
</object>
<object> // 下面是其他目标的信息,这里略掉
......
</object>
</annotation>
一般而言,有图片的名称、长和宽等信息,之后一张图片对应有多个 object,每个 object 中包含了类名、然后三个对于目标检测而言不重要的信息,以及 bndbox 信息,bndbox 信息十分重要,主要是左上角的坐标和右下角的坐标。
COCO格式
coco 数据集为 json 文件,一般包含5个字段
- info
- images
- annotations
- licenses
- categories
info
info 字段包含了数据集的基本信息,包括数据集的来源,提供者之类的,info 字段在写程序的时候一般不会使用到,内容如下:
info: {
"year": int, # 年份
"version": str, # 版本
"description": str, # 数据集描述
"contributor": str, # 提供者
"url": str, # 下载地址
"date_created": datetime
}
licenses
licenses 字段表明了图片的版权信息之类的,一般的程序中也用不到,licenses字段的结构如下:
license{
"id": int,
"name": str,
"url": str,
}
images
images 字段是整个 json 文件中最重要的字段之一,包含了图片的基本信息,包括图片的名称,宽高。images 目录由多个 image 构成数组,可以遍历。每一个 image 的实例是一个 dict。其中有一个 id 字段,代表的是图片的 id,每一张图片具有唯一的一个独特的 id。结构如下:
image{
"id": int, # 图片的ID编号(每张图片ID是唯一的)
"width": int, # 宽
"height": int, # 高
"file_name": str, # 图片名
"license": int,
"flickr_url": str, # flickr网路地址
"coco_url": str, # 网路地址路径
"date_captured": datetime # 数据获取日期
}
annotations
annotations 存储图片的标注信息,结构如下:
annotation{
"id": int, # 对象ID,因为每一个图像有不止一个对象,所以要对每一个对象编号(每个对象的ID是唯一的)
"image_id": int, # 对应的图片ID(与images中的ID对应)
"category_id": int, # 类别ID(与categories中的ID对应)
"segmentation": RLE or [polygon], # 对象的边界点(边界多边形,此时iscrowd=0)。
# segmentation 格式取决于这个实例是一个单个的对象(即iscrowd=0,将使用polygons格式)还是一组对象(即iscrowd=1,将使用RLE格式)
"area": float, # 区域面积
"bbox": [x,y,width,height], # 定位边框 [x,y,w,h]
"iscrowd": 0 or 1 # 见下
}
其中注意这里的bbox格式为[x,y,width,height]。[x,y,w,h]是没有进行归一化的,所以在进行yolo的转化时需要进行归一化的处理。
categories
categories字段是记录annotations字段中的类别信息,结构如下:
{
"supercategory": str, # 主类别
"id": int, # 类对应的id (0 默认为背景)
"name": str # 子类别
}
YOLO数据集格式
yolo标注格式保存在.txt文件中,一共5个数据,用空格隔开,举例说明如下图所示:
若图像的高和宽别为h,w,bbox的左上角坐标为(x1, y2),右下角坐标为(x2, y2),则bbox中心坐标(x_c, y_c)为:x_c = x1 + (x2 - x1)/2 = (x1 + x2)/2;y_c = y1 + (y2 - y1)/2 = (y1 + y2)/2
假设yolo的5个数据分别为:label, x_, y_, w_, h_,则有对应关系:
x_ = (x1 + x2) / 2w;y_ = (y1 + y2) / 2h;w_ = (x2 - x1) / w;h_ = (y2 - y1) / h
反过来,则有:
x1 = w * x_ - 0.5 * w * w_;x2 = w * x_ + 0.5 * w * w_ y1 = h * y_ - 0.5 * h * h_;y2 = h * y_ + 0.5* h * h_
批量修改XML标注文件标签名
"""
使用python xml解析树解析xml文件,批量修改xml文件里object节点下name节点的text
"""
import glob
import xml.etree.ElementTree as ET
path = r'D:\Software\OneDrive\桌面\吊弓' # xml文件夹路径
for xml_file in glob.glob(path + '/*.xml'):
tree = ET.parse(xml_file)
obj_list = tree.getroot().findall('object')
for per_obj in obj_list:
if per_obj[0].text == '吊弓': # 待修改的标签名
per_obj[0].text = 'flaw' # 欲修改成的标签名
i = i+1
tree.write(xml_file) # 将改好的文件重新写入,会覆盖原文件
?
天池布匹瑕疵竞赛数据集格式转换(json —> yolo)
?只检测疵点并不对疵点进行具体的分类
import numpy as np # linear algebra
import os
import json
from tqdm.auto import tqdm
import shutil as sh
import cv2
# 标签json存放地址
josn_path = r".\Annotations\annp_train.json"
# 图片存放地址
image_path = r".\images"
name_list = []
image_h_list = []
image_w_list = []
c_list = [] # defect_name
w_list = []
h_list = []
x_center_list = [] # bbox中心坐标
y_center_list = []
'''
with 语句用于异常处理,封装了 try…except…finally 编码范式,提高了易用性。
open(name[, mode[, buffering]])
name:包含待访问的文件名称的字符串值
mode:决定了打开文件的模式:只读,写入,追加等。参数非强制,默认文件访问模式为只读(r)。
tqdm模块是python进度条库
'''
with open(josn_path, 'r') as f:
temps = tqdm(json.loads(f.read())) # 默认读取整个文件
for temp in temps:
# image_w = temp["image_width"]
# image_h = temp["image_height"]
'''
str.split("[")[1]. split("]")[0]输出的是 [ 后的内容以及 ] 前的内容。
str.split("[")[1]. split("]")[0]. split(".") 是先输出 [ 后的内容以及 ] 前的内容,然后通过 . 作为分 隔符对字符串进行切片。
'''
name = temp["name"].split('.')[0] # 字典
path = os.path.join(image_path, temp["name"])
print('path: ',path)
im = cv2.imread(path)
sp = im.shape # shape函数可以读取矩阵的形状(高宽通道)
image_h, image_w = sp[0], sp[1]
# print("image_h, image_w: ", image_h, image_w)
# print("defect_name: ",temp["defect_name"])
#bboxs
x_l, y_l, x_r, y_r = temp["bbox"]
# print(temp["name"], temp["bbox"])
if temp["defect_name"]=="沾污":
defect_name = '0'
elif temp["defect_name"]=="错花":
defect_name = '0'
elif temp["defect_name"] == "水印":
defect_name = '0'
elif temp["defect_name"] == "花毛":
defect_name = '0'
elif temp["defect_name"] == "缝头":
defect_name = '0'
elif temp["defect_name"] == "缝头印":
defect_name = '0'
elif temp["defect_name"] == "虫粘":
defect_name = '0'
elif temp["defect_name"] == "破洞":
defect_name = '0'
elif temp["defect_name"] == "褶子":
defect_name = '0'
elif temp["defect_name"] == "织疵":
defect_name = '0'
elif temp["defect_name"] == "漏印":
defect_name = '0'
elif temp["defect_name"] == "蜡斑":
defect_name = '0'
elif temp["defect_name"] == "色差":
defect_name = '0'
elif temp["defect_name"] == "网折":
defect_name = '0'
elif temp["defect_name"] == "其他":
defect_name = '0'
else:
defect_name = '0'
# print("----------------------------------error---------------------------")
# raise("erro") # 主动抛出异常
# print(image_w, image_h)
# print(defect_name)
x_center = (x_l + x_r)/(2*image_w)
y_center = (y_l + y_r)/(2*image_h)
w = (x_r - x_l)/(image_w)
h = (y_r - y_l)/(image_h)
# print(x_center, y_center, w, h)
name_list.append(temp["name"])
c_list.append(defect_name)
image_h_list.append(image_w)
image_w_list.append(image_h)
x_center_list.append(x_center)
y_center_list.append(y_center)
w_list.append(w)
h_list.append(h)
index = list(set(name_list)) # set() 函数创建一个无序不重复元素集,list() 将元组转换为列表。
# print(len(index))
fold = 0
# for fold in [0]:
val_index = index[len(index) * fold // 5:len(index) * (fold + 1) // 5] # //整数除法
# list2[1:5]: [2, 3, 4, 5]
# print(len(val_index))
# enumerate() 用于将一个可遍历的数据对象(如列表、元组)组合为一个索引序列,同时列出数据和数据下标
for num, name in enumerate(name_list):
# print(c_list[num], x_center_list[num], y_center_list[num], w_list[num], h_list[num])
row = [c_list[num], x_center_list[num], y_center_list[num], w_list[num], h_list[num]]
if name in val_index:
path2save = 'val/'
else:
path2save = 'train/'
# print('convertor\\fold{}\\labels\\'.format(fold) + path2save)
# print('convertor\\fold{}/labels\\'.format(fold) + path2save + name.split('.')[0] + ".txt")
# print("{}/{}".format(image_path, name))
# print('convertor\\fold{}\\images\\{}{}'.format(fold, path2save, name))
if not os.path.exists('convertor/fold{}/labels/'.format(fold) + path2save):
os.makedirs('convertor/fold{}/labels/'.format(fold) + path2save)
with open('convertor/fold{}/labels/'.format(fold) + path2save + name.split('.')[0] + ".txt", 'a+') as f:
for data in row:
f.write('{} '.format(data))
f.write('\n')
if not os.path.exists('convertor/fold{}/images/{}'.format(fold, path2save)):
os.makedirs('convertor/fold{}/images/{}'.format(fold, path2save))
# print(os.path.join(image_path, name))
# print('convertor/fold{}/images/{}/{}'.format(fold, path2save, name))
sh.copy(os.path.join(image_path, name),
'convertor/fold{}/images/{}/{}'.format(fold, path2save, name))
待续。。
参考文章
批量修改XML标注文件标签(label)名称_SYGgogogo的博客-CSDN博客
目标检测常用数据集格式转化voc yolo coco_dejahu的博客-CSDN博客_常用数据集格式
目标检测常用数据集格式 - 知乎 (zhihu.com)
YOLO数据格式说明与转换_lokvke的博客-CSDN博客_yolo数据格式
team-learning-cv/DefectDetection at master · datawhalechina/team-learning-cv · GitHub
|