1、json转txt

import os
import json
from glob import glob

def convert_labelme_to_yolo(json_dir, output_dir):
    """
    将labelme格式的JSON标注文件转换为YOLO格式
    :param json_dir: 包含JSON文件的目录
    :param output_dir: 输出YOLO格式文件的目录
    """
    # 创建输出目录
    os.makedirs(output_dir, exist_ok=True)
    
    # 收集所有类别
    classes = set()
    json_files = glob(os.path.join(json_dir, '*.json'))
    
    # 第一次遍历:收集所有类别
    for json_file in json_files:
        with open(json_file, 'r') as f:
            data = json.load(f)
        for shape in data.get('shapes', []):
            if shape['shape_type'] == 'rectangle':
                classes.add(shape['label'])
    
    # 创建类别映射(类别名到ID)
    class_to_id = {cls: idx for idx, cls in enumerate(sorted(classes))}
    
    # 保存类别文件
    with open(os.path.join(output_dir, 'classes.txt'), 'w') as f:
        for cls in sorted(classes):
            f.write(f"{cls}\n")
    
    # 第二次遍历:转换标注
    for json_file in json_files:
        with open(json_file, 'r') as f:
            data = json.load(f)
        
        # 获取图像尺寸
        img_width = data.get('imageWidth', 0)
        img_height = data.get('imageHeight', 0)
        
        if img_width == 0 or img_height == 0:
            print(f"警告: {json_file} 中缺少图像尺寸信息,跳过")
            continue
        
        # 从imagePath中提取纯文件名(不含路径和扩展名)
        image_path = data.get('imagePath', '')
        # 处理路径分隔符(兼容Windows和Linux)
        image_path = image_path.replace('\\', os.sep).replace('/', os.sep)
        # 获取文件名部分(不含路径)
        base_name = os.path.basename(image_path)
        # 去除扩展名
        base_name = os.path.splitext(base_name)[0]
        
        # 如果没有获取到文件名,使用JSON文件名
        if not base_name:
            base_name = os.path.splitext(os.path.basename(json_file))[0]
        
        # 检查文件名冲突
        output_path = os.path.join(output_dir, f"{base_name}.txt")
        counter = 1
        while os.path.exists(output_path):
            # 如果文件已存在,添加后缀
            output_path = os.path.join(output_dir, f"{base_name}_{counter}.txt")
            counter += 1
        
        # 转换标注
        yolo_annotations = []
        for shape in data.get('shapes', []):
            if shape['shape_type'] == 'rectangle':
                # 获取类别ID
                class_id = class_to_id.get(shape['label'])
                if class_id is None:
                    continue
                
                # 获取矩形坐标(两个对角点)
                points = shape['points']
                if len(points) != 2:
                    continue
                
                x1, y1 = points[0]
                x2, y2 = points[1]
                
                # 确保x1 < x2, y1 < y2
                xmin = min(x1, x2)
                ymin = min(y1, y2)
                xmax = max(x1, x2)
                ymax = max(y1, y2)
                
                # 计算YOLO格式参数
                width = xmax - xmin
                height = ymax - ymin
                center_x = (xmin + width / 2) / img_width
                center_y = (ymin + height / 2) / img_height
                norm_width = width / img_width
                norm_height = height / img_height
                
                # 添加到标注列表
                yolo_annotations.append(f"{class_id} {center_x:.6f} {center_y:.6f} {norm_width:.6f} {norm_height:.6f}")
        
        # 写入YOLO格式文件
        with open(output_path, 'w') as f:
            f.write('\n'.join(yolo_annotations))

# 使用示例
if __name__ == "__main__":
    json_directory = "labels"  # 输入JSON文件目录
    output_directory = "txt"  # 输出YOLO文件目录
    
    convert_labelme_to_yolo(json_directory, output_directory)
    print(f"转换完成!结果保存在: {output_directory}")

2、xml转txt

# xml转为txt
import os
import xml.etree.ElementTree as ET
from pathlib import Path

# 标签到数字的映射
label_to_number = {
    'label1': 0,
    'label2': 1
    'label3': 2
}

