X-Anylabeling 标签转换笔记

1.Pose关键点 json2yolotxt

写在前面:

  • 这个转换仅支持单个类别多个关键点转换
  • 只需要修改class_list, keypoint_list, path
  • txt默认保存的路径是源路径
# 说明: 
# 仅支持单个目标导出,即检测的对象仅有一个,关键点可以有多个
# 输出默认相同路径下,不支持更改
# 关键点输出默认是(x, y, 可见)

import numpy as np
import json
from tqdm import tqdm
from pathlib import Path
import inspect



class_list = ["grass"]  # json中标注的类别
# keypoint_list = ["keyPoints_name"]  # json中关键点的名称,顺序填写例如下方
keypoint_list = ["0", "1", "2"]  # json中关键点的类别,有几个顺序写
path = r"C:\Users\jft\Desktop\oa" # json文件路径
# check_length = len(class_list) * 5 + len(keypoint_list) * 3


# 获取当前行数
def get_current_line():
    return inspect.currentframe().f_back.f_lineno

def read_json(json_file):
    with open(json_file, 'r', encoding='utf-8') as file:
        try:
            json_data = json.load(file)
            return json_data
        except:
            print(f'第{get_current_line}行json文件错误:{json_file}')
            return False

def xyxy2cxcywh(bbox, h, w):
    x_coords = [point[0] for point in bbox]
    y_coords = [point[1] for point in bbox]
    
    # 计算边界
    xmin = min(x_coords)
    xmax = max(x_coords)
    ymin = min(y_coords)
    ymax = max(y_coords)
    
    # 计算中心点 (cx, cy)
    center_x = (xmin + xmax) / 2
    center_y = (ymin + ymax) / 2
    
    # 计算宽度 w 和高度 h
    width = abs(xmax - xmin)
    height = abs(ymax - ymin)
    
    center_x /= w
    center_y /= h
    width /= w
    height /= h
    # 保留6位小数
    center_x = round(center_x, 6)
    center_y = round(center_y, 6)
    width = round(width, 6)
    height = round(height, 6)
    return [center_x, center_y, width, height]

def json2yoloTxt(path):
    # 步骤:
    # 0. 读取json文件
    # 1. 找出所有的矩形,记录下矩形的坐标,以及对应group_id
    # 2. 遍历所有的head和tail,记下点的坐标,以及对应group_id,加入到对应的矩形中
    # 3. 转为yolo格式
    json_data = read_json(path)
    if not json_data: return None
    h, w = json_data['imageHeight'], json_data['imageWidth']
    yolo_txt = []
    
    data = json_data['shapes'] # 标注信息
    # 1. 寻找矩形框
    for data_ in data:
        if data_['shape_type'] == 'rectangle':
            bbox = data_['points'] 
            
    # 2. 寻找关键点
    points_list = []
    class_id_list = []
    for keypoint_label_name in keypoint_list:
        is_match = False
        for data_ in data:
            if data_['shape_type'] == 'point' and data_['label'] == keypoint_label_name:
                points_list.append(data_['points'][0])
        if is_match:
            points_list.append([None])

    # 3. 转为yolo格式
    label_id = 0
    yolo_txt.extend([label_id])
    yolo_txt.extend(xyxy2cxcywh(bbox, h, w))

    # 点
    for point in points_list:
        if point is None: # 没有标注
            yolo_txt.extend([0, 0, 0])
            continue
        x, y = point[0], point[1]
        x,y = int(x), int(y)
        x /= w
        y /= h
        # 保留6位小数
        x = round(x, 6)
        y = round(y, 6)
        yolo_txt.extend([x, y, 2])
    
    return yolo_txt
def write_yolo_txt(yolo_txt_path, yolo_txt):
    with open(yolo_txt_path, "w") as f:
        
        if (len(yolo_txt) - 5) % 3 != 0 : # 检测到关键点数量不对
            print(f'第{get_current_line}行出现错误:{yolo_txt_path}: {len(yolo_txt)}  {yolo_txt}')

        for i in range(len(yolo_txt)):
            if i == 0:
                f.write(str(yolo_txt[i]))
            else:
                f.write(" " + str(yolo_txt[i]))
        f.write("\n")






if __name__ == '__main__':
    json_paths = list(Path(path).rglob("*.json"))
    for json_path in tqdm(json_paths):
        yolo_txt_path = str(json_path).replace(".json", ".txt")
        yolo_txt = json2yoloTxt(json_path)
        write_yolo_txt(yolo_txt_path, yolo_txt)
        

如果想要试试是否转换成果,可以使用下面代码反向画出图像查看。

from pathlib import Path
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
import glob


# 检查json转yolo是否成功,保存图片到保存路径

paths = Path(r'C:\Users\jft\Desktop\x-lable')  # 源文件
SavePath = r".\temp_save"     # 保存路径
class_list = ["grass"]        # 类别名字类别
keypoint_list = ["0", "1"]    # 关键点名字


if not os.path.exists(SavePath):
    os.mkdir(SavePath)



# 类别的颜色
class_color = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255,255,0)]
# 关键点的顺序
# 关键点的颜色
keypoint_color = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (0, 255, 255)]

