一、标注格式转换
1.voc2yolo
import os
import xml.etree.ElementTree as ET
# VOC格式标注文件夹路径
voc_annotations_folder = "E:\\Data_set\\on_water\\Changjiangdata\\Changjiangdata\\voc\\ann1_c2"
# YOLO格式标注保存路径
yolo_annotations_folder = "E:\\Data_set\\on_water\\Changjiangdata\\Changjiangdata\\voc\\label1_c2"
# 类别映射字典,根据自己的数据集修改
class_map = {
"passenger_ship": 0,
"cargo_ship": 1,
# "class3": 2,
# Add more classes as needed
}
def convert_coordinates(size, box):
dw = 1.0 / size[0]
dh = 1.0 / size[1]
x = (box[0] + box[1]) / 2.0
y = (box[2] + box[3]) / 2.0
w = box[1] - box[0]
h = box[3] - box[2]
x = x * dw
w = w * dw
y = y * dh
h = h * dh
return x, y, w, h
def convert_voc_to_yolo(voc_annotation_path, yolo_annotation_path, class_map):
tree = ET.parse(voc_annotation_path)
root = tree.getroot()
size = root.find('size')
width = float(size.find('width').text)
height = float(size.find('height').text)
yolo_lines = []
for obj in root.iter('object'):
difficult = obj.find('difficult').text
cls = obj.find('name').text
if cls not in class_map or int(difficult) == 1:
continue
cls_id = class_map[cls]
xmlbox = obj.find('bndbox')
xmin = float(xmlbox.find('xmin').text)
xmax = float(xmlbox.find('xmax').text)
ymin = float(xmlbox.find('ymin').text)
ymax = float(xmlbox.find('ymax').text)
b = (xmin, xmax, ymin, ymax)
bb = convert_coordinates((width, height), b)
yolo_lines.append(f"{cls_id} {' '.join([str(a) for a in bb])}")
with open(yolo_annotation_path, 'w') as f:
for line in yolo_lines:
f.write(line + '\n')
if not os.path.exists(yolo_annotations_folder):
os.makedirs(yolo_annotations_folder)
# 遍历VOC格式标注文件夹
for filename in os.listdir(voc_annotations_folder):
if filename.endswith('.xml'):
voc_annotation_path = os.path.join(voc_annotations_folder, filename)
# 构建对应的YOLO格式标注文件名
yolo_annotation_path = os.path.join(yolo_annotations_folder, os.path.splitext(filename)[0] + ".txt")
convert_voc_to_yolo(voc_annotation_path, yolo_annotation_path, class_map)
print("Conversion completed.")
2.yolo2voc
from xml.dom.minidom import Document
import os
import cv2
# def makexml(txtPath, xmlPath, picPath): # txt所在文件夹路径,xml文件保存路径,图片所在文件夹路径
def makexml(picPath, txtPath, xmlPath): # txt所在文件夹路径,xml文件保存路径,图片所在文件夹路径
"""此函数用于将yolo格式txt标注文件转换为voc格式xml标注文件
"""
dic = {'0': "cargo_ship", # 创建字典用来对类型进行转换
'1': "container_ship", # 此处的字典要与自己的classes.txt文件中的类对应,且顺序要一致
'2': "passenger_ship",
'3':'speed_boat',
'4':'buoy'
}
files = os.listdir(txtPath)
for i, name in enumerate(files):
xmlBuilder = Document()
annotation = xmlBuilder.createElement("annotation") # 创建annotation标签
xmlBuilder.appendChild(annotation)
txtFile = open(txtPath + name)
txtList = txtFile.readlines()
img = cv2.imread(picPath + name[0:-4] + ".jpg")
Pheight, Pwidth, Pdepth = img.shape
folder = xmlBuilder.createElement("folder") # folder标签
foldercontent = xmlBuilder.createTextNode("driving_annotation_dataset")
folder.appendChild(foldercontent)
annotation.appendChild(folder) # folder标签结束
filename = xmlBuilder.createElement("filename") # filename标签
filenamecontent = xmlBuilder.createTextNode(name[0:-4] + ".jpg")
filename.appendChild(filenamecontent)
annotation.appendChild(filename) # filename标签结束
size = xmlBuilder.createElement("size") # size标签
width = xmlBuilder.createElement("width") # size子标签width
widthcontent = xmlBuilder.createTextNode(str(Pwidth))
width.appendChild(widthcontent)
size.appendChild(width) # size子标签width结束
height = xmlBuilder.createElement("height") # size子标签height
heightcontent = xmlBuilder.createTextNode(str(Pheight))
height.appendChild(heightcontent)
size.appendChild(height) # size子标签height结束
depth = xmlBuilder.createElement("depth") # size子标签depth
depthcontent = xmlBuilder.createTextNode(str(Pdepth))
depth.appendChild(depthcontent)
size.appendChild(depth) # size子标签depth结束
annotation.appendChild(size) # size标签结束
for j in txtList:
oneline = j.strip().split(" ")
object = xmlBuilder.createElement("object") # object 标签
picname = xmlBuilder.createElement("name") # name标签
namecontent = xmlBuilder.createTextNode(dic[oneline[0]])
picname.appendChild(namecontent)
object.appendChild(picname) # name标签结束
pose = xmlBuilder.createElement("pose") # pose标签
posecontent = xmlBuilder.createTextNode("Unspecified")
pose.appendChild(posecontent)
object.appendChild(pose) # pose标签结束
truncated = xmlBuilder.createElement("truncated") # truncated标签
truncatedContent = xmlBuilder.createTextNode("0")
truncated.appendChild(truncatedContent)
object.appendChild(truncated) # truncated标签结束
difficult = xmlBuilder.createElement("difficult") # difficult标签
difficultcontent = xmlBuilder.createTextNode("0")
difficult.appendChild(difficultcontent)
object.appendChild(difficult) # difficult标签结束
bndbox = xmlBuilder.createElement("bndbox") # bndbox标签
xmin = xmlBuilder.createElement("xmin") # xmin标签
mathData = int(((float(oneline[1])) * Pwidth + 1) - (float(oneline[3])) * 0.5 * Pwidth)
xminContent = xmlBuilder.createTextNode(str(mathData))
xmin.appendChild(xminContent)
bndbox.appendChild(xmin) # xmin标签结束
ymin = xmlBuilder.createElement("ymin") # ymin标签
mathData = int(((float(oneline[2])) * Pheight + 1) - (float(oneline[4])) * 0.5 * Pheight)
yminContent = xmlBuilder.createTextNode(str(mathData))
ymin.appendChild(yminContent)
bndbox.appendChild(ymin) # ymin标签结束
xmax = xmlBuilder.createElement("xmax") # xmax标签
mathData = int(((float(oneline[1])) * Pwidth + 1) + (float(oneline[3])) * 0.5 * Pwidth)
xmaxContent = xmlBuilder.createTextNode(str(mathData))
xmax.appendChild(xmaxContent)
bndbox.appendChild(xmax) # xmax标签结束
ymax = xmlBuilder.createElement("ymax") # ymax标签
mathData = int(((float(oneline[2])) * Pheight + 1) + (float(oneline[4])) * 0.5 * Pheight)
ymaxContent = xmlBuilder.createTextNode(str(mathData))
ymax.appendChild(ymaxContent)
bndbox.appendChild(ymax) # ymax标签结束
object.appendChild(bndbox) # bndbox标签结束
annotation.appendChild(object) # object标签结束
f = open(xmlPath + name[0:-4] + ".xml", 'w')
xmlBuilder.writexml(f, indent='\t', newl='\n', addindent='\t', encoding='utf-8')
f.close()
if __name__ == "__main__":
picPath = "D:/Data_set/on_water/Changjiangdata/Changjiangdata/images/val/" # 图片所在文件夹路径,后面的/一定要带上
txtPath = "D:/Data_set/on_water/Changjiangdata/Changjiangdata/labels/val/" # txt所在文件夹路径,后面的/一定要带上
xmlPath = "D:/Data_set/on_water/Changjiangdata/Changjiangdata/Annotations/" # xml文件保存路径,后面的/一定要带上
makexml(picPath, txtPath, xmlPath)
二、数据集划分
1.voc(xml)划分
import os
import random
import argparse
parser = argparse.ArgumentParser()
#xml文件的地址,根据自己的数据进行修改 xml一般存放在Annotations下
parser.add_argument('--xml_path',default='seaships7000/Annotations', type=str, help='input xml label path') # 路径自行修改
#数据集的划分,地址选择自己数据下的ImageSets/Main
parser.add_argument('--txt_path',default='seaships7000/ImageSets/Main',type=str, help='output txt label path')# 路径自行修改
opt = parser.parse_args()
trainval_percent = 1.0
train_percent = 0.7
xmlfilepath = opt.xml_path
txtsavepath = opt.txt_path
total_xml = os.listdir(xmlfilepath)
if not os.path.exists(txtsavepath):
os.makedirs(txtsavepath)
num = len(total_xml)
list_index = range(num)
tv = int(num * trainval_percent)
tr = int(tv * train_percent)
trainval = random.sample(list_index, tv)
train = random.sample(trainval, tr)
file_trainval = open(txtsavepath + '/trainval.txt', 'w')
file_test = open(txtsavepath + '/test.txt', 'w')
file_train = open(txtsavepath + '/train.txt', 'w')
file_val = open(txtsavepath + '/val.txt', 'w')
for i in list_index:
name = total_xml[i][:-4] + '\n'
if i in trainval:
file_trainval.write(name)
if i in train:
file_train.write(name)
else:
file_val.write(name)
else:
file_test.write(name)
file_trainval.close()
file_train.close()
file_val.close()
file_test.close()
2.yolo格式划分
import shutil
import random
import os
# 原始路径
image_original_path = "D:/Data_set/Fusion_Dataset/FusionDataset_20231127_1630/Img_20231127_1630/mydata/images/"
label_original_path = "D:/Data_set/Fusion_Dataset/FusionDataset_20231127_1630/Img_20231127_1630/mydata/labels/"
cur_path = os.getcwd()
# 划分后的数据集根目录
dataset_root = "D:/Data_set/Fusion_Dataset/FusionDataset_20231127_1630/Img_20231127_1630"
# 训练集路径
train_image_path = os.path.join(dataset_root, "images/train/")
train_label_path = os.path.join(dataset_root, "labels/train/")
# 验证集路径
val_image_path = os.path.join(dataset_root, "images/val/")
val_label_path = os.path.join(dataset_root, "labels/val/")
# 测试集路径
test_image_path = os.path.join(dataset_root, "images/test/")
test_label_path = os.path.join(dataset_root, "labels/test/")
# 训练集目录
list_train = os.path.join(dataset_root, "train.txt")
list_val = os.path.join(dataset_root, "val.txt")
list_test = os.path.join(dataset_root, "test.txt")
train_percent = 0.8
val_percent = 0.2
test_percent = 0
def del_file(path):
for i in os.listdir(path):
file_data = path + "\\" + i
os.remove(file_data)
def mkdir():
if not os.path.exists(train_image_path):
os.makedirs(train_image_path)
else:
del_file(train_image_path)
if not os.path.exists(train_label_path):
os.makedirs(train_label_path)
else:
del_file(train_label_path)
if not os.path.exists(val_image_path):
os.makedirs(val_image_path)
else:
del_file(val_image_path)
if not os.path.exists(val_label_path):
os.makedirs(val_label_path)
else:
del_file(val_label_path)
if not os.path.exists(test_image_path):
os.makedirs(test_image_path)
else:
del_file(test_image_path)
if not os.path.exists(test_label_path):
os.makedirs(test_label_path)
else:
del_file(test_label_path)
def clearfile():
if os.path.exists(list_train):
os.remove(list_train)
if os.path.exists(list_val):
os.remove(list_val)
if os.path.exists(list_test):
os.remove(list_test)
def main():
mkdir()
clearfile()
file_train = open(list_train, 'w')
file_val = open(list_val, 'w')
file_test = open(list_test, 'w')
total_txt = os.listdir(label_original_path)
num_txt = len(total_txt)
list_all_txt = range(num_txt)
num_train = int(num_txt * train_percent)
num_val = int(num_txt * val_percent)
num_test = num_txt - num_train - num_val
train = random.sample(list_all_txt, num_train)
# train从list_all_txt取出num_train个元素
# 所以list_all_txt列表只剩下了这些元素
val_test = [i for i in list_all_txt if not i in train]
# 再从val_test取出num_val个元素,val_test剩下的元素就是test
val = random.sample(val_test, num_val)
print("训练集数目:{}, 验证集数目:{}, 测试集数目:{}".format(len(train), len(val), len(val_test) - len(val)))
for i in list_all_txt:
name = total_txt[i][:-4]
srcImage = image_original_path + name + '.jpg'
srcLabel = label_original_path + name + ".txt"
if i in train:
dst_train_Image = train_image_path + name + '.jpg'
dst_train_Label = train_label_path + name + '.txt'
shutil.copyfile(srcImage, dst_train_Image)
shutil.copyfile(srcLabel, dst_train_Label)
file_train.write(dst_train_Image + '\n')
elif i in val:
dst_val_Image = val_image_path + name + '.jpg'
dst_val_Label = val_label_path + name + '.txt'
shutil.copyfile(srcImage, dst_val_Image)
shutil.copyfile(srcLabel, dst_val_Label)
file_val.write(dst_val_Image + '\n')
else:
dst_test_Image = test_image_path + name + '.jpg'
dst_test_Label = test_label_path + name + '.txt'
shutil.copyfile(srcImage, dst_test_Image)
shutil.copyfile(srcLabel, dst_test_Label)
file_test.write(dst_test_Image + '\n')
file_train.close()
file_val.close()
file_test.close()
if __name__ == "__main__":
main()
三、统计目标个数
1.voc标注格式(xml)
import os
import xml.etree.ElementTree as ET
# Path of xml files folder
xml_path = r"E:\\Data_set\\on_water\\Changjiangdata\\Changjiangdata\\voc\\ann1"
files = os.listdir(xml_path)
gt_dict = {}
if __name__ == '__main__':
for xm in files:
xmlfile = os.path.join(xml_path, xm)
# Skip directories
if not os.path.isfile(xmlfile):
continue
try:
tree = ET.parse(xmlfile)
root = tree.getroot()
filenamelist = root.findall("filename")
filename = filenamelist[0].text
objectlist = root.findall("object")
##
for objects in objectlist:
namelist = objects.findall("name")
objectname = namelist[0].text
if objectname == '-':
print(filename)
if objectname in gt_dict:
gt_dict[objectname] += 1
else:
gt_dict[objectname] = 1
except ET.ParseError:
print(f"无法解析XML文件:{xmlfile}")
dic = sorted(gt_dict.items(), key=lambda d: d[1], reverse=True)
print(dic)
print(len(dic))
2.YOLO格式
import os
label_folder = "D:/Data_set/on_water/Changjiangdata/Changjiangdata/labels/train" # 标签文件夹路径
class_counts = {} # 类别计数字典
label_list = os.listdir(label_folder)
for label_file in label_list:
label_path = os.path.join(label_folder, label_file)
with open(label_path, 'r') as file:
lines = file.readlines()
for line in lines:
class_id = int(line.split()[0])
class_counts[class_id] = class_counts.get(class_id, 0) + 1
# 输出类别计数结果
for class_id, count in class_counts.items():
print("Class {}: {}".format(class_id, count))
四、YOLO标注可视化
1.全部可视化
import cv2
import os
from tqdm import tqdm
# 读取YOLO标注文件
def read_yolo_annotation(file_path):
with open(file_path, 'r') as file:
data = file.read().strip().split(' ')
class_id = int(data[0])
x, y, w, h = map(float, data[1:])
return class_id, x, y, w, h
# 绘制标注框并保存
def draw_and_save_boxes(image_path, txt_path, output_folder):
img = cv2.imread(image_path)
class_id, x, y, w, h = read_yolo_annotation(txt_path)
height, width, _ = img.shape
x1 = int((x - w / 2) * width)
y1 = int((y - h / 2) * height)
x2 = int((x + w / 2) * width)
y2 = int((y + h / 2) * height)
cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
output_path = os.path.join(output_folder, os.path.basename(image_path))
cv2.imwrite(output_path, img)
# 遍历文件夹中的图片和YOLO标注文件
def process_images_in_folders(image_folder, txt_folder, output_folder):
if not os.path.exists(output_folder):
os.makedirs(output_folder) # 创建输出文件夹
image_files = [file for file in os.listdir(image_folder) if file.endswith(".jpg")]
for file in tqdm(image_files, desc='Processing images'):
image_path = os.path.join(image_folder, file)
txt_file = os.path.splitext(file)[0] + ".txt"
txt_path = os.path.join(txt_folder, txt_file)
if os.path.exists(txt_path):
draw_and_save_boxes(image_path, txt_path, output_folder)
# 示例使用
image_folder = "D:/Data_set/Fusion_Dataset/FusionDataset_20231127_1630/Img_20231127_1630/mydata/images"
txt_folder = "D:/Data_set/Fusion_Dataset/FusionDataset_20231127_1630/Img_20231127_1630/mydata/labels"
output_folder = "D:/Data_set/Fusion_Dataset/FusionDataset_20231127_1630/Img_20231127_1630/mydata/output_folder"
process_images_in_folders(image_folder, txt_folder, output_folder)
2.其他版本
import cv2
import os
def draw_box_in_single_image(image_path, txt_path):
# 读取图像
image = cv2.imread(image_path)
# 读取txt文件信息
def read_list(txt_path):
pos = []
with open(txt_path, 'r') as file_to_read:
while True:
lines = file_to_read.readline() # 整行读取数据
if not lines:
break
# 将整行数据分割处理,如果分割符是空格,括号里就不用传入参数,如果是逗号, 则传入‘,'字符。
p_tmp = [float(i) for i in lines.split(' ')]
pos.append(p_tmp) # 添加新读取的数据
# Efield.append(E_tmp)
pass
return pos
# txt转换为box
def convert(size, box):
xmin = (box[1]-box[3]/2.)*size[1]
xmax = (box[1]+box[3]/2.)*size[1]
ymin = (box[2]-box[4]/2.)*size[0]
ymax = (box[2]+box[4]/2.)*size[0]
box = (int(xmin), int(ymin), int(xmax), int(ymax))
return box
pos = read_list(txt_path)
print(pos)
tl = int((image.shape[0]+image.shape[1])/2)
lf = max(tl-1,1)
for i in range(len(pos)):
label = str(int(pos[i][0]))
print('label is '+label)
box = convert(image.shape, pos[i])
image = cv2.rectangle(image,(box[0], box[1]),(box[2],box[3]),(0,0,255),2)
cv2.putText(image,label,(box[0],box[1]-2), 0, 1, [0,0,255], thickness=2, lineType=cv2.LINE_AA)
pass
if pos:
cv2.imwrite('D:/Data_set/on_water/Changjiangdata/Changjiangdata/see_labels_images/{}.png'.format(image_path.split('\\')[-1][:-4]), image)
else:
print('None')
print('D:/Data_set/on_water/Changjiangdata/Changjiangdata/see_labels_images/{}.png'.format(image_path.split('\\')[-1][:-4]))
# cv2.imshow("images", image)
# cv2.waitKey(0)
# cv2.destroyAllWindows()
img_folder = "D:/Data_set/on_water/Changjiangdata/Changjiangdata/images/train"
img_list = os.listdir(img_folder)
img_list.sort()
label_folder = "D:/Data_set/on_water/Changjiangdata/Changjiangdata/labels/train"
label_list = os.listdir(label_folder)
label_list.sort()
for i in range(len(img_list)):
image_path = img_folder + "\\" + img_list[i]
txt_path = label_folder + "\\" + label_list[i]
draw_box_in_single_image(image_path, txt_path)
五、标注修改
1.名称替换
import os
import xml.etree.ElementTree as ET
# 定义类别名称的替换映射
class_mapping = {
# 'fishing_ship': 'cargo_ship',
# 'Cargo_ship': 'cargo_ship',
# 'Cargo_Ship': 'cargo_ship',
# 'Fishing_ship': 'fishing_ship',
# 'Passenger_ship': 'passenger_ship',
# 'Container_ship': 'container_ship',
# 'Buoy': 'buoy',
# 'Speed_boat': 'speed_boat'
'Sailing_boat': 'Passenger_ship'
}
# 指定包含XML标注文件的文件夹路径
folder_path = "D:/Data_set/haihang/分组/1"
# 遍历文件夹中的每个XML标注文件
for filename in os.listdir(folder_path):
if not filename.endswith('.xml'):
continue
file_path = os.path.join(folder_path, filename)
# 解析XML文件
tree = ET.parse(file_path)
root = tree.getroot()
# 查找并替换类别名称
for obj in root.iter('object'):
name = obj.find('name').text
if name in class_mapping:
new_name = class_mapping[name]
obj.find('name').text = new_name
# 保存修改后的XML文件
tree.write(file_path)
2.删除指定名称
import os
import xml.etree.ElementTree as ET
def remove_yes_boxes(xml_folder):
for xml_file in os.listdir(xml_folder):
if not xml_file.endswith('.xml'):
continue
xml_path = os.path.join(xml_folder, xml_file)
tree = ET.parse(xml_path)
root = tree.getroot()
# 查找所有的 object 元素
objects = root.findall('object')
for obj in objects:
# 查找名为 'name' 的子元素
name_elem = obj.find('name')
if name_elem is not None and name_elem.text == 'speed_boat':
# 删除包含 'Yes' 的标注框
root.remove(obj)
# print('ok')
# 保存修改后的 XML 文件
tree.write(xml_path)
# 指定包含 XML 文件的文件夹路径
xml_folder = 'E:\\Data_set\\on_water\\Changjiangdata\\Changjiangdata\\voc\\ann1'
# 调用函数删除名为 'Yes' 的标注框
remove_yes_boxes(xml_folder)
3.删除小像素目标
import os
import xml.etree.ElementTree as ET
# 定义目标框的最小面积阈值
min_area = 64
# 指定包含XML标注文件的文件夹路径
folder_path = "D:\Data_set\on_water\Ship\labels"
# 遍历文件夹中的每个XML标注文件
for filename in os.listdir(folder_path):
if not filename.endswith('.xml'):
continue
file_path = os.path.join(folder_path, filename)
# 解析XML文件
tree = ET.parse(file_path)
root = tree.getroot()
# 查找并删除面积小于最小阈值的目标框
for obj in root.findall('object'):
bbox = obj.find('bndbox')
xmin = int(bbox.find('xmin').text)
ymin = int(bbox.find('ymin').text)
xmax = int(bbox.find('xmax').text)
ymax = int(bbox.find('ymax').text)
area = (xmax - xmin) * (ymax - ymin)
if area < min_area:
root.remove(obj)
# 保存修改后的XML文件
tree.write(file_path)
4.txt内容替换
def replace_text_in_file(file_path, old_text, new_text):
# 读取原始文件内容
with open(file_path, 'r') as file:
file_content = file.read()
# 替换文本内容
new_content = file_content.replace(old_text, new_text)
# 将替换后的内容写回文件
with open(file_path, 'w') as file:
file.write(new_content)
# 指定文件路径、要替换的旧文本和新文本
file_path = "val.txt"
old_text = 'yolov9' #旧文本
new_text = 'yolov5_5' #新文本
# 调用函数进行文本替换
replace_text_in_file(file_path, old_text, new_text)
print(f"文本替换完成。")