yolo标注文件转换工具,python做的

目录

1 事先准备的文件:

1.1 需要训练的图片

1.2 标注的xml文件

2 功能:

3 用法:

4 注意事项:


1 事先准备的文件:

1.1 需要训练的图片

       要求路径中有一个子目录名称是 images (或 imgs ) ,训练程序需要通过 /images/ (或 /imgs/ )  替换为 /labels/ 找同名.txt文件提取标注数据
       默认情况下,yolov5是 images, yolov4是 imgs
       图片无需拷贝到yolo目录

1.2 标注的xml文件

# 支持xml格式 或 voc 格式,可用标注精灵或labelimg。后者是python程序,但连续标注效率高,推荐使用

2 功能:

    1)将标注数据转为yolo格式,保存到 <labeltxtpath>/<img>.txt

# <labeltxtpath>=<imgpath> 中最后一个 images (或 imgs ) 换为 labels,扩展名改为txt
    2)生成 data/obj.names,类别名称列表
    3)创建清单文件 data/train.txt,val.txt,test.txt

# 三者按指定比例随机比例, 不重复。5000-1万以上的图片,建议val 500左右,test 100 左右

3 用法:

1) 测试、调整图片配比
 python make_label_yolo.py --runtest yes --imgpath E:\models\test1\images --labelxmlpath E:\models\test1\xmls --train_percent 0.9 --val_percent 0.08 --test_percent 0.02

2) 正式转换
 python make_label_yolo.py --imgpath E:\models\test1\images --labelxmlpath E:\models\test1\xmls --train_percent 0.9 --val_percent 0.08 --test_percent 0.02

4 注意事项:

#1)忽略 images 和 labelsxml 以.开头的文件;
#2)labelsxml中的标签文件,只有在 imgpath 存在同名图片才采用。
#3)需要训练的图片无需拷贝到 <yolo>/data/images
#4)根据yolo要求,txt标注文件生成在实际图片 images (或 imgs ) 并列目录 labels
#5)训练用到的文件清单描述文件,保存在 <yolo>/data/train.txt, val.txt, test.txt
#6)跳过宽w或高h小于指定大小的标注
#7)train/训练,val/校准,test/测验配比默认为 0.9 0.08 0.02,命令行参数修改
#8)视频提取的图片文件名称带 grp_ 前缀,这些文件不参与训练时校准val、测验test

 

脚本文件1: make_label_yolo.py,转换格式、随机分配 train val test清单
 

# 事先准备的文件:
#   需要训练的图片
#       要求路径中有一个子目录名称是 images (或 imgs ) ,训练程序需要通过 /images/ (或 /imgs/ )  替换为 /labels/ 找同名.txt文件提取标注数据
#       默认情况下,yolov5是 images, yolov4是 imgs
#       图片无需拷贝到yolo目录
#   标注的xml文件 # xml格式 或 voc 格式
# 功能:
#    1)将标注数据转为yolo格式,保存到 <labeltxtpath>/<img>.txt # <labeltxtpath>=<imgpath> 中最后一个 images (或 imgs ) 换为 labels,扩展名改为txt
#    3)生成 data/obj.names
#    4)创建清单文件 data/train.txt,val.txt,test.txt # 三者分配比例由命令行参数确定, 不重复
#    5)坐标越界的标注,会自动纠正,所以不应担心中间异常数据导致中途退出
# 用法:
# 测试、调整图片配比
# python make_label_yolo.py --runtest yes --imgpath E:\models\test1\images --labelxmlpath E:\models\test1\xmls --train_percent 0.9 --val_percent 0.08 --test_percent 0.02
# 正式转换
# python make_label_yolo.py --imgpath E:\models\test1\images --labelxmlpath E:\models\test1\xmls --train_percent 0.9 --val_percent 0.08 --test_percent 0.02

#注意事项:
#1)忽略 images 和 labelsxml 以.开头的文件;
#2)labelsxml中的标签文件,只有在 imgpath 存在同名图片才采用。
#3)需要训练的图片无需拷贝到 <yolo>/data/images
#4)根据yolo要求,txt标注文件生成在实际图片 images (或 imgs ) 并列目录 labels
#5)训练用到的文件清单描述文件,保存在 <yolo>/data/train.txt, val.txt, test.txt
#6)跳过宽w或高h小于指定大小的标注
#7)train/训练,val/校准,test/测验配比默认为 0.9 0.08 0.02,命令行参数修改
#8)视频提取的图片文件名称带 grp_ 前缀,这些文件不参与训练时校准val、测验test