def checkSave(img_path):
    img = cv2.imread(img_path)
    yolo_txt_path = img_path.replace('jpg', 'txt')
    with open(yolo_txt_path, 'r') as f:
        lines = f.readlines()
    lines = [x.strip() for x in lines]
    
    if not (len(lines[0].split()) - 5) % 3 == 0 :
        print(f'{img_path}: {lines}')
        return 0

    label = np.array([x.split() for x in lines], dtype=np.float32)

    # 绘制检测框
    img_copy = img.copy()
    h,w = img_copy.shape[:2]
    for id,l in enumerate( label ):
        # label_id ,center x,y and width, height
        label_id, cx, cy, bw, bh = l[0:5]
        label_text = class_list[int(label_id)]
        # rescale to image size
        cx *= w
        cy *= h
        bw *= w
        bh *= h
        
        # draw the bounding box
        xmin = int(cx - bw/2)
        ymin = int(cy - bh/2)
        xmax = int(cx + bw/2)
        ymax = int(cy + bh/2)
        cv2.rectangle(img_copy, (xmin, ymin), (xmax, ymax), class_color[int(label_id)], 2)
        cv2.putText(img_copy, label_text, (xmin, ymin-10), cv2.FONT_HERSHEY_SIMPLEX, 1, class_color[int(label_id)], 2)
    
        # draw 17 keypoints, px,py,pv,px,py,pv...
        for i in range(5, len(l), 3):
            px, py, pv = l[i:i+3]
            # rescale to image size
            px *= w
            py *= h
            # puttext the index 
            index = int((i-5)/3)
            # draw the keypoints
            cv2.circle(img_copy, (int(px), int(py)), 10, keypoint_color[int(index)], -1)
            keypoint_text = "{}_{}".format(index, keypoint_list[index])
            cv2.putText(img_copy, keypoint_text, (int(px), int(py)-10), cv2.FONT_HERSHEY_SIMPLEX, 1, keypoint_color[int(index)], 2)


    cv2.imwrite(SavePath + '/' + Path(img_path).name, img_copy)


if __name__ == '__main__':
    path = [i for i in paths.rglob('*') if i.suffix.lower() in {'.png', '.jpg', '.jpeg', '.bmp'}]
    for img_path in path:
        # checkSave(str(img_path))
        try:
            checkSave(str(img_path))
        except:
            print(img_path)

2.目标检测和图像分割

在导出的时候加载配置的txt文件,例如txt配置,例如我们有3各类别是dog,cat,person,配置如下:

dog
cat
person

数据分割

yolov8 数据分割代码

- 会把当前文件夹里的数据按照比例分割

- 把图像和txt标签分别复制到一个新的文件夹里,结构如下

├─images
│  ├─train
│  └─val
└─labels
    ├─train
    └─val

你可以直接修改代码内容的参数或者使用命令运行:按照9:1比例分割

python split.py -r 0.9 -s "源文件路径" -t "目标文件路径"
from pathlib import Path
import random
import numpy as np
import argparse
import shutil
import cv2 as cv


# 数据分割,需要输入源路径(自动递归查找图片'.png', '.jpg', '.jpeg'),目标路径,和分割比例,
# 数据自动分割后复制到 目标路径

parser = argparse.ArgumentParser()
parser.add_argument('-r', type=float, default=0.95, help='分割比例')
parser.add_argument('-s', type=str, default=r'D:\jft-Datas\jft-2DDatas\jft-labeled\guakemiao-pose', help='源文件路径')
parser.add_argument('-t', type=str, default=r'D:\jft-Datas\traingDatas\pose\20250424-guake', help='目标文件路径')
args = parser.parse_args()

img_list = {'.png', '.jpg', '.jpeg', '.bmp'}

def get_img_path(path):
    path = Path(path)
    def is_image(path):
        return path.suffix.lower() in img_list
    images = [path for path in path.rglob('*') if is_image(path)]
    return images

def check_and_create_path(path):
    path = Path(path) # 路径
    if path.exists():
        return path
    else:
        try:
            path.mkdir(parents=True, exist_ok=True) 
            return path
        except Exception as e:
            raise f"创建路径 {path} 失败: {e}"


def split(path, r, save_path):

    

    index_number = int(len(path) * r)
    train_index = random.sample(range(0, len(path)), index_number)
    unlabel_num = 0
    for i, img_path in enumerate(path):
        label_path = img_path.with_suffix('.txt') # 替换后缀
        if img_path.exists() and label_path.exists():
            if i in train_index:
                # cv.imwrite(f'{save_path}' + f"/images/train/{img_path.name}", img_)
                shutil.copy(img_path, f'{save_path}' + f"/images/train/{img_path.name}")
                shutil.copy(label_path, f'{save_path}' + f"/labels/train/{label_path.name}")
            else:
                shutil.copy(img_path, f'{save_path}' + f"/images/val/{img_path.name}")
                # cv.imwrite(f'{save_path}' + f"/images/val/{img_path.name}", img_)
                shutil.copy(label_path, f'{save_path}' + f"/labels/val/{label_path.name}")
            print(f"{img_path}")
        else:
            unlabel_num += 1
            if i in train_index:
                shutil.copy(img_path, f'{save_path}' + f"/images/train/{img_path.name}")
            else:
                shutil.copy(img_path, f'{save_path}' + f"/images/val/{img_path.name}")
    print(f"未标注图片数量: {unlabel_num}")
    return len(train_index), len(path) - len(train_index)
            
if __name__ == "__main__":

    # 创建路径
    check_and_create_path(rf'{args.t}/images/train')
    check_and_create_path(rf'{args.t}/images/val')
    check_and_create_path(rf'{args.t}/labels/train')
    check_and_create_path(rf'{args.t}/labels/val')

    img_paths = get_img_path(args.s)
    try:
        train_length, val_length = split(img_paths, args.r, args.t)
        print(f"训练集数量: {train_length}", f"测试集数量: {val_length}")

    except Exception as e:
        print(f"出现错误: {e}")
    print(f"图片数量: {len(img_paths)}")


待更新20250424

posted @ 2025-04-24 14:05  qinchaojie  阅读(591)  评论(0)    收藏  举报  来源