【darknet】1、yolov4模型训练之数据处理

文章目录

  • 1、COCO数据转VOC
  • 2、 转换后VOC格式COCO数据的处理
  • 3、COCO转换VOC后数据集统计
  • 4、从COCO转VOC的数据集中提取特定分类并改名字
  • 5、对提取并改名后的数据集进行分析
  • 6、分析VOC2007和2012数据集
  • 7、提取VOC2007和VOC2012中的目标数据
  • 8、对VOC提取后的数据集做分析
  • 9、整个数据集的可视化

本文主要写了使用yolov4进行模型训练前的数据准备。darknet框架使用的数据格式是yolo格式,但我们常见的目标检测数据格式是COCO和VOC.所以本文将以车辆检测为例,将coco2017和voc2007及voc2012中所有数据中的车辆中的图片进行转换,组合成一个新的数据集。总共有四个分类,bicycle,bus,car,motorcycle,truck.对于同一类型,coco和voc起的名字不同,所以我们会有处理。关于数据处理这块,也可以参考我的其它博客

1、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分

2、 转换后VOC格式COCO数据的处理

主要处理非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]

3、COCO转换VOC后数据集统计

在获得一份数据之后,我们总是要对数据进行分析的,获取一些统计信息,做为先验知识。这部分代码可以对所有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

4、从COCO转VOC的数据集中提取特定分类并改名字

我们提取车辆类数据,然后和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]

5、对提取并改名后的数据集进行分析

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

6、分析VOC2007和2012数据集

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

7、提取VOC2007和VOC2012中的目标数据

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] 

8、对VOC提取后的数据集做分析

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

9、整个数据集的可视化

这个功能是为了查看数据集的是否标注正确的

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 中动态的一张一张自动更新的查看,方法可以参考本文

你可能感兴趣的:(目标检测,#,darknet,目标检测,深度学习,计算机视觉)