转载:从VOC格式到YOLO格式:图像标注数据的转换过程_voc转yolo-CSDN博客
随着深度学习在目标检测领域的广泛应用,各类数据格式的处理和转换显得尤为重要。其中,PASCAL VOC (Visual Object Classes) 格式和YOLO (You Only Look Once) 格式是两种广泛使用的图像标注格式。本文将详细介绍如何将VOC格式的图像标注数据转换为YOLO格式。
一、VOC格式简介
PASCAL VOC格式是一种流行的目标检测数据集格式,它包含XML文件记录每张图片中目标的位置(矩形框坐标)、类别等信息。每个XML文件对应一张图片,描述了图片中的所有物体实例。
二、YOLO格式简介
YOLO格式则更为简洁直接,它通常将标注信息存储在一个文本文件中,每一行对应一张图片的数据。YOLO标注格式一般包括图片路径、每个目标的中心坐标(相对于整图宽高的比例)、目标的宽度和高度(同样以比例形式表示)以及目标的类别索引。
三、VOC转YOLO格式的具体步骤
1. **读取VOC数据**:首先需要读取VOC格式的XML标注文件,提取出每张图片的文件名、目标类别以及对应的矩形框坐标。
2. **坐标转换**:VOC格式使用的是绝对坐标(像素值),而YOLO格式使用相对坐标(比例)。因此,需要将矩形框的左上角坐标(xmin, ymin)和右下角坐标(xmax, ymax)转换为YOLO格式下的中心点坐标(x, y)和宽高(w, h),它们均是以图像宽度和高度的比例表示。
3. **格式化输出**:按照YOLO的格式要求,将每张图片的文件名、每个目标的类别索引(根据YOLO的类别字典进行映射)、以及转换后的中心坐标、宽高信息整理成一行文本,写入到YOLO格式的标注文件中。
通过以上步骤,我们就成功地将原本的VOC格式图像标注数据转换为了YOLO格式,以便于在基于YOLO的目标检测模型训练时使用。值得注意的是,在实际操作过程中可能需要编写相应的脚本或程序来自动化完成这一系列数据处理工作。
import xml.etree.ElementTree as ET
import sys
import os.path
import cv2
class XmlParse:
def __init__(self, file_path):
# 初始化成员变量:self.tree 和 self.root 分别用于存储XML文件解析后的ElementTree对象和根节点;self.xml_file_path 存储传入的XML文件路径。
self.tree = None
self.root = None
self.xml_file_path = file_path
# 使用 try...except...finally 结构处理可能出现的异常情况。
def ReadXml(self): # 该方法用于读取XML文件并解析为ElementTree对象。
try:
self.tree = ET.parse(self.xml_file_path) # 使用 xml.etree.ElementTree.parse() 方法解析XML文件,将结果赋值给 self.tree
self.root = self.tree.getroot() # 获取XML文件的根节点并赋值给 self.root。
except Exception as e: # 在 except Exception as e 块内,捕获并打印解析失败的错误信息,并通过 sys.exit() 终止程序执行。
print("parse xml faild!")
sys.exit()
else:
pass
finally: # finally 块会在不论是否出现异常的情况下都会被执行,这里返回解析好的 self.tree。
return self.tree
def WriteXml(self, destfile):
dses_xml_file = os.path.abspath(destfile)
self.tree.write(dses_xml_file, encoding="utf-8", xml_declaration=True)
def xml2txt(xml, labels, name_list, img_path):
for i, j in zip(os.listdir(xml), os.listdir(img_path)):
p = os.path.join(xml + '/' + i) # xml路径
xml_file = os.path.abspath(p) # 绝对路径
parse = XmlParse(xml_file)
tree = parse.ReadXml() # xml树
root = tree.getroot() # 根节点
W = float(root.find('size').find('width').text)
H = float(root.find('size').find('height').text)
fil_name = root.find('filename').text[:-4]
if not os.path.exists(labels): # 如果路径不存在则创建
os.mkdir(labels)
out = open(labels + './' + fil_name + '.txt', 'w+')
for obj in root.iter('object'):
x_min = float(obj.find('bndbox').find('xmin').text)
x_max = float(obj.find('bndbox').find('xmax').text)
y_min = float(obj.find('bndbox').find('ymin').text)
y_max = float(obj.find('bndbox').find('ymax').text)
print(f'------------------------{i}-----------------------')
print('W:', W, 'H:', H)
# 计算公式
xcenter = x_min + (x_max - x_min) / 2
ycenter = y_min + (y_max - y_min) / 2
w = x_max - x_min
h = y_max - y_min
# 目标框的中心点 宽高
print('center_X: ', xcenter)
print('center_Y: ', ycenter)
print('target box_w: ', w)
print('target box_h: ', h)
# 归一化
xcenter = round(xcenter / W, 6)
ycenter = round(ycenter / H, 6)
w = round(w / W, 6)
h = round(h / H, 6)
print('>>>>>>>>>>')
print(xcenter)
print(ycenter)
print(w)
print(h)
class_dict = dict(zip(name_list, range(0, len(name_list))))
class_name = obj.find('name').text
if class_name not in name_list:
pass
else:
class_id = class_dict[class_name]
print('类别: ', class_id)
print("创建成功: {}".format(fil_name + '.txt'))
print('----------------------------------------------------')
out.write(str(class_id) + " " + str(xcenter) + " " + str(ycenter) + " " + str(w) + " " + str(h) + "\n")
# show_img
m = os.path.join(img_path + '/' + j)
block = cv2.imread(m)
cv2.rectangle(block, pt1=(int((xcenter - w / 2) * W), int((ycenter - h / 2) * H)),
pt2=(int((xcenter + w / 2) * W), int((ycenter + h / 2) * H)),
color=(0, 255, 0), thickness=2)
cv2.imshow('block', block)
cv2.waitKey(300)
def folder_Path():
img_path = './images_new'
xml_path = './xmls_new' # xml路径
labels = './labels_new' # 转txt路径
name_list = ['trafficlight', 'stop', 'speedlimit', 'crosswalk'] # 类别名
xml2txt(xml_path, labels, name_list, img_path)
if __name__ == '__main__':
folder_Path()