# 入参 see def parse_opt(known=False):

import time
import xml.etree.ElementTree as ET
import os
import random
# from os import listdir, getcwd
# from os.path import join
from pathlib import Path
import argparse
import sys

FILE = Path(__file__).resolve()
ROOT = FILE.parents[0]  # YOLOv5 root directory
if str(ROOT) not in sys.path:
    sys.path.append(str(ROOT))  # add ROOT to PATH
ROOT = Path(os.path.relpath(ROOT, Path.cwd()))  # relative

# IMG_FORMATS = 'bmp', 'dng', 'jpeg', 'jpg', 'mpo', 'png', 'tif', 'tiff', 'webp'  # include image suffixes
file_ext_sets = ['.jpg', '.jpeg','.png','.bmp','.tif','.tiff','.ppm','.webp']
clsfilename="data/obj.names"
g_fileprefix="grp_"

label_totalnum=0
label_oldfilenum=0
label_newfilenum=0
label_skipnum=0

classes = []
gPicFileList= []
gPicFilemap= {}
# 不存在xml标注文件的图片文件名称,make_yololabel()
skipPicList_nolabelxml=[] # print(f"not exist xmlfile={xmlfile}, p={lastps}")
# 没有合格标签而被删除标签文件的图片文件名称,make_yololabel() --> convert_annotation()
skipPicList_annotation=[] # print(f"skip {image_filename} when convert_annotation, no suitable labels ")
# 不存在标签文件的图片文件名称, make_trainvaltest_list()
skipPicList_nolabeltxt=[] # print(f"skip {fn} when make_trainvaltest_list,not exist labelFile={labelFile1}")

# 分析标注文件列表,分配 train test val 清单,其中 val 随机选择
# 要求必须在imgpath存在同名的图片文件, 如果已经生成 yolo的txt标注,则还需在该目录存在同名文件
def make_trainvaltest_list():

    global label_totalnum
    global skipPicList_nolabeltxt
    global g_fileprefix
    
    labelsxmlpath=opt.labelxmlpath
    labelstxtpath=getlabelpath(opt.imgpath)
    if not os.path.exists(labelstxtpath):
        os.makedirs(labelstxtpath)

    filetype = ""
    
    global gPicFileList
    if len(gPicFileList)<=0:
        gPicFileList=getPicfilelist(opt.imgpath)

    total_pic=[] # not grp 文件, total_pic.clear()
    total_pic_grp=[] # grp文件, 不参与 val test
    
    for file1 in gPicFileList:   #遍历所有文件
        st1 = os.path.splitext(file1)
        fn = st1[0]
        ext= st1[1]
        
        if file1.startswith('.'):
            continue

        xmlfile=os.path.join(labelsxmlpath,fn+".xml")
        if not os.path.exists(xmlfile):
            xmlfile=os.path.join(labelsxmlpath,fn+".XML")
        if not os.path.exists(xmlfile):
            # print(f"not exist xmlfile={xmlfile}")
            continue
        if label_totalnum>0: # 目前判断已生成 yolo txt的策略。如果有变化,相应做修改
            labelFile1=os.path.join(labelstxtpath, fn+'.txt')
            if not os.path.exists(labelFile1):
                # print(f"skip {fn} when make_trainvaltest_list,not exist labelFile={labelFile1}")
                skipPicList_nolabeltxt.append(fn)
                continue
        
        if file1.startswith(g_fileprefix):
            total_pic_grp.append(file1)
        else:
            total_pic.append(file1)
        if len(filetype)==0:
            filetype=check_labeltype(xmlfile)

    totalnum =  len(total_pic)
    totallist = range(totalnum)
    # val test 数量
    valtestn = totalnum * (opt.val_percent + opt.test_percent)
    if valtestn<2:
        valtestn=2
    valtestnum = int(valtestn) # val+test的数量
    # val test 列表
    valtestList = random.sample(totallist, valtestnum)
    # test数量
    testn = int( valtestnum* (opt.test_percent/(opt.val_percent + opt.test_percent)) )
    if testn<1:
        testn=1
    # test 列表
    testList = random.sample(valtestList, testn)
    
    num_train=0
    num_val=0    
    num_test=0

    txtsets = ['train.txt','val.txt','test.txt']

    ftrain = open(os.path.join("data/", txtsets[0]), 'w')
    fval = open(os.path.join("data/", txtsets[1]), 'w')
    ftest = open(os.path.join("data/", txtsets[2]), 'w')
    
    for i in totallist:
        picfile1=os.path.join(opt.imgpath, total_pic[i])+'\n' # 取文件名
        if i in valtestList:
            if i in testList:
                if opt.runtest=="no":
                    ftest.write(picfile1)
                num_test+=1
            else:
                if opt.runtest=="no":
                    fval.write(picfile1)
                num_val+=1
        else:
            if opt.runtest=="no":
                ftrain.write(picfile1)
            num_train+=1
    for file1 in total_pic_grp:
        picfile1=os.path.join(opt.imgpath, file1)+'\n' # 取文件名
        if opt.runtest=="no":
            ftrain.write(picfile1)
        num_train+=1
    ftrain.close()
    fval.close()
    ftest.close()
    print(f"""
train/val/test pic file:
    ref labelsxmlpath={labelsxmlpath}
    filetype={filetype}
    total={totalnum}
    num_train={num_train}
    num_val={num_val}
    num_test={num_test}
    output=data/: {txtsets}
    """)


