1. 半自动标注 YOLO 数据的原理与方法
- 模型辅助标注:首先,通过训练一个初步的 YOLO 模型,使其能够识别出大致的目标。这一步通常是基于少量手动标注的数据集进行训练。
- 推理辅助标注:将初步训练的 YOLO 模型应用于新图像,生成初步的检测结果,这些检测框会作为半自动标注的基础。
- 手动校正:对于模型生成的标注框,使用标注工具(如 LabelImg 等)进行必要的手动微调和修正,以提高标注的准确性。
2. 将 YOLO 格式标注转换为 LabelImg 格式的流程
- YOLO 格式与 PASCAL VOC 格式的区别:介绍 YOLO 使用的是相对坐标的格式,而 LabelImg 使用的 PASCAL VOC XML 格式是绝对坐标。
- 坐标转换:在生成 YOLO 标注时,坐标是归一化的中心点坐标和宽高,而在生成 LabelImg 的 XML 文件时,需要将这些坐标转换成左上角和右下角的像素坐标。
- 类别映射:解释如何将 YOLO 的类别 ID 与具体的类别名称对应,并在生成的 XML 文件中正确写入。
3. 自动化脚本示例
- 博客通常会提供一段 Python 脚本,详细说明如何从 YOLO 格式自动生成 PASCAL VOC 格式的 XML 文件。脚本可能包括:
- 读取 YOLO 文件并解析数据:解析每个 YOLO 文件中的类别 ID 和坐标。
- 反归一化 YOLO 坐标:将 YOLO 的中心点坐标和宽高转换为左上角和右下角的坐标。
- 生成 XML 文件:用 PASCAL VOC XML 格式的标签信息填充每个目标对象,并将生成的 XML 文件保存在指定文件夹。
4. 实现效果与应用场景
- 提升标注效率:解释如何利用半自动标注技术大幅减少人工标注的工作量,尤其适用于需要标注大量数据的场景。
- 适用数据集:博客可能会提到,这种方法适用于目标比较简单且明显的数据集,例如交通标志、自然场景中的单类或少类目标。
一、Labelimg标注图像数据
pascl voc格式标注文件
二、将pascl voc 转化为yolo格式
python
import os
import xml.etree.ElementTree as ET
import shutil
# 输入和输出路径
annotations_dir = 'F:\\trafficsigntest\\TT100K\\tt100k_2021\\tt100k_2021\\yolo\\yolonew\yololabel' # 标注文件夹路径
images_dir = 'F:\\trafficsigntest\\TT100K\\tt100k_2021\\tt100k_2021\\yolo\\yolonew\\yolonew' # 图像文件夹路径
output_dir = 'F:\\trafficsigntest\\TT100K\\tt100k_2021\\tt100k_2021\\yolonew' # YOLO 格式输出文件夹
# 检查输出文件夹是否存在
os.makedirs(output_dir, exist_ok=True)
os.makedirs(os.path.join(output_dir, "images"), exist_ok=True)
os.makedirs(os.path.join(output_dir, "labels"), exist_ok=True)
# 类别映射(假设有单一类别"monostyle",否则需要更改类别)
class_mapping = {"monostyle": 0,"single_cantilever":1,"gantry":2,"road_attachment":"3","double_cantilever":4,"polystyle":5}
# 遍历 XML 文件
for xml_file in os.listdir(annotations_dir):
if xml_file.endswith('.xml'):
# 解析 XML 文件
tree = ET.parse(os.path.join(annotations_dir, xml_file))
root = tree.getroot()
# 获取图像文件名和尺寸
filename = root.find('filename').text
width = int(root.find('size/width').text)
height = int(root.find('size/height').text)
# 生成 YOLO 格式标注
yolo_annotations = []
for obj in root.findall('object'):
class_name = obj.find('name').text
if class_name in class_mapping:
class_id = class_mapping[class_name]
# 获取边界框并转换为 YOLO 格式
xmin = int(obj.find('bndbox/xmin').text)
ymin = int(obj.find('bndbox/ymin').text)
xmax = int(obj.find('bndbox/xmax').text)
ymax = int(obj.find('bndbox/ymax').text)
# 转换为 YOLO 格式坐标
x_center = ((xmin + xmax) / 2) / width
y_center = ((ymin + ymax) / 2) / height
box_width = (xmax - xmin) / width
box_height = (ymax - ymin) / height
# 添加到标注列表
yolo_annotations.append(f"{class_id} {x_center} {y_center} {box_width} {box_height}")
# 写入 YOLO 格式的标注文件
yolo_filename = os.path.splitext(filename)[0] + '.txt'
with open(os.path.join(output_dir, "labels", yolo_filename), 'w') as f:
f.write("\n".join(yolo_annotations))
# 拷贝图像文件
shutil.copy(os.path.join(images_dir, filename), os.path.join(output_dir, "images", filename))
print("转换完成!所有标注文件已转换为 YOLO 格式并拷贝对应图像文件。")
yolo 格式标注文件
三、整理为YOLO v5 模型训练格式
python
# Ultralytics YOLOv5 🚀, AGPL-3.0 license
# COCO128 dataset https://www.kaggle.com/ultralytics/coco128 (first 128 images from COCO train2017) by Ultralytics
# Example usage: python train.py --data coco128.yaml
# parent
# ├── yolov5
# └── datasets
# └── coco128 ← downloads here (7 MB)
# Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..]
path: /home/lyy/yolo # dataset root dir
train: images/train # train images (relative to 'path') 128 images
val: images/val # val images (relative to 'path') 128 images
#test: images/test # test images (optional)
#{"monostyle": 0,"single_cantilever":1,"gantry":2,"road_attachment":"3","double_cantilever":4,"polystyle":5}
# Classes
names:
0: monostyle
1: single_cantilever
2: gantry
3: road_attachment
4: double_cantilever
5: polystyle
训练模型:
bash
python train.py --data='data/tt100k_yolo.yaml'
模型预测:
bash
python detect.py --data='data/tt100k_yolo.yaml' --weights='runs/train/exp38/weights/best.pt' --source='/common/dataset/bdd100k_images/bdd100k/images/10k/test' --name='/common/dataset/bdd100k_images/bdd100k/images/10k/exp'
四、逆向将yolo格式数据集生成labelimg标注的数据
python
import os
import xml.etree.ElementTree as ET
from xml.dom.minidom import parseString
from PIL import Image
# # 输入和输出路径
# yolo_labels_dir = 'path/to/yolo_labels' # YOLO格式标签路径
# images_dir = 'path/to/images' # 原始图像路径
# output_dir = 'path/to/voc_labels' # 输出的VOC XML文件路径
# 输入和输出路径
yolo_labels_dir = 'F:\\trafficsigntest\\TT100K\\exp2\\labels' # YOLO格式标签路径
images_dir = 'F:\\trafficsigntest\\TT100K\\exp2\\test' # 原始图像路径
output_dir = 'F:\\trafficsigntest\\TT100K\\exp2\\newlabels' # 输出的Labelme JSON文件路径
# 类别映射,确保类别 ID 与 LabelImg 标签一致
class_mapping = {0:"monostyle",1:"single_cantilever",2:"gantry",3:"road_attachment",4:"double_cantilever",5:"polystyle"}
# 创建输出目录
os.makedirs(output_dir, exist_ok=True)
# 定义生成 XML 标签的函数
def create_voc_xml(image_path, image_size, shapes, output_path):
root = ET.Element("annotation")
folder = ET.SubElement(root, "folder")
folder.text = os.path.basename(os.path.dirname(image_path))
filename = ET.SubElement(root, "filename")
filename.text = os.path.basename(image_path)
path = ET.SubElement(root, "path")
path.text = image_path
source = ET.SubElement(root, "source")
database = ET.SubElement(source, "database")
database.text = "Unknown"
size = ET.SubElement(root, "size")
width = ET.SubElement(size, "width")
width.text = str(image_size[0])
height = ET.SubElement(size, "height")
height.text = str(image_size[1])
depth = ET.SubElement(size, "depth")
depth.text = "3"
segmented = ET.SubElement(root, "segmented")
segmented.text = "0"
for shape in shapes:
obj = ET.SubElement(root, "object")
name = ET.SubElement(obj, "name")
name.text = shape["label"]
pose = ET.SubElement(obj, "pose")
pose.text = "Unspecified"
truncated = ET.SubElement(obj, "truncated")
truncated.text = "0"
difficult = ET.SubElement(obj, "difficult")
difficult.text = "0"
bndbox = ET.SubElement(obj, "bndbox")
xmin = ET.SubElement(bndbox, "xmin")
xmin.text = str(int(shape["xmin"]))
ymin = ET.SubElement(bndbox, "ymin")
ymin.text = str(int(shape["ymin"]))
xmax = ET.SubElement(bndbox, "xmax")
xmax.text = str(int(shape["xmax"]))
ymax = ET.SubElement(bndbox, "ymax")
ymax.text = str(int(shape["ymax"]))
# 格式化输出 XML
xml_str = ET.tostring(root, encoding="utf-8")
dom = parseString(xml_str)
with open(output_path, "w") as f:
f.write(dom.toprettyxml(indent=" "))
# 遍历 YOLO 标签文件
for label_file in os.listdir(yolo_labels_dir):
if label_file.endswith('.txt'):
image_file = os.path.splitext(label_file)[0] + '.jpg'
image_path = os.path.join(images_dir, image_file)
# 打开图像以获取其尺寸
with Image.open(image_path) as img:
img_width, img_height = img.size
# 读取 YOLO 标签文件
yolo_path = os.path.join(yolo_labels_dir, label_file)
shapes = []
with open(yolo_path, 'r') as f:
for line in f.readlines():
parts = line.strip().split()
class_id = int(parts[0])
x_center, y_center, width, height = map(float, parts[1:])
# 反归一化 YOLO 坐标
x_center *= img_width
y_center *= img_height
width *= img_width
height *= img_height
# 计算左上角和右下角的坐标
xmin = x_center - width / 2
ymin = y_center - height / 2
xmax = x_center + width / 2
ymax = y_center + height / 2
# 添加到 shapes 列表
shape = {
"label": class_mapping[class_id],
"xmin": xmin,
"ymin": ymin,
"xmax": xmax,
"ymax": ymax
}
shapes.append(shape)
# 创建并保存 XML 文件
output_path = os.path.join(output_dir, os.path.splitext(label_file)[0] + '.xml')
create_voc_xml(image_path, (img_width, img_height), shapes, output_path)
print("转换完成!YOLO 格式数据已转换为 PASCAL VOC 格式 XML 文件。")