Files
zjsh_yolov11/yolo11_detect/cvattodetect.py

100 lines
3.3 KiB
Python
Raw Normal View History

2025-12-16 15:00:24 +08:00
import os
import xml.etree.ElementTree as ET
from pathlib import Path
def cvat_to_yolo_detect(xml_path, output_dir, class_name_to_id=None):
"""
CVAT 导出的 XML目标检测模式转换为 YOLO Detect 格式
Args:
xml_path (str): CVAT 导出的 XML 文件路径
output_dir (str): 输出 .txt 标注文件的目录
class_name_to_id (dict, optional): 类别名到 ID 的映射
如果为 None则自动从 XML <labels> 中按顺序分配0,1,2...
"""
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
tree = ET.parse(xml_path)
root = tree.getroot()
# 自动提取类别顺序(如果未提供映射)
if class_name_to_id is None:
class_name_to_id = {}
labels_elem = root.find(".//labels")
if labels_elem is not None:
for idx, label in enumerate(labels_elem.findall("label")):
name = label.find("name").text
class_name_to_id[name] = idx
else:
print("⚠️ 未找到 <labels>,请手动提供 class_name_to_id")
print(f"类别映射: {class_name_to_id}")
# 遍历所有 <image>
for image in root.findall("image"):
img_name = image.get("name")
width = int(image.get("width"))
height = int(image.get("height"))
# 构建 .txt 文件名(去掉扩展名)
stem = Path(img_name).stem
txt_path = output_dir / f"{stem}.txt"
boxes = []
for box in image.findall("box"):
label = box.get("label")
if label not in class_name_to_id:
print(f"⚠️ 未知类别 '{label}',跳过(图片: {img_name}")
continue
class_id = class_name_to_id[label]
xtl = float(box.get("xtl"))
ytl = float(box.get("ytl"))
xbr = float(box.get("xbr"))
ybr = float(box.get("ybr"))
# 转为 YOLO 格式(归一化)
x_center = (xtl + xbr) / 2 / width
y_center = (ytl + ybr) / 2 / height
w = (xbr - xtl) / width
h = (ybr - ytl) / height
# 限制在 [0,1](防止因标注误差越界)
x_center = max(0.0, min(1.0, x_center))
y_center = max(0.0, min(1.0, y_center))
w = max(0.0, min(1.0, w))
h = max(0.0, min(1.0, h))
boxes.append(f"{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}")
# 写入 .txt 文件(即使无框也创建空文件)
with open(txt_path, "w") as f:
f.write("\n".join(boxes))
print(f"{img_name}{len(boxes)} 个目标")
print(f"\n🎉 转换完成YOLO 标注已保存至: {output_dir}")
if __name__ == "__main__":
# ====== 配置区 ======
XML_PATH = "annotations.xml" # 替换为你的 CVAT XML 路径
OUTPUT_LABELS_DIR = "labels" # 输出的 YOLO .txt 目录
# 方式1自动从 XML 提取类别(推荐)
2026-03-10 13:58:21 +08:00
#CLASS_MAP = None
2025-12-16 15:00:24 +08:00
# 方式2手动指定确保与训练时一致
2026-03-10 13:58:21 +08:00
CLASS_MAP = {
"bag": 0,
"bag35": 1,
}
2025-12-16 15:00:24 +08:00
# ====== 执行转换 ======
cvat_to_yolo_detect(
xml_path=XML_PATH,
output_dir=OUTPUT_LABELS_DIR,
class_name_to_id=CLASS_MAP
)