def save_nolabel_pic():
    global skipPicList_nolabelxml
    global skipPicList_annotation
    global skipPicList_nolabeltxt
    
    skipPicList_xml_str=fprintlist(skipPicList_nolabelxml,5)
    skipPicList_str=fprintlist(skipPicList_annotation,5)
    skipPicList_txt_str=fprintlist(skipPicList_nolabeltxt,5)
    skipfilelist=[]
    skipUse={}
    skipfilelist, skipUse=getFilelist(skipfilelist, skipUse, skipPicList_nolabelxml)
    skipfilelist, skipUse=getFilelist(skipfilelist, skipUse, skipPicList_annotation)
    skipfilelist, skipUse=getFilelist(skipfilelist, skipUse, skipPicList_nolabeltxt)

    print(f"""debug info:
    skip image num={len(skipPicList_nolabelxml)} when make_yololabel,not exist labelxmlFile, {skipPicList_xml_str}
    skip image num={len(skipPicList_annotation)} when convert_annotation, no suitable labels, {skipPicList_str}
    skip image num={len(skipPicList_nolabeltxt)} when make_trainvaltest_list,not exist labeltxtFile, {skipPicList_txt_str}
    total skip image num={len(skipfilelist)}, detail list see data/skippic.txt
    """)
    fskipfile = open("data/skippic.txt", 'w')
    if opt.runtest=="no":
        fskipfile.write('\n'.join(skipfilelist)+"\n")
    fskipfile.close()

# x1,y1,x2,y2 --> x_center,y_center,w,h(归一化) 
def convert_normalize(size, box):
   
    w0 = size[0]
    h0 = size[1]
    xmin,xmax,ymin,ymax = box[0],box[1],box[2],box[3]
    if xmin<0:
        xmin=0
    if xmax>=w0:
        xmax=w0-1
    if ymin<0:
        ymin=0
    if ymax>=h0:
        ymax=h0-1
    dw = 1. / w0
    dh = 1. / h0
    x = (box[0] + box[1]) / 2.0
    y = (box[2] + box[3]) / 2.0
    w = box[1] - box[0]
    h = box[3] - box[2]
    if h>=h0:
        h=h0-1
    if w>=w0:
        w=w0-1
    
    if w<opt.labelminsize or h<opt.labelminsize or w*h<300:
        return (0, 0, 0, 0),False
    x = x * dw
    w = w * dw
    y = y * dh
    h = h * dh
    return (x, y, w, h),True

# xml:xml格式; voc:voc格式
def check_labeltype(labelxmlpathfileIn):
    in_file = open(labelxmlpathfileIn)
    tree = ET.parse(in_file)
    root = tree.getroot()
    o1 = root.find('outputs')
    if o1 != None:
       o2 = o1.find('object')
       if o2 != None:
           if o2.find('item') != None:
                return "xml"
    o1 = root.find('object')
    if o1 != None:
       o2 = o1.find('bndbox')
       if o2 != None:
           return "voc"
    return "unknown"

