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

浙公网安备 33010602011771号