| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- import os
- import json
- import shutil
- import numpy as np
- import cv2
- from scipy.optimize import leastsq
- from tqdm import tqdm
- def calc_R(x, y, xc, yc):
- return np.sqrt((x - xc) ** 2 + (y - yc) ** 2)
- def f(c, x, y):
- Ri = calc_R(x, y, *c)
- return Ri - Ri.mean()
- def fit_arc_and_create_mask(points, shape, point_step_deg=0.5, radius=2):
- x_data = points[:, 0]
- y_data = points[:, 1]
- center_estimate = np.mean(x_data), np.mean(y_data)
- try:
- center, _ = leastsq(f, center_estimate, args=(x_data, y_data))
- xc, yc = center
- Ri = calc_R(x_data, y_data, xc, yc)
- R = Ri.mean()
- h, w = shape
- if R <= 0 or not np.isfinite(R) or not np.isfinite(xc) or not np.isfinite(yc):
- raise ValueError("拟合圆非法")
- # 生成完整圆的点
- circle_angles = np.linspace(0, 2 * np.pi, int(360 / point_step_deg), endpoint=False)
- full_circle_points = np.stack([
- xc + R * np.cos(circle_angles),
- yc + R * np.sin(circle_angles)
- ], axis=-1).astype(np.float32)
- # 只保留图像内部的点
- in_bounds_mask = (
- (full_circle_points[:, 0] >= 0) & (full_circle_points[:, 0] < w) &
- (full_circle_points[:, 1] >= 0) & (full_circle_points[:, 1] < h)
- )
- clipped_points = full_circle_points[in_bounds_mask]
- if len(clipped_points) < 3:
- print(f"拟合失败使用的三个点为:\n{points}")
- raise ValueError("拟合圆点全部在图像外")
- return None, clipped_points.tolist()
- except Exception as e:
- print(f"⚠️ 圆拟合失败:{e}")
- return None, []
- def build_shapes_from_points(points, label="arc"):
- points_np = np.array(points)
- if points_np.shape[0] < 3:
- return []
- xs = points_np[:, 0]
- ys = points_np[:, 1]
- shape = {
- "label": label,
- "points": points,
- "shape_type": "polygon",
- "flags": {},
- "xmin": int(xs.min()),
- "ymin": int(ys.min()),
- "xmax": int(xs.max()),
- "ymax": int(ys.max())
- }
- return [shape]
- def process_folder_labelme(input_dir, output_dir, point_step_deg=0.5, radius=2):
- os.makedirs(output_dir, exist_ok=True)
- for file_name in tqdm(os.listdir(input_dir)):
- if not file_name.endswith(".json"):
- continue
- json_path = os.path.join(input_dir, file_name)
- image_path = json_path.replace(".json", ".jpg")
- if not os.path.exists(image_path):
- print(f"图像不存在,跳过:{image_path}")
- continue
- with open(json_path, "r") as f:
- label_data = json.load(f)
- arc_points_raw = [item['points'][0] for item in label_data['shapes']
- if item.get('label') == 'arc' and len(item.get('points', [])) == 1]
- if len(arc_points_raw) < 3:
- print(f"{file_name} 中 arc 点数不足 3,跳过")
- continue
- image = cv2.imread(image_path)
- h, w = image.shape[:2]
- arc_shapes = []
- num_groups = len(arc_points_raw) // 3
- for i in range(num_groups):
- group_points = arc_points_raw[i*3:(i+1)*3]
- if len(group_points) < 3:
- print(f"{file_name} 第 {i+1} 组点数不足 3,跳过")
- continue
- points = np.array(group_points)
- try:
- _, arc_pts = fit_arc_and_create_mask(points, (h, w), point_step_deg, radius)
- shapes = build_shapes_from_points(arc_pts, label="arc")
- arc_shapes.extend(shapes)
- except Exception as e:
- print(f"{file_name} 第 {i+1} 组拟合失败,跳过。错误:{e}")
- continue
- if len(arc_shapes) == 0:
- print(f"{file_name} 没有成功拟合的 arc 区域,跳过")
- continue
- output_json = {
- "version": "5.0.1",
- "flags": {},
- "shapes": arc_shapes,
- "imagePath": os.path.basename(image_path),
- "imageHeight": h,
- "imageWidth": w
- }
- base_name = os.path.splitext(os.path.basename(image_path))[0]
- out_json_path = os.path.join(output_dir, base_name + ".json")
- out_img_path = os.path.join(output_dir, base_name + ".jpg")
- with open(out_json_path, "w") as f:
- json.dump(output_json, f, indent=4)
- shutil.copy(image_path, out_img_path)
- print(f"\n✅ 所有 arc 区域已保存为 labelme 格式(图像 + json)到目录:{output_dir}")
- # ===== 修改为你自己的输入输出路径 =====
- input_dir = r"G:\python_ws_g\data\test\jsonjpg_out"
- output_dir = r"G:\python_ws_g\data\test\jsonout"
- point_step_deg = 0.5
- draw_radius = 2
- if __name__ == "__main__":
- process_folder_labelme(input_dir, output_dir, point_step_deg, draw_radius)
|