def convert_annotation(image_filename, xmlfile, labeltxtpathIn):

    global classes
    global label_totalnum
    global label_oldfilenum
    global label_newfilenum
    global label_skipnum
    global skipPicList_annotation

    in_file = open(xmlfile) # 'data/labelxmlpath/%s.xml' % (image_filename)
    labelFile1=os.path.join(labeltxtpathIn, image_filename+'.txt')
    
    if opt.forcetxt!="yes" and os.path.exists(labelFile1):  # default
        txtf1=open(labelFile1, "r")
        labellist=txtf1.readlines()
        for str1 in labellist:
            if len(str1.strip())>0:
                label_totalnum+=1
        label_oldfilenum+=1
        return
    
    label_newfilenum+=1
    out_file = open(labelFile1, 'w')

    tree = ET.parse(in_file)
    root = tree.getroot()
    size = root.find('size')
    w = int(size.find('width').text)
    h = int(size.find('height').text)

    label_usenum=0
    attrTest = 'outputs'
    if root.find(attrTest) != None: # if attrTest in root.tag: # if root.get(attrTest) == 'outputs':
        # print(f"------type=xml, image_filename={image_filename}------")
        itemsRoot = root.find('outputs').find("object")
        for obj in itemsRoot.iter('item'):
            cls = obj.find('name').text.strip()
            if cls not in classes :
                classes.append(cls) # continue
            cls_id = classes.index(cls)
            xmlbox = obj.find('bndbox')
            b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
                 float(xmlbox.find('ymax').text))

            bb,Flag = convert_normalize((w, h), b)
            label_totalnum+=1
            if Flag==False:
                label_skipnum+=1
                continue
            
            if opt.runtest=="no":
                out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')
            label_usenum+=1
    else:
        # print(f"------type=voc, image_filename={image_filename}------", image_filename)
        for obj in root.iter('object'):
            difficult = obj.find('difficult').text
            if int(difficult) == 1:
                continue
            cls = obj.find('name').text
            if cls not in classes:
                classes.append(cls) # continue
            cls_id = classes.index(cls)
            xmlbox = obj.find('bndbox')
            b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
                 float(xmlbox.find('ymax').text))

            bb,Flag = convert_normalize((w, h), b)
            label_totalnum+=1
            if Flag==False:
                label_skipnum+=1
                continue
            if opt.runtest=="no":
                out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')
            label_usenum+=1
    out_file.close()
    if label_usenum<=0 and os.path.exists(labelFile1):
        skipPicList_annotation.append(image_filename) # print(f"skip {image_filename} when convert_annotation, no suitable labels ")
        os.remove(labelFile1)

# 创建yolo需要的格式,每图片一个txt,每行一个对象
# class/类别编号 x_center/x中心坐标 y_center/y中心坐标 width/宽 height/高, 参数均为0-1归一化
def make_yololabel():
    
    global classes
    global file_ext_sets
    global label_totalnum
    global label_oldfilenum
    global label_newfilenum
    global label_skipnum
    global clsfilename
    global skipPicList_nolabelxml

    labelsxmlpath=opt.labelxmlpath
    labelstxtpath=getlabelpath(opt.imgpath)
    if not os.path.exists(labelstxtpath):
        os.makedirs(labelstxtpath)

    ticks = time.time()
    
    global gPicFileList
    if len(gPicFileList)<=0:
        gPicFileList=getPicfilelist(opt.imgpath)
    
    count1=0
    pnum=0
    lastp=0.0
    for file1 in gPicFileList:   #遍历所有文件
        # if os.path.isdir(file1):   #如果是文件夹则跳过
        #     continue
        # # file1=file1.lower()
        # st1 = os.path.splitext(file1)
        # fn = st1[0]
        # ext= st1[1]
        # # print("--splitext",os.path.splitext(file1))
        # if file1.startswith('.') or ext.lower() not in file_ext_sets:
        #     continue
        fn = os.path.splitext(file1)[0]

        pnum+=1
        xmlfile=os.path.join(labelsxmlpath,fn+".xml")
        if not os.path.exists(xmlfile):
            xmlfile=os.path.join(labelsxmlpath,fn+".XML")
        if not os.path.exists(xmlfile):
            skipPicList_nolabelxml.append(fn)