def xml_to_txt(xml_file, txt_file, image_width, image_height, label_to_number):
    """
    将 XML 文件转换为 TXT 文件,并将标签映射为数字,坐标值归一化到 [0, 1]。
    """
    if image_width == 0 or image_height == 0:
        raise ValueError(f"Image width or height is zero in file: {xml_file}")

    tree = ET.parse(xml_file)
    root = tree.getroot()
    
    with open(txt_file, 'w') as f:
        for obj in root.findall('object'):
            name = obj.find('name').text
            if name in label_to_number:
                number = label_to_number[name]
                bbox = obj.find('bbox')
                if bbox is None:
                    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)
                
                # 归一化坐标值
                x_center = (xmin + xmax) / 2.0 / image_width
                y_center = (ymin + ymax) / 2.0 / image_height
                box_width = (xmax - xmin) / image_width
                box_height = (ymax - ymin) / image_height
                
                # 确保坐标值在 [0, 1] 范围内
                x_center = max(0, min(x_center, 1))
                y_center = max(0, min(y_center, 1))
                box_width = max(0, min(box_width, 1))
                box_height = max(0, min(box_height, 1))
                
                f.write(f"{number} {x_center:.6f} {y_center:.6f} {box_width:.6f} {box_height:.6f}\n")

# 指定文件夹路径
xml_folder = 'xml'    # 替换为你的 XML 文件夹路径
txt_folder = 'txt'  # 替换为你的 TXT 文件夹路径

# 确保 TXT 文件夹存在
Path(txt_folder).mkdir(parents=True, exist_ok=True)

# 遍历 XML 文件夹
for xml_file in os.listdir(xml_folder):
    if xml_file.endswith('.xml'):
        xml_path = os.path.join(xml_folder, xml_file)
        txt_path = os.path.join(txt_folder, xml_file.replace('.xml', '.txt'))
        
        # 从 XML 文件中获取图片的宽度和高度
        tree = ET.parse(xml_path)
        root = tree.getroot()
        size = root.find('size')
        if size is None:
            print(f"Size tag not found in file: {xml_file}")
            continue
        width = size.find('width')
        height = size.find('height')
        if width is None or height is None:
            print(f"Width or height tag not found in file: {xml_file}")
            continue
        image_width = int(width.text)
        image_height = int(height.text)
        
        if image_width == 0 or image_height == 0:
            print(f"Image width or height is zero in file: {xml_file}")
            continue
        
        # 转换并归一化坐标值
        xml_to_txt(xml_path, txt_path, image_width, image_height, label_to_number)

print("All XML files have been converted to TXT files.")

3、xml类别统计

# 统计xml标注文件中的各个标签数量
import os
import xml.etree.ElementTree as ET
from collections import defaultdict

def count_xml_categories(folder_path):
    """
    统计指定文件夹下所有 XML 文件中的类别数量及每个类别的标签数量。
    
    参数:
        folder_path (str): 包含 XML 文件的文件夹路径
    
    返回:
        dict: 类别及其对应的标签数量
    """
    category_count = defaultdict(int)
    total_files = 0

    # 遍历文件夹中的 XML 文件
    for filename in os.listdir(folder_path):
        if filename.endswith(".xml"):
            total_files += 1
            file_path = os.path.join(folder_path, filename)
            try:
                tree = ET.parse(file_path)
                root = tree.getroot()
                
                # 查找所有类别标签(假设类别在 <name> 标签中,且属于 <object> 子节点)
                for obj in root.findall("object"):
                    name = obj.find("name")
                    if name is not None:
                        category = name.text.strip()
                        category_count[category] += 1
            except ET.ParseError as e:
                print(f"解析文件 {filename} 时出错: {e}")
    
    # 输出结果
    print(f"共处理 {total_files} 个 XML 文件")
    print(f"总类别数: {len(category_count)}")
    print("各分类及标签数量:")
    for category, count in sorted(category_count.items()):
        print(f"{category}: {count}")
    
    return category_count

# 示例调用
if __name__ == "__main__":
    folder_path = ''  # 替换为你的 XML 文件夹路径
    result = count_xml_categories(folder_path)

4、txt类别统计

# 统计指定文件夹下所有 TXT 文件中的类别数量及每个类别的标签数量。
import os
from collections import Counter

