labelme关键点标注转yolo txt
标签都是数字的 比如 1 2 3 4 5
点击查看代码
import os
import json
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
import math
# ==============================
# 工具:中文字体加载(防止中文乱码)
# 优先加载黑体,没有则加载Arial,都没有用默认字体
# ==============================
try:
font = ImageFont.truetype("simhei.ttf", 40)
except:
try:
font = ImageFont.truetype("Arial.ttf", 20)
except:
font = ImageFont.load_default()
# ==============================
# 功能:判断一个点 (px,py) 是否在矩形框内
# 输入:点坐标 + 矩形对角坐标(已归一化或像素坐标都可以)
# ==============================
def point_in_rect(px, py, x1, y1, x2, y2):
"""判断点是否在矩形内(x1,y1,x2,y2 是规范化的 min/max)"""
return x1 <= px <= x2 and y1 <= py <= y2
# ==============================
# 功能:计算两点之间欧氏距离
# 用于:当同一个关键点有多个标注时,选择离框中心最近的那个
# ==============================
def distance(p1, p2):
return math.hypot(p1[0] - p2[0], p1[1] - p2[1])
# ==============================
# 核心函数:单个json文件转换 + 可视化
# 作用:把LabelMe标注的矩形+关键点 → YOLO格式关键点txt + 标注效果图
# ==============================
def convert_and_visualize(
labelme_json_path, # LabelMe导出的json文件路径
image_path, # 对应原图路径
output_txt_path, # 输出YOLO格式txt路径
output_vis_path, # 输出可视化图片路径
img_width, # 图像宽度(用于归一化)
img_height # 图像高度(用于归一化)
):
# 1. 读取LabelMe的json标注文件
with open(labelme_json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
# 2. 从json中分离:矩形框 + 关键点
shapes = data.get("shapes", [])
rectangles = [s for s in shapes if s["shape_type"] == "rectangle"] # 所有矩形框
points = [s for s in shapes if s["shape_type"] == "point"] # 所有关键点
# 3. 过滤有效关键点:label必须是数字 1~7(代表6个关键点编号)
valid_points = []
for p in points:
label = p["label"]
try:
idx = int(label)
if 1 <= idx <= 7: # 只保留编号1-7的关键点
x, y = p["points"][0] # 取出点坐标
valid_points.append((idx, x, y))
except (ValueError, IndexError, TypeError):
continue # 忽略无效标签、格式错误的点
# 4. 打开原图,准备绘制可视化结果
img = Image.open(image_path).convert("RGB")
draw = ImageDraw.Draw(img)
yolo_lines = [] # 存储最终YOLO格式行
colors = ["red", "blue", "green", "orange", "purple", "brown", "cyan", "magenta"]
# 5. 遍历每一个标注的矩形(每个矩形对应一个目标)
for i, rect in enumerate(rectangles):
# 【可选】如果只想转换特定类别框,可在这里加过滤
# if rect["label"] != "mangkong":
# continue
# 6. 获取矩形框的两个对角点
pts = rect["points"]
if len(pts) != 2:
print(f"⚠️ 跳过无效 rectangle(非两点): {labelme_json_path.name}")
continue
# 7. 统一矩形坐标:x1=最小x, x2=最大x,y同理
x_coords = [p[0] for p in pts]
y_coords = [p[1] for p in pts]
x1, x2 = min(x_coords), max(x_coords)
y1, y2 = min(y_coords), max(y_coords)
rect_center = ((x1 + x2) / 2.0, (y1 + y2) / 2.0) # 矩形中心点
# 8. 把【落在当前矩形内的关键点】归为这个目标
# 如果同一个编号出现多次 → 选离框中心最近的那个
candidate_points = {} # key:关键点编号 value:坐标(x,y)
for idx, px, py in valid_points:
if point_in_rect(px, py, x1, y1, x2, y2):
if idx not in candidate_points:
candidate_points[idx] = (px, py)
else:
# 已存在同编号点 → 保留距离框中心更近的
old_pt = candidate_points[idx]
if distance((px, py), rect_center) < distance(old_pt, rect_center):
candidate_points[idx] = (px, py)
# 9. 构建YOLO关键点格式:固定6个关键点(编号2~7)
# 格式:x y v,v=2可见,v=0不可见/无标注
keypoints = []
vis_list = [] # 用于可视化:保存有效点(编号,x,y)
for k in range(2, 8): # 关键点固定取 2,3,4,5,6,7
if k in candidate_points:
px, py = candidate_points[k]
px_norm = px / img_width # 归一化到 0~1
py_norm = py / img_height
keypoints.extend([f"{px_norm:.6f}", f"{py_norm:.6f}", "2"])
vis_list.append((k, px, py))
else:
keypoints.extend(["0", "0", "0"]) # 无标注则填 0 0 0
# 10. 计算YOLO标准框:中心x、中心y、宽、高(全部归一化)
cx = (x1 + x2) / 2.0 / img_width
cy = (y1 + y2) / 2.0 / img_height
w = (x2 - x1) / img_width
h = (y2 - y1) / img_height
# 11. 拼接成一行YOLO格式:class_id cx cy w h kp1_x kp1_y v1 ... kp6 v6
line = f"0 {cx:.6f} {cy:.6f} {w:.6f} {h:.6f} " + " ".join(keypoints)
yolo_lines.append(line)
# 12. 绘制可视化框 + 关键点 + 编号
color = colors[i % len(colors)]
draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
for idx, px, py in vis_list:
r = 5
draw.ellipse([px - r, py - r, px + r, py + r], fill=color, outline="white", width=1)
draw.text((px + 6, py - 14), str(idx), fill=color, font=font)
# 13. 保存YOLO标注txt
with open(output_txt_path, 'w') as f:
f.write("\n".join(yolo_lines))
# 14. 保存可视化效果图
img.save(output_vis_path)
print(f"✅ 转换完成: {labelme_json_path.name}")
# ==============================
# 批量转换函数
# 遍历文件夹所有json → 自动匹配图片 → 批量生成txt + 可视化图
# ==============================
def batch_convert_with_vis(labelme_dir, images_dir, output_labels_dir, output_vis_dir):
# 路径统一转为Path对象(方便跨平台)
labelme_dir = Path(labelme_dir)
images_dir = Path(images_dir)
output_labels_dir = Path(output_labels_dir)
output_vis_dir = Path(output_vis_dir)
# 创建输出文件夹(不存在则创建)
output_labels_dir.mkdir(parents=True, exist_ok=True)
output_vis_dir.mkdir(parents=True, exist_ok=True)
# 遍历所有 .json 文件
for json_path in labelme_dir.glob("*.json"):
# 自动匹配同名图片(支持多种格式)
img_path = None
for ext in [".jpg", ".jpeg", ".png", ".bmp", ".JPG", ".PNG"]:
p = images_dir / (json_path.stem + ext)
if p.exists():
img_path = p
break
if not img_path:
print(f"⚠️ 图像未找到: {json_path.stem}")
continue
# 获取图片宽高(用于归一化)
try:
with Image.open(img_path) as im:
img_w, img_h = im.size
except Exception as e:
print(f"❌ 无法打开图像 {img_path}: {e}")
continue
# 输出文件路径
txt_path = output_labels_dir / (json_path.stem + ".txt")
vis_path = output_vis_dir / (json_path.stem + "_vis.jpg")
# 调用核心转换函数
convert_and_visualize(
labelme_json_path=json_path,
image_path=img_path,
output_txt_path=txt_path,
output_vis_path=vis_path,
img_width=img_w,
img_height=img_h
)
# ==============================
# 主函数:程序入口
# 只需要在这里改路径即可!
# ==============================
def main():
# 源文件夹(放json和原图的目录)
src_dir = r"D:\pic\Guangzhoumeiwei\img_raw_3mangkong\img_raw_3mangkong"
# 批量转换
batch_convert_with_vis(
labelme_dir = src_dir, # json所在目录
images_dir = src_dir, # 图片所在目录
output_labels_dir = src_dir + "_yolo", # 输出YOLO txt
output_vis_dir = src_dir + "_view" # 输出可视化图
)
if __name__ == "__main__":
main()

浙公网安备 33010602011771号