#           p2 = 100.0*float(pnum)/float(len(gPicFileList))
#           if p2>lastp+10.0:
#               lastp=p2
#               lastps="%0.2f%%" % (lastp)
#               print(f"not exist xmlfile={xmlfile}, p={lastps}")
            continue

        count1 += 1
        convert_annotation(fn, xmlfile, labelstxtpath) # xmlfile, labeltxtpathIn
        # time.sleep(0.01)
        if time.time()-ticks>=1.5:
            ticks=time.time()
            p1 = 100.0*float(count1)/float(len(gPicFileList))
            print(time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime()),', %5.1f%%' % p1 )
   
    # save obj.names
    cls_file = open(clsfilename, 'w')
    if opt.runtest=="no":
        cls_file.write("\n".join(classes) + '\n')
    usep="%0.2f" % (100.0*float(label_totalnum-label_skipnum)/float(label_totalnum))
    print(f"""label info:
    pic_num={pnum}
    labeltxt={labelstxtpath}
    label_totalnum={label_totalnum}
    label_oldfilenum={label_oldfilenum}
    label_newfilenum={label_newfilenum}
    label_usenum={label_totalnum-label_skipnum}, {usep}%
    label_skipnum={label_skipnum}, obj too small
    classes={clsfilename}, num={len(classes)}
    """)

# images (或 imgs ) 
def getlabelpath(imgPath):
    i1 = imgPath.rfind('images')
    if i1>=0:
        labelp = imgPath[:i1]+"labels"+imgPath[i1+len("images"):]
        return labelp

    i1 = imgPath.rfind('imgs')
    if i1>=0:
        labelp = imgPath[:i1]+"labels"+imgPath[i1+len("imgs"):]
        return labelp

    return os.path.join(imgPath,"../labels")

def ftest():
    if True:
        p1 = "E:\\opencv_org/windows\\Opencv4\\sources.base\\data\\haarcascades_cudA/haarcascade_eye.xml"
        imgPathspilit=p1.lower().replace('\\','/').split('/')
        print("----imgPathspilit=",imgPathspilit)
        return
    if False:
        if not os.path.exists(txtsaveTempPath):
            os.makedirs(txtsaveTempPath)
        pf1 = os.path.join(txtsaveTempPath, "train.txt")
        print("----pf1=",pf1)
    if False:
        in_file = open('E:/opencv_org/models/smoke/test1/outputs-xml/000000.xml')
        tree = ET.parse(in_file)
        root = tree.getroot()
        attrTest = 'outputs'
        print("---find=", root.find(attrTest))
        if root.find(attrTest) != None: # if attrTest in root.tag: # if root.get(attrTest) == 'outputs':
            print("---xml")
        else:
            print("---voc")

def parse_opt(known=False):
    parser = argparse.ArgumentParser()
    # 图片路径。需要有一个子目录名称为 images 或 imgs ; 标签txt位置,通过将该子目录名称替换为 labels 获得
    parser.add_argument('--imgpath', type=str, default=ROOT / 'data/images', help='images path')
    # voc 或 xml 格式的标注文件,可用 标注精灵 或 lableimg 工具,后者是个python程序,推荐使用
    parser.add_argument('--labelxmlpath', type=str, default=ROOT / 'data/labelxmlpath', help='labels xml path')
    # w 或 h 小于指定值的标签被忽略
    parser.add_argument('--labelminsize', type=int, default=12, help='labels min width and height, and w*h>=300')
    # 图片分配比例
    # val 比例, 总数控制 500 左右?
    # test 比例, 总数控制100左右? ( 训练期间不参与,训练完成后,用来人工检测识别成果 )    
    parser.add_argument('--train_percent', type=float, default=0.90, help='train_percent, train+val+test=1.0')
    parser.add_argument('--val_percent', type=float, default=0.08, help='val_percent, train+val+test=1.0')
    parser.add_argument('--test_percent', type=float, default=0.02, help='test_percent, train+val+test=1.0')
    # 强制重建yolo格式标签文件,否则跳过已存在的文件
    parser.add_argument('--forcetxt', type=str, default='yes', help='force rebuild yolo label txt file')
    # 在 xml 标签文件目录中,如果在 images 目录下没有同名图片文件,则该 xml标签文件 被移到 xmlbak 目录
    parser.add_argument('--movexmlbak', type=str, default='no', help='move xml label file when no same name image file')
    # 仅测试, 不写文件yolo txt文件、train/val/test 文件
    parser.add_argument('--runtest', type=str, default='no', help='run ftest() only')

    opt = parser.parse_known_args()[0] if known else parser.parse_args()
    return opt