def count_labels_in_txt_files(txt_folder):
    """
    统计指定文件夹下所有 TXT 文件中的类别数量及每个类别的标签数量。
    
    :param txt_folder: 包含 TXT 标注文件的文件夹路径
    :return: 总的类别数量和每个类别的标签数量
    """
    label_counts = Counter()

    # 遍历文件夹中的所有 TXT 文件
    for root, dirs, files in os.walk(txt_folder):
        for file in files:
            if file.endswith('.txt'):  # 确保是 TXT 文件
                file_path = os.path.join(root, file)
                try:
                    with open(file_path, 'r') as f:
                        for line in f:
                            parts = line.strip().split()
                            if parts:  # 确保行不为空
                                label = parts[0]
                                label_counts[label] += 1
                except Exception as e:
                    print(f"Error processing file {file_path}: {e}")

    return label_counts

# 指定文件夹路径train
txt_folder = 's'  # 替换为你的 TXT 文件夹路径

# 调用函数并打印结果
label_counts = count_labels_in_txt_files(txt_folder)
print(f"Total unique labels: {len(label_counts)}")
for label, count in label_counts.items():
    print(f"Label {label}: {count}")

5、类别与图片匹配对应

5.1xml格式与图片互相匹配

import os

def find_unmatched_files(image_folder, txt_folder):
    """
    找出没有对应标注文件的图片和没有对应图片的标注文件。
    
    :param image_folder: 包含图片的文件夹路径
    :param txt_folder: 包含 TXT 标注文件的文件夹路径
    :return: 没有对应标注文件的图片列表和没有对应图片的标注文件列表
    """
    # 获取所有图片文件名(不包含扩展名)
    image_files = {os.path.splitext(f)[0] for f in os.listdir(image_folder) if f.lower().endswith(('.png', '.jpg', '.jpeg'))}
    
    # 获取所有 TXT 文件名(不包含扩展名)
    txt_files = {os.path.splitext(f)[0] for f in os.listdir(txt_folder) if f.lower().endswith('.txt')}
    
    # 找出没有对应标注文件的图片
    unmatched_images = image_files - txt_files
    
    # 找出没有对应图片的标注文件
    unmatched_txts = txt_files - image_files
    
    return unmatched_images, unmatched_txts

# 指定文件夹路径
image_folder = ''  # 替换为你的图片文件夹路径
txt_folder = ''  # 替换为你的 TXT 文件夹路径

# 调用函数并打印结果
unmatched_images, unmatched_txts = find_unmatched_files(image_folder, txt_folder)

print("Unmatched images (no corresponding TXT file):")
for image in unmatched_images:
    print(f"Image: {image}.jpg")

print("\nUnmatched TXT files (no corresponding image):")
for txt in unmatched_txts:
    print(f"TXT: {txt}.txt")

5.2 txt与图片相互匹配

import os
import shutil
from pathlib import Path

def pair_copy(img_root: str, lbl_root: str,
              new_img_dir: str, new_lbl_dir: str,
              img_exts=('.jpg', '.png', '.bmp', '.jpeg')):
    """
    把成对的图片 + YOLO 标签拷贝到新目录
    """
    img_root, lbl_root = Path(img_root), Path(lbl_root)
    new_img_dir, new_lbl_dir = Path(new_img_dir), Path(new_lbl_dir)
    new_img_dir.mkdir(parents=True, exist_ok=True)
    new_lbl_dir.mkdir(parents=True, exist_ok=True)

    # 建立标签文件名集合(无后缀)
    txts = {p.stem for p in lbl_root.rglob('*.txt')}

    paired_cnt = 0
    # 遍历所有图片
    for img_path in img_root.rglob('*'):
        if img_path.suffix.lower() not in img_exts:
            continue
        stem = img_path.stem
        txt_path = lbl_root / f"{stem}.txt"
        if txt_path.exists():
            shutil.copy2(img_path, new_img_dir / img_path.name)
            shutil.copy2(txt_path, new_lbl_dir / txt_path.name)
            paired_cnt += 1

    print(f"✅ 共拷贝 {paired_cnt} 对文件到 {new_img_dir} 和 {new_lbl_dir}")

