文章目录
前言
一个目标检测项目需要自己找图片标注数据进行训练,训练需要YOLO格式,但数据增广需要VOC格式,该文记录如何将labelme标注的数据格式转为YOLO格式,再从YOLO格式转为VOC格式,只作为自己用的记录,如果你刚好也需要这么干,或者需要文中提到的某一种转换,也可以参考一下。文中有些代码是参考其他地方的,时间长已经记不清了,如有侵权请联系更改。
注意:路径不要有中文,标签也用相应的英文
第一步:将图片和标签分为两个单独的文件夹
手动完成即可,标签的文件夹最好加个json后缀,因为后面会有其他格式的标签文件。
第二步:将jpeg、png等格式都改为jpg格式
因为搜集的图片什么格式都有,为了方便训练,统一为jpg格式。
代码如下:
# trans_others_to_jpg.py
import os
import cv2 as cv
image_path = 'D:/DeskTop/Datasets/clothes/images/' #设置图片读取路径
save_path = 'D:/DeskTop/Datasets/clothes/images_jpg/' #设置图片保存路径,新建文件夹,不然其他格式会依然存在
if not os.path.exists(save_path): #判断路径是否正确,并打开
os.makedirs(save_path)
image_file = os.listdir(image_path)
# print(image_file)
for image in image_file:
# print(image)
if image.split('.')[-1] in ['bmp', 'jpg', 'jpeg', 'png', 'JPG', 'PNG']:
str = image.rsplit(".", 1) #从右侧判断是否有符号“.”,并对image的名称做一次分割。如112345.jpeg分割后的str为["112345","jpeg"]
# print(str)
output_img_name = str[0] + ".jpg" #取列表中的第一个字符串与“.jpg”放在一起。
# print(output_img_name)
dir = os.path.join(image_path, image)
# print("dir:",dir)
src = cv.imread(dir)
# print(src)
cv.imwrite(save_path + output_img_name, src)
print('FINISHED')
第三步:重命名图片和标签
将文件和对应的标签重命名为从六位数的名字,从000001开始,注意:图片和标签都需要进行重命名
代码如下:
# rename.py
import os
path = "D:/DeskTop/Datasets/clothes/label_json/" # json标签文件的保存路径
filelist = os.listdir(path)
count=1
for file in filelist:
print(file)
for file in filelist:
Olddir=os.path.join(path,file)
if os.path.isdir(Olddir):
continue
filename=os.path.splitext(file)[0]
filetype=os.path.splitext(file)[1]
Newdir=os.path.join(path,str(count).zfill(6)+filetype) # zfill(6):表示命名为6位数
os.rename(Olddir,Newdir)
count+=1
第四步:修改json中的imagePath
因为上一步只改变了名字,标签内的imagePath并没有跟着变,所以还要改一下,和图片对应起来,其实这一步不做也没事,因为YOLO格式就是根据标签文件名读取图片路径的,为了以后可能需要json的标签,还是改一下最好。
代码如下:
# change_json_imagePath.py
import json
import os
import re
path = 'D:/DeskTop/Datasets/clothes/label_json/' # json文件路径
dirs = os.listdir(path)
num_flag = 0
for file in dirs: # 循环读取路径下的文件并筛选输出
if os.path.splitext(file)[1] == ".json": # 筛选Json文件
num_flag = num_flag + 1
print("path = ", file) # 此处file为json文件名,之前修改为与图片jpg同名
# print(os.path.join(path,file))
with open(os.path.join(path, file), 'r') as load_f: # 若有中文,可将r改为rb
load_dict = json.load(load_f) # 用json.load()函数读取文件句柄,可以直接读取到这个文件中的所有内容,并且读取的结果返回为python的dict对象
n = len(load_dict) # 获取字典load_dict中list值
print('n = ', n)
print("imagePath = ", load_dict['imagePath']) # 此处因为我的json文件要修改的imagePath, 没有那么多弯弯绕, 直接在顶层, 所以一层[]即可, 如果你们的不是这种结构, 需自行修改
filename = file[:-5] # 去掉拓展名5位 .json
print("filename = ", filename)
load_dict['imagePath'] = filename + '.jpg' # 存到当前路径下, 如果有其它存储要求, 自行修改即可
print("new imagePath = ", load_dict['imagePath'])
with open(os.path.join(path, file), 'w') as dump_f:
json.dump(load_dict, dump_f)
if (num_flag == 0):
print('所选文件夹不存在json文件,请重新确认要选择的文件夹')
else:
print('共{}个json文件'.format(num_flag))
第五步:将labelme格式转为YOLO格式
将labelme的json格式转为YOLO的txt格式,同样保存txt标签的文件夹最好也加个后缀,方便和json区分,注意把代码第12行改为自己数据集的类别,从0开始
代码如下:
# trans_labelme_to_yolo.py
import cv2
import os
import json
import shutil
import numpy as np
from pathlib import Path
from glob import glob
id2cls = {0: 'clothing'}
cls2id = {'clothing': 0}
#支持中文路径
def cv_imread(filePath):
cv_img=cv2.imdecode(np.fromfile(filePath,dtype=np.uint8),flags=cv2.IMREAD_COLOR)
return cv_img
def labelme2yolo_single(img_path,label_file):
anno= json.load(open(label_file, "r", encoding="utf-8"))
shapes = anno['shapes']
w0, h0 = anno['imageWidth'], anno['imageHeight']
image_path = os.path.basename(img_path + anno['imagePath'])
labels = []
for s in shapes:
pts = s['points']
x1, y1 = pts[0]
x2, y2 = pts[1]
x = (x1 + x2) / 2 / w0
y = (y1 + y2) / 2 / h0
w = abs(x2 - x1) / w0
h = abs(y2 - y1) / h0
cid = cls2id[s['label']]
labels.append([cid, x, y, w, h])
return np.array(labels), image_path
def labelme2yolo(img_path,labelme_label_dir, save_dir='res/'):
labelme_label_dir = str(Path(labelme_label_dir)) + '/'
save_dir = str(Path(save_dir))
yolo_label_dir = save_dir + '/'
""" yolo_image_dir = save_dir + 'images/'
if not os.path.exists(yolo_image_dir):
os.makedirs(yolo_image_dir) """
if not os.path.exists(yolo_label_dir):
os.makedirs(yolo_label_dir)
json_files = glob(labelme_label_dir + '*.json')
for ijf, jf in enumerate(json_files):
print(ijf+1, '/', len(json_files), jf)
filename = os.path.basename(jf).rsplit('.', 1)[0]
labels, image_path = labelme2yolo_single(img_path,jf)
if len(labels) > 0:
np.savetxt(yolo_label_dir + filename + '.txt', labels)
# shutil.copy(labelme_label_dir + image_path, yolo_image_dir + image_path)
print('Completed!')
if __name__ == '__main__':
img_path = 'D:/DeskTop/Datasets/clothes/images/' # 数据集图片的路径
json_dir = 'D:/DeskTop/Datasets/clothes/label_json/' # json标签的路径
save_dir = 'D:/DeskTop/Datasets/clothes/label_txt/' # 保存的txt标签的路径
labelme2yolo(img_path,json_dir, save_dir)
第六步:将YOLO格式转为xml格式
因为数据增广需要xml格式,所以再进行一次转换,注意把代码第十四行改为自己数据集的类别
代码如下:
# trans_YOLOtxt_to_VOCxml.py
import xml.dom.minidom
import glob
from PIL import Image
from math import ceil
import shutil
import os
yolo_file = 'D:/DeskTop/Datasets/clothes/label_txt2/'# yolo格式下的存放txt标注文件的文件夹
turn_xml_file = 'D:/DeskTop/Datasets/clothes/label_xml/'# 转换后储存xml的文件夹地址
img_file = 'D:/DeskTop/Datasets/clothes/images/'# 存放图片的文件夹
labels = ['clothes'] #这里要改为自己的类别
src_img_dir = img_file
src_txt_dir = yolo_file
src_xml_dir = turn_xml_file #转换后储存xml的文件夹地址
img_Lists = glob.glob(src_img_dir + '/*.jpg')
img_basenames = []
for item in img_Lists:
img_basenames.append(os.path.basename(item))#os.path.basename返回path最后的文件名
img_names = []
for item in img_basenames:
temp1, temp2 = os.path.splitext(item) #os.path.splitext(“文件路径”) 分离文件名与扩展名
img_names.append(temp1)
total_num = len(img_names) #统计当前总共要转换的图片标注数量
count = 0 #技术变量
for img in img_names: #这里的img是不加后缀的图片名称,如:'GF3_SAY_FSI_002732_E122.3_N29.9_20170215_L1A_HH_L10002188179__1__4320___10368'
count +=1
if count % 1000 == 0:
print("当前转换进度{}/{}".format(count,total_num))
im = Image.open((src_img_dir + img + '.jpg'))
width, height = im.size
#打开yolo格式下的txt文件
gt = open(src_txt_dir + img + '.txt').read().splitlines()
if gt:
# 将主干部分写入xml文件中
xml_file = src_xml_dir + img + '.xml'
xml_file = open((src_xml_dir + img + '.xml'), 'w')
xml_file.write('<annotation>\n')
xml_file.write(' <folder>VOC2007</folder>\n')
xml_file.write(' <filename>' + str(img) + '.jpg' + '</filename>\n')
xml_file.write(' <size>\n')
xml_file.write(' <width>' + str(width) + '</width>\n')
xml_file.write(' <height>' + str(height) + '</height>\n')
xml_file.write(' <depth>3</depth>\n')
xml_file.write(' </size>\n')
# write the region of image on xml file
for img_each_label in gt:
spt = img_each_label.split(' ') # 这里如果txt里面是以逗号‘,’隔开的,那么就改为spt = img_each_label.split(',')。
xml_file.write(' <object>\n')
xml_file.write(' <name>' + str(labels[int(float(spt[0]))]) + '</name>\n')
xml_file.write(' <pose>Unspecified</pose>\n')
xml_file.write(' <truncated>0</truncated>\n')
xml_file.write(' <difficult>0</difficult>\n')
xml_file.write(' <bndbox>\n')
center_x = round(float(spt[1].strip()) * width)
center_y = round(float(spt[2].strip()) * height)
bbox_width = round(float(spt[3].strip()) * width)
bbox_height = round(float(spt[4].strip()) * height)
xmin = str(int(center_x - bbox_width / 2))
ymin = str(int(center_y - bbox_height / 2))
xmax = str(int(center_x + bbox_width / 2))
ymax = str(int(center_y + bbox_height / 2))
xml_file.write(' <xmin>' + xmin + '</xmin>\n')
xml_file.write(' <ymin>' + ymin + '</ymin>\n')
xml_file.write(' <xmax>' + xmax + '</xmax>\n')
xml_file.write(' <ymax>' + ymax + '</ymax>\n')
xml_file.write(' </bndbox>\n')
xml_file.write(' </object>\n')
xml_file.write('</annotation>')
else:
# 将主干部分写入xml文件中
xml_file = open((src_xml_dir + '/' + img + '.xml'), 'w')
xml_file.write('<annotation>\n')
xml_file.write(' <folder>VOC2007</folder>\n')
xml_file.write(' <filename>' + str(img) + '.jpg' + '</filename>\n')
xml_file.write(' <size>\n')
xml_file.write(' <width>' + str(width) + '</width>\n')
xml_file.write(' <height>' + str(height) + '</height>\n')
xml_file.write(' <depth>3</depth>\n')
xml_file.write(' </size>\n')
xml_file.write('</annotation>')
第七步:可视化
验证标签转换后知否正确,用xml标签进行可视化,多测试几张图片,找一些目标多的图片验证标签的正确性
代码如下:
# visualization_xml_OD.py
from lxml import etree
import cv2 as cv
import matplotlib.pyplot as plt
from copy import deepcopy
import numpy as np
def parse_xml_to_dict(xml):
"""
将xml文件解析成字典形式,参考tensorflow的recursive_parse_xml_to_dict
Args:
xml: xml tree obtained by parsing XML file contents using lxml.etree
Returns:
Python dictionary holding XML contents.
"""
if len(xml) == 0: # 遍历到底层,直接返回tag对应的信息
return {xml.tag: xml.text}
result = {}
for child in xml:
child_result = parse_xml_to_dict(child) # 递归遍历标签信息
if child.tag != 'object':
result[child.tag] = child_result[child.tag]
else:
if child.tag not in result: # 因为object可能有多个,所以需要放入列表里
result[child.tag] = []
result[child.tag].append(child_result[child.tag])
return {xml.tag: result}
def get_xml_info(xml_path):
with open(xml_path) as fid:
xml_str = fid.read()
xml = etree.fromstring(xml_str)
data = parse_xml_to_dict(xml)["annotation"]
bboxes = []
for index, obj in enumerate(data["object"]):
# 获取每个object的box信息
xmin = int(obj["bndbox"]["xmin"])
xmax = int(obj["bndbox"]["xmax"])
ymin = int(obj["bndbox"]["ymin"])
ymax = int(obj["bndbox"]["ymax"])
# bbox = np.array([xmin, ymin, xmax, ymax])
bbox = [xmin, ymin, xmax, ymax]
bboxes.append(bbox)
return bboxes
img_path = "D:/DeskTop/Datasets/clothes/images/000056.jpg" # 需要可是化的图片
xml_path = "D:/DeskTop/Datasets/clothes/label_xml/000056.xml" # 图片对应的标签
img = cv.imread(img_path)
bboxes = np.array(get_xml_info(xml_path))
for box in bboxes:
pt1 = (box[0], box[1])
pt2 = (box[2], box[3])
cv.rectangle(img, pt1, pt2, (0, 0, 255), 4)
plt.figure(1)
plt.imshow(img[:, :, ::-1], cmap='gray')
plt.show()
最终结果
最终的处理结果包含四个文件夹,数据集图片以及三种类型的标签
至此,从labelme格式转为YOLO和VOC格式的任务就完成了。
下面是将txt标签中的科学计数法表示转为float的代码,有需要的或是强迫症患者可以参考一下。
代码如下:
先将txt中的’+‘替换为’-’
# change_txt_'+'_to_'-'.py
import os
def trans(input_dir, output_dir, word, splitword):
for root, dirs, files in os.walk(input_dir):
for item in files:
if os.path.splitext(item)[1] == ".txt":
f = open(input_dir+item, "r", encoding='UTF-8')
content = f.read()
content = content.replace(word, splitword)
with open(os.path.join(output_dir, item), 'w', encoding='UTF-8') as fval:
fval.write(content)
f.close()
if __name__ == '__main__':
# 老文件夹
input_dir = "D:\DeskTop\Datasets\clothes\label_txt/"
# 新文件夹
output_dir = "D:\DeskTop\Datasets\clothes\label_txt/"
# 要删除的字符
word='+'
# 要替换成的字符
splitword = "-"
trans(input_dir, output_dir, word, splitword)
再将科学计数法转为float
# !usr/bin env python
# -*- coding: utf-8 -*-
import re
import math
import os
def ConvertELogStrToValue(eLogStr):
"""
convert string of natural logarithm base of E to value
return (convertOK, convertedValue)
eg:
input: -1.1694737e-03
output: -0.001169
input: 8.9455025e-04
output: 0.000895
"""
(convertOK, convertedValue) = (False, 0.0)
foundEPower = re.search("(?P<coefficientPart>-?\d+\.\d+)e(?P<ePowerPart>-\d+)", eLogStr, re.I)
#print "foundEPower=",foundEPower
if(foundEPower):
coefficientPart = foundEPower.group("coefficientPart")
ePowerPart = foundEPower.group("ePowerPart")
#print "coefficientPart=%s,ePower=%s"%(coefficientPart, ePower)
coefficientValue = float(coefficientPart)
ePowerValue = float(ePowerPart)
#print "coefficientValue=%f,ePowerValue=%f"%(coefficientValue, ePowerValue)
#math.e= 2.71828182846
# wholeOrigValue = coefficientValue * math.pow(math.e, ePowerValue)
wholeOrigValue = coefficientValue * math.pow(10, ePowerValue)
#print "wholeOrigValue=",wholeOrigValue;
(convertOK, convertedValue) = (True, wholeOrigValue)
else:
(convertOK, convertedValue) = (False, 0.0)
return (convertOK, convertedValue)
def parseIntEValue(intEValuesStr):
# print "intEValuesStr=", intEValuesStr
intEStrList = re.findall("-?\d+\.\d+e-\d+", intEValuesStr)
# intEStrList = intEValuesStr.split(' ')
# print "intEStrList=", intEStrList
for eachIntEStr in intEStrList:
# intValue = int(eachIntEStr)
# print "intValue=",intValue
(convertOK, convertedValue) = ConvertELogStrToValue(eachIntEStr)
#print "convertOK=%s,convertedValue=%f"%(convertOK, convertedValue)
print("eachIntEStr=%s,\tconvertedValue=%f" % (eachIntEStr, convertedValue))
trans(txt_path,txt_path,eachIntEStr,convertedValue)
def trans(input_dir, output_dir, word, splitword):
for root, dirs, files in os.walk(input_dir):
for item in files:
if os.path.splitext(item)[1] == ".txt":
f = open(input_dir+item, "r", encoding='UTF-8')
content = f.read()
content = content.replace(str(word), str(splitword))
with open(os.path.join(output_dir, item), 'w', encoding='UTF-8') as fval:
fval.write(content)
f.close()
# intEValuesStr= 2.1690427e-005 -1.1694737e-003 -6.1193734e-004
# 8.9455025e-004 -8.6277081e-004 -7.2735757e-004
# intEStrList= ['2.1690427e-005', '-1.1694737e-003', '-6.1193734e-004', '8.9455025e-004', '-8.6277081e-004', '-7.2735757e-004']
# eachIntEStr=2.1690427e-005, convertedValue=0.014615
# eachIntEStr=-1.1694737e-003, convertedValue=-0.058225
# eachIntEStr=-6.1193734e-004, convertedValue=-0.112080
# eachIntEStr=8.9455025e-004, convertedValue=0.163843
# eachIntEStr=-8.6277081e-004, convertedValue=-0.158022
# eachIntEStr=-7.2735757e-004, convertedValue=-0.133220
if __name__ == "__main__":
txt_path = "D:\DeskTop\Datasets\clothes\label_txt/"
output_dir = "D:\DeskTop\Datasets\clothes\label_txt/"
# data_path = "D:/DeskTop/000001.txt"
for root, dirs, files in os.walk(txt_path):
for item in files:
if os.path.splitext(item)[1] == ".txt":
with open(txt_path + item, 'r') as f:
for line in f.readlines():
linestr = line.strip()
# print linestr
parseIntEValue(linestr)