def getPicfilelist(imgPath):

    global file_ext_sets
    global gPicFilemap

    gPicFilemap.clear()
    
    picFileListOut = []
    picFileList0 = os.listdir(imgPath)
    # print("--picFileList0",picFileList0)
    for file1 in picFileList0:   #遍历所有文件
        if os.path.isdir(file1):   #如果是文件夹则跳过
            continue
        st1 = os.path.splitext(file1)
        fn = st1[0]
        ext= st1[1]
        # print("--splitext",os.path.splitext(file1))
        if (not file1.startswith('.')) and ext.lower() in file_ext_sets:
            picFileListOut.append(file1)
            gPicFilemap[fn]=file1
    print(f"get picFileList={len(picFileListOut)}")
    return picFileListOut

# 不以.开头、扩展名为 file_ext_sets 的文件数
# images (或 imgs ) 
def getpicfilenum(imgPath):

    if not os.path.exists(imgPath):
        print(f"--imgpath not exist={imgPath}")
        return -1
    
    imgPathspilit=imgPath.lower().replace('\\','/').split('/')
    if "images" not in imgPathspilit and "imgs" not in imgPathspilit:
        print("--imgpath need contains images or imgs subdir")
        return -1

    global gPicFileList
    if len(gPicFileList)<=0:
        gPicFileList=getPicfilelist(imgPath)
    return len(gPicFileList)

# 不以.开头、扩展名为xml的文件数
def getxmlfilenum(labelsxmlPath):
    if not os.path.exists(labelsxmlPath):
        print(f"--labelsxmlPath not exist={labelsxmlPath}")
        return -1
    xmlFileList = os.listdir(labelsxmlPath)
    count=0
    for file1 in xmlFileList:   #遍历所有文件
        if os.path.isdir(file1):   #如果是文件夹则跳过
            continue
        file1=file1.lower()
        if (not file1.startswith('.')) and file1.endswith('.xml'):
            count += 1
    return count

def fprintlist(listIn, num):
    str1=""
    if len(listIn)>num:
        str1="%s"%listIn[:num]+"..."
    else:
        str1="%s"%listIn
    return str1

def getFilelist(skipfilelist, skipUse, listIn):
    for fn in listIn:
        if skipUse.get(fn)!=None:
            continue
        skipUse[fn]=1
        f1=os.path.join(opt.imgpath,gPicFilemap[fn])
        if os.path.exists(f1):
            skipfilelist.append(f1)
    return skipfilelist,skipUse

def movexmlfile(labelsxmlPath):
    global gPicFileList
    global gPicFilemap
    
    if not os.path.exists(labelsxmlPath):
        print(f"--labelsxmlPath not exist={labelsxmlPath}")
        return -1

    print(f"--enter move not use xml file")
    
    if len(gPicFileList)<=0:
        gPicFileList=getPicfilelist(imgPath)

    labelsxmlPathBak=labelsxmlPath+"_bak"
    if not os.path.exists(labelsxmlPathBak):
        os.makedirs(labelsxmlPathBak)

    xmlFileList = os.listdir(labelsxmlPath)
    for file1 in xmlFileList:   #遍历所有文件
        if os.path.isdir(file1):   #如果是文件夹则跳过
            continue
        file2=file1.lower()
        if (file2.startswith('.')) or not file2.endswith('.xml'):
            continue
        fn = os.path.splitext(file1)[0]
        if gPicFilemap.get(fn)==None: # xml文件在 images 没有同名文件
            os.replace(os.path.join(labelsxmlPath, file1), os.path.join(labelsxmlPathBak, file1))
    print(f"--end to move xml file")

# 执行重建yolo txt文件时,将全部已有文件移到 labels_bak
def movetxtfile(labelstxtPath):

    if not os.path.exists(labelstxtPath):
        print(f"--labelstxtPath not exist={labelstxtPath}")
        return -1

    print(f"--enter move exist txt file")

    labelstxtPathBak=labelstxtPath+"_bak"
    if not os.path.exists(labelstxtPathBak):
        os.makedirs(labelstxtPathBak)

    FileList = os.listdir(labelstxtPath)
    for file1 in FileList:   #遍历所有文件
        if os.path.isdir(file1):   #如果是文件夹则跳过
            continue
        os.replace(os.path.join(labelstxtPath, file1), os.path.join(labelstxtPathBak, file1))
    print(f"--end to move txt file")