if __name__ == '__main__':
    pair_copy(
        img_root=r'',     # 原始图片文件夹
        lbl_root=r'',     # 原始 YOLO 标注文件夹
        new_img_dir=r'',
        new_lbl_dir=r''
    )

6、数据集划分

# 将图片和对应的 TXT 标注文件按照指定比例划分为训练集和验证集
import os
import random
import shutil
from pathlib import Path

def split_images_and_txt(image_folder, txt_folder, train_folder, val_folder, train_ratio=0.9):
    """
    将图片和对应的 TXT 标注文件按照指定比例划分为训练集和验证集。
    
    :param image_folder: 包含图片的文件夹路径
    :param txt_folder: 包含 TXT 标注文件的文件夹路径
    :param train_folder: 训练集文件夹路径
    :param val_folder: 验证集文件夹路径
    :param train_ratio: 训练集比例(默认为 0.9)
    """
    # 确保输出文件夹存在
    Path(train_folder).mkdir(parents=True, exist_ok=True)
    Path(val_folder).mkdir(parents=True, exist_ok=True)
    Path(os.path.join(train_folder, 'images')).mkdir(parents=True, exist_ok=True)
    Path(os.path.join(train_folder, 'annotations')).mkdir(parents=True, exist_ok=True)
    Path(os.path.join(val_folder, 'images')).mkdir(parents=True, exist_ok=True)
    Path(os.path.join(val_folder, 'annotations')).mkdir(parents=True, exist_ok=True)
    
    # 获取所有图片文件名(不包含扩展名)
    image_files = {os.path.splitext(f)[0] for f in os.listdir(image_folder) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif'))}
    
    # 获取所有 TXT 文件名(不包含扩展名)
    txt_files = {os.path.splitext(f)[0] for f in os.listdir(txt_folder) if f.lower().endswith('.txt')}
    
    # 找出有对应 TXT 文件的图片
    matched_files = list(image_files & txt_files)
    
    # 随机打乱文件顺序
    random.shuffle(matched_files)
    
    # 计算训练集和验证集的大小
    train_size = int(len(matched_files) * train_ratio)
    train_files = matched_files[:train_size]
    val_files = matched_files[train_size:]
    
    # 移动训练集文件
    for file_name in train_files:
        # 查找图片扩展名
        image_path = next((os.path.join(image_folder, f"{file_name}{ext}") for ext in ['.jpg', '.jpeg', '.png', '.bmp', '.gif'] if os.path.exists(os.path.join(image_folder, f"{file_name}{ext}"))), None)
        txt_path = os.path.join(txt_folder, f"{file_name}.txt")
        
        if image_path:
            shutil.move(image_path, os.path.join(train_folder, 'images', f"{file_name}{os.path.splitext(image_path)[1]}"))
            shutil.move(txt_path, os.path.join(train_folder, 'annotations', f"{file_name}.txt"))
            print(f"Copied {file_name} to train set")
    
    # 移动验证集文件
    for file_name in val_files:
        # 查找图片扩展名
        image_path = next((os.path.join(image_folder, f"{file_name}{ext}") for ext in ['.jpg', '.jpeg', '.png', '.bmp', '.gif'] if os.path.exists(os.path.join(image_folder, f"{file_name}{ext}"))), None)
        txt_path = os.path.join(txt_folder, f"{file_name}.txt")
        
        if image_path:
            shutil.move(image_path, os.path.join(val_folder, 'images', f"{file_name}{os.path.splitext(image_path)[1]}"))
            shutil.move(txt_path, os.path.join(val_folder, 'annotations', f"{file_name}.txt"))
            print(f"Copied {file_name} to val set")
    
    print(f"Total files in train set: {len(train_files)}")
    print(f"Total files in val set: {len(val_files)}")

# 指定文件夹路径
image_folder = ''  # 替换为你的图片文件夹路径
txt_folder = ''  # 替换为你的 TXT 文件夹路径
train_folder = ''  # 替换为你的训练集文件夹路径
val_folder = ''  # 替换为你的验证集文件夹路径

# 调用函数
split_images_and_txt(image_folder, txt_folder, train_folder, val_folder, train_ratio=0.9)

更多推荐