$ tree -L 1 VOCdevkit/VOC2007
VOCdevkit/VOC2007
├── Annotations
├── ImageSets
├── JPEGImages
├── SegmentationClass
└── SegmentationObject
5 directories, 0 files
要注意两点,首先是图像文件名后缀,有时是jpg
,有时是JPG
;其次是有些灰度图混在数据集中,这时用OpenCV打开图片,可以查看图片Channel数或用PIL打开图片,查看img.mode,如果输出是L
,那么则是灰度图;但有时候灰度图重复三次,伪装成RGB图,这时我们将不会发现。当然,对于灰度图,我们处理时可以直接移除,也可以伪装成RGB图,整体对检测性能影响不大。当图片是灰度图时,darknet 会报错错误Error in load_data_detection() - OpenCV
。
在做其它操作前,选对数据进行正确的转换有两部分第一部分是非RGB图转换成RGB,另一个操作是对文件夹中文件名后缀整体变小写jpg(方便后续代码处理).代码如下:
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)
#对标注文件进行重命名
if not background:
os.rename(annpath,destannpath)
else:
destimgpath=os.path.join(imgpaths,imgfile)
img = Image.open(destimgpath)
if img.mode !='RGB':
img = img.convert('RGB')
#删除原图,保存转换后的图
os.remove(destimgpath)
img.save(destimgpath,quality=95)
file_name,file_extend = os.path.splitext(imgfile)
if not file_extend=='.jpg':
file_extend = '.jpg'
os.rename(destimgpath,file_name+file_extend)
检查数据集的目地是对数据质量、数据的整体指标做一个分析。
主要检查有:
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))
else:
os.makedirs(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(f"打开标注文件{ann}失败,文件将被处理")
shutil.move(ann,result_path)
im_path=os.path.join(ann.split(os.sep)[0],ann.split(os.sep)[1],'JPEGImages',os.path.splitext(ann)[0].split(os.sep)[-1]+'.jpg')
shutil.move(im_path,result_path)
continue
root =tree.getroot()
try:
size = root.find('size')
# print image_id
w = int(size.find('width').text)
h = int(size.find('height').text)
except:
print(f"取标注尺寸错误,标注文件{ann}将被处理")
shutil.move(ann,result_path)
im_path=os.path.join(ann.split(os.sep)[0],ann.split(os.sep)[1],'JPEGImages',os.path.splitext(ann)[0].split(os.sep)[-1]+'.jpg')
shutil.move(im_path,result_path)
continue
img_area = w * h
if img_area< 100:
print(f"有标注文件{ann}无图片尺寸,将被处理")
shutil.move(ann,result_path)
im_path=os.path.join(ann.split(os.sep)[0],ann.split(os.sep)[1],'JPEGImages',os.path.splitext(ann)[0].split(os.sep)[-1]+'.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.split(os.sep)[0],ann.split(os.sep)[1],'JPEGImages',os.path.splitext(ann)[0].split(os.sep)[-1]+'.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},标注文件:{ann}")
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()
以VOC2007数据集为例,查看一下处理结果
check('VOC2007',True)
########################################################################################数据集VOC2007检验结果如下:######################################################################################################
所有图片都有对应标注文件
所有标注文件都有对应图片
100%|██████████| 9963/9963 [00:02<00:00, 4454.07it/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
应用场景就是把某些分类数据从原始数据集中移出,比如人工标注的数据集有些标错了,如把car标成carr,会改变原始数据集,该工作也是接着上一步操作进行.移出原则是图中有一个框在要移出类别中,即要将整个图片和他对应的标注文件移出。对于移出后的数据,我们可以进行进一步处理。
移出后保存但置对应数VOCdevkit/数据名/据集名_result
import os
import shutil
from tqdm import tqdm
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from collections import defaultdict
import xml.etree.ElementTree as ET
def remove_classes(year='VOC2007',classes=None):
"""
输入数据文件名,将指定分类数据移出
classes:如果是None,那么保持原数据集不变,否则是个列表,列出要移动的分类即可
"""
######################################################################################################
##########################本节代码检查只有图或只有标注文件的情况##########################################
#######################################################################################################
data_path=os.path.join("VOCdevkit",year)
imgs_path = os.path.join(data_path,'JPEGImages')
anns_path = os.path.join(data_path,'Annotations')
if not len(os.listdir(imgs_path)):
print(' 该数据集没有图片')
return
#####################################################################################################
###############################################################保存结果文件 ##########################
#####################################################################################################
result_path = os.path.join(data_path,year+'_result')
if os.path.exists(result_path):
print(' 结果文件{}已经存在,请检查'.format(result_path))
else:
os.makedirs(result_path)
if classes is not None:
source_anns=os.listdir(anns_path)
for source_ann in tqdm(source_anns):
tree = ET.parse(os.path.join(anns_path,source_ann))
root = tree.getroot()
result = root.findall("object")
for obj in result:
if obj.find("name").text in classes:
shutil.move(os.path.join(anns_path,source_ann),result_path)
img_path = os.path.join(data_path,'JPEGImages',os.path.splitext(source_ann)[0])+'.jpg'
shutil.move(img_path,result_path)
break
else:
pass
比如数据集VOC2007,20个分类中车辆类有car,bicycle,motobike,bus四类,我们将其移出
remove_classes(year='VOC2007',classes=["bus",'bicycle','car','motorbike'])
100%|██████████| 9963/9963 [00:00<00:00, 19330.61it/s]
查看一下移出后结果:
$ tree -L 1 VOCdevkit/VOC2007
VOCdevkit/VOC2007
├── Annotations
├── ImageSets
├── JPEGImages
├── SegmentationClass
├── SegmentationObject
└── VOC2007_result #这是移出后数据的存放位置
6 directories, 0 files
看一下VOC2007_result中的结果:
$ tree -L 1 VOCdevkit/VOC2007/VOC2007_result/
VOCdevkit/VOC2007/VOC2007_result/
├── 000004.jpg
├── 000004.xml
├── 000007.jpg
├── 000007.xml
├── 000012.jpg
├── 000012.xml
......
├── 009959.xml
├── 009963.jpg
└── 009963.xml
0 directories, 4428 files
接着再看一下VOC2007数据集,移出4个分类以后,应该还有16个分类:
check('VOC2007')
########################################################################################数据集VOC2007检验结果如下:######################################################################################################
所有图片都有对应标注文件
所有标注文件都有对应图片
结果文件VOCdevkit/VOC2007/VOC2007_result已经存在,请检查
100%|██████████| 7362/7362 [00:00<00:00, 11036.42it/s]
数据集VOC2007一共有7362张合格的标注图片,21430个标注框,平均每张图有2.91个标注框;一共有16个分类,分别是['aeroplane', 'bird', 'boat', 'bottle', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', 'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor'];图片中标注框个数最少是1, 最多是42.图片高度最小值是99,最大值是500;图片宽度最小值是127,最大值是500; 图片面积最小值是43750,最大值是250000 ;图片高宽比最小值是0.2,图片高宽比最大值是3.15
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 426 618 13 465 14 499 208 213642 3.4 99.8
1 bird 618 1171 9 490 5 498 126 217070 2.6 99.5
2 boat 342 743 5 497 4 499 44 186127 1.6 99.6
3 bottle 485 1264 10 499 4 468 80 152195 2.1 99.7
4 cat 668 751 22 499 25 499 625 241056 5.8 99.7
5 chair 1082 2740 5 499 4 499 78 212768 2.0 99.8
6 cow 264 652 8 490 7 499 56 179280 1.8 97.8
7 diningtable 506 605 10 476 21 499 567 185754 5.7 99.5
8 dog 841 1038 10 499 10 499 100 232035 2.3 99.7
9 horse 550 768 21 499 11 499 297 197691 4.0 99.3
10 person 2970 7185 8 499 6 499 48 248003 1.7 99.8
11 pottedplant 503 1153 6 499 6 498 84 226080 2.1 99.6
12 sheep 192 654 5 482 9 485 45 226980 1.5 96.2
13 sofa 703 791 33 499 24 499 1638 209237 10.0 99.8
14 train 491 594 20 499 20 499 725 185381 6.2 99.6
15 tvmonitor 516 703 11 498 13 499 176 185754 3.4 99.5
应用场景是有的数据集比较大,但是我们需要的只是其中几个类别,此时要进行提取,不改变原始数据集,但会生成新的数据集。这种情况下,我们的主要目地是对提取的数据比较感兴趣。上一步中我们处理后的数据还原,接着进行本步骤实验。
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(classes=['bicycle','car','motorbike','bus'])
100%|██████████| 9963/9963 [00:02<00:00, 4188.22it/s]
接着查看提取后数据集VOC2007_dest:
tl@aiot:~/VOC/VOCdevkit$ tree -L 1 VOC2007_dest/
VOC2007_dest/
├── Annotations
├── ImageSets
└── JPEGImages
3 directories, 0 files
check("VOC2007_dest")
########################################################################################数据集VOC2007_dest检验结果如下:######################################################################################################
所有图片都有对应标注文件
所有标注文件都有对应图片
100%|██████████| 2601/2601 [00:00<00:00, 13031.95it/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
应用场景是从多个原始数据集中提取特定分类,但同一个类别在不同数据集中使用不同的名称,这时需要统一名称,不改变原始数据集。比如VOC中叫motorbike,COCO数据集叫motorcycle
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)
如我们提取VOC2007中机动车数据,但是要修改motorbike为motorcycle,car改为汽车的拼音
get_needed_classes_change_name(source_dataset="VOCdevkit/VOC2007",dest_dataset="VOCdevkit/VOC2007_dest",classes=['bicycle','car','motorbike','bus'],new_classes={"car":"qiche","motorbike":"motorcycle"})
100%|██████████| 9963/9963 [00:00<00:00, 10228.99it/s]
查看一下提取后的数据集
check("VOC2007_dest")
########################################################################################数据集VOC2007_dest检验结果如下:######################################################################################################
所有图片都有对应标注文件
所有标注文件都有对应图片
100%|██████████| 2601/2601 [00:00<00:00, 12146.91it/s]
数据集VOC2007_dest一共有2601张合格的标注图片,5277个标注框,平均每张图有2.03个标注框;一共有4个分类,分别是['bicycle', 'bus', 'motorcycle', 'qiche'];图片中标注框个数最少是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 motorcycle 482 759 18 498 7 499 182 198000 3.1 99.4
3 qiche 1536 3185 4 497 6 499 48 196560 1.6 99.7
将数据集画图后进行保存,可以进行查看,可视化主要是要将标注框画到图中,从而看训练数据的标注情况。
以下将提供两种画图方法,一种是对数据集整体画数据结果,另一种是对单张图片进行查看,结果自动保存在数据集所在文件夹下draw_result中
import os
import xml.etree.ElementTree as ET
import shutil
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')
if not os.path.exists(source_img):
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)
def draw_single_image(ann_path,img_path,save_path=None):
"""
ann_path:指定xml的绝对路径
img_path:指定xml的绝对路径
save_path:如果不是None,那么将是结果图的保存路径;反之则画出来
"""
img = cv2.imdecode(np.fromfile(img_path,dtype=np.uint8),-1)
if img is None or not img.any():
raise '有空图'
tree = ET.parse(ann_path)
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))
if save_path is None:
imgrgb = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
plt.figure(figsize=(20,10))
plt.imshow(imgrgb)
else:
cv2.imencode('.jpg',img)[1].tofile(save_path)
draw(source_dataset="VOCdevkit/VOC2007_dest")
100%|██████████| 2601/2601 [00:12<00:00, 215.68it/s]
对于画好的多张图片,可以在本机直接查看,对于在jupyter中可以对于批量图(最少一张)来进行查看):
from IPython.display import clear_output, display, HTML
from PIL import Image
import matplotlib.pyplot as plt
import time
import cv2
import base64
import glob
current_time = 0
# 图像处理函数
def processImg(img):
# 画出一个框
# cv2.rectangle(img, (500, 300), (800, 400), (0, 0, 255), 5, 1, 0)
# 上下翻转
# img= cv2.flip(img, 0)
# 显示FPS
global current_time
if current_time == 0:
current_time = time.time()
else:
last_time = current_time
current_time = time.time()
fps = 1. / (current_time - last_time)
text = "FPS: %d" % int(fps)
cv2.putText(img, text , (0,100), cv2.FONT_HERSHEY_TRIPLEX, 3.65, (255, 0, 0), 2)
# img = cv2.resize(img,(1080,1080))
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
return img
def arrayShow(imageArray):
# ret, png = cv2.imencode('.png', imageArray)
# encoded = base64.b64encode(png)
# return Image(data=encoded.decode('ascii'))
return Image.fromarray(imageArray)
img_paths = glob.glob('VOCdevkit/VOC2007_dest/draw_results/*.jpg')
small=1
while(True):
try:
clear_output(wait=True)
ret, frame = video.read()
lines, columns, _ = frame.shape
frame = processImg(frame)
frame = cv2.resize(frame, (int(columns / small), int(lines / small)))
img = arrayShow(frame)
display(img)
# 控制帧率
time.sleep(0.02)
except KeyboardInterrupt:
video.release()
import os
import time
from tqdm import tqdm
from PIL import Image
import cv2
from IPython.display import clear_output, display, HTML
def show_images(images:list,small:int=1) -> str:
"""
images用来存放图片的绝对路径
small用来缩小图像大小,便于显示
"""
current_time = 0
for img_path in tqdm(images):
clear_output(wait=True)
img = cv2.imdecode(np.fromfile(img_path,dtype=np.uint8),-1)
if img is None or not img.any():
continue
h,w,_ = img.shape
##这里可以对图像进行处理操作
######################
if current_time==0:
current_time=time.time()
else:
last_time=current_time
current_time=time.time()
fps = 1. / (current_time - last_time)
text = "FPS: %d" % int(fps)
cv2.putText(img, text , (0,50), cv2.FONT_HERSHEY_TRIPLEX, 1, (255, 0, 0), 1)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, (int(w / small), int(h / small)))
img = Image.fromarray(img)
display(img)
# 控制帧率
time.sleep(5)
import glob
img_paths = glob.glob('VOCdevkit/VOC2007_dest/draw_results/*.jpg')
show_images(img_paths)
对于单张图可以直接可视化,也可以保存到文件中
draw_single_image('VOCdevkit/VOC2007_dest/Annotations/000020.xml','VOCdevkit/VOC2007_dest/JPEGImages/000012.jpg')
如果是保存成图片,想要显示在jupyter notebook中还可以这样
%%html
<img src="VOCdevkit/VOC2007_dest/JPEGImages/000012.jpg",width=400,height=200>