def finitLoad():
    global classes
    classes.clear()
    # obj.names
    if os.path.exists(clsfilename):
        cls_file = open(clsfilename, 'r')
        clsList1=cls_file.readlines()
        for cls in clsList1:
            cls=cls.strip()
            if len(cls)>0:
                classes.append(cls)
    print("classes load from file=",classes)
    return

def fmain(opt):

    global skipPicList_nolabelxml
    global skipPicList_annotation
    global skipPicList_nolabeltxt
    global gPicFilemap

    finitLoad()

    if opt.labelminsize<8:
        opt.labelminsize=12

    pn=getpicfilenum(opt.imgpath)
    if pn<=0:
        if pn==0:
            print("no pic file in --imgpath ")
        exit(0)
    xn=getxmlfilenum(opt.labelxmlpath)
    if xn<=0:
        if xn==0:
            print("no xml file in --labelxmlpath ")
        exit(0)

    # if True:
    if opt.movexmlbak!="no":
        movexmlfile(opt.labelxmlpath)  # 将 xml 文件中,在 images 没有对应图片的xml文件移到 xmlbak 目录
    if opt.forcetxt=="yes":
        movetxtfile(getlabelpath(opt.imgpath))

    # print("cur dir=", getcwd())
    make_yololabel()
    make_trainvaltest_list()
    save_nolabel_pic()
    
if __name__ == "__main__":
    opt = parse_opt()
    print("\nargparse:")
    print(vars(opt))
    print("\n")

#    if True:
    if False:
        # movetxtfile("E:\opencv_org\models\dataset\smoke,dataset\labels_test")
        exit(0)

    if False:
        ftest()
        exit(0)
    
    fmain(opt)

脚本文件2:打开视频文件,按指定的间隔提取图片,文件名称 grp_<videoname>_<nnnnnn>.jpg

# e.g.
# cls && python make_video_pic.py --source G:\archive\fire_sample\t1.mp4 --imageoutput G:\archive\v1  --interval 0.2
# cls && python make_video_pic.py --source G:\archive\fire_sample\smoke9.avi --imageoutput G:\archive\v1  --interval 2.0
# 提取的图片文件名称带 grp_ 前缀,这些文件不参与训练时校准val、测验test

# Python 2/3 compatibility
from __future__ import print_function
import argparse
import numpy as np
import cv2 as cv
import os
import sys
from pathlib import Path
# import utils
import time

FILE = Path(__file__).resolve()
ROOT = FILE.parents[0]  # YOLOv5 root directory
if str(ROOT) not in sys.path:
    sys.path.append(str(ROOT))  # add ROOT to PATH
ROOT = Path(os.path.relpath(ROOT, Path.cwd()))  # relative

g_fileprefix="grp_"

WHlimited=640 # maxn(w,h)<WHlimited时,将其放大为 WHlimited

def parse_opt(known=False):
    parser = argparse.ArgumentParser()
    # 视频文件。
    parser.add_argument('--source', type=str, default=ROOT / 'data/videofile', help='videofile')
    parser.add_argument('--imageoutput', type=str, default=ROOT / 'data/imageoutput', help='imageoutput path')
    # interval 每张间隔秒数
    parser.add_argument('--interval', type=float, default=0.5, help='interval')
    # 正常播放速度
    parser.add_argument('--play', type=str, default='no', help='play')
    # image 文件前缀
    parser.add_argument('--prefix', type=str, default='', help='image file prefix')
    # 仅测试, 不写image文件
    parser.add_argument('--runtest', type=str, default='no', help='run ftest() only')

    opt = parser.parse_known_args()[0] if known else parser.parse_args()
    return opt

def main():

    def decode_fourcc(v):
        v = int(v)
        return "".join([chr((v >> 8 * i) & 0xFF) for i in range(4)])
# 检测源文件是否存在
    if not os.path.exists(opt.source):
        print(f"not exist: {opt.source}")
        exit(0)
# 目标目录不存在则创建
    if not os.path.exists(opt.imageoutput):
        os.makedirs(opt.imageoutput)
# 每秒取多少图片
    if opt.interval<=0.0:
        opt.interval=0.5
    fpsgrab = float( 1.0/float(opt.interval) ) # 每秒取多少图片
