【图像分割】Labelme JSON标注转换为TXT代码详解

  书接上文:https://blog.csdn.net/qq_49092686/article/details/145114987?spm=1001.2014.3001.5502  

  这里给出更细致的json2txt代码,同时考虑了circle、rectangle这两种情况

   各位朋友,在使用前,你一定要注意你的json格式是不是跟我的一致,不一致的话是用不了的

(是不是也用的labelme制作的数据集

import json
import os

#归一化的原理(算法)很简单:
#就是你标注的点坐标(即json文件中的points):横坐标(x)/图像的宽度,纵坐标(y)/图像的高度
#归一化的目的是为了减少模型的计算量,不然数值会越算越大(这是我的理解)

#circle的情况,我只考虑points中圆心这一点的归一化
def normalize_coordinates(x, y, image_width, image_height):
    """
    将坐标归一化到 [0, 1] 范围。
    """
    x_normalized = x / image_width
    y_normalized = y / image_height
    return x_normalized, y_normalized

#rectangle、polygon的情况,把points中的每一点都归一化
def normalize_points(points, image_width, image_height):
    """Normalize polygon points to be in the range [0, 1]."""
    return [(x / image_width, y / image_height) for x, y in points]

# 将Labelme JSON标注转换为TXT格式函数,并进行归一化处理。
def labelme_json_to_txt(json_dir, txt_dir):
   
    #如果存放txt的目录不存在,那么就创建存放txt的目录
    if not os.path.exists(txt_dir):
        os.makedirs(txt_dir)
        
    #存放json目录下的所有文件
    for filename in os.listdir(json_dir):
        #挑选出json目录下的以.json为后缀名的文件,即我们要的用labelme分割图像产生的json文件
        if filename.endswith('.json'):
            #这步是路径的拼接,即把存放json目录的路径和json文件名,用/(路径分隔符)拼接起来,就是json文件的路径
            json_file_path = os.path.join(json_dir, filename)
            #os.path.splitext()用于将文件路径分割成两部分:文件的主体部分(即去除扩展名后的部分)和文件的扩展名(包括点.)。
            #比如:如果我们有一个文件名"example.json",os.path.splitext("example.json")[0]的结果将是"example"
            image_name = os.path.splitext(filename)[0]
            #这步是路径的拼接,即把存放txt目录的路径和与对应的json文件同名的txt文件名,就是产生的txt文件的路径
            txt_file_path = os.path.join(txt_dir, f'{image_name}.txt')
            #打开json文件,读取并把json文件里面的内容全加载(赋给)到data这个字典(注意json文件里的格式就是字典格式)
            with open(json_file_path, 'r', encoding='utf-8') as json_file:
                data = json.load(json_file)

            #相应数据的获取,结合json文件里的内容来理解(注意这些数据的包含还是并列关系,理清就不会弄混)
            #shapes、 image_height、image_width就是并列关系,并不是谁包含谁

            #尝试从data字典中获取与键'shapes'相关联的值(这里是一个列表)。如果'shapes'键不存在,它将返回一个空列表[]作为默认值。
            shapes = data.get('shapes', [])
            #尝试从data字典中获取与键'imageHeight'相关联的值(这里就是数值),该值表示图像的高度。如果'imageHeight'键不存在,它将返回0作为默认值。
            image_height = data.get('imageHeight', 0)
            #与上一行代码同理
            image_width = data.get('imageWidth', 0)

            #打开生成的txt文件,准备写下数据
            with open(txt_file_path, 'w', encoding='utf-8') as out_f:
                #这里需要for循环的原因是一张图像上你可能分割的不止一个,可能是多个分割对象,多个标签和形状
                for shape in shapes:
                    #shape和shape_type就是包含关系
                    #尝试从shape字典中找到键'shape_type'对应的值。如果'shape_type'键不存在于shape字典中,那么将返回一个空字符串''作为默认值
                    shape_type = shape.get('shape_type', '')
                    #同理
                    label = shape.get('label', '')
                    #同理,只是返回值类型不一样,是列表
                    points = shape.get('points', [])

                    #shape_type就是你用labelme分割图像使用的是哪一种形状分割,有三种情况如下:circle、polygon、rectangle
                    if shape_type == 'circle':
                        #排除一个极端情况,就是圆心和圆边上的点重合的情况
                        if len(points) < 2:
                            print(f"Warning: Circle shape in {json_file_path} has less than 2 points, skipping.")
                            continue
                        # 一般情况下,第一个点是圆心,第二个点是圆边上的一个点(这里你不确定的话,自己用labelme分割的时候注意下其相对位置)
                        center_x, center_y = points[0]
                        edge_x, edge_y = points[1]
                        # 计算半径
                        radius = ((edge_x - center_x) ** 2 + (edge_y - center_y) ** 2) ** 0.5

                        # 归一化圆心坐标和半径(这里半径不需要归一化到[0,1],但可以根据需要调整)
                        # 如果需要,可以将半径也进行某种形式的归一化,比如缩放到图像的尺寸比例
                        # 但通常半径是实际尺寸,不需要归一化到[0,1]
                        # radius_norm = radius / max(image_width, image_height)  # 示例:缩放到图像尺寸的比例
                        #这里可能大家可能会困惑为什么这里不把圆心和圆心边上的点归一化再求半径呢?(当然大家也可以试试,这里也蛮好改的)
                        #这是因为我考虑到把圆心和圆心边上的点归一化再求半径可能更合理,但是变回去实际尺寸的算法比较麻烦
                        cx_norm, cy_norm = normalize_coordinates(center_x, center_y, image_width, image_height)

                        # 输出到TXT文件
                        # (我认为这里把circle输出是有必要的,虽然我不知道怎么告诉模型是用circle分割的,
                        # 但如果只有两个点的信息,Ultralytics会认为是一条直线,
                        # 这也是为什么Ultralytics要求的数据格式至少三个点,至少三个点才能围成一个封闭图形)
                        out_f.write(f'{label} circle {cx_norm} {cy_norm} {radius}\n')

                    #多边形的情况
                    elif shape['shape_type'] == "polygon":
                        #调用归一化函数
                        normalized_points = normalize_points(points, image_width, image_height)
                        # 输出到txt文件,这里如果你是给Ultralytics模型训练,这里输出的时候去掉polygon
                        out_f.write(f"{label} polygon  ")
                        out_f.write(" ".join(f"{x:.6f} {y:.6f}" for x, y in normalized_points))
                        out_f.write("\n")

                    #矩形的情况
                    elif shape['shape_type'] == 'rectangle':
                        # 调用归一化函数
                        normalized_points = normalize_points(points, image_width, image_height)
                        # Write the label and normalized coordinates to the TXT file
                        out_f.write(
                            f"{label} rectangle {normalized_points[0][0]:.6f} {normalized_points[0][1]:.6f} {normalized_points[1][0]:.6f} {normalized_points[1][1]:.6f}\n")

                    else:
                        print(f'Warning: Unknown shape type "{shape_type}" found in {json_file_path}')


# 使用示例
#修改输入
json_directory = 'C:\\Users\\Administrator\\Desktop\\ceshi\\json'
#修改输出
txt_output_directory = 'C:\\Users\\Administrator\\Desktop\\ceshi\\label'
#调用将Labelme JSON标注转换为TXT格式函数
labelme_json_to_txt(json_directory, txt_output_directory)

  望大家关注点赞,小小支持一下,谢谢大家喽