本文主要写了使用yolov4进行模型训练前的数据准备。darknet框架使用的数据格式是yolo格式,但我们常见的目标检测数据格式是COCO和VOC.所以本文将以车辆检测为例,将coco2017和voc2007及voc2012中所有数据中的车辆中的图片进行转换,组合成一个新的数据集。总共有四个分类,bicycle,bus,car,motorcycle,truck.对于同一类型,coco和voc起的名字不同,所以我们会有处理。关于数据处理这块,也可以参考我的其它博客
from pycocotools.coco import COCO
import os, cv2, shutil
from lxml import etree, objectify
from tqdm import tqdm
from PIL import Image
import numpy as np
import time
import json
def cover_copy(src,dst):
'''
src和dst都必须是文件,该函数是执行覆盖操作
'''
if os.path.exists(dst):
os.remove(dst)
shutil.copy(src,dst)
else:
shutil.copy(src,dst)
def coco2voc(basedir='VOCdevkit/COCO_VOC',sourcedir='COCO'):
"""
basedir:用来存放转换后数据和标注文件
sourcedir:用来指定原始COCO数据集的存放位置
"""
img_savepath= os.path.join(basedir,'JPEGImages')
ann_savepath=os.path.join(basedir,'Annotations')
main_path = os.path.join(basedir,"ImageSets/Main")
for p in [basedir,img_savepath,ann_savepath,main_path]:
if os.path.exists(p):
shutil.rmtree(p)
os.makedirs(p)
else:
os.makedirs(p)
datasets = ['train2017','val2017']
# datasets = ['val2017']
for dataset in datasets:
start = time.time()
print(f"start {dataset}")
no_ann=[] #用来存放没有标注数据的图片的id,并将这些图片复制到results文件夹中
not_rgb=[] #是灰度图,同样将其
annfile = 'instances_{}.json'.format(dataset)
annpath=os.path.join(sourcedir,'annotations',annfile)
print('loading annotations into memory...')
tic = time.time()
with open(annpath, 'r') as f:
dataset_ann = json.load(f)
assert type(
dataset_ann
) == dict, 'annotation file format {} not supported'.format(
type(dataset))
print('Done (t={:0.2f}s)'.format(time.time() - tic))
coco = COCO(annpath)
classes = dict()
for cat in coco.dataset['categories']:
classes[cat['id']] = cat['name']
imgIds = coco.getImgIds()
# imgIds=imgIds[0:1000]#测试用,抽取10张图片,看下存储效果
for imgId in tqdm(imgIds):
img = coco.loadImgs(imgId)[0]
filename = img['file_name']
filepath=os.path.join(sourcedir,dataset,filename)
annIds = coco.getAnnIds(imgIds=img['id'], iscrowd=None)
anns = coco.loadAnns(annIds)
if not len(anns):
# print(f"{dataset}:{imgId}该文件没有标注信息,将其复制到{dataset}_noann_result中,以使查看")
no_ann.append(imgId)
result_path = os.path.join(sourcedir,dataset+"_noann_result")
dest_path = os.path.join(result_path,filename)
if not os.path.exists(result_path):
os.makedirs(result_path)
cover_copy(filepath,dest_path)
continue #如果没有标注信息,则把没有标注信息的图片移动到相关结果文件 noann_result中,go fj tf sj rhf ,然后返回做下一张图
#有标注信息,接着往下走,获取标注信息
objs = []
for ann in anns:
name = classes[ann['category_id']]
if 'bbox' in ann:
# print('bbox in ann',imgId)
bbox = ann['bbox']
xmin = (int)(bbox[0])
ymin = (int)(bbox[1])
xmax = (int)(bbox[2] + bbox[0])
ymax = (int)(bbox[3] + bbox[1])
obj = [name, 1.0, xmin, ymin, xmax, ymax]
#标错框在这里
if not(xmin-xmax==0 or ymin-ymax==0):
objs.append(obj)
else:
print(f"{dataset}:{imgId}bbox在标注文件中不存在")# 单张图有多个标注框,某个类别没有框
annopath = os.path.join(ann_savepath,filename[:-3] + "xml") #生成的xml文件保存路径
dst_path = os.path.join(img_savepath,filename)
im = Image.open(filepath)
image = np.array(im).astype(np.uint8)
if im.mode != "RGB":
# if img.shape[-1] != 3:
# print(f"{dataset}:{imgId}该文件非rgb图,其复制到{dataset}_notrgb_result中,以使查看")
# print(f"img.shape{image.shape} and img.mode{im.mode}")
not_rgb.append(imgId)
result_path = os.path.join(sourcedir,dataset+"_notrgb_result")
dest_path = os.path.join(result_path,filename)
if not os.path.exists(result_path):
os.makedirs(result_path)
cover_copy(filepath,dest_path) #复制到notrgb_result来方便查看
im=im.convert('RGB')
image = np.array(im).astype(np.uint8)
im.save(dst_path,quality=95)#图片经过转换后,放到我们需要的位置片
im.close()
else:
cover_copy(filepath, dst_path)#把原始图像复制到目标文件夹
E = objectify.ElementMaker(annotate=False)
anno_tree = E.annotation(
E.folder('VOC'),
E.filename(filename),
E.source(
E.database('COCO'),
E.annotation('VOC'),
E.image('COCO')
),
E.size(
E.width(image.shape[1]),
E.height(image.shape[0]),
E.depth(image.shape[2])
),
E.segmented(0)
)
for obj in objs:
E2 = objectify.ElementMaker(annotate=False)
anno_tree2 = E2.object(
E.name(obj[0]),
E.pose(),
E.truncated("0"),
E.difficult(0),
E.bndbox(
E.xmin(obj[2]),
E.ymin(obj[3]),
E.xmax(obj[4]),
E.ymax(obj[5])
)
)
anno_tree.append(anno_tree2)
etree.ElementTree(anno_tree).write(annopath, pretty_print=True)
print(f"{dataset}该数据集有{len(no_ann)}/{len(imgIds)}张图片没有instance标注信息,已经这些图片复制到{dataset}_noann_result中以使进行查看")
print(f"{dataset}该数据集有{len(not_rgb)}/{len(imgIds)}张图片是非RGB图像,已经这些图片复制到{dataset}_notrgb_result中以使进行查看")
duriation = time.time()-start
print(f"数据集{dataset}处理完成用时{round(duriation/60,2)}分")
coco2voc()
start train2017
loading annotations into memory...
Done (t=33.31s)
loading annotations into memory...
Done (t=18.20s)
creating index...
0%| | 15/118287 [00:00<13:26, 146.57it/s]
index created!
100%|██████████| 118287/118287 [36:04<00:00, 54.64it/s]
train2017该数据集有1021/118287张图片没有instance标注信息,已经这些图片复制到train2017_noann_result中以使进行查看
train2017该数据集有226/118287张图片是非RGB图像,已经这些图片复制到train2017_notrgb_result中以使进行查看
数据集train2017处理完成用时36.96分
start val2017
loading annotations into memory...
Done (t=13.58s)
loading annotations into memory...
Done (t=0.49s)
creating index...
index created!
100%|██████████| 5000/5000 [01:23<00:00, 60.12it/s]
val2017该数据集有48/5000张图片没有instance标注信息,已经这些图片复制到val2017_noann_result中以使进行查看
val2017该数据集有10/5000张图片是非RGB图像,已经这些图片复制到val2017_notrgb_result中以使进行查看
数据集val2017处理完成用时1.68分
主要处理非RGB图像以及后缀是JPG的图像转换成jpg,同时对片名称进行合格性检验,否则在darknet运行过程中会生成一个bad.list,有部分是因为图片名称不合格而无法读取造成的。
import os
from PIL import Image
from tqdm import tqdm
import re
import copy
def convert(datapath,background=False):
imgpaths = os.path.join('VOCdevkit',datapath,'JPEGImages')
annpaths = os.path.join('VOCdevkit',datapath,'Annotations')
if not os.path.exists(imgpaths):
print("该数据集不存")
return
imgfiles = sorted(os.listdir(imgpaths))
if not len(imgfiles):
print("该数据集中无图片")
else:
#避免使用加号、减号或者"."作为普通文件的第一个字符,文件名避免使用下列特殊字符,包括制表符和退格符
#['/', '\t', '\b', '@', '#', '$', '%', '^', '&', '*', '(', ')', '[', ']'],最长不超过255
p = "^[^+-./\s\t\b@#$%*()\[\]][^/\s\t\b@#$%*()\[\]]{1,254}$"
for imgfile in tqdm(imgfiles):
#进行文件名检查
newimgfile = copy.deepcopy(imgfile)
if not re.match(p,imgfile):
#文件名不符合要求进行处理
if not re.match("[^+-./\s\t\b@#$%*()\[\]]",imgfile[0]):
newimgfile=newimgfile[1:]
p1 = "[/\s\t\b@#$%*()\[\]]"
b = set(re.findall(p1,newimgfile))
for i in b:
newimgfile=newimgfile.replace(i,'_')
file_name,file_extend = os.path.splitext(imgfile)
new_file_name,new_file_extend = os.path.splitext(newimgfile)
imgpath = os.path.join(imgpaths,imgfile)
annpath = os.path.join(annpaths,file_name+'.xml')
destimgpath = os.path.join(imgpaths,new_file_name+file_extend)
destannpath = os.path.join(annpaths,new_file_name+'.xml')
#对图片进行重命名
os.rename(imgpath,destimgpath)
#对标注文件进行重命名,如果是背景图片,则不会有标注xml文件,因此不用对xml重命名
if not background:
os.rename(annpath,destannpath)
else:
destimgpath=os.path.join(imgpaths,imgfile)
try:
img = Image.open(destimgpath)
except:
print(f'{destimgpath} can not open,delete it ')
os.remove(destimgpath)
continue
if img.mode !='RGB':
img = img.convert('RGB')
#删除原图,保存转换后的图
os.remove(destimgpath)
img.save(destimgpath,quality=95)
file_name,file_extend = os.path.splitext(destimgpath)
if not file_extend=='.jpg':
file_extend = '.jpg'
os.rename(destimgpath,file_name+file_extend)
convert('COCO_VOC')
100%|██████████| 122218/122218 [00:11<00:00, 10727.26it/s]
在获得一份数据之后,我们总是要对数据进行分析的,获取一些统计信息,做为先验知识。这部分代码可以对所有VOC格式的数据进行统计。
import os
import shutil
import numpy as np
import pandas as pd
from tqdm import tqdm
import matplotlib.pyplot as plt
from collections import defaultdict
import xml.etree.ElementTree as ET
def check(year='VOC2007',show=False):
"""
输入数据文件名,返回有图没标注文件和有标注文件没图的数据路径
"""
######################################################################################################
##########################本节代码检查只有图或只有标注文件的情况##########################################
#######################################################################################################
data_path=os.path.join("VOCdevkit",year)
imgs_path = os.path.join(data_path,'JPEGImages')
anns_path = os.path.join(data_path,'Annotations')
#获取图片文件
img_names = set([os.path.splitext(i)[0] for i in os.listdir(imgs_path)])
ann_names = set([os.path.splitext(i)[0] for i in os.listdir(anns_path)])
print("########################################################################################数据集{}检验结果如下:######################################################################################################".format(year))
if not len(img_names):
print(' 该数据集没有图片')
return
img_ann = img_names-ann_names #有图没标注文件
ann_img = ann_names-img_names #有标注文件没有图
if len(img_ann):
print(" 有图片没标注文件的图片是:{} 等(只列前50个) 注意检查这些图片是否是背景图片".format({v for k,v in enumerate(img_ann) if k<50}))
else:
print(" 所有图片都有对应标注文件")
if len(ann_img):
print(" 有标注文件没有图片的标注文件是:{}(只列前50个)".format({v for k,v in enumerate(ann_img) if k<50}))
else:
print(" 所有标注文件都有对应图片")
#####################################################################################################
#######本节代码对于上节检查结果有问题的图片和标注文件统一移动到结果文件夹中进行下一步查看 ##################
#####################################################################################################
result_path = os.path.join(data_path,year+'_result')
if os.path.exists(result_path):
print(' 结果文件{}已经存在,请检查'.format(result_path))
if len(ann_img)+len(img_ann):
# 把只有图或只有标注文件的数据集全部移出来
if (not os.path.exists(result_path)):
os.makedirs(result_path)
else:
print(' 存在有图无标注或有标注无图的文件,另结果文件{}已经存在,请检查'.format(result_path))
# return
img_anns = [os.path.join(imgs_path,i+'.jpg') for i in img_ann]
ann_imgs = [os.path.join(anns_path,i+'.xml') for i in ann_img]
if len(img_anns):
for img in img_anns:
shutil.move(img,result_path)
print(' 移动只有图无标注文件完成')
if len(ann_img):
for ann in ann_imgs:
shutil.move(ann,result_path)
print(' 移动只有标注文件无图完成')
###################################################################################################
##########本节内容提取分类文件夹标注文件夹中所有的分类类别,这个部分由于数据可能是#######################
##########多个人标的,所在对于使用数据的人还是要看一下分类的,很有必要 #######################
ann_names_new = [os.path.join(anns_path,i) for i in os.listdir(anns_path)]#得新获取经过检查处理的标注文件
total_images_num = len(ann_names_new)
classes=list() #用来存放所有的标注框的分类名称
img_boxes = list() #用来存放单张图片的框的个数
hw_percents = list() #用来存放图像的高宽比,因为图像是要进行resize的,所以可能会有resize和scaled resize区分
num_imgs = defaultdict(int) # 存放每个分类有多少张图片出现
num_boxes = dict() # 存放每个分类有多少个框出现
h_imgs = list() # 存放每张图的高
w_imgs = list() # 存放每张图的宽
area_imgs = list() #存放每张图的面积
h_boxes = defaultdict(list) #存放每个分类框的高
w_boxes = defaultdict(list) #存放每个分类框的宽
area_boxes = defaultdict(list) #存放每个分类框的面积
area_percents = defaultdict(list) #存放每个分类框与图像面积的百分比
for ann in tqdm(ann_names_new):
try:
in_file=open(ann)
tree=ET.parse(in_file)
except:
print("打开标注文件失败:",ann)
root =tree.getroot()
size = root.find('size')
# print image_id
w = int(size.find('width').text)
h = int(size.find('height').text)
img_area = w * h
if img_area< 100:
print(f"有标注文件{ann}无图片尺寸,将被处理")
shutil.move(ann,result_path)
im_path=os.path.join(ann.rsplit(os.sep,2)[0],'JPEGImages',os.path.splitext(os.path.basename(ann))[0]+'.jpg')
shutil.move(im_path,result_path)
continue
img_boxes.append(len(root.findall('object')))
if not len(root.findall('object')):
print(f"有标注文件{ann}但没有标注框,将被处理,但也注意看是否是背景图")
shutil.move(ann,result_path)
i_path=os.path.join(ann.rsplit(os.sep,2)[0],'JPEGImages',os.path.splitext(os.path.basename(ann))[0]+'.jpg')
shutil.move(i_path,result_path)
continue
img_classes=[]
ok_flag=True
for obj in root.iter('object'):
difficult = obj.find('difficult').text
cls_name = obj.find('name').text
if isinstance(cls_name,type(None)) :
print(f"标注框类名有问题,标注文件将被处理,类名:{cls_name},标注文件:{ann}")
shutil.move(ann,result_path)
ok_flag=False
continue
elif isinstance(cls_name,str) and len(cls_name)<2:
ok_flag=False
print(f"标注框类名有问题,标注文件将被处理,类名:{cls_name},标注文件:{ann}")
shutil.move(ann,result_path)
continue
else:
pass
# if int(difficult) == 1:
# continue
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)) #左,右,上,下
if int(b[1]-b[0])==0 or int(b[3]-b[2])==0:
ok_flag=False
print(f"有零存在,框为点或直线,将被处理,边框:{b},标注文件:{ann},类名称:{cls_name}")
shutil.move(ann,result_path)
box_area = (b[1]-b[0])*(b[3]-b[2])
area_percent = round(np.sqrt(box_area/float(img_area)),3)*100
hw_percents.append(float(h/w))
if not (cls_name in classes):
classes.append(cls_name)
img_classes.append(cls_name)
num_boxes[cls_name]= num_boxes.get(cls_name,0)+1
h_boxes[cls_name].append(int(b[3]-b[2]))
w_boxes[cls_name].append(int(b[1]-b[0]))
area_boxes[cls_name].append(int(box_area))
area_percents[cls_name].append(area_percent)
if ok_flag:
h_imgs.append(h)
w_imgs.append(w)
area_imgs.append(img_area)
for img_cls_name in set(img_classes):
num_imgs[img_cls_name] = num_imgs.get(img_cls_name,0)+1
classes=sorted(classes)
print(f"数据集{year}一共有{total_images_num}张合格的标注图片,{sum(img_boxes)}个标注框,平均每张图有{round(sum(img_boxes)/total_images_num,2)}个标注框;一共有{len(classes)}个分类,分别是{classes};图片中标注框个数最少是{min(img_boxes)}, \
最多是{max(img_boxes)}.图片高度最小值是{min(h_imgs)},最大值是{max(h_imgs)};图片宽度最小值是{min(w_imgs)},最大值是{max(w_imgs)}; \
图片面积最小值是{min(area_imgs)},最大值是{max(area_imgs)} ;图片高宽比最小值是{round(min(hw_percents),2)},图片高宽比最大值是{round(max(hw_percents),2)}")
num_imgs_class = [num_imgs[class_name] for class_name in classes]
num_boxes_class = [num_boxes[class_name] for class_name in classes] #各分类的标注框个数
min_h_boxes = [min(h_boxes[class_name]) for class_name in classes] #各分类标注框高度最小值
max_h_boxes = [max(h_boxes[class_name]) for class_name in classes] #各分类标注框高度最大值
min_w_boxes = [min(w_boxes[class_name]) for class_name in classes] #各分类标注框宽度最小值
max_w_boxes = [max(w_boxes[class_name]) for class_name in classes] #各分类标注框宽度最大值
min_area_boxes = [min(area_boxes[class_name]) for class_name in classes] #各分类标注框面积最小值
max_area_boxes = [max(area_boxes[class_name]) for class_name in classes] #各分类标注框面积最大值
min_area_percents = [min(area_percents[class_name]) for class_name in classes] #各分类标注框面积与图像面积比最小值
max_area_percents = [max(area_percents[class_name]) for class_name in classes] #各分类标注框面积与图像面积比最大值
result = {'cls_names':classes,'images':num_imgs_class,'objects':num_boxes_class,'min_h_bbox':min_h_boxes,'max_h_bbox':max_h_boxes,'min_w_bbox':min_w_boxes,
'max_w_bbox':max_w_boxes,'min_area_bbox':min_area_boxes,'max_area_bbox':max_area_boxes,'min_area_box/img':min_area_percents,'max_area_box/img':max_area_percents}
#显示所有列(参数设置为None代表显示所有行,也可以自行设置数字)
pd.set_option('display.max_columns',None)
#显示所有行
pd.set_option('display.max_rows',None)
#设置数据的显示长度,默认为50
pd.set_option('max_colwidth',50)
#禁止自动换行(设置为Flase不自动换行,True反之)
pd.set_option('expand_frame_repr', False)
result_df = pd.DataFrame(result)
print(result_df)
# plt.figure(figsize=(10.8,6.4))
# result_df.iloc[:,1:3].plot(kind='bar',)
if show:
##############################################画各个类别图片数与框数的直方图############################################################
plt.figure(figsize=(15,6.4))
x1 = [i+4*i for i in range(len(classes))]
x2 = [i+2 for i in x1]
y1= [int(num_boxes[cl]) for cl in classes]
y2 = [int(num_imgs[cl]) for cl in classes]
lb1=["" for i in x1]
lb2=classes
plt.bar(x1,y1,alpha=0.7,width=2,color='b',label='objects',tick_label=lb1)
plt.bar(x2,y2,alpha=0.7,width=2,color='r',label='images',tick_label=lb2)
plt.xticks(rotation=45)
# plt.axis('off')
plt.legend()
#plt.savefig
##############################################画单张图标注框数量的直方图################################################################
#接着用直方图把这些结果画出来
plt.figure(figsize=(15,6.4))
# 定义组数,默认60
# 定义一个间隔大小
a = 1
# 得出组数
group_num= int((max(img_boxes) - min(img_boxes)) / a)
n,bins,patches=plt.hist(x=img_boxes,bins=group_num,color='c',edgecolor='red',density=False,rwidth=0.8)
for k in range(len(n)):
plt.text(bins[k], n[k]*1.02, int(n[k]), fontsize=12, horizontalalignment="center") #打标签,在合适的位置标注每个直方图上面样本数
# 组距
distance=int((max(img_boxes)-min(img_boxes)) /group_num)
if distance<1:
distance=1
plt.xticks(range(min(img_boxes),max(img_boxes)+2,distance),fontsize=8)
# 辅助显示设置
plt.xlabel('number of bbox in each image')
plt.ylabel('image numbers')
plt.xticks(rotation=45)
plt.title(f"The number of bbox min:{round(np.min(img_boxes),2)},max:{round(np.max(img_boxes),2)} \n mean:{round(np.mean(img_boxes),2)} std:{round(np.std(img_boxes),2)}")
plt.grid(True)
plt.tight_layout()
##############################################画单张图高宽比的直方图################################################################
plt.figure(figsize=(15,6.4))
# 定义组数,默认60
a = 0.1
# 得出组数
group_num= int((max(hw_percents) - min(hw_percents)) / a)
n,bins,patches=plt.hist(x=hw_percents,bins=group_num,color='c',edgecolor='red',density=False,rwidth=0.8)
for k in range(len(n)):
plt.text(bins[k], n[k]*1.02, int(n[k]), fontsize=12, horizontalalignment="center") #打标签,在合适的位置标注每个直方图上面样本数
# 组距
distance=int((max(hw_percents)-min(hw_percents)) /group_num)
if distance<1:
distance=1
plt.xticks(range(int(min(hw_percents)),int(max(hw_percents))+2,distance),fontsize=8)
# 辅助显示设置
plt.xlabel('image height/width in each image')
plt.ylabel('image numbers')
plt.xticks(rotation=45)
plt.title(f"image height/width min:{round(np.min(hw_percents))},max:{round(np.max(hw_percents),2)} \n mean:{round(np.mean(hw_percents),2)} std:{round(np.std(hw_percents),2)}")
plt.grid(True)
plt.tight_layout()
##############################################画各个分类框图面积比直方图################################################################
plt.figure(figsize=(8*3,8*round(len(classes)/3)))
for i,name in enumerate(classes):
plt.subplot(int(np.ceil(len(classes)/3)),3,i+1)
# 定义组数,默认60
a = 5
# 得出组数
group_num= int((max(area_percents[name]) - min(area_percents[name])) / a)
n,bins,patches=plt.hist(x=area_percents[name],bins=group_num,color='c',edgecolor='red',density=False,rwidth=0.8)
for k in range(len(n)):
plt.text(bins[k], n[k]*1.02, int(n[k]), fontsize=12, horizontalalignment="center") #打标签,在合适的位置标注每个直方图上面样本数
# 组距
distance=int((max(area_percents[name])-min(area_percents[name])) /group_num)
if distance<1:
distance=1
plt.xticks(range(int(min(area_percents[name])),int(max(area_percents[name]))+2,distance),fontsize=8)
# 辅助显示设置
plt.xlabel('area percent bbox/img')
plt.ylabel('boxes numbers')
plt.xticks(rotation=45)
plt.title(f"id {i+1} class {name} area percent min:{round(np.min(area_percents[name]),2)},max:{round(np.max(area_percents[name]),2)} \n mean:{round(np.mean(area_percents[name]),2)} std:{round(np.std(area_percents[name]),2)}")
plt.grid(True)
plt.tight_layout()
check("COCO_VOC")
0%| | 0/122218 [00:00, ?it/s]
########################################################################################数据集COCO_VOC检验结果如下:######################################################################################################
所有图片都有对应标注文件
所有标注文件都有对应图片
100%|██████████| 122218/122218 [00:30<00:00, 3964.01it/s]
数据集COCO_VOC一共有122218张合格的标注图片,896764个标注框,平均每张图有7.34个标注框;一共有80个分类,分别是['airplane', 'apple', 'backpack', 'banana', 'baseball bat', 'baseball glove', 'bear', 'bed', 'bench', 'bicycle', 'bird', 'boat', 'book', 'bottle', 'bowl', 'broccoli', 'bus', 'cake', 'car', 'carrot', 'cat', 'cell phone', 'chair', 'clock', 'couch', 'cow', 'cup', 'dining table', 'dog', 'donut', 'elephant', 'fire hydrant', 'fork', 'frisbee', 'giraffe', 'hair drier', 'handbag', 'horse', 'hot dog', 'keyboard', 'kite', 'knife', 'laptop', 'microwave', 'motorcycle', 'mouse', 'orange', 'oven', 'parking meter', 'person', 'pizza', 'potted plant', 'refrigerator', 'remote', 'sandwich', 'scissors', 'sheep', 'sink', 'skateboard', 'skis', 'snowboard', 'spoon', 'sports ball', 'stop sign', 'suitcase', 'surfboard', 'teddy bear', 'tennis racket', 'tie', 'toaster', 'toilet', 'toothbrush', 'traffic light', 'train', 'truck', 'tv', 'umbrella', 'vase', 'wine glass', 'zebra'];图片中标注框个数最少是1, 最多是93.图片高度最小值是51,最大值是640;图片宽度最小值是59,最大值是640; 图片面积最小值是3672,最大值是409600 ;图片高宽比最小值是0.16,图片高宽比最大值是4.1
cls_names images objects min_h_bbox max_h_bbox min_w_bbox max_w_bbox min_area_bbox max_area_bbox min_area_box/img max_area_box/img
0 airplane 3083 5278 2 629 3 640 6 389351 0.4 100.0
1 apple 1662 6090 2 622 2 640 8 367360 0.5 100.0
2 backpack 5756 9091 3 634 1 640 4 303360 0.4 100.0
3 banana 2346 9837 1 640 2 640 5 372668 0.4 100.0
4 baseball bat 2603 3422 2 637 2 595 8 319627 0.5 92.4
5 baseball glove 2729 3895 1 455 1 639 2 263907 0.3 98.2
6 bear 1009 1365 9 640 9 638 117 372698 2.0 100.0
7 bed 3831 4355 5 640 19 640 279 408960 3.2 100.0
8 bench 5805 10251 1 640 3 640 8 395605 0.5 100.0
9 bicycle 3401 7429 3 640 1 640 12 373932 0.6 100.0
10 bird 3362 11245 1 623 1 640 1 376420 0.2 99.7
11 boat 3146 11188 1 640 1 640 3 342081 0.3 100.0
12 book 5562 25876 1 632 1 640 5 388542 0.5 100.0
13 bottle 8880 25367 2 640 2 639 6 304164 0.4 99.5
14 bowl 7425 14984 2 640 3 640 24 402578 0.9 100.0
15 broccoli 2010 7624 3 570 5 640 15 320340 0.7 99.5
16 bus 4141 6354 2 640 4 640 15 399408 0.7 100.0
17 cake 3049 6669 2 630 6 640 14 381524 0.7 99.9
18 car 12786 45799 1 640 1 640 3 353280 0.3 100.0
19 carrot 1764 8223 1 624 2 640 4 387296 0.4 99.7
20 cat 4298 4970 12 638 6 640 90 381699 1.8 100.0
21 cell phone 5016 6695 1 626 1 640 1 305920 0.2 99.8
22 chair 13354 40281 1 640 1 640 2 367211 0.3 100.0
23 clock 4863 6601 2 626 2 640 4 375240 0.4 99.4
24 couch 4618 6040 6 640 14 640 204 405120 2.7 100.0
25 cow 2055 8527 2 619 3 640 12 380066 0.6 99.5
26 cup 9579 21549 1 632 2 639 4 374544 0.4 100.0
27 dining table 12338 16411 1 640 2 640 8 409600 0.5 100.0
28 dog 4562 5726 3 640 4 640 16 391707 0.8 100.0
29 donut 1585 7517 3 639 4 640 18 331360 0.8 99.8
30 elephant 2232 5768 2 639 4 640 8 392482 0.5 99.9
31 fire hydrant 1797 1966 5 640 3 640 20 296960 0.8 98.4
32 fork 3710 5694 1 483 2 640 9 271788 0.5 99.7
33 frisbee 2268 2797 2 492 4 639 10 236160 0.6 87.7
34 giraffe 2647 5363 6 640 4 640 40 367192 1.3 100.0
35 hair drier 198 209 7 390 10 408 147 124236 2.9 81.4
36 handbag 7133 12893 2 581 1 640 3 305920 0.3 99.8
37 horse 3069 6860 4 638 4 640 16 359744 0.7 99.2
38 hot dog 1273 3044 2 612 4 640 28 293280 1.0 98.8
39 keyboard 2221 3008 2 521 8 640 30 307200 1.0 100.0
40 kite 2352 9412 1 634 1 639 2 311748 0.3 99.8
41 knife 4507 8095 1 611 1 640 6 315500 0.5 91.3
42 laptop 3707 5201 2 633 3 640 6 397458 0.5 100.0
43 microwave 1601 1728 9 531 8 640 132 338778 2.9 100.0
44 motorcycle 3661 9096 4 640 2 640 8 358160 0.5 100.0
45 mouse 1964 2368 1 376 4 607 6 164388 0.4 73.2
46 orange 1784 6686 2 639 2 640 4 408321 0.4 99.9
47 oven 2992 3477 5 640 8 640 40 409600 1.2 100.0
48 parking meter 742 1345 3 640 2 640 6 386560 0.4 99.3
49 person 66808 273468 1 640 1 640 2 408321 0.2 100.0
50 pizza 3319 6106 2 637 7 640 16 400653 0.7 100.0
51 potted plant 4624 8995 4 640 4 640 25 398720 0.9 100.0
52 refrigerator 2461 2763 20 640 8 640 632 396264 5.0 100.0
53 remote 3221 5986 1 620 2 640 3 372645 0.5 97.9
54 sandwich 2463 4550 4 630 8 640 44 352728 1.3 99.7
55 scissors 975 1517 5 613 4 640 24 311370 0.9 99.2
56 sheep 1594 9870 2 599 2 639 4 318861 0.4 99.8
57 sink 4865 5835 1 630 3 640 6 362880 0.5 100.0
58 skateboard 3603 5722 1 577 3 640 6 318696 0.4 98.7
59 skis 3201 6883 1 630 2 637 3 350208 0.3 97.3
60 snowboard 1703 2752 1 600 2 640 2 272130 0.3 94.1
61 spoon 3682 6418 2 572 1 640 6 181506 0.5 73.3
62 sports ball 4431 6610 1 594 1 628 1 274946 0.2 95.7
63 stop sign 1803 2058 4 629 2 639 16 303525 0.7 100.0
64 suitcase 2507 6495 3 640 3 640 12 388696 0.6 100.0
65 surfboard 3635 6395 1 640 2 640 3 294674 0.4 99.4
66 teddy bear 2234 4984 7 640 8 639 70 349160 1.5 99.8
67 tennis racket 3561 5035 1 601 1 639 3 253920 0.3 94.9
68 tie 3955 6749 1 640 1 639 4 369981 0.4 99.9
69 toaster 225 234 11 377 7 483 156 151554 2.3 78.3
70 toilet 3502 4336 9 640 6 640 84 329478 1.5 99.9
71 toothbrush 1041 2011 1 591 1 607 6 217490 0.4 99.3
72 traffic light 4330 13520 2 626 1 620 2 299854 0.3 98.8
73 train 3745 4761 5 640 6 640 48 408960 1.4 100.0
74 truck 6377 10388 3 634 3 640 18 392960 0.8 100.0
75 tv 4768 6093 4 629 3 640 16 365976 0.9 100.0
76 umbrella 4142 11844 1 640 2 640 4 403840 0.4 100.0
77 vase 3730 6890 2 632 2 640 6 313140 0.5 99.3
78 wine glass 2643 8256 3 640 3 640 9 301301 0.6 99.0
79 zebra 2001 5571 6 636 2 640 14 382236 0.7 99.8
我们提取车辆类数据,然后和VOC中数据集进行合并,有些同一个类别但是不同名字
import os
import xml.etree.ElementTree as ET
import shutil
from tqdm import tqdm
def get_needed_classes_change_name(source_dataset="VOCdevkit/VOC2007",dest_dataset="VOCdevkit/VOC2007_dest",classes=None,new_classes=None):
"""
source_dataset:提取数据集位置
dest_daaset:提取后数据集存放位置
classes:指定要提取的分类,所有出现在该参数中的类都会被提取,如果是None则复制整个数据集
new_classes: 在classes 提取的分类中选取部分或全部进行修改,是个字典,如果是None则不需要进行修改这个是默认的
"""
if os.path.exists(dest_dataset):
shutil.rmtree(dest_dataset)
os.mkdir(dest_dataset)
else:
os.mkdir(dest_dataset)
if classes is not None:
img_filepath=os.path.join(source_dataset,'JPEGImages')
ann_filepath=os.path.join(source_dataset,'Annotations')
img_savepath= os.path.join(dest_dataset,'JPEGImages')
ann_savepath=os.path.join(dest_dataset,'Annotations')
main_path = os.path.join(dest_dataset,"ImageSets/Main")
if not os.path.exists(img_savepath):
os.makedirs(img_savepath)
if not os.path.exists(ann_savepath):
os.makedirs(ann_savepath)
if not os.path.exists(main_path):
os.makedirs(main_path)
change=False
if new_classes:
change=True
for name in new_classes.keys():
if not name in classes:
print("要改的名称必须要在所提取的类别中")
return
source_anns=os.listdir(ann_filepath)
for source_ann in tqdm(source_anns):
tree = ET.parse(os.path.join(ann_filepath,source_ann))
root = tree.getroot()
result = root.findall("object")
bool_num=0
for obj in result:
if obj.find("name").text not in classes:
root.remove(obj)
else:
if change and obj.find("name").text in new_classes.keys():
obj.find("name").text = new_classes[obj.find("name").text]
bool_num = 1
if bool_num:
tree.write(os.path.join(ann_savepath,source_ann),encoding='utf-8') #写进原始的xml文件中,防止中文乱码
name_img =os.path.splitext(source_ann)[0]+'.jpg'
shutil.copy(os.path.join(img_filepath,name_img),os.path.join(img_savepath,name_img))
else:
shutil.copytree(source_dataset,dest_dataset)
get_needed_classes_change_name(source_dataset="VOCdevkit/COCO_VOC",dest_dataset="VOCdevkit/COCO_VOC_DEST",classes=['bicycle','bus','car','motorcycle','truck'],new_classes={'motorcycle':'motorbike'})
100%|██████████| 122218/122218 [00:24<00:00, 4934.87it/s]
check("COCO_VOC_DEST")
3%|▎ | 562/20629 [00:00<00:03, 5615.65it/s]
########################################################################################数据集COCO_VOC_DEST检验结果如下:######################################################################################################
所有图片都有对应标注文件
所有标注文件都有对应图片
100%|██████████| 20629/20629 [00:03<00:00, 5984.76it/s]
数据集COCO_VOC_DEST一共有20629张合格的标注图片,79066个标注框,平均每张图有3.83个标注框;一共有5个分类,分别是['bicycle', 'bus', 'car', 'motorbike', 'truck'];图片中标注框个数最少是1, 最多是40.图片高度最小值是120,最大值是640;图片宽度最小值是160,最大值是640; 图片面积最小值是19200,最大值是409600 ;图片高宽比最小值是0.21,图片高宽比最大值是3.33
cls_names images objects min_h_bbox max_h_bbox min_w_bbox max_w_bbox min_area_bbox max_area_bbox min_area_box/img max_area_box/img
0 bicycle 3401 7429 3 640 1 640 12 373932 0.6 100.0
1 bus 4141 6354 2 640 4 640 15 399408 0.7 100.0
2 car 12786 45799 1 640 1 640 3 353280 0.3 100.0
3 motorbike 3661 9096 4 640 2 640 8 358160 0.5 100.0
4 truck 6377 10388 3 634 3 640 18 392960 0.8 100.0
check("VOC2007")
1%| | 102/9963 [00:00<00:09, 1019.40it/s]
########################################################################################数据集VOC2007检验结果如下:######################################################################################################
所有图片都有对应标注文件
所有标注文件都有对应图片
100%|██████████| 9963/9963 [00:03<00:00, 2568.30it/s]
数据集VOC2007一共有9963张合格的标注图片,30638个标注框,平均每张图有3.08个标注框;一共有20个分类,分别是['aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor'];图片中标注框个数最少是1, 最多是42.图片高度最小值是96,最大值是500;图片宽度最小值是127,最大值是500; 图片面积最小值是43090,最大值是250000 ;图片高宽比最小值是0.19,图片高宽比最大值是3.21
cls_names images objects min_h_bbox max_h_bbox min_w_bbox max_w_bbox min_area_bbox max_area_bbox min_area_box/img max_area_box/img
0 aeroplane 445 642 13 465 14 499 208 213642 3.4 99.8
1 bicycle 505 807 9 499 10 499 110 186626 2.4 99.8
2 bird 622 1175 9 490 5 498 126 217070 2.6 99.5
3 boat 364 791 5 497 4 499 44 186127 1.6 99.6
4 bottle 502 1291 10 499 4 468 80 152195 2.1 99.7
5 bus 380 526 9 475 12 499 198 188325 3.2 99.7
6 car 1536 3185 4 497 6 499 48 196560 1.6 99.7
7 cat 676 759 22 499 25 499 625 241056 5.8 99.7
8 chair 1117 2806 5 499 4 499 78 212768 2.0 99.8
9 cow 273 685 7 490 7 499 56 179280 1.8 97.8
10 diningtable 510 609 10 476 21 499 567 185754 5.7 99.5
11 dog 863 1068 10 499 10 499 100 232035 2.3 99.7
12 horse 573 801 21 499 11 499 297 197691 4.0 99.3
13 motorbike 482 759 18 498 7 499 182 198000 3.1 99.4
14 person 4192 10674 8 499 4 499 48 248003 1.7 99.8
15 pottedplant 527 1217 6 499 6 498 84 226080 2.1 99.6
16 sheep 195 664 5 482 9 485 45 226980 1.5 96.2
17 sofa 727 821 31 499 24 499 1638 209237 9.4 99.8
18 train 522 630 20 499 20 499 725 186127 6.2 99.6
19 tvmonitor 534 728 11 498 11 499 176 185754 3.4 99.5
check("VOC2012")
1%| | 100/17125 [00:00<00:17, 993.85it/s]
########################################################################################数据集VOC2012检验结果如下:######################################################################################################
所有图片都有对应标注文件
所有标注文件都有对应图片
100%|██████████| 17125/17125 [00:06<00:00, 2743.98it/s]
数据集VOC2012一共有17125张合格的标注图片,40138个标注框,平均每张图有2.34个标注框;一共有20个分类,分别是['aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor'];图片中标注框个数最少是1, 最多是56.图片高度最小值是71,最大值是500;图片宽度最小值是142,最大值是500; 图片面积最小值是35200,最大值是250000 ;图片高宽比最小值是0.14,图片高宽比最大值是3.52
cls_names images objects min_h_bbox max_h_bbox min_w_bbox max_w_bbox min_area_bbox max_area_bbox min_area_box/img max_area_box/img
0 aeroplane 716 1002 4 481 10 499 44 191117 1.6 99.8
1 bicycle 603 837 11 499 8 499 108 213323 2.4 99.8
2 bird 811 1271 7 493 6 499 48 214722 1.7 99.8
3 boat 549 1059 4 499 5 499 40 186626 1.5 99.8
4 bottle 812 1561 7 499 5 499 55 187798 1.7 99.8
5 bus 467 685 12 499 7 499 153 239121 3.3 99.8
6 car 1284 2492 4 499 6 499 30 230175 1.3 99.8
7 cat 1128 1277 18 499 13 499 234 249001 3.5 99.8
8 chair 1366 3056 6 499 9 499 126 197904 2.6 99.8
9 cow 340 771 7 499 9 499 99 191117 3.0 99.8
10 diningtable 691 800 8 499 13 499 377 186626 4.5 99.8
11 dog 1341 1598 8 499 11 499 128 242055 2.6 99.8
12 horse 526 803 13 499 11 499 168 218562 3.2 99.8
13 motorbike 575 801 13 499 14 499 221 249001 3.8 99.8
14 person 9583 17401 6 499 2 499 14 242015 0.9 99.8
15 pottedplant 613 1202 8 499 7 499 72 248502 2.0 99.8
16 sheep 357 1084 5 468 7 499 35 223552 1.4 99.8
17 sofa 742 841 27 499 27 499 729 245009 6.6 99.8
18 train 589 704 12 499 8 499 96 204091 2.3 99.8
19 tvmonitor 645 893 9 499 14 499 272 225944 3.9 99.8
import os
import xml.etree.ElementTree as ET
import shutil
from tqdm import tqdm
def get_needed_classes(source_dataset="VOCdevkit/VOC2007",dest_dataset="VOCdevkit/VOC2007_dest",classes=None):
"""
source_dataset:提取数据集位置
dest_daaset:提取后数据集存放位置
classes:指定要提取的分类,所有出现在该参数中的类都会被提取,如果是None则复制整个数据集
"""
if os.path.exists(dest_dataset):
shutil.rmtree(dest_dataset)
os.mkdir(dest_dataset)
else:
os.mkdir(dest_dataset)
if classes is not None:
img_filepath=os.path.join(source_dataset,'JPEGImages')
ann_filepath=os.path.join(source_dataset,'Annotations')
img_savepath= os.path.join(dest_dataset,'JPEGImages')
ann_savepath=os.path.join(dest_dataset,'Annotations')
main_path = os.path.join(dest_dataset,"ImageSets/Main")
if not os.path.exists(img_savepath):
os.makedirs(img_savepath)
if not os.path.exists(ann_savepath):
os.makedirs(ann_savepath)
if not os.path.exists(main_path):
os.makedirs(main_path)
source_anns=os.listdir(ann_filepath)
for source_ann in tqdm(source_anns):
tree = ET.parse(os.path.join(ann_filepath,source_ann))
root = tree.getroot()
result = root.findall("object")
bool_num=0
for obj in result:
if obj.find("name").text not in classes:
root.remove(obj)
else:
bool_num = 1
if bool_num:
tree.write(os.path.join(ann_savepath,source_ann))
name_img =os.path.splitext(source_ann)[0]+'.jpg'
shutil.copy(os.path.join(img_filepath,name_img),os.path.join(img_savepath,name_img))
else:
shutil.copytree(source_dataset,dest_dataset)
get_needed_classes(source_dataset="VOCdevkit/VOC2007",dest_dataset="VOCdevkit/VOC2007_DEST",classes=['bicycle','car','bus','motorbike'])
100%|██████████| 9963/9963 [00:14<00:00, 668.37it/s]
get_needed_classes(source_dataset="VOCdevkit/VOC2012",dest_dataset="VOCdevkit/VOC2012_DEST",classes=['bicycle','car','bus','motorbike'])
100%|██████████| 17125/17125 [00:22<00:00, 761.74it/s]
check("VOC2007_DEST")
32%|███▏ | 827/2601 [00:00<00:00, 8260.46it/s]
########################################################################################数据集VOC2007_DEST检验结果如下:######################################################################################################
所有图片都有对应标注文件
所有标注文件都有对应图片
100%|██████████| 2601/2601 [00:00<00:00, 7237.91it/s]
数据集VOC2007_DEST一共有2601张合格的标注图片,5277个标注框,平均每张图有2.03个标注框;一共有4个分类,分别是['bicycle', 'bus', 'car', 'motorbike'];图片中标注框个数最少是1, 最多是15.图片高度最小值是96,最大值是500;图片宽度最小值是156,最大值是500; 图片面积最小值是43090,最大值是250000 ;图片高宽比最小值是0.19,图片高宽比最大值是3.21
cls_names images objects min_h_bbox max_h_bbox min_w_bbox max_w_bbox min_area_bbox max_area_bbox min_area_box/img max_area_box/img
0 bicycle 505 807 9 499 10 499 110 186626 2.4 99.8
1 bus 380 526 9 475 12 499 198 188325 3.2 99.7
2 car 1536 3185 4 497 6 499 48 196560 1.6 99.7
3 motorbike 482 759 18 498 7 499 182 198000 3.1 99.4
check("VOC2012_DEST")
27%|██▋ | 668/2512 [00:00<00:00, 6678.30it/s]
########################################################################################数据集VOC2012_DEST检验结果如下:######################################################################################################
所有图片都有对应标注文件
所有标注文件都有对应图片
100%|██████████| 2512/2512 [00:00<00:00, 7576.06it/s]
数据集VOC2012_DEST一共有2512张合格的标注图片,4815个标注框,平均每张图有1.92个标注框;一共有4个分类,分别是['bicycle', 'bus', 'car', 'motorbike'];图片中标注框个数最少是1, 最多是24.图片高度最小值是112,最大值是500;图片宽度最小值是191,最大值是500; 图片面积最小值是44000,最大值是250000 ;图片高宽比最小值是0.22,图片高宽比最大值是2.62
cls_names images objects min_h_bbox max_h_bbox min_w_bbox max_w_bbox min_area_bbox max_area_bbox min_area_box/img max_area_box/img
0 bicycle 603 837 11 499 8 499 108 213323 2.4 99.8
1 bus 467 685 12 499 7 499 153 239121 3.3 99.8
2 car 1284 2492 4 499 6 499 30 230175 1.3 99.8
3 motorbike 575 801 13 499 14 499 221 249001 3.8 99.8
这个功能是为了查看数据集的是否标注正确的
import os
import xml.etree.ElementTree as ET
import shutil
import numpy as np
from tqdm import tqdm
import cv2
def draw(source_dataset="VOCdevkit/VOC2007_dest"):
#生成保存画图后结果的文件
draw_path = os.path.join(source_dataset,"draw_results")
if not os.path.exists(draw_path):
os.makedirs(draw_path)
else:
shutil.rmtree(draw_path)
os.makedirs(draw_path)
ann_filepath=os.path.join(source_dataset,'Annotations')
source_anns=os.listdir(ann_filepath)
for source_ann in tqdm(source_anns):
source_img = os.path.join(source_dataset,'JPEGImages',os.path.splitext(source_ann)[0]+'.jpg')
save_img = os.path.join(draw_path,os.path.splitext(source_ann)[0]+'.jpg')
img = cv2.imdecode(np.fromfile(source_img,dtype=np.uint8),-1)
if img is None or not img.any():
continue
tree = ET.parse(os.path.join(ann_filepath,source_ann))
root = tree.getroot()
result = root.findall("object")
for obj in result:
name = obj.find("name").text
x1=int(obj.find('bndbox').find('xmin').text)
y1=int(obj.find('bndbox').find('ymin').text)
x2=int(obj.find('bndbox').find('xmax').text)
y2=int(obj.find('bndbox').find('ymax').text)
cv2.rectangle(img,(x1,y1),(x2,y2),(0,0,255),2)
cv2.putText(img,name,(max(x1,15),max(y1,15)),cv2.FONT_ITALIC,1,(0,255,0,2))
cv2.imencode('.jpg',img)[1].tofile(save_img)
draw("VOCdevkit/COCO_VOC_DEST")
100%|██████████| 20629/20629 [05:17<00:00, 64.99it/s]
#在jupyter中逐张图显示
import cv2
from matplotlib import pyplot as plt
import glob
import numpy as np
img_paths = glob.glob('VOCdevkit/COCO_VOC_DEST/draw_results/*.jpg')
plt.figure(figsize=(15*5,15))
for i,img_path in enumerate(np.random.choice(img_paths,5,False)):
plt.subplot(1,5,i+1)
img = cv2.imread(img_path)
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
plt.imshow(img)
plt.axis('off')
plt.tight_layout()
plt.show()
生成好的数据需要检查,如果是在本机上,可以打开文件件进行查看,如果是远程服务器,那么可以在jupyter 中动态的一张一张自动更新的查看,方法可以参考本文