# 打开视频
    cap = cv.VideoCapture(opt.source) # VideoCapture(0)
    if not cap.isOpened():
        print(f"open fail, video={opt.source}")

    # cap.set(cv.CAP_PROP_AUTOFOCUS, 0)  # Known bug: https://github.com/opencv/opencv/pull/5474
    if opt.play=="yes":
        cv.namedWindow("Video")
#    convert_rgb = True
# 视频的帧率,每帧延时
    fps = float(cap.get(cv.CAP_PROP_FPS))
    if fps<0.0001:
        fps=25.0
    delay1 = float(1.0)/fps
    # focus = int(min(cap.get(cv.CAP_PROP_FOCUS) * 100, 2**31-1))  # ceil focus to C_LONG as Python3 int can go to +inf
    # cv.createTrackbar("FPS", "Video", int(fps), 30, lambda v: cap.set(cv.CAP_PROP_FPS, v))
    # cv.createTrackbar("Focus", "Video", focus, 100, lambda v: cap.set(cv.CAP_PROP_FOCUS, v / 100))
    frameWidth = int(cap.get(cv.CAP_PROP_FRAME_WIDTH))
    frameHeight = int(cap.get(cv.CAP_PROP_FRAME_HEIGHT))
# 视频宽 高缩放比例
    f1 = float(frameWidth)
    if frameWidth < frameHeight:
        f1 = float(frameHeight)
    if f1<1.0:
        print(f"get w h fail, video={opt.source},fps={fps},w={frameWidth},h={frameHeight}")
        return
    resize_ratio = -1.0
    if float(WHlimited)> f1:
        resize_ratio = float(WHlimited) / f1

    ticksPrn = time.time()
    count=0
    findex=0 #从零开始
    countSeg=int(fps/fpsgrab) # 每隔多少帧抓取一张
    indexseg=0
    
    print(f"video={opt.source},fps={fps},delay={delay1},w={frameWidth},h={frameHeight},countSeg={countSeg}")
    
    global g_fileprefix
    while True:
        ticks1 = time.time()

        hasFrame, img = cap.read()
        if not hasFrame or img is None:
            if count==0:
                print('No frames grabbed!')
            break
        fourcc = decode_fourcc(cap.get(cv.CAP_PROP_FOURCC))
        count+=1
        indexseg+=1
        
        if opt.play=="yes":
            ticks2 = time.time()-ticks1
            d1 = delay1-ticks2
            if d1>0.0001:
                time.sleep(d1)
        ticks1 = time.time()-ticks1
#       if not bool(cap.get(cv.CAP_PROP_CONVERT_RGB)):
#           if fourcc == "MJPG":
#               img = cv.imdecode(img, cv.IMREAD_GRAYSCALE)
#           elif fourcc == "YUYV":
#               img = cv.cvtColor(img, cv.COLOR_YUV2GRAY_YUYV)
#           else:
#               print("unsupported format")
#               break
        if time.time()-ticksPrn>=1.5:
            ticksPrn=time.time()
            p1 = 1000.0*float(ticks1)
            print(time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime()),', tick=%5.1f ms' % p1 )
        if resize_ratio>0.01:
            img = cv.resize(img, (0, 0), fx=resize_ratio, fy=resize_ratio)
        if indexseg>=countSeg:
            indexseg=0
            findex+=1
            NewFile = os.path.join(opt.imageoutput, g_fileprefix+opt.prefix+str(findex).zfill(6)+".jpg")
            if opt.runtest!="yes":
                cv.imwrite(NewFile, img)
            print(f"save to={NewFile}")
        if opt.play=="yes":
            cv.imshow("Video", img)

        k = cv.waitKey(1)

        if k == 27:
            break
#        elif k == ord('g'):
#            convert_rgb = not convert_rgb
#            cap.set(cv.CAP_PROP_CONVERT_RGB, 1 if convert_rgb else 0)
    cap.release()  # 释放
    print('Done')

def ftest():
    return

if __name__ == '__main__':
    opt = parse_opt()

# 获取视频文件的名称
    if len(opt.prefix)==0:
        f1 = os.path.basename(opt.source)
        opt.prefix = os.path.splitext(f1)[0]+"_"
        
    print("\nargparse:")
    print(vars(opt))
    print("\n")
    if False:
        ftest()
        exit(0)
    main()
    cv.destroyAllWindows()

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值