123 lines
3.4 KiB
Python
123 lines
3.4 KiB
Python
|
|
# convert_yolo_seg_to_cvat.py
|
|||
|
|
import os
|
|||
|
|
import cv2
|
|||
|
|
import xml.etree.ElementTree as ET
|
|||
|
|
|
|||
|
|
# ================== 配置 ==================
|
|||
|
|
labels_dir = "./labels/labels" # YOLO segmentation txt 目录
|
|||
|
|
images_dir = "/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/6111c/1"
|
|||
|
|
output_xml = "2annotations_cvat_seg.xml"
|
|||
|
|
|
|||
|
|
# 类别映射(id -> name)
|
|||
|
|
class_mapping_reverse = {
|
|||
|
|
0: "yemian",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ================== 工具函数 ==================
|
|||
|
|
def unnormalize_polygon(img_w, img_h, coords):
|
|||
|
|
"""
|
|||
|
|
YOLO seg 归一化坐标 -> 像素坐标
|
|||
|
|
coords: [x1, y1, x2, y2, ...]
|
|||
|
|
"""
|
|||
|
|
pts = []
|
|||
|
|
for i in range(0, len(coords), 2):
|
|||
|
|
x = float(coords[i]) * img_w
|
|||
|
|
y = float(coords[i + 1]) * img_h
|
|||
|
|
pts.append((x, y))
|
|||
|
|
return pts
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ================== 构建 XML ==================
|
|||
|
|
root = ET.Element("annotations")
|
|||
|
|
ET.SubElement(root, "version").text = "1.1"
|
|||
|
|
|
|||
|
|
meta = ET.SubElement(root, "meta")
|
|||
|
|
task = ET.SubElement(meta, "task")
|
|||
|
|
ET.SubElement(task, "name").text = "converted_from_yolo_seg"
|
|||
|
|
|
|||
|
|
txt_files = sorted([f for f in os.listdir(labels_dir) if f.endswith(".txt")])
|
|||
|
|
ET.SubElement(task, "size").text = str(len(txt_files))
|
|||
|
|
|
|||
|
|
# labels
|
|||
|
|
labels_elem = ET.SubElement(task, "labels")
|
|||
|
|
for name in class_mapping_reverse.values():
|
|||
|
|
lab = ET.SubElement(labels_elem, "label")
|
|||
|
|
ET.SubElement(lab, "name").text = name
|
|||
|
|
ET.SubElement(lab, "color").text = "#ffffff"
|
|||
|
|
ET.SubElement(lab, "type").text = "any"
|
|||
|
|
ET.SubElement(lab, "attributes")
|
|||
|
|
|
|||
|
|
ET.SubElement(meta, "dumped").text = ""
|
|||
|
|
|
|||
|
|
# ================== 处理每个 txt ==================
|
|||
|
|
for idx, txt_file in enumerate(txt_files):
|
|||
|
|
base = os.path.splitext(txt_file)[0]
|
|||
|
|
|
|||
|
|
# 查找图片
|
|||
|
|
img_path = None
|
|||
|
|
for ext in [".jpg", ".jpeg", ".png", ".bmp"]:
|
|||
|
|
p = os.path.join(images_dir, base + ext)
|
|||
|
|
if os.path.exists(p):
|
|||
|
|
img_path = p
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
if img_path is None:
|
|||
|
|
print(f"❗缺少图片:{base}.*(跳过)")
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
img = cv2.imread(img_path)
|
|||
|
|
if img is None:
|
|||
|
|
print(f"❗无法读取图片:{img_path}")
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
h, w = img.shape[:2]
|
|||
|
|
|
|||
|
|
image_elem = ET.SubElement(
|
|||
|
|
root, "image",
|
|||
|
|
id=str(idx),
|
|||
|
|
name=os.path.basename(img_path),
|
|||
|
|
width=str(w),
|
|||
|
|
height=str(h)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
yolo_path = os.path.join(labels_dir, txt_file)
|
|||
|
|
with open(yolo_path, "r") as f:
|
|||
|
|
lines = [l.strip() for l in f.readlines() if l.strip()]
|
|||
|
|
|
|||
|
|
if not lines:
|
|||
|
|
print(f"⚠ 空标签:{txt_file}")
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
# 支持一张图多个实例
|
|||
|
|
for line in lines:
|
|||
|
|
parts = line.split()
|
|||
|
|
cls_id = int(parts[0])
|
|||
|
|
coords = parts[1:]
|
|||
|
|
|
|||
|
|
if len(coords) < 6 or len(coords) % 2 != 0:
|
|||
|
|
print(f"⚠ 非法 segmentation 点数:{txt_file}")
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
label_name = class_mapping_reverse.get(cls_id, str(cls_id))
|
|||
|
|
|
|||
|
|
pts = unnormalize_polygon(w, h, coords)
|
|||
|
|
pts_str = ";".join([f"{x:.2f},{y:.2f}" for x, y in pts])
|
|||
|
|
|
|||
|
|
ET.SubElement(
|
|||
|
|
image_elem,
|
|||
|
|
"polygon",
|
|||
|
|
label=label_name,
|
|||
|
|
source="manual",
|
|||
|
|
occluded="0",
|
|||
|
|
points=pts_str,
|
|||
|
|
z_order="0"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# ================== 写出 XML ==================
|
|||
|
|
tree = ET.ElementTree(root)
|
|||
|
|
tree.write(output_xml, encoding="utf-8", xml_declaration=True)
|
|||
|
|
|
|||
|
|
print("\n🎉 YOLO Segmentation → CVAT 转换完成!")
|
|||
|
|
print(f"📄 输出文件:{output_xml}")
|
|||
|
|
print(f"📊 共处理 {len(txt_files)} 个 txt")
|