commit c134abf7499ced86fb6d768489b8638e98894c9f
Author: 琉璃月光 <15630071+llyg777@user.noreply.gitee.com>
Date: Tue Oct 21 11:07:29 2025 +0800
first commit
diff --git a/ailai_pc/1.py b/ailai_pc/1.py
new file mode 100644
index 0000000..212789d
--- /dev/null
+++ b/ailai_pc/1.py
@@ -0,0 +1,25 @@
+from ultralytics import YOLO
+import cv2
+import torch
+
+# 加载模型
+model = YOLO('best.pt')
+
+# 读取一张真实图像
+img_path = '/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/point2/train/1.jpg' # 替换成您的图像路径
+image = cv2.imread(img_path)
+
+# 将图像转换成RGB格式,并调整大小
+image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
+image_resized = cv2.resize(image_rgb, (640, 640))
+
+# 进行推理
+results = model(image_resized)
+
+# 打印关键点数据形状和样本
+if len(results) > 0 and hasattr(results[0], 'keypoints') and results[0].keypoints is not None:
+ print("Keypoints data shape:", results[0].keypoints.data.shape)
+ if results[0].keypoints.data.shape[0] > 0:
+ print("Keypoints data sample:", results[0].keypoints.data[0, :12])
+else:
+ print("No keypoints detected or invalid keypoints data.")
\ No newline at end of file
diff --git a/ailai_pc/1.xml b/ailai_pc/1.xml
new file mode 100644
index 0000000..f53ff4e
--- /dev/null
+++ b/ailai_pc/1.xml
@@ -0,0 +1,42 @@
+
+ train
+ 1.jpg
+ /home/hx/桌面/ailai_test/train/1.jpg
+
+ Unknown
+
+
+ 1280
+ 720
+ 3
+
+ 0
+
+
+
\ No newline at end of file
diff --git a/ailai_pc/angle.py b/ailai_pc/angle.py
new file mode 100644
index 0000000..398af50
--- /dev/null
+++ b/ailai_pc/angle.py
@@ -0,0 +1,145 @@
+from ultralytics import YOLO
+import cv2
+import os
+import numpy as np
+
+# 设置类别名称(必须与训练时一致)
+CLASS_NAMES = ['ban', 'bag'] # ✅ 确保顺序正确,对应模型的 class_id
+COLORS = [(0, 255, 0), (255, 0, 0)] # ban: 绿色, bag: 蓝色
+
+
+def get_best_angles_per_class(image_path, weight_path, return_degree=False):
+ """
+ 输入:
+ image_path: 图像路径
+ weight_path: YOLO OBB 权重路径
+ return_degree: 是否返回角度(单位:度),否则为弧度
+ 输出:
+ 字典:{ class_name: best_angle 或 None }
+ """
+ img = cv2.imread(image_path)
+ if img is None:
+ print(f"❌ 无法读取图像:{image_path}")
+ return {cls: None for cls in CLASS_NAMES}
+
+ model = YOLO(weight_path)
+ results = model(img, save=False, imgsz=640, conf=0.15, task='obb')
+ result = results[0]
+
+ boxes = result.obb
+ if boxes is None or len(boxes) == 0:
+ print("⚠️ 未检测到任何目标。")
+ return {cls: None for cls in CLASS_NAMES}
+
+ # 提取数据
+ xywhr = boxes.xywhr.cpu().numpy() # (N, 5) -> cx, cy, w, h, r (弧度)
+ confs = boxes.conf.cpu().numpy() # (N,)
+ class_ids = boxes.cls.cpu().numpy().astype(int) # (N,)
+
+ # 初始化结果字典
+ best_angles = {cls: None for cls in CLASS_NAMES}
+
+ # 对每个类别找置信度最高的框
+ for class_id, class_name in enumerate(CLASS_NAMES):
+ mask = (class_ids == class_id)
+ if not np.any(mask):
+ print(f"🟡 未检测到类别: {class_name}")
+ continue
+
+ # 找该类别中置信度最高的
+ idx_in_class = np.argmax(confs[mask])
+ global_idx = np.where(mask)[0][idx_in_class]
+ angle_rad = xywhr[global_idx][4]
+
+ best_angles[class_name] = np.degrees(angle_rad) if return_degree else angle_rad
+
+ return best_angles
+
+
+def save_obb_visual(image_path, weight_path, save_path):
+ """
+ 输入:
+ image_path: 图像路径
+ weight_path: YOLO权重路径
+ save_path: 保存带标注图像路径
+ 功能:
+ 检测所有 OBB,绘制框、类别名、旋转角度,保存图片
+ """
+ img = cv2.imread(image_path)
+ if img is None:
+ print(f"❌ 无法读取图像:{image_path}")
+ return
+
+ model = YOLO(weight_path)
+ results = model(img, save=False, imgsz=640, conf=0.05, task='obb')
+ result = results[0]
+
+ boxes = result.obb
+ if boxes is None or len(boxes) == 0:
+ print("⚠️ 未检测到任何目标。")
+ # 仍保存原图
+ os.makedirs(os.path.dirname(save_path), exist_ok=True)
+ cv2.imwrite(save_path, img)
+ return
+
+ # 提取信息
+ xywhr = boxes.xywhr.cpu().numpy()
+ confs = boxes.conf.cpu().numpy()
+ class_ids = boxes.cls.cpu().numpy().astype(int)
+
+ # 绘制
+ annotated_img = img.copy()
+ for i in range(len(boxes)):
+ cx, cy, w, h, r = xywhr[i]
+ angle_deg = np.degrees(r)
+ class_id = class_ids[i]
+ class_name = CLASS_NAMES[class_id] if class_id < len(CLASS_NAMES) else f"cls{class_id}"
+ conf = confs[i]
+ color = COLORS[class_id % len(COLORS)] if class_id < len(CLASS_NAMES) else (128, 128, 128)
+
+ # 绘制旋转框
+ rect = ((cx, cy), (w, h), angle_deg)
+ box_pts = cv2.boxPoints(rect).astype(int)
+ cv2.polylines(annotated_img, [box_pts], isClosed=True, color=color, thickness=2)
+
+ # 标注文本:类别 + 置信度 + 角度
+ text = f"{class_name} {conf:.2f} {angle_deg:.1f}°"
+ font_scale = 0.7
+ thickness = 2
+ text_size, _ = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, font_scale, thickness)
+
+ # 文本背景
+ cv2.rectangle(annotated_img,
+ (box_pts[0][0], box_pts[0][1] - text_size[1] - 8),
+ (box_pts[0][0] + text_size[0], box_pts[0][1] + 2),
+ color, -1)
+ # 文本
+ cv2.putText(annotated_img, text,
+ (box_pts[0][0], box_pts[0][1] - 5),
+ cv2.FONT_HERSHEY_SIMPLEX, font_scale, (255, 255, 255), thickness)
+
+ # 保存
+ os.makedirs(os.path.dirname(save_path), exist_ok=True)
+ cv2.imwrite(save_path, annotated_img)
+ print(f"✅ 检测结果已保存至: {save_path}")
+
+
+# ===============================
+# 示例调用
+# ===============================
+if __name__ == "__main__":
+ weight = r"/home/hx/yolo/ultralytics_yolo11-main/runs/train/exp_obb3/weights/best.pt"
+ image = r"/home/hx/yolo/output_masks/2.jpg"
+ save_path = "./inference_results/visualized_2.jpg"
+
+ # 获取每个类别的最佳角度(以度为单位)
+ angles_deg = get_best_angles_per_class(image, weight, return_degree=True)
+ print("\n🎯 各类别最佳旋转角度(度):")
+ for cls_name, angle in angles_deg.items():
+ if angle is not None:
+ print(f" {cls_name}: {angle:.2f}°")
+ else:
+ print(f" {cls_name}: 未检测到")
+
+ # 可视化所有检测结果
+ save_obb_visual(image, weight, save_path)
\ No newline at end of file
diff --git a/ailai_pc/angle_and_diff.py b/ailai_pc/angle_and_diff.py
new file mode 100644
index 0000000..1393eee
--- /dev/null
+++ b/ailai_pc/angle_and_diff.py
@@ -0,0 +1,210 @@
+from ultralytics import YOLO
+import cv2
+import numpy as np
+import os
+
+# ================================
+# 配置区(所有固定参数放在这里)
+# ================================
+
+# --- 模型与类别 ---
+CLASS_NAMES = ['ban', 'bag'] # 类别名称
+WEIGHT_PATH = r"/home/hx/yolo/ultralytics_yolo11-main/runs/train/exp_obb_ailai2/weights/best.pt"
+IMAGE_PATH = r"/home/hx/ailai_image_obb/ailai_obb/camera01/1.jpg"
+
+# --- 结果保存路径 ---
+RESULT_DIR = "./inference_results"
+os.makedirs(RESULT_DIR, exist_ok=True) # 确保目录存在
+RESULT_IMAGE_PATH = os.path.join(RESULT_DIR, os.path.basename(IMAGE_PATH)) # 保存路径
+FIXED_BAG_CENTER_FILE = os.path.join(RESULT_DIR, "bag_center.txt") # 固定参考点,只读'
+
+# --- ban 的实际物理尺寸(单位:毫米 mm)---
+BAN_REAL_WIDTH_MM = 890 # 实际宽度(例如:800 mm)
+BAN_REAL_HEIGHT_MM = 1990 # 实际高度(例如:1990 mm)
+
+# --- ban 在图像中的平均像素尺寸(由模型检测得到,稳定值)---
+BAN_PIXEL_WIDTH_PX = 536.35 # 图像中检测到的平均宽度(像素)
+BAN_PIXEL_HEIGHT_PX = 242.83 # 图像中检测到的平均高度(像素)
+
+# --- 计算缩放因子(mm/px)---
+SCALE_X = BAN_REAL_WIDTH_MM / BAN_PIXEL_WIDTH_PX # mm/px
+SCALE_Y = BAN_REAL_HEIGHT_MM / BAN_PIXEL_HEIGHT_PX # mm/px
+
+print(f"标定信息 1 像素 = {SCALE_X:.3f} mm (水平) {SCALE_Y:.3f} mm (垂直)")
+
+# --- 固定 bag 角度(单位:度)---
+FIXED_BAG_ANGLE_DEG = 0.0 # 固定参考角度
+
+# ================================
+# 工具函数
+# ================================
+
+def pixels_to_physical(dx_px, dy_px):
+ """
+ 将像素偏移量转换为物理偏移量(毫米)
+ """
+ dx_mm = dx_px * SCALE_X
+ dy_mm = dy_px * SCALE_Y
+ return dx_mm, dy_mm
+
+def load_center_from_txt(txt_path):
+ """加载文本中的中心点 (cx, cy)"""
+ if not os.path.exists(txt_path):
+ print(f"文件不存在 {txt_path}")
+ return None
+ try:
+ with open(txt_path, "r") as f:
+ line = f.readline().strip()
+ if not line:
+ return None
+ x, y = map(float, line.split())
+ return (x, y)
+ except Exception as e:
+ print(f"读取文件失败 {txt_path} {e}")
+ return None
+
+def get_angles_and_centers(image_path, weight_path):
+ """获取每个类别的角度和中心点"""
+ img = cv2.imread(image_path)
+ if img is None:
+ print(f"无法读取图像 {image_path}")
+ return {cls: None for cls in CLASS_NAMES}, None
+
+ model = YOLO(weight_path)
+ results = model(img, imgsz=640, conf=0.15, task='obb')
+ result = results[0]
+
+ boxes = result.obb
+ if boxes is None or len(boxes) == 0:
+ print("未检测到任何目标")
+ return {cls: None for cls in CLASS_NAMES}, img
+
+ xywhr = boxes.xywhr.cpu().numpy()
+ confs = boxes.conf.cpu().numpy()
+ class_ids = boxes.cls.cpu().numpy().astype(int)
+
+ angles_and_centers = {}
+ for class_id, class_name in enumerate(CLASS_NAMES):
+ mask = (class_ids == class_id)
+ if not np.any(mask):
+ print(f"未检测到类别 {class_name}")
+ angles_and_centers[class_name] = None
+ continue
+
+ idx_in_class = np.argmax(confs[mask])
+ global_idx = np.where(mask)[0][idx_in_class]
+ cx, cy, w, h, r = xywhr[global_idx]
+ angle_deg = np.degrees(r)
+
+ angles_and_centers[class_name] = {
+ 'angle': angle_deg,
+ 'center': (float(cx), float(cy)),
+ 'width': float(w),
+ 'height': float(h),
+ 'box_points': cv2.boxPoints(((cx, cy), (w, h), angle_deg)) # 获取四个角点
+ }
+
+ return angles_and_centers, img
+
+def draw_results_and_save(image, angles_and_centers, fixed_bag_center, save_path):
+ """绘制旋转框、中心点、标签、偏移向量,并保存图像"""
+ vis_img = image.copy()
+
+ # 颜色定义
+ colors = {
+ 'ban': (0, 255, 0), # 绿色
+ 'bag': (255, 0, 0) # 蓝色
+ }
+
+ # 绘制每个检测结果
+ for cls_name, info in angles_and_centers.items():
+ if info is None:
+ continue
+
+ # 绘制旋转框
+ box_pts = np.int32(info['box_points'])
+ cv2.drawContours(vis_img, [box_pts], 0, colors[cls_name], 2)
+
+ # 绘制中心点
+ cx, cy = int(info['center'][0]), int(info['center'][1])
+ cv2.circle(vis_img, (cx, cy), 5, (0, 0, 255), -1) # 红色实心圆
+
+ # 添加标签
+ label = f"{cls_name} {info['angle']:.1f}°"
+ cv2.putText(vis_img, label, (cx - 30, cy - 10),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, colors[cls_name], 2)
+
+ # 绘制 fixed_bag_center 和偏移向量
+ current_bag = angles_and_centers.get('bag')
+ if fixed_bag_center and current_bag:
+ fx, fy = int(fixed_bag_center[0]), int(fixed_bag_center[1])
+ cx, cy = int(current_bag['center'][0]), int(current_bag['center'][1])
+
+ # 绘制 fixed center(空心圆)
+ cv2.circle(vis_img, (fx, fy), 7, (255, 255, 0), 2) # 青色
+
+ # 绘制从 fixed 到 current 的箭头(偏移向量)
+ cv2.arrowedLine(vis_img, (fx, fy), (cx, cy), (0, 255, 255), 2, tipLength=0.05)
+
+ # 添加偏移文字
+ dx_px = cx - fx
+ dy_px = cy - fy
+ dx_mm, dy_mm = pixels_to_physical(dx_px, dy_px)
+ offset_text = f"Δx={dx_mm:.1f}mm Δy={dy_mm:.1f}mm"
+ cv2.putText(vis_img, offset_text, (fx + 10, fy + 20),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)
+
+ # 保存图像
+ cv2.imwrite(save_path, vis_img)
+ print(f"\n✅ 可视化结果已保存至: {save_path}")
+
+# ===============================
+# 主程序
+# ===============================
+if __name__ == "__main__":
+ # 1. 获取当前检测结果和图像
+ angles_and_centers, img = get_angles_and_centers(IMAGE_PATH, WEIGHT_PATH)
+ if img is None:
+ exit(1)
+
+ print("\n各类别旋转角度 中心点")
+ for cls_name, info in angles_and_centers.items():
+ if info is not None:
+ print(f" {cls_name} 角度 {info['angle']:.2f} 中心点 {info['center'][0]:.2f} {info['center'][1]:.2f} 尺寸 {info['width']:.2f} {info['height']:.2f} px")
+ else:
+ print(f" {cls_name} 未检测到")
+
+ # 2. 获取固定参考 bag 中心点
+ fixed_bag_center = load_center_from_txt(FIXED_BAG_CENTER_FILE)
+ print(f"\n固定参考 bag 中心点 {fixed_bag_center}")
+
+ # 3. 计算偏移量(像素)
+ current_bag = angles_and_centers.get('bag')
+ if current_bag and current_bag['center'] and fixed_bag_center:
+ dx_px = current_bag['center'][0] - fixed_bag_center[0]
+ dy_px = current_bag['center'][1] - fixed_bag_center[1]
+
+ print(f"\n像素偏移量")
+ print(f" Δx {dx_px:.2f} px Δy {dy_px:.2f} px")
+
+ # 转换为物理偏移量
+ dx_mm, dy_mm = pixels_to_physical(dx_px, dy_px)
+ print(f"\n物理偏移量 基于 ban 尺寸标定")
+ print(f" ΔX {dx_mm:.2f} mm ΔY {dy_mm:.2f} mm")
+
+ # 计算角度偏移量
+ current_bag_angle = current_bag['angle']
+ angle_diff = current_bag_angle - FIXED_BAG_ANGLE_DEG
+ print(f"\n角度偏移量")
+ print(f" 当前 bag 角度 {current_bag_angle:.2f}")
+ print(f" 固定 bag 角度 {FIXED_BAG_ANGLE_DEG}")
+ print(f" 角度差 {angle_diff:.2f}")
+ else:
+ print(f"\n偏移量计算失败 数据缺失")
+ if not current_bag:
+ print(" 未检测到 bag")
+ if not fixed_bag_center:
+ print(" 固定参考点文件为空或不存在")
+
+ # 4. 绘制并保存可视化结果
+ draw_results_and_save(img, angles_and_centers, fixed_bag_center, RESULT_IMAGE_PATH)
\ No newline at end of file
diff --git a/ailai_pc/annotations.xml b/ailai_pc/annotations.xml
new file mode 100644
index 0000000..b4f1e64
--- /dev/null
+++ b/ailai_pc/annotations.xml
@@ -0,0 +1,3466 @@
+
+
+ 1.1
+
+
+ 197
+ 10.17ailai
+ 576
+ annotation
+ 0
+
+ 2025-10-19 08:02:10.197347+00:00
+ 2025-10-20 07:34:00.089890+00:00
+ default
+ 0
+ 575
+
+
+
+ 140
+ 0
+ 575
+ http://www.xj-robot.com:9000/api/jobs/140
+
+
+
+ huangxin
+ 2193534909@qq.com
+
+
+
+
+
+
+
+
+
+ 2025-10-20 08:32:33.431770+00:00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ailai_pc/bag_bushu.py b/ailai_pc/bag_bushu.py
new file mode 100644
index 0000000..a57228d
--- /dev/null
+++ b/ailai_pc/bag_bushu.py
@@ -0,0 +1,171 @@
+import cv2
+import numpy as np
+import math
+from shapely.geometry import Polygon
+from rknnlite.api import RKNNLite
+import os
+
+CLASSES = ['clamp']
+nmsThresh = 0.4
+objectThresh = 0.5
+
+# ------------------- 工具函数 -------------------
+def letterbox_resize(image, size, bg_color=114):
+ target_width, target_height = size
+ image_height, image_width, _ = image.shape
+ scale = min(target_width / image_width, target_height / image_height)
+ new_width, new_height = int(image_width * scale), int(image_height * scale)
+ image = cv2.resize(image, (new_width, new_height), interpolation=cv2.INTER_AREA)
+ canvas = np.ones((target_height, target_width, 3), dtype=np.uint8) * bg_color
+ offset_x, offset_y = (target_width - new_width) // 2, (target_height - new_height) // 2
+ canvas[offset_y:offset_y + new_height, offset_x:offset_x + new_width] = image
+ return canvas, scale, offset_x, offset_y
+
+class DetectBox:
+ def __init__(self, classId, score, xmin, ymin, xmax, ymax, angle):
+ self.classId = classId
+ self.score = score
+ self.xmin = xmin
+ self.ymin = ymin
+ self.xmax = xmax
+ self.ymax = ymax
+ self.angle = angle
+
+def rotate_rectangle(x1, y1, x2, y2, a):
+ cx, cy = (x1 + x2) / 2, (y1 + y2) / 2
+ x1_new = int((x1 - cx) * math.cos(a) - (y1 - cy) * math.sin(a) + cx)
+ y1_new = int((x1 - cx) * math.sin(a) + (y1 - cy) * math.cos(a) + cy)
+ x2_new = int((x2 - cx) * math.cos(a) - (y2 - cy) * math.sin(a) + cx)
+ y2_new = int((x2 - cx) * math.sin(a) + (y2 - cy) * math.cos(a) + cy)
+ x3_new = int((x1 - cx) * math.cos(a) - (y2 - cy) * math.sin(a) + cx)
+ y3_new = int((x1 - cx) * math.sin(a) + (y2 - cy) * math.cos(a) + cy)
+ x4_new = int((x2 - cx) * math.cos(a) - (y1 - cy) * math.sin(a) + cx)
+ y4_new = int((x2 - cx) * math.sin(a) + (y1 - cy) * math.cos(a) + cy)
+ return [(x1_new, y1_new), (x3_new, y3_new), (x2_new, y2_new), (x4_new, y4_new)]
+
+def intersection(g, p):
+ g = Polygon(np.array(g).reshape(-1,2))
+ p = Polygon(np.array(p).reshape(-1,2))
+ if not g.is_valid or not p.is_valid:
+ return 0
+ inter = g.intersection(p).area
+ union = g.area + p.area - inter
+ return 0 if union == 0 else inter / union
+
+def NMS(detectResult):
+ predBoxs = []
+ sort_detectboxs = sorted(detectResult, key=lambda x: x.score, reverse=True)
+ for i in range(len(sort_detectboxs)):
+ if sort_detectboxs[i].classId == -1:
+ continue
+ p1 = rotate_rectangle(sort_detectboxs[i].xmin, sort_detectboxs[i].ymin,
+ sort_detectboxs[i].xmax, sort_detectboxs[i].ymax,
+ sort_detectboxs[i].angle)
+ predBoxs.append(sort_detectboxs[i])
+ for j in range(i + 1, len(sort_detectboxs)):
+ if sort_detectboxs[j].classId == sort_detectboxs[i].classId:
+ p2 = rotate_rectangle(sort_detectboxs[j].xmin, sort_detectboxs[j].ymin,
+ sort_detectboxs[j].xmax, sort_detectboxs[j].ymax,
+ sort_detectboxs[j].angle)
+ if intersection(p1, p2) > nmsThresh:
+ sort_detectboxs[j].classId = -1
+ return predBoxs
+
+def sigmoid(x):
+ return np.where(x >= 0, 1 / (1 + np.exp(-x)), np.exp(x) / (1 + np.exp(x)))
+
+def softmax(x, axis=-1):
+ exp_x = np.exp(x - np.max(x, axis=axis, keepdims=True))
+ return exp_x / np.sum(exp_x, axis=axis, keepdims=True)
+
+def process(out, model_w, model_h, stride, angle_feature, index, scale_w=1, scale_h=1):
+ class_num = len(CLASSES)
+ angle_feature = angle_feature.reshape(-1)
+ xywh = out[:, :64, :]
+ conf = sigmoid(out[:, 64:, :]).reshape(-1)
+ boxes = []
+ for ik in range(model_h * model_w * class_num):
+ if conf[ik] > objectThresh:
+ w = ik % model_w
+ h = (ik % (model_w * model_h)) // model_w
+ c = ik // (model_w * model_h)
+ # 解析xywh
+ xywh_ = xywh[0, :, (h * model_w) + w].reshape(1, 4, 16, 1)
+ data = np.arange(16).reshape(1, 1, 16, 1)
+ xywh_ = softmax(xywh_, 2)
+ xywh_ = np.sum(xywh_ * data, axis=2).reshape(-1)
+ xywh_add = xywh_[:2] + xywh_[2:]
+ xywh_sub = (xywh_[2:] - xywh_[:2]) / 2
+ # 安全取角度
+ angle_idx = min(index + (h * model_w) + w, len(angle_feature) - 1)
+ angle = (angle_feature[angle_idx] - 0.25) * math.pi
+ cos_a, sin_a = math.cos(angle), math.sin(angle)
+ xy = xywh_sub[0] * cos_a - xywh_sub[1] * sin_a, xywh_sub[0] * sin_a + xywh_sub[1] * cos_a
+ xywh1 = np.array([xy[0] + w + 0.5, xy[1] + h + 0.5, xywh_add[0], xywh_add[1]])
+ xywh1 *= stride
+ xmin = (xywh1[0] - xywh1[2]/2) * scale_w
+ ymin = (xywh1[1] - xywh1[3]/2) * scale_h
+ xmax = (xywh1[0] + xywh1[2]/2) * scale_w
+ ymax = (xywh1[1] + xywh1[3]/2) * scale_h
+ boxes.append(DetectBox(c, conf[ik], xmin, ymin, xmax, ymax, angle))
+ return boxes
+
+# ------------------- 主函数 -------------------
+def detect_boxes_angle_rknn(model_path, image_path, save_path=None):
+ img = cv2.imread(image_path)
+ if img is None:
+ print(f"❌ 无法读取图像: {image_path}")
+ return None, None
+
+ img_resized, scale, offset_x, offset_y = letterbox_resize(img, (640, 640))
+ infer_img = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB)
+ infer_img = np.expand_dims(infer_img, 0)
+
+ rknn_lite = RKNNLite(verbose=False)
+ rknn_lite.load_rknn(model_path)
+ rknn_lite.init_runtime(core_mask=RKNNLite.NPU_CORE_0)
+
+ results = rknn_lite.inference([infer_img])
+ detect_boxes = []
+ for x in results[:-1]:
+ index, stride = 0, 0
+ if x.shape[2] == 20:
+ stride, index = 32, 20*4*20*4 + 20*2*20*2
+ elif x.shape[2] == 40:
+ stride, index = 16, 20*4*20*4
+ elif x.shape[2] == 80:
+ stride, index = 8, 0
+ feature = x.reshape(1, 65, -1)
+ detect_boxes += process(feature, x.shape[3], x.shape[2], stride, results[-1], index)
+
+ detect_boxes = NMS(detect_boxes)
+
+ # 输出每个检测框角度
+ for i, box in enumerate(detect_boxes):
+ print(f"框 {i+1}: angle = {box.angle:.4f} rad ({np.degrees(box.angle):.2f}°)")
+ if save_path:
+ xmin = int((box.xmin - offset_x)/scale)
+ ymin = int((box.ymin - offset_y)/scale)
+ xmax = int((box.xmax - offset_x)/scale)
+ ymax = int((box.ymax - offset_y)/scale)
+ points = rotate_rectangle(xmin, ymin, xmax, ymax, box.angle)
+ cv2.polylines(img, [np.array(points, np.int32)], True, (0, 255, 0), 1)
+ cv2.putText(img, f"{np.degrees(box.angle):.1f}°", (xmin, ymin-5),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255), 1)
+
+ if save_path:
+ os.makedirs(os.path.dirname(save_path), exist_ok=True)
+ cv2.imwrite(save_path, img)
+ print(f"✅ 带角度的检测结果已保存到 {save_path}")
+
+ rknn_lite.release()
+ return detect_boxes, img
+
+# ------------------- 使用示例 -------------------
+if __name__ == "__main__":
+ model_path = "obb.rknn"
+ image_path = "2.jpg"
+ save_path = "./inference_results/boxes_with_angle.jpg"
+ os.makedirs(os.path.dirname(save_path), exist_ok=True)
+ detect_boxes_angle_rknn(model_path, image_path, save_path)
+
diff --git a/ailai_pc/best.pt b/ailai_pc/best.pt
new file mode 100644
index 0000000..a9e6a58
Binary files /dev/null and b/ailai_pc/best.pt differ
diff --git a/ailai_pc/bushu_angle.py b/ailai_pc/bushu_angle.py
new file mode 100644
index 0000000..891ee62
--- /dev/null
+++ b/ailai_pc/bushu_angle.py
@@ -0,0 +1,197 @@
+
+import cv2
+import numpy as np
+import math
+from shapely.geometry import Polygon
+from rknnlite.api import RKNNLite
+import os
+
+# ------------------- 配置 -------------------
+CLASSES = ['clamp']
+nmsThresh = 0.4
+objectThresh = 0.5
+
+# ------------------- 全局原图尺寸 -------------------
+ORIG_W = 2560 # 原图宽
+ORIG_H = 1440 # 原图高
+
+# ------------------- 工具函数 -------------------
+def letterbox_resize(image, size, bg_color=114):
+ target_width, target_height = size
+ image_height, image_width, _ = image.shape
+ scale = min(target_width / image_width, target_height / image_height)
+ new_width, new_height = int(image_width * scale), int(image_height * scale)
+ image_resized = cv2.resize(image, (new_width, new_height), interpolation=cv2.INTER_AREA)
+ canvas = np.ones((target_height, target_width, 3), dtype=np.uint8) * bg_color
+ offset_x, offset_y = (target_width - new_width) // 2, (target_height - new_height) // 2
+ canvas[offset_y:offset_y + new_height, offset_x:offset_x + new_width] = image_resized
+ return canvas, scale, offset_x, offset_y
+
+class DetectBox:
+ def __init__(self, classId, score, xmin, ymin, xmax, ymax, angle):
+ self.classId = classId
+ self.score = score
+ self.xmin = xmin
+ self.ymin = ymin
+ self.xmax = xmax
+ self.ymax = ymax
+ self.angle = angle
+
+def rotate_rectangle(x1, y1, x2, y2, a):
+ cx, cy = (x1 + x2) / 2, (y1 + y2) / 2
+ x1_new = int((x1 - cx) * math.cos(a) - (y1 - cy) * math.sin(a) + cx)
+ y1_new = int((x1 - cx) * math.sin(a) + (y1 - cy) * math.cos(a) + cy)
+ x2_new = int((x2 - cx) * math.cos(a) - (y2 - cy) * math.sin(a) + cx)
+ y2_new = int((x2 - cx) * math.sin(a) + (y2 - cy) * math.cos(a) + cy)
+ x3_new = int((x1 - cx) * math.cos(a) - (y2 - cy) * math.sin(a) + cx)
+ y3_new = int((x1 - cx) * math.sin(a) + (y2 - cy) * math.cos(a) + cy)
+ x4_new = int((x2 - cx) * math.cos(a) - (y1 - cy) * math.sin(a) + cx)
+ y4_new = int((x2 - cx) * math.sin(a) + (y1 - cy) * math.cos(a) + cy)
+ return [(x1_new, y1_new), (x3_new, y3_new), (x2_new, y2_new), (x4_new, y4_new)]
+
+def intersection(g, p):
+ g = Polygon(np.array(g).reshape(-1,2))
+ p = Polygon(np.array(p).reshape(-1,2))
+ if not g.is_valid or not p.is_valid:
+ return 0
+ inter = g.intersection(p).area
+ union = g.area + p.area - inter
+ return 0 if union == 0 else inter / union
+
+def NMS(detectResult):
+ predBoxs = []
+ sort_detectboxs = sorted(detectResult, key=lambda x: x.score, reverse=True)
+ for i in range(len(sort_detectboxs)):
+ if sort_detectboxs[i].classId == -1:
+ continue
+ p1 = rotate_rectangle(sort_detectboxs[i].xmin, sort_detectboxs[i].ymin,
+ sort_detectboxs[i].xmax, sort_detectboxs[i].ymax,
+ sort_detectboxs[i].angle)
+ predBoxs.append(sort_detectboxs[i])
+ for j in range(i + 1, len(sort_detectboxs)):
+ if sort_detectboxs[j].classId == sort_detectboxs[i].classId:
+ p2 = rotate_rectangle(sort_detectboxs[j].xmin, sort_detectboxs[j].ymin,
+ sort_detectboxs[j].xmax, sort_detectboxs[j].ymax,
+ sort_detectboxs[j].angle)
+ if intersection(p1, p2) > nmsThresh:
+ sort_detectboxs[j].classId = -1
+ return predBoxs
+
+def sigmoid(x):
+ return np.where(x >= 0, 1 / (1 + np.exp(-x)), np.exp(x) / (1 + np.exp(x)))
+
+def softmax(x, axis=-1):
+ exp_x = np.exp(x - np.max(x, axis=axis, keepdims=True))
+ return exp_x / np.sum(exp_x, axis=axis, keepdims=True)
+
+# ------------------- 关键修改:process函数加入scale -------------------
+def process(out, model_w, model_h, stride, angle_feature, index, scale=1.0, offset_x=0, offset_y=0):
+ class_num = len(CLASSES)
+ angle_feature = angle_feature.reshape(-1)
+ xywh = out[:, :64, :]
+ conf = sigmoid(out[:, 64:, :]).reshape(-1)
+ boxes = []
+ for ik in range(model_h * model_w * class_num):
+ if conf[ik] > objectThresh:
+ w = ik % model_w
+ h = (ik % (model_w * model_h)) // model_w
+ c = ik // (model_w * model_h)
+ # 解析xywh
+ xywh_ = xywh[0, :, (h * model_w) + w].reshape(1, 4, 16, 1)
+ xywh_ = softmax(xywh_, 2)
+ data = np.arange(16).reshape(1, 1, 16, 1)
+ xywh_ = np.sum(xywh_ * data, axis=2).reshape(-1)
+ xywh_add = xywh_[:2] + xywh_[2:]
+ xywh_sub = (xywh_[2:] - xywh_[:2]) / 2
+ # 取角度
+ angle_idx = min(index + (h * model_w) + w, len(angle_feature) - 1)
+ angle = (angle_feature[angle_idx] - 0.25) * math.pi
+ cos_a, sin_a = math.cos(angle), math.sin(angle)
+ xy = xywh_sub[0] * cos_a - xywh_sub[1] * sin_a, xywh_sub[0] * sin_a + xywh_sub[1] * cos_a
+ xywh1 = np.array([xy[0] + w + 0.5, xy[1] + h + 0.5, xywh_add[0], xywh_add[1]])
+ xywh1 *= stride
+ # 映射回原图坐标
+ xmin = (xywh1[0] - xywh1[2]/2 - offset_x) / scale
+ ymin = (xywh1[1] - xywh1[3]/2 - offset_y) / scale
+ xmax = (xywh1[0] + xywh1[2]/2 - offset_x) / scale
+ ymax = (xywh1[1] + xywh1[3]/2 - offset_y) / scale
+ boxes.append(DetectBox(c, conf[ik], xmin, ymin, xmax, ymax, angle))
+ return boxes
+
+# ------------------- 新可调用函数 -------------------
+def detect_boxes_rknn(model_path, image_path):
+ img = cv2.imread(image_path)
+ if img is None:
+ print(f"❌ 无法读取图像: {image_path}")
+ return None, None
+
+ img_resized, scale, offset_x, offset_y = letterbox_resize(img, (640, 640))
+ infer_img = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB)
+ infer_img = np.expand_dims(infer_img, 0)
+
+ rknn_lite = RKNNLite(verbose=False)
+ rknn_lite.load_rknn(model_path)
+ rknn_lite.init_runtime(core_mask=RKNNLite.NPU_CORE_0)
+
+ results = rknn_lite.inference([infer_img])
+ detect_boxes = []
+ for x in results[:-1]:
+ index, stride = 0, 0
+ if x.shape[2] == 20:
+ stride, index = 32, 20*4*20*4 + 20*2*20*2
+ elif x.shape[2] == 40:
+ stride, index = 16, 20*4*20*4
+ elif x.shape[2] == 80:
+ stride, index = 8, 0
+ feature = x.reshape(1, 65, -1)
+ detect_boxes += process(feature, x.shape[3], x.shape[2], stride, results[-1], index,
+ scale=scale, offset_x=offset_x, offset_y=offset_y)
+
+ detect_boxes = NMS(detect_boxes)
+ rknn_lite.release()
+ return detect_boxes, img
+
+# ------------------- 绘制与辅助函数 -------------------
+def get_angles(detect_boxes):
+ return [box.angle for box in detect_boxes]
+
+def draw_boxes(img, detect_boxes, save_path=None):
+ for box in detect_boxes:
+ points = rotate_rectangle(box.xmin, box.ymin, box.xmax, box.ymax, box.angle)
+ cv2.polylines(img, [np.array(points, np.int32)], True, (0, 255, 0), 1)
+ cv2.putText(img, f"{np.degrees(box.angle):.1f}°", (int(box.xmin), int(box.ymin)-5),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255), 1)
+ if save_path:
+ os.makedirs(os.path.dirname(save_path), exist_ok=True)
+ cv2.imwrite(save_path, img)
+ print(f"✅ 带角度的检测结果已保存到 {save_path}")
+ return img
+
+def visualize_top_box(img, detect_boxes, save_path=None):
+ if not detect_boxes:
+ return img
+ top_box = max(detect_boxes, key=lambda x: x.score)
+ points = rotate_rectangle(top_box.xmin, top_box.ymin, top_box.xmax, top_box.ymax, top_box.angle)
+ cv2.polylines(img, [np.array(points, np.int32)], True, (0, 255, 0), 2)
+ cv2.putText(img, f"{np.degrees(top_box.angle):.1f}°", (int(top_box.xmin), int(top_box.ymin)-5),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,0,255), 2)
+ if save_path:
+ os.makedirs(os.path.dirname(save_path), exist_ok=True)
+ cv2.imwrite(save_path, img)
+ return img
+
+# ------------------- 使用示例 -------------------
+if __name__ == "__main__":
+ model_path = "obb.rknn"
+ image_path = "2.jpg"
+
+ detect_boxes, img = detect_boxes_rknn(model_path, image_path)
+ angles = get_angles(detect_boxes)
+ for i, angle in enumerate(angles):
+ print(f"框 {i+1}: angle = {angle:.4f} rad ({np.degrees(angle):.2f}°)")
+
+ save_path_all = "./inference_results/boxes_all.jpg"
+ draw_boxes(img.copy(), detect_boxes, save_path_all)
+
+ save_path_top = "./inference_results/top_box.jpg"
+ visualize_top_box(img.copy(), detect_boxes, save_path_top)
diff --git a/ailai_pc/caculate_center.py b/ailai_pc/caculate_center.py
new file mode 100644
index 0000000..5cfc0a9
--- /dev/null
+++ b/ailai_pc/caculate_center.py
@@ -0,0 +1,145 @@
+from ultralytics import YOLO
+import cv2
+import os
+import numpy as np
+
+# 设置类别名称(必须与训练时一致)
+CLASS_NAMES = ['ban', 'bag'] # ✅ 确保顺序正确,对应模型的 class_id
+COLORS = [(0, 255, 0), (255, 0, 0)] # ban: 绿色, bag: 蓝色
+
+
+def get_best_angles_per_class(image_path, weight_path, return_degree=False):
+ """
+ 输入:
+ image_path: 图像路径
+ weight_path: YOLO OBB 权重路径
+ return_degree: 是否返回角度(单位:度),否则为弧度
+ 输出:
+ 字典:{ class_name: best_angle 或 None }
+ """
+ img = cv2.imread(image_path)
+ if img is None:
+ print(f"❌ 无法读取图像:{image_path}")
+ return {cls: None for cls in CLASS_NAMES}
+
+ model = YOLO(weight_path)
+ results = model(img, save=False, imgsz=640, conf=0.15, task='obb')
+ result = results[0]
+
+ boxes = result.obb
+ if boxes is None or len(boxes) == 0:
+ print("⚠️ 未检测到任何目标。")
+ return {cls: None for cls in CLASS_NAMES}
+
+ # 提取数据
+ xywhr = boxes.xywhr.cpu().numpy() # (N, 5) -> cx, cy, w, h, r (弧度)
+ confs = boxes.conf.cpu().numpy() # (N,)
+ class_ids = boxes.cls.cpu().numpy().astype(int) # (N,)
+
+ # 初始化结果字典
+ best_angles = {cls: None for cls in CLASS_NAMES}
+
+ # 对每个类别找置信度最高的框
+ for class_id, class_name in enumerate(CLASS_NAMES):
+ mask = (class_ids == class_id)
+ if not np.any(mask):
+ print(f"🟡 未检测到类别: {class_name}")
+ continue
+
+ # 找该类别中置信度最高的
+ idx_in_class = np.argmax(confs[mask])
+ global_idx = np.where(mask)[0][idx_in_class]
+ angle_rad = xywhr[global_idx][4]
+
+ best_angles[class_name] = np.degrees(angle_rad) if return_degree else angle_rad
+
+ return best_angles
+
+
+def save_obb_visual(image_path, weight_path, save_path):
+ """
+ 输入:
+ image_path: 图像路径
+ weight_path: YOLO权重路径
+ save_path: 保存带标注图像路径
+ 功能:
+ 检测所有 OBB,绘制框、类别名、旋转角度,保存图片
+ """
+ img = cv2.imread(image_path)
+ if img is None:
+ print(f"❌ 无法读取图像:{image_path}")
+ return
+
+ model = YOLO(weight_path)
+ results = model(img, save=False, imgsz=640, conf=0.15, task='obb')
+ result = results[0]
+
+ boxes = result.obb
+ if boxes is None or len(boxes) == 0:
+ print("⚠️ 未检测到任何目标。")
+ # 仍保存原图
+ os.makedirs(os.path.dirname(save_path), exist_ok=True)
+ cv2.imwrite(save_path, img)
+ return
+
+ # 提取信息
+ xywhr = boxes.xywhr.cpu().numpy()
+ confs = boxes.conf.cpu().numpy()
+ class_ids = boxes.cls.cpu().numpy().astype(int)
+
+ # 绘制
+ annotated_img = img.copy()
+ for i in range(len(boxes)):
+ cx, cy, w, h, r = xywhr[i]
+ angle_deg = np.degrees(r)
+ class_id = class_ids[i]
+ class_name = CLASS_NAMES[class_id] if class_id < len(CLASS_NAMES) else f"cls{class_id}"
+ conf = confs[i]
+ color = COLORS[class_id % len(COLORS)] if class_id < len(CLASS_NAMES) else (128, 128, 128)
+
+ # 绘制旋转框
+ rect = ((cx, cy), (w, h), angle_deg)
+ box_pts = cv2.boxPoints(rect).astype(int)
+ cv2.polylines(annotated_img, [box_pts], isClosed=True, color=color, thickness=2)
+
+ # 标注文本:类别 + 置信度 + 角度
+ text = f"{class_name} {conf:.2f} {angle_deg:.1f}°"
+ font_scale = 0.7
+ thickness = 2
+ text_size, _ = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, font_scale, thickness)
+
+ # 文本背景
+ cv2.rectangle(annotated_img,
+ (box_pts[0][0], box_pts[0][1] - text_size[1] - 8),
+ (box_pts[0][0] + text_size[0], box_pts[0][1] + 2),
+ color, -1)
+ # 文本
+ cv2.putText(annotated_img, text,
+ (box_pts[0][0], box_pts[0][1] - 5),
+ cv2.FONT_HERSHEY_SIMPLEX, font_scale, (255, 255, 255), thickness)
+
+ # 保存
+ os.makedirs(os.path.dirname(save_path), exist_ok=True)
+ cv2.imwrite(save_path, annotated_img)
+ print(f"✅ 检测结果已保存至: {save_path}")
+
+
+# ===============================
+# 示例调用
+# ===============================
+if __name__ == "__main__":
+ weight = r"/home/hx/yolo/ultralytics_yolo11-main/runs/train/exp_obb_ailai/weights/best.pt"
+ image = r"/home/hx/yolo/ailai_obb/camera01/1.jpg"
+ save_path = "./inference_results/visualized_2.jpg"
+
+ # 获取每个类别的最佳角度(以度为单位)
+ angles_deg = get_best_angles_per_class(image, weight, return_degree=True)
+ print("\n🎯 各类别最佳旋转角度(度):")
+ for cls_name, angle in angles_deg.items():
+ if angle is not None:
+ print(f" {cls_name}: {angle:.2f}°")
+ else:
+ print(f" {cls_name}: 未检测到")
+
+ # 可视化所有检测结果
+ save_obb_visual(image, weight, save_path)
\ No newline at end of file
diff --git a/ailai_pc/caculate_xiangsu.py b/ailai_pc/caculate_xiangsu.py
new file mode 100644
index 0000000..aa235ef
--- /dev/null
+++ b/ailai_pc/caculate_xiangsu.py
@@ -0,0 +1,51 @@
+import cv2
+
+# 全局变量
+refPt = []
+drawing = False
+
+
+def draw_line(event, x, y, flags, param):
+ global refPt, drawing, image
+
+ # 鼠标左键按下时,记录起始点坐标并开始绘制
+ if event == cv2.EVENT_LBUTTONDOWN:
+ refPt = [(x, y)]
+ drawing = True
+
+ # 当鼠标移动且处于绘制状态时,更新图像以显示当前的线段
+ elif event == cv2.EVENT_MOUSEMOVE and drawing:
+ temp_image = image.copy()
+ cv2.line(temp_image, refPt[0], (x, y), (0, 255, 0), 2)
+ cv2.imshow("Image", temp_image)
+
+ # 鼠标左键释放时,记录终点坐标,结束绘制并计算线段长度
+ elif event == cv2.EVENT_LBUTTONUP:
+ refPt.append((x, y))
+ drawing = False
+ # 在图像上画线
+ cv2.line(image, refPt[0], refPt[1], (0, 255, 0), 2)
+ cv2.imshow("Image", image)
+
+ # 计算线段长度
+ dx = refPt[1][0] - refPt[0][0]
+ dy = refPt[1][1] - refPt[0][1]
+ length = (dx ** 2 + dy ** 2) ** 0.5
+ print(f"线段长度: {length:.2f} 像素")
+
+
+# 加载图像
+image_path = 'your_image_path_here.jpg' # 替换为你的图像路径
+image = cv2.imread(image_path)
+cv2.namedWindow("Image")
+cv2.setMouseCallback("Image", draw_line)
+
+while True:
+ cv2.imshow("Image", image)
+ key = cv2.waitKey(1) & 0xFF
+
+ # 按下 'q' 键退出循环
+ if key == ord('q'):
+ break
+
+cv2.destroyAllWindows()
\ No newline at end of file
diff --git a/ailai_pc/caculatet.py b/ailai_pc/caculatet.py
new file mode 100644
index 0000000..c4c467b
--- /dev/null
+++ b/ailai_pc/caculatet.py
@@ -0,0 +1,40 @@
+import cv2
+import numpy as np
+
+# 全局变量
+points = []
+drawing = False # 是否开始绘图
+
+
+def select_point(event, x, y, flags, param):
+ global drawing, points
+
+ if event == cv2.EVENT_LBUTTONDOWN:
+ # 当鼠标左键按下时记录第一个点
+ drawing = True
+ points = [(x, y)]
+ elif event == cv2.EVENT_LBUTTONUP:
+ # 当鼠标左键释放时记录第二个点,并完成线段的选择
+ drawing = False
+ points.append((x, y))
+ # 绘制线段
+ cv2.line(img, points[0], points[1], (0, 255, 0), 2)
+ # 计算两点间的距离
+ distance = np.sqrt((points[1][0] - points[0][0]) ** 2 + (points[1][1] - points[0][1]) ** 2)
+ print(f"线段的长度为: {distance:.2f} 像素")
+ # 显示更新后的图像
+ cv2.imshow('image', img)
+
+
+img_path = '/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/point1/val/192.168.0.234_01_20251014154410130.jpg' # 替换为你的图片路径
+img = cv2.imread(img_path)
+cv2.namedWindow('image')
+cv2.setMouseCallback('image', select_point)
+
+while(1):
+ cv2.imshow('image', img)
+ k = cv2.waitKey(1) & 0xFF
+ if k == 27: # 按下ESC退出
+ break
+
+cv2.destroyAllWindows()
\ No newline at end of file
diff --git a/ailai_pc/camera01/1.jpg b/ailai_pc/camera01/1.jpg
new file mode 100644
index 0000000..e1749d0
Binary files /dev/null and b/ailai_pc/camera01/1.jpg differ
diff --git a/ailai_pc/camera01/2.jpg b/ailai_pc/camera01/2.jpg
new file mode 100644
index 0000000..5065644
Binary files /dev/null and b/ailai_pc/camera01/2.jpg differ
diff --git a/ailai_pc/camera01/3.jpg b/ailai_pc/camera01/3.jpg
new file mode 100644
index 0000000..de1561e
Binary files /dev/null and b/ailai_pc/camera01/3.jpg differ
diff --git a/ailai_pc/camera01/4.jpg b/ailai_pc/camera01/4.jpg
new file mode 100644
index 0000000..e914314
Binary files /dev/null and b/ailai_pc/camera01/4.jpg differ
diff --git a/ailai_pc/cover_image.py b/ailai_pc/cover_image.py
new file mode 100644
index 0000000..7292321
--- /dev/null
+++ b/ailai_pc/cover_image.py
@@ -0,0 +1,41 @@
+import os
+import shutil
+
+def overwrite_images(folder1, folder2):
+ """
+ 在 folder1 和 folder2 中查找同名图片,
+ 用 folder1 的图片覆盖 folder2 中的同名图片。
+ """
+ # 确保两个路径存在
+ if not os.path.exists(folder1) or not os.path.exists(folder2):
+ print("❌ 输入的文件夹路径不存在")
+ return
+
+ # 获取 folder1 所有文件
+ files1 = set(os.listdir(folder1))
+ files2 = set(os.listdir(folder2))
+
+ # 找出交集(相同名字的文件)
+ common_files = files1 & files2
+
+ if not common_files:
+ print("⚠️ 没有找到同名文件")
+ return
+
+ for file in common_files:
+ src = os.path.join(folder1, file)
+ dst = os.path.join(folder2, file)
+
+ # 仅处理图片文件(可根据需要扩展)
+ if src.lower().endswith((".jpg", ".jpeg", ".png", ".bmp", ".tif", ".tiff")):
+ shutil.copy2(src, dst)
+ print(f"✅ 已覆盖: {dst}")
+
+ print("🎯 覆盖完成!")
+
+
+if __name__ == "__main__":
+ folder1 = r"/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/classdata1/save/class4" # 源文件夹
+ folder2 = r"/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/seg/resize_seg2/train" # 目标文件夹
+
+ overwrite_images(folder1, folder2)
diff --git a/ailai_pc/diff.py b/ailai_pc/diff.py
new file mode 100644
index 0000000..87fee12
--- /dev/null
+++ b/ailai_pc/diff.py
@@ -0,0 +1,174 @@
+import cv2
+import numpy as np
+from ultralytics import YOLO
+
+# ====================== 用户配置 ======================
+MODEL_PATH = 'best.pt'
+IMAGE_PATH = '/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/point2/train/1.jpg' # 👈 修改为你的具体图像路径
+OUTPUT_DIR = './output_images'
+
+# 固定点(例如标定得到的理论位置)
+FIXED_REF_POINT = (535.0, 605)# (x, y),单位:像素
+def calculate_scale(width_mm, width_px):
+ """
+ 计算缩放因子(单位:mm/px)
+ :param width_mm: 实际宽度(单位:毫米)
+ :param width_px: 宽度的像素数量
+ :return: 缩放因子(单位:mm/px)
+ """
+ if width_px == 0:
+ print("像素宽度不能为0")
+ return None
+ return width_mm / float(width_px)
+
+# 示例使用:
+# 假设我们知道一个参考物体的实际宽度是50毫米,在图像中占据100个像素
+width_mm = 70.0 # 实际宽度(单位:毫米)
+width_px = 42 # 在图像中的宽度(单位:像素)
+
+SCALE_X= calculate_scale(width_mm, width_px)
+print(f"水平方向的缩放因子为: {SCALE_X:.3f} mm/px")
+
+def calculate_scale_y(height_mm, height_px):
+ """
+ 计算垂直方向的缩放因子(单位:mm/px)
+ :param height_mm: 实际高度(单位:毫米)
+ :param height_px: 高度的像素数量
+ :return: 缩放因子(单位:mm/px)
+ """
+ if height_px == 0:
+ print("像素高度不能为0")
+ return None
+ return height_mm / float(height_px)
+
+# 同样地,对于高度来说
+height_mm = 890.0 # 实际高度(单位:毫米)
+height_px = 507 # 在图像中的高度(单位:像素)
+
+SCALE_Y = calculate_scale_y(height_mm, height_px)
+print(f"垂直方向的缩放因子为: {SCALE_Y:.3f} mm/px")
+# 创建输出目录
+import os
+os.makedirs(OUTPUT_DIR, exist_ok=True)
+
+# ====================== 可视化函数(增强版)======================
+def draw_keypoints_and_offset(image, kpts_xy, kpts_conf, orig_shape, fixed_point, scale_x, scale_y):
+ """
+ 在图像上绘制关键点、中心点、参考点、偏移箭头和文本
+ :param image: OpenCV 图像
+ :param kpts_xy: (N, K, 2) 坐标
+ :param kpts_conf: (N, K) 置信度
+ :param orig_shape: 原图尺寸 (H, W)
+ :param fixed_point: 固定参考点 (fx, fy)
+ :param scale_x: x方向缩放 mm/px
+ :param scale_y: y方向缩放 mm/px
+ :return: 处理后的图像,偏移信息列表
+ """
+ colors = [(0, 0, 255), (255, 0, 0), (0, 255, 0), (255, 255, 0)] # 1红, 2蓝, 3绿, 4青
+ results_info = []
+
+ for i in range(len(kpts_xy)):
+ xy = kpts_xy[i] # (K, 2)
+ conf = kpts_conf[i] if kpts_conf.ndim == 2 else kpts_conf[i:i+1]
+
+ # 检查是否有至少两个关键点
+ if len(xy) < 2:
+ print(f"⚠️ 实例 {i} 的关键点数量不足2个")
+ continue
+
+ p1 = xy[0] # 第一个关键点
+ p2 = xy[1] # 第二个关键点
+
+ c1 = conf[0] if hasattr(conf, '__len__') else conf
+ c2 = conf[1] if hasattr(conf, '__len__') else conf
+ if c1 < 0.5 or c2 < 0.5:
+ print(f"⚠️ 实例 {i} 的前两个关键点置信度过低: c1={c1:.3f}, c2={c2:.3f}")
+ continue
+
+ # 转为整数坐标(仅用于绘制)
+ p1_int = tuple(map(int, p1))
+ p2_int = tuple(map(int, p2))
+
+ h, w = orig_shape
+ valid = all(0 <= x < w and 0 <= y < h for x, y in [p1, p2])
+ if not valid:
+ print(f"⚠️ 实例 {i} 的关键点超出图像边界")
+ continue
+
+ # 绘制前两个关键点
+ cv2.circle(image, p1_int, radius=15, color=colors[0], thickness=-1) # 红色
+ cv2.circle(image, p2_int, radius=15, color=colors[1], thickness=-1) # 蓝色
+
+ # 标注编号
+ cv2.putText(image, "1", (p1_int[0] + 20, p1_int[1] - 20),
+ cv2.FONT_HERSHEY_SIMPLEX, 1.5, colors[0], 5)
+ cv2.putText(image, "2", (p2_int[0] + 20, p2_int[1] - 20),
+ cv2.FONT_HERSHEY_SIMPLEX, 1.5, colors[1], 5)
+
+ center_x = (p1[0] + p2[0]) / 2.0
+ center_y = (p1[1] + p2[1]) / 2.0
+ dynamic_center = (int(center_x), int(center_y))
+
+ cv2.circle(image, dynamic_center, radius=18, color=(0, 255, 0), thickness=3)
+ cv2.putText(image, "Center", (dynamic_center[0] + 30, dynamic_center[1]),
+ cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 0), 3)
+
+ fx, fy = map(int, fixed_point)
+ cv2.circle(image, (fx, fy), radius=20, color=(255, 255, 0), thickness=3)
+ cv2.putText(image, "Ref", (fx + 30, fy), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (255, 255, 0), 3)
+
+ dx_px = center_x - fixed_point[0]
+ dy_px = center_y - fixed_point[1]
+ dx_mm = dx_px * scale_x
+ dy_mm = dy_px * scale_y
+
+ cv2.arrowedLine(image, (fx, fy), dynamic_center, (0, 255, 255), 3, tipLength=0.05)
+
+ cv2.putText(image, f"ΔX={dx_mm:+.1f}mm", (fx + 40, fy - 40),
+ cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 255), 3)
+ cv2.putText(image, f"ΔY={dy_mm:+.1f}mm", (fx + 40, fy + 40),
+ cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 255), 3)
+
+ results_info.append({
+ 'instance': i,
+ 'center': (center_x, center_y),
+ 'dx_px': dx_px, 'dy_px': dy_px,
+ 'dx_mm': dx_mm, 'dy_mm': dy_mm
+ })
+
+ return image, results_info
+
+
+if __name__ == "__main__":
+ img = cv2.imread(IMAGE_PATH)
+ if img is None:
+ print(f"❌ 无法读取图像,检查路径: {IMAGE_PATH}")
+ exit(1)
+
+ model = YOLO(MODEL_PATH)
+ results = model(img)
+
+ for i, result in enumerate(results):
+ if result.keypoints is not None:
+ kpts = result.keypoints
+ orig_shape = kpts.orig_shape
+
+ kpts_xy = kpts.xy.cpu().numpy()
+ kpts_conf = kpts.conf.cpu().numpy() if kpts.conf is not None else np.ones(kpts_xy.shape[:2])
+
+ img_with_kpts = img.copy()
+
+ img_with_kpts, offset_results = draw_keypoints_and_offset(
+ img_with_kpts, kpts_xy, kpts_conf, orig_shape,
+ fixed_point=FIXED_REF_POINT,
+ scale_x=SCALE_X, scale_y=SCALE_Y
+ )
+
+ for info in offset_results:
+ print(f" 📌 实例 {info['instance']}: "
+ f"ΔX={info['dx_mm']:+.2f}mm, ΔY={info['dy_mm']:+.2f}mm")
+
+ save_filename = f"offset_{os.path.basename(IMAGE_PATH)}"
+ save_path = os.path.join(OUTPUT_DIR, save_filename)
+ cv2.imwrite(save_path, img_with_kpts)
+ print(f" 💾 结果已保存: {save_path}")
\ No newline at end of file
diff --git a/ailai_pc/inference_results/1.jpg b/ailai_pc/inference_results/1.jpg
new file mode 100644
index 0000000..99053bd
Binary files /dev/null and b/ailai_pc/inference_results/1.jpg differ
diff --git a/ailai_pc/inference_results/2.jpg b/ailai_pc/inference_results/2.jpg
new file mode 100644
index 0000000..a3c9e66
Binary files /dev/null and b/ailai_pc/inference_results/2.jpg differ
diff --git a/ailai_pc/inference_results/3.jpg b/ailai_pc/inference_results/3.jpg
new file mode 100644
index 0000000..453ba0d
Binary files /dev/null and b/ailai_pc/inference_results/3.jpg differ
diff --git a/ailai_pc/inference_results/4.jpg b/ailai_pc/inference_results/4.jpg
new file mode 100644
index 0000000..c329c84
Binary files /dev/null and b/ailai_pc/inference_results/4.jpg differ
diff --git a/ailai_pc/inference_results/bag_center.txt b/ailai_pc/inference_results/bag_center.txt
new file mode 100644
index 0000000..664d82b
--- /dev/null
+++ b/ailai_pc/inference_results/bag_center.txt
@@ -0,0 +1 @@
+617.1340942382812 225.45236206054688
diff --git a/ailai_pc/inference_results/ban_center.txt b/ailai_pc/inference_results/ban_center.txt
new file mode 100644
index 0000000..664d82b
--- /dev/null
+++ b/ailai_pc/inference_results/ban_center.txt
@@ -0,0 +1 @@
+617.1340942382812 225.45236206054688
diff --git a/ailai_pc/inference_results/best_center_vis_2.jpg b/ailai_pc/inference_results/best_center_vis_2.jpg
new file mode 100644
index 0000000..8a63055
Binary files /dev/null and b/ailai_pc/inference_results/best_center_vis_2.jpg differ
diff --git a/ailai_pc/output_images/offset_1.jpg b/ailai_pc/output_images/offset_1.jpg
new file mode 100644
index 0000000..5899316
Binary files /dev/null and b/ailai_pc/output_images/offset_1.jpg differ
diff --git a/ailai_pc/point.pt b/ailai_pc/point.pt
new file mode 100644
index 0000000..133e9fe
Binary files /dev/null and b/ailai_pc/point.pt differ
diff --git a/ailai_pc/point_test.py b/ailai_pc/point_test.py
new file mode 100644
index 0000000..1793fcb
--- /dev/null
+++ b/ailai_pc/point_test.py
@@ -0,0 +1,129 @@
+import cv2
+import numpy as np
+from ultralytics import YOLO
+import os
+
+# ====================== 用户配置 ======================
+MODEL_PATH = 'best.pt'
+IMAGE_SOURCE_DIR = './train' # 👈 修改为你的图像文件夹路径
+OUTPUT_DIR = './output_images' # 保存结果的文件夹
+
+# 支持的图像扩展名
+IMG_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.bmp', '.tiff', '.tif', '.webp'}
+
+os.makedirs(OUTPUT_DIR, exist_ok=True)
+
+# ====================== 可视化函数 ======================
+def draw_keypoints_on_image(image, kpts_xy, kpts_conf, orig_shape):
+ """
+ 在图像上绘制关键点
+ :param image: OpenCV 图像
+ :param kpts_xy: (N, K, 2) 坐标
+ :param kpts_conf: (N, K) 置信度
+ :param orig_shape: 原图尺寸 (H, W)
+ """
+ colors = [(0, 0, 255), (255, 0, 0), (0, 255, 0), (255, 255, 0)] # 1红, 2蓝, 3绿, 4青
+
+ for i in range(len(kpts_xy)):
+ xy = kpts_xy[i] # (4, 2)
+ conf = kpts_conf[i] if kpts_conf.ndim == 2 else kpts_conf[i:i+1] # (4,) 或标量
+
+ for j in range(len(xy)):
+ x, y = xy[j]
+ c = conf[j] if hasattr(conf, '__len__') else conf
+
+ x, y = int(x), int(y)
+
+ # 检查坐标是否在图像范围内
+ if x < 0 or y < 0 or x >= orig_shape[1] or y >= orig_shape[0]:
+ continue
+
+ # 只绘制置信度 > 0.5 的点
+ if c < 0.5:
+ continue
+
+ # 绘制实心圆
+ cv2.circle(image, (x, y), radius=15, color=colors[j], thickness=-1)
+ # 标注编号(偏移避免遮挡)
+ cv2.putText(image, f'{j+1}', (x + 20, y - 20),
+ cv2.FONT_HERSHEY_SIMPLEX, 1.5, colors[j], 5)
+
+ return image
+
+
+# ====================== 主程序 ======================
+if __name__ == "__main__":
+ print("🚀 开始批量关键点检测任务")
+
+ # 加载模型
+ print("🔄 加载 YOLO 模型...")
+ model = YOLO(MODEL_PATH)
+ print(f"✅ 模型加载完成: {MODEL_PATH}")
+
+ # 获取所有图像文件
+ image_files = [
+ f for f in os.listdir(IMAGE_SOURCE_DIR)
+ if os.path.splitext(f.lower())[1] in IMG_EXTENSIONS
+ ]
+
+ if not image_files:
+ print(f"❌ 错误:在 {IMAGE_SOURCE_DIR} 中未找到支持的图像文件")
+ exit(1)
+
+ print(f"📁 发现 {len(image_files)} 张图像待处理")
+
+ # 遍历每张图像
+ for img_filename in image_files:
+ img_path = os.path.join(IMAGE_SOURCE_DIR, img_filename)
+ print(f"\n🖼️ 正在处理: {img_filename}")
+
+ # 读取图像
+ img = cv2.imread(img_path)
+ if img is None:
+ print(f"❌ 无法读取图像,跳过: {img_path}")
+ continue
+ print(f" ✅ 图像加载成功 (shape: {img.shape})")
+
+ # 推理
+ print(" 🔍 正在推理...")
+ results = model(img)
+
+ processed = False # 标记是否处理了关键点
+
+ for i, result in enumerate(results):
+ if result.keypoints is not None:
+ kpts = result.keypoints
+ orig_shape = kpts.orig_shape # (H, W)
+
+ # 获取坐标和置信度
+ kpts_xy = kpts.xy.cpu().numpy() # (N, K, 2)
+ kpts_conf = kpts.conf.cpu().numpy() if kpts.conf is not None else np.ones(kpts_xy.shape[:2])
+
+ print(f" ✅ 检测到 {len(kpts_xy)} 个实例")
+
+ # 绘制关键点
+ img_with_kpts = draw_keypoints_on_image(img.copy(), kpts_xy, kpts_conf, orig_shape)
+
+ # 保存图像
+ save_filename = f"keypoints_{img_filename}"
+ save_path = os.path.join(OUTPUT_DIR, save_filename)
+ cv2.imwrite(save_path, img_with_kpts)
+ print(f" 💾 结果已保存: {save_path}")
+
+ # 可选:显示图像(每次一张,按任意键继续)
+ # display_img = cv2.resize(img_with_kpts, (1280, 720))
+ # cv2.imshow("Keypoints Detection", display_img)
+ # print(" ⌨️ 按任意键继续...")
+ # cv2.waitKey(0)
+ # cv2.destroyAllWindows()
+
+ processed = True
+
+ if not processed:
+ print(f" ❌ 未检测到关键点,跳过保存")
+
+ print("\n" + "=" * 60)
+ print("🎉 批量推理完成!")
+ print(f"📊 总共处理 {len(image_files)} 张图像")
+ print(f"📁 结果保存在: {OUTPUT_DIR}")
+ print("=" * 60)
\ No newline at end of file
diff --git a/ailai_pc/rename_file.py b/ailai_pc/rename_file.py
new file mode 100644
index 0000000..57d4f5a
--- /dev/null
+++ b/ailai_pc/rename_file.py
@@ -0,0 +1,40 @@
+import os
+import shutil
+
+# ================= 用户配置 =================
+FOLDER_PATH = '/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/point2' # 图片和 txt 所在文件夹
+IMG_EXT = '.jpg'
+TXT_EXT = '.txt'
+START_NUM = 1 # 从 1 开始编号
+
+# ================= 获取文件列表 =================
+files = os.listdir(FOLDER_PATH)
+
+# 分别筛选图片和 txt
+images = sorted([f for f in files if f.lower().endswith(IMG_EXT)])
+txts = sorted([f for f in files if f.lower().endswith(TXT_EXT)])
+
+# 检查数量是否一致
+if len(images) != len(txts):
+ print(f"⚠️ 图片数量 ({len(images)}) 与 txt 文件数量 ({len(txts)}) 不一致!")
+
+# ================= 重命名 =================
+for idx, (img_file, txt_file) in enumerate(zip(images, txts), start=START_NUM):
+ img_new = f"{idx}{IMG_EXT}"
+ txt_new = f"{idx}{TXT_EXT}"
+
+ # 原始完整路径
+ img_path = os.path.join(FOLDER_PATH, img_file)
+ txt_path = os.path.join(FOLDER_PATH, txt_file)
+
+ # 新路径
+ img_new_path = os.path.join(FOLDER_PATH, img_new)
+ txt_new_path = os.path.join(FOLDER_PATH, txt_new)
+
+ # 重命名(如果已经存在则覆盖)
+ shutil.move(img_path, img_new_path)
+ shutil.move(txt_path, txt_new_path)
+
+ print(f"✅ {img_file} -> {img_new}, {txt_file} -> {txt_new}")
+
+print("🎉 全部文件重命名完成!")
diff --git a/ailai_pc/rename_image.py b/ailai_pc/rename_image.py
new file mode 100644
index 0000000..6f800bb
--- /dev/null
+++ b/ailai_pc/rename_image.py
@@ -0,0 +1,56 @@
+import os
+from pathlib import Path
+
+def rename_images_in_folder(folder_path):
+ # 支持的图片扩展名(不区分大小写)
+ image_extensions = {'.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff', '.webp'}
+
+ # 转换为 Path 对象
+ folder = Path(folder_path)
+
+ # 检查文件夹是否存在
+ if not folder.exists():
+ print(f"❌ 文件夹不存在: {folder_path}")
+ return
+
+ if not folder.is_dir():
+ print(f"❌ 路径不是文件夹: {folder_path}")
+ return
+
+ # 获取所有图片文件
+ image_files = [f for f in folder.iterdir()
+ if f.is_file() and f.suffix.lower() in image_extensions]
+
+ if not image_files:
+ print("🔍 文件夹中没有找到图片文件。")
+ return
+
+ # 排序(按文件名排序,确保顺序一致)
+ image_files.sort()
+
+ print(f"📁 正在处理文件夹: {folder}")
+ print(f"🖼️ 找到 {len(image_files)} 个图片文件")
+
+ renamed_count = 0
+ for idx, file_path in enumerate(image_files, start=1):
+ new_name = f"{idx}.jpg" # 统一输出为 .jpg 格式
+ new_path = folder / new_name
+
+ # 防止覆盖已存在的目标文件
+ while new_path.exists():
+ print(f"⚠️ {new_name} 已存在,跳过或改名?")
+ # 可以选择跳过,或用不同逻辑处理
+ break
+ else:
+ file_path.rename(new_path)
+ print(f"✅ {file_path.name} → {new_name}")
+ renamed_count += 1
+
+ print(f"\n✅ 完成!共重命名 {renamed_count} 个文件。")
+
+# ===========================
+# 🔧 使用这里:设置你的文件夹路径
+# ===========================
+if __name__ == "__main__":
+ folder = r"/home/hx/下载/2025-09-24" # <-- 修改为你的图片文件夹路径
+ rename_images_in_folder(folder)
\ No newline at end of file
diff --git a/ailai_pc/train/192.168.0.234_01_20251014144144937.jpg b/ailai_pc/train/192.168.0.234_01_20251014144144937.jpg
new file mode 100644
index 0000000..9141761
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014144144937.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014144222222.jpg b/ailai_pc/train/192.168.0.234_01_20251014144222222.jpg
new file mode 100644
index 0000000..d8c323f
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014144222222.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014144303840.jpg b/ailai_pc/train/192.168.0.234_01_20251014144303840.jpg
new file mode 100644
index 0000000..2b40fc3
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014144303840.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014144341882.jpg b/ailai_pc/train/192.168.0.234_01_20251014144341882.jpg
new file mode 100644
index 0000000..4d981d8
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014144341882.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014144421259.jpg b/ailai_pc/train/192.168.0.234_01_20251014144421259.jpg
new file mode 100644
index 0000000..3f41d3d
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014144421259.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014144500393.jpg b/ailai_pc/train/192.168.0.234_01_20251014144500393.jpg
new file mode 100644
index 0000000..34d51dc
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014144500393.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014144538968.jpg b/ailai_pc/train/192.168.0.234_01_20251014144538968.jpg
new file mode 100644
index 0000000..ac86440
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014144538968.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014144619387.jpg b/ailai_pc/train/192.168.0.234_01_20251014144619387.jpg
new file mode 100644
index 0000000..686be48
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014144619387.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014144814974.jpg b/ailai_pc/train/192.168.0.234_01_20251014144814974.jpg
new file mode 100644
index 0000000..fded362
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014144814974.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014144929890.jpg b/ailai_pc/train/192.168.0.234_01_20251014144929890.jpg
new file mode 100644
index 0000000..0841f84
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014144929890.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014145006232.jpg b/ailai_pc/train/192.168.0.234_01_20251014145006232.jpg
new file mode 100644
index 0000000..a5b4f5b
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014145006232.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014145045330.jpg b/ailai_pc/train/192.168.0.234_01_20251014145045330.jpg
new file mode 100644
index 0000000..f74e1a4
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014145045330.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014145128164.jpg b/ailai_pc/train/192.168.0.234_01_20251014145128164.jpg
new file mode 100644
index 0000000..13a9fb0
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014145128164.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014145244471.jpg b/ailai_pc/train/192.168.0.234_01_20251014145244471.jpg
new file mode 100644
index 0000000..a231114
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014145244471.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014145321687.jpg b/ailai_pc/train/192.168.0.234_01_20251014145321687.jpg
new file mode 100644
index 0000000..322f64e
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014145321687.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014150210497.jpg b/ailai_pc/train/192.168.0.234_01_20251014150210497.jpg
new file mode 100644
index 0000000..e160439
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014150210497.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014150246105.jpg b/ailai_pc/train/192.168.0.234_01_20251014150246105.jpg
new file mode 100644
index 0000000..4632b44
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014150246105.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014150325231.jpg b/ailai_pc/train/192.168.0.234_01_20251014150325231.jpg
new file mode 100644
index 0000000..75dd03b
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014150325231.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014150406560.jpg b/ailai_pc/train/192.168.0.234_01_20251014150406560.jpg
new file mode 100644
index 0000000..3222f2f
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014150406560.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014150444617.jpg b/ailai_pc/train/192.168.0.234_01_20251014150444617.jpg
new file mode 100644
index 0000000..4db12d2
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014150444617.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_2025101415052359.jpg b/ailai_pc/train/192.168.0.234_01_2025101415052359.jpg
new file mode 100644
index 0000000..fd8a6cc
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_2025101415052359.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014150601333.jpg b/ailai_pc/train/192.168.0.234_01_20251014150601333.jpg
new file mode 100644
index 0000000..bdb9a8b
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014150601333.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014150642365.jpg b/ailai_pc/train/192.168.0.234_01_20251014150642365.jpg
new file mode 100644
index 0000000..9812532
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014150642365.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014150720686.jpg b/ailai_pc/train/192.168.0.234_01_20251014150720686.jpg
new file mode 100644
index 0000000..46490dd
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014150720686.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014150918102.jpg b/ailai_pc/train/192.168.0.234_01_20251014150918102.jpg
new file mode 100644
index 0000000..4d0d8d9
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014150918102.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_2025101415103290.jpg b/ailai_pc/train/192.168.0.234_01_2025101415103290.jpg
new file mode 100644
index 0000000..fa923d5
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_2025101415103290.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_2025101415111060.jpg b/ailai_pc/train/192.168.0.234_01_2025101415111060.jpg
new file mode 100644
index 0000000..26ada89
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_2025101415111060.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014151150768.jpg b/ailai_pc/train/192.168.0.234_01_20251014151150768.jpg
new file mode 100644
index 0000000..7be2d3a
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014151150768.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014151232368.jpg b/ailai_pc/train/192.168.0.234_01_20251014151232368.jpg
new file mode 100644
index 0000000..da51dd3
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014151232368.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014151311222.jpg b/ailai_pc/train/192.168.0.234_01_20251014151311222.jpg
new file mode 100644
index 0000000..0b0bfe2
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014151311222.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014151353357.jpg b/ailai_pc/train/192.168.0.234_01_20251014151353357.jpg
new file mode 100644
index 0000000..20a047d
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014151353357.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_202510141514352.jpg b/ailai_pc/train/192.168.0.234_01_202510141514352.jpg
new file mode 100644
index 0000000..e69faee
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_202510141514352.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_2025101415152183.jpg b/ailai_pc/train/192.168.0.234_01_2025101415152183.jpg
new file mode 100644
index 0000000..b44e534
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_2025101415152183.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014151922555.jpg b/ailai_pc/train/192.168.0.234_01_20251014151922555.jpg
new file mode 100644
index 0000000..f69e644
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014151922555.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014153132629.jpg b/ailai_pc/train/192.168.0.234_01_20251014153132629.jpg
new file mode 100644
index 0000000..ccf2994
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014153132629.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014153215762.jpg b/ailai_pc/train/192.168.0.234_01_20251014153215762.jpg
new file mode 100644
index 0000000..4dbcba7
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014153215762.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014153257985.jpg b/ailai_pc/train/192.168.0.234_01_20251014153257985.jpg
new file mode 100644
index 0000000..8f5c9d5
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014153257985.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014153338300.jpg b/ailai_pc/train/192.168.0.234_01_20251014153338300.jpg
new file mode 100644
index 0000000..a66d909
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014153338300.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014153420611.jpg b/ailai_pc/train/192.168.0.234_01_20251014153420611.jpg
new file mode 100644
index 0000000..4a04002
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014153420611.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014153506692.jpg b/ailai_pc/train/192.168.0.234_01_20251014153506692.jpg
new file mode 100644
index 0000000..1d5ca4f
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014153506692.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014153632704.jpg b/ailai_pc/train/192.168.0.234_01_20251014153632704.jpg
new file mode 100644
index 0000000..ee75f44
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014153632704.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014153800800.jpg b/ailai_pc/train/192.168.0.234_01_20251014153800800.jpg
new file mode 100644
index 0000000..7f7dfd2
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014153800800.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014153839977.jpg b/ailai_pc/train/192.168.0.234_01_20251014153839977.jpg
new file mode 100644
index 0000000..cd1f939
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014153839977.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014153918385.jpg b/ailai_pc/train/192.168.0.234_01_20251014153918385.jpg
new file mode 100644
index 0000000..f582908
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014153918385.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014153957167.jpg b/ailai_pc/train/192.168.0.234_01_20251014153957167.jpg
new file mode 100644
index 0000000..b43fa95
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014153957167.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014154037360.jpg b/ailai_pc/train/192.168.0.234_01_20251014154037360.jpg
new file mode 100644
index 0000000..3f30529
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014154037360.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014154121106.jpg b/ailai_pc/train/192.168.0.234_01_20251014154121106.jpg
new file mode 100644
index 0000000..ca82513
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014154121106.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014154203390.jpg b/ailai_pc/train/192.168.0.234_01_20251014154203390.jpg
new file mode 100644
index 0000000..87b6456
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014154203390.jpg differ
diff --git a/ailai_pc/train/192.168.0.234_01_20251014154328204.jpg b/ailai_pc/train/192.168.0.234_01_20251014154328204.jpg
new file mode 100644
index 0000000..35263bc
Binary files /dev/null and b/ailai_pc/train/192.168.0.234_01_20251014154328204.jpg differ
diff --git a/ailai_pc/trans_cvattoobb.py b/ailai_pc/trans_cvattoobb.py
new file mode 100644
index 0000000..e1758d2
--- /dev/null
+++ b/ailai_pc/trans_cvattoobb.py
@@ -0,0 +1,116 @@
+# pascal_robndbox_to_yolo_obb.py
+import xml.etree.ElementTree as ET
+import numpy as np
+from pathlib import Path
+import argparse
+
+def robndbox_to_yolo_obb(xml_path, output_dir, class_names):
+ """
+ 将单个带有 的 Pascal VOC XML 转换为 YOLO-OBB 格式 .txt
+ """
+ try:
+ tree = ET.parse(xml_path)
+ root = tree.getroot()
+
+ # 获取图像尺寸
+ width_elem = root.find("size/width")
+ height_elem = root.find("size/height")
+ if width_elem is None or height_elem is None:
+ print(f"❌ 跳过 {xml_path}: 缺少 size/width 或 size/height")
+ return
+ img_w = int(width_elem.text)
+ img_h = int(height_elem.text)
+
+ if img_w == 0 or img_h == 0:
+ print(f"❌ 跳过 {xml_path}: 图像尺寸为 0")
+ return
+
+ # 输出文件路径
+ label_file = Path(output_dir) / "labels" / (Path(xml_path).stem + ".txt")
+ label_file.parent.mkdir(parents=True, exist_ok=True)
+
+ lines = []
+ for obj in root.findall("object"):
+ name = obj.find("name").text
+ if name not in class_names:
+ print(f"⚠️ 跳过未知类别: {name} (文件: {xml_path.name})")
+ continue
+ class_id = class_names.index(name)
+
+ rb = obj.find("robndbox")
+ if rb is None:
+ print(f"⚠️ 跳过无 robndbox 的对象: {name}")
+ continue
+
+ cx = float(rb.find("cx").text)
+ cy = float(rb.find("cy").text)
+ w = float(rb.find("w").text)
+ h = float(rb.find("h").text)
+ angle_deg = float(rb.find("angle").text)
+
+ # 计算四个角点(相对于中心旋转)
+ angle_rad = np.radians(angle_deg)
+ cos_a, sin_a = np.cos(angle_rad), np.sin(angle_rad)
+
+ corners = np.array([
+ [-w/2, -h/2],
+ [ w/2, -h/2],
+ [ w/2, h/2],
+ [-w/2, h/2]
+ ])
+ rotation_matrix = np.array([[cos_a, -sin_a], [sin_a, cos_a]])
+ rotated_corners = np.dot(corners, rotation_matrix.T) + [cx, cy]
+
+ # 归一化到 [0,1]
+ rotated_corners[:, 0] /= img_w
+ rotated_corners[:, 1] /= img_h
+
+ # 展平并生成 YOLO-OBB 行
+ coords = rotated_corners.flatten()
+ line = str(class_id) + " " + " ".join(f"{x:.6f}" for x in coords)
+ lines.append(line)
+
+ # 只有存在有效标注才写入文件
+ if lines:
+ with open(label_file, "w", encoding="utf-8") as f:
+ f.write("\n".join(lines) + "\n")
+ print(f"✅ 已生成: {label_file}")
+ else:
+ print(f"🟡 无有效标注,跳过生成: {label_file}")
+
+ except Exception as e:
+ print(f"❌ 处理 {xml_path} 时出错: {e}")
+
+
+def main():
+ # ==================== 配置区 ====================
+ # ✅ 修改以下路径和类别
+ XML_DIR = "/home/hx/桌面/ailai_test/train" # 包含 .xml 文件的目录
+ OUTPUT_DIR = "yolo_obb_dataset" # 输出目录
+ CLASS_NAMES = ["ban", "bag"] # 你的类别列表,顺序即 class_id
+ # ==============================================
+
+ xml_dir = Path(XML_DIR)
+ output_dir = Path(OUTPUT_DIR)
+
+ if not xml_dir.exists():
+ raise FileNotFoundError(f"未找到 XML 目录: {xml_dir}")
+
+ # 查找所有 .xml 文件
+ xml_files = list(xml_dir.glob("*.xml"))
+ if not xml_files:
+ print(f"⚠️ 在 {xml_dir} 中未找到 .xml 文件")
+ return
+
+ print(f"🔍 找到 {len(xml_files)} 个 XML 文件")
+ print(f"📦 类别映射: { {i: name for i, name in enumerate(CLASS_NAMES)} }")
+
+ # 批量转换
+ for xml_file in xml_files:
+ robndbox_to_yolo_obb(xml_file, output_dir, CLASS_NAMES)
+
+ print(f"\n🎉 转换完成!标签已保存至: {output_dir / 'labels'}")
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train.cache b/ailai_pc/yolo_obb_dataset/ailai_test/train.cache
new file mode 100644
index 0000000..6f9abf6
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test/train.cache differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/1.jpg b/ailai_pc/yolo_obb_dataset/ailai_test/train/1.jpg
new file mode 100644
index 0000000..b6a6c2d
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test/train/1.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/1.txt b/ailai_pc/yolo_obb_dataset/ailai_test/train/1.txt
new file mode 100644
index 0000000..d368f2e
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test/train/1.txt
@@ -0,0 +1,2 @@
+0 0.287799 0.126397 0.708264 0.166508 0.698037 0.505318 0.277572 0.465207
+1 0.405951 0.167969 0.626046 0.189171 0.618472 0.437663 0.398377 0.416461
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/10.jpg b/ailai_pc/yolo_obb_dataset/ailai_test/train/10.jpg
new file mode 100644
index 0000000..b6a6c2d
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test/train/10.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/10.txt b/ailai_pc/yolo_obb_dataset/ailai_test/train/10.txt
new file mode 100644
index 0000000..d368f2e
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test/train/10.txt
@@ -0,0 +1,2 @@
+0 0.287799 0.126397 0.708264 0.166508 0.698037 0.505318 0.277572 0.465207
+1 0.405951 0.167969 0.626046 0.189171 0.618472 0.437663 0.398377 0.416461
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/11.jpg b/ailai_pc/yolo_obb_dataset/ailai_test/train/11.jpg
new file mode 100644
index 0000000..b6a6c2d
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test/train/11.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/11.txt b/ailai_pc/yolo_obb_dataset/ailai_test/train/11.txt
new file mode 100644
index 0000000..d368f2e
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test/train/11.txt
@@ -0,0 +1,2 @@
+0 0.287799 0.126397 0.708264 0.166508 0.698037 0.505318 0.277572 0.465207
+1 0.405951 0.167969 0.626046 0.189171 0.618472 0.437663 0.398377 0.416461
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/12.jpg b/ailai_pc/yolo_obb_dataset/ailai_test/train/12.jpg
new file mode 100644
index 0000000..b6a6c2d
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test/train/12.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/12.txt b/ailai_pc/yolo_obb_dataset/ailai_test/train/12.txt
new file mode 100644
index 0000000..d368f2e
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test/train/12.txt
@@ -0,0 +1,2 @@
+0 0.287799 0.126397 0.708264 0.166508 0.698037 0.505318 0.277572 0.465207
+1 0.405951 0.167969 0.626046 0.189171 0.618472 0.437663 0.398377 0.416461
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/2.jpg b/ailai_pc/yolo_obb_dataset/ailai_test/train/2.jpg
new file mode 100644
index 0000000..b6a6c2d
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test/train/2.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/2.txt b/ailai_pc/yolo_obb_dataset/ailai_test/train/2.txt
new file mode 100644
index 0000000..d368f2e
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test/train/2.txt
@@ -0,0 +1,2 @@
+0 0.287799 0.126397 0.708264 0.166508 0.698037 0.505318 0.277572 0.465207
+1 0.405951 0.167969 0.626046 0.189171 0.618472 0.437663 0.398377 0.416461
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/3.jpg b/ailai_pc/yolo_obb_dataset/ailai_test/train/3.jpg
new file mode 100644
index 0000000..b6a6c2d
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test/train/3.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/3.txt b/ailai_pc/yolo_obb_dataset/ailai_test/train/3.txt
new file mode 100644
index 0000000..d368f2e
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test/train/3.txt
@@ -0,0 +1,2 @@
+0 0.287799 0.126397 0.708264 0.166508 0.698037 0.505318 0.277572 0.465207
+1 0.405951 0.167969 0.626046 0.189171 0.618472 0.437663 0.398377 0.416461
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/4.jpg b/ailai_pc/yolo_obb_dataset/ailai_test/train/4.jpg
new file mode 100644
index 0000000..b6a6c2d
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test/train/4.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/4.txt b/ailai_pc/yolo_obb_dataset/ailai_test/train/4.txt
new file mode 100644
index 0000000..d368f2e
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test/train/4.txt
@@ -0,0 +1,2 @@
+0 0.287799 0.126397 0.708264 0.166508 0.698037 0.505318 0.277572 0.465207
+1 0.405951 0.167969 0.626046 0.189171 0.618472 0.437663 0.398377 0.416461
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/5.jpg b/ailai_pc/yolo_obb_dataset/ailai_test/train/5.jpg
new file mode 100644
index 0000000..b6a6c2d
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test/train/5.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/5.txt b/ailai_pc/yolo_obb_dataset/ailai_test/train/5.txt
new file mode 100644
index 0000000..d368f2e
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test/train/5.txt
@@ -0,0 +1,2 @@
+0 0.287799 0.126397 0.708264 0.166508 0.698037 0.505318 0.277572 0.465207
+1 0.405951 0.167969 0.626046 0.189171 0.618472 0.437663 0.398377 0.416461
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/6.jpg b/ailai_pc/yolo_obb_dataset/ailai_test/train/6.jpg
new file mode 100644
index 0000000..b6a6c2d
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test/train/6.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/6.txt b/ailai_pc/yolo_obb_dataset/ailai_test/train/6.txt
new file mode 100644
index 0000000..d368f2e
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test/train/6.txt
@@ -0,0 +1,2 @@
+0 0.287799 0.126397 0.708264 0.166508 0.698037 0.505318 0.277572 0.465207
+1 0.405951 0.167969 0.626046 0.189171 0.618472 0.437663 0.398377 0.416461
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/7.jpg b/ailai_pc/yolo_obb_dataset/ailai_test/train/7.jpg
new file mode 100644
index 0000000..b6a6c2d
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test/train/7.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/7.txt b/ailai_pc/yolo_obb_dataset/ailai_test/train/7.txt
new file mode 100644
index 0000000..d368f2e
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test/train/7.txt
@@ -0,0 +1,2 @@
+0 0.287799 0.126397 0.708264 0.166508 0.698037 0.505318 0.277572 0.465207
+1 0.405951 0.167969 0.626046 0.189171 0.618472 0.437663 0.398377 0.416461
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/8.jpg b/ailai_pc/yolo_obb_dataset/ailai_test/train/8.jpg
new file mode 100644
index 0000000..b6a6c2d
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test/train/8.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/8.txt b/ailai_pc/yolo_obb_dataset/ailai_test/train/8.txt
new file mode 100644
index 0000000..d368f2e
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test/train/8.txt
@@ -0,0 +1,2 @@
+0 0.287799 0.126397 0.708264 0.166508 0.698037 0.505318 0.277572 0.465207
+1 0.405951 0.167969 0.626046 0.189171 0.618472 0.437663 0.398377 0.416461
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/9.jpg b/ailai_pc/yolo_obb_dataset/ailai_test/train/9.jpg
new file mode 100644
index 0000000..b6a6c2d
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test/train/9.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/train/9.txt b/ailai_pc/yolo_obb_dataset/ailai_test/train/9.txt
new file mode 100644
index 0000000..d368f2e
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test/train/9.txt
@@ -0,0 +1,2 @@
+0 0.287799 0.126397 0.708264 0.166508 0.698037 0.505318 0.277572 0.465207
+1 0.405951 0.167969 0.626046 0.189171 0.618472 0.437663 0.398377 0.416461
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/val.cache b/ailai_pc/yolo_obb_dataset/ailai_test/val.cache
new file mode 100644
index 0000000..cb0f535
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test/val.cache differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/val/1.jpg b/ailai_pc/yolo_obb_dataset/ailai_test/val/1.jpg
new file mode 100644
index 0000000..b6a6c2d
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test/val/1.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test/val/1.txt b/ailai_pc/yolo_obb_dataset/ailai_test/val/1.txt
new file mode 100644
index 0000000..d368f2e
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test/val/1.txt
@@ -0,0 +1,2 @@
+0 0.287799 0.126397 0.708264 0.166508 0.698037 0.505318 0.277572 0.465207
+1 0.405951 0.167969 0.626046 0.189171 0.618472 0.437663 0.398377 0.416461
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train.cache b/ailai_pc/yolo_obb_dataset/ailai_test1/train.cache
new file mode 100644
index 0000000..6b081bd
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train.cache differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/093615_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/train/093615_Color.jpg
new file mode 100644
index 0000000..914507d
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train/093615_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/093615_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/train/093615_Color.txt
new file mode 100644
index 0000000..01f79ba
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/train/093615_Color.txt
@@ -0,0 +1 @@
+0 0.284333 0.178383 0.700034 0.122815 0.713823 0.448839 0.298122 0.504407
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094130_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094130_Color.jpg
new file mode 100644
index 0000000..5065644
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094130_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094130_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094130_Color.txt
new file mode 100644
index 0000000..bb1a8b5
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094130_Color.txt
@@ -0,0 +1,2 @@
+0 0.279006 0.177951 0.698267 0.121908 0.712393 0.455882 0.293131 0.511925
+1 0.322506 0.193800 0.549706 0.169096 0.558525 0.425436 0.331325 0.450140
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094244_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094244_Color.jpg
new file mode 100644
index 0000000..3562784
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094244_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094244_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094244_Color.txt
new file mode 100644
index 0000000..37781b2
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094244_Color.txt
@@ -0,0 +1,2 @@
+0 0.281420 0.175105 0.700689 0.119060 0.714815 0.453034 0.295545 0.509079
+1 0.375245 0.190536 0.612882 0.166916 0.620872 0.420992 0.383235 0.444611
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094316_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094316_Color.jpg
new file mode 100644
index 0000000..0e9cfb4
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094316_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094316_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094316_Color.txt
new file mode 100644
index 0000000..b423905
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094316_Color.txt
@@ -0,0 +1,2 @@
+0 0.281888 0.173424 0.701158 0.117380 0.715284 0.451367 0.296014 0.507412
+1 0.467663 0.181701 0.704471 0.159638 0.711766 0.407104 0.474958 0.429167
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094348_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094348_Color.jpg
new file mode 100644
index 0000000..556ea78
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094348_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094348_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094348_Color.txt
new file mode 100644
index 0000000..97d53f7
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094348_Color.txt
@@ -0,0 +1,2 @@
+0 0.280474 0.176785 0.699744 0.120741 0.713869 0.454715 0.294600 0.510759
+1 0.368151 0.196284 0.603445 0.153057 0.618380 0.409980 0.383086 0.453207
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094401_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094401_Color.jpg
new file mode 100644
index 0000000..629adfa
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094401_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094401_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094401_Color.txt
new file mode 100644
index 0000000..615c383
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094401_Color.txt
@@ -0,0 +1,2 @@
+0 0.282777 0.174770 0.702198 0.122630 0.715340 0.456730 0.295919 0.508870
+1 0.349052 0.244794 0.583194 0.147929 0.616128 0.399526 0.381985 0.496391
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094411_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094411_Color.jpg
new file mode 100644
index 0000000..f56dccb
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094411_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094411_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094411_Color.txt
new file mode 100644
index 0000000..6bf9e04
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094411_Color.txt
@@ -0,0 +1,2 @@
+0 0.281419 0.178452 0.700689 0.122407 0.714815 0.456395 0.295545 0.512440
+1 0.346724 0.185278 0.578903 0.181676 0.580135 0.432750 0.347957 0.436352
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094419_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094419_Color.jpg
new file mode 100644
index 0000000..c4742cf
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094419_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094419_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094419_Color.txt
new file mode 100644
index 0000000..2e10efb
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094419_Color.txt
@@ -0,0 +1,2 @@
+0 0.282639 0.171298 0.702613 0.123016 0.714854 0.459522 0.294879 0.507803
+1 0.315949 0.253618 0.553513 0.164618 0.582816 0.411827 0.345253 0.500827
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094441_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094441_Color.jpg
new file mode 100644
index 0000000..de1561e
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094441_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094441_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094441_Color.txt
new file mode 100644
index 0000000..7693340
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094441_Color.txt
@@ -0,0 +1,3 @@
+0 0.281888 0.176785 0.701158 0.120741 0.715283 0.454715 0.296014 0.510759
+1 0.479766 0.170000 0.715750 0.170000 0.715750 0.424625 0.479766 0.424625
+
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094455_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094455_Color.jpg
new file mode 100644
index 0000000..c5fde51
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094455_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094455_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094455_Color.txt
new file mode 100644
index 0000000..2cbab79
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094455_Color.txt
@@ -0,0 +1,2 @@
+0 0.282365 0.177618 0.701627 0.121575 0.715752 0.455549 0.296490 0.511592
+1 0.443906 0.168333 0.684656 0.168333 0.684656 0.423778 0.443906 0.423778
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094519_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094519_Color.jpg
new file mode 100644
index 0000000..e1749d0
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094519_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094519_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094519_Color.txt
new file mode 100644
index 0000000..ca11c60
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094519_Color.txt
@@ -0,0 +1,2 @@
+0 0.282365 0.176785 0.701627 0.120741 0.715752 0.454715 0.296490 0.510759
+1 0.412803 0.229630 0.645321 0.152271 0.671673 0.402606 0.439156 0.479965
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094531_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094531_Color.jpg
new file mode 100644
index 0000000..e36966c
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094531_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094531_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094531_Color.txt
new file mode 100644
index 0000000..c3666c0
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094531_Color.txt
@@ -0,0 +1,2 @@
+0 0.282834 0.174271 0.702096 0.118227 0.716221 0.452201 0.296959 0.508245
+1 0.404539 0.156370 0.640439 0.174681 0.634117 0.432116 0.398217 0.413805
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094544_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094544_Color.jpg
new file mode 100644
index 0000000..a1c564f
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094544_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094544_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094544_Color.txt
new file mode 100644
index 0000000..346ef8e
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094544_Color.txt
@@ -0,0 +1,2 @@
+0 0.282365 0.176785 0.701627 0.120741 0.715752 0.454715 0.296490 0.510759
+1 0.375077 0.248962 0.608783 0.159891 0.637782 0.400372 0.404077 0.489442
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094551_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094551_Color.jpg
new file mode 100644
index 0000000..52a9e44
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094551_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094551_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094551_Color.txt
new file mode 100644
index 0000000..eb76335
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094551_Color.txt
@@ -0,0 +1,2 @@
+0 0.281420 0.176785 0.700689 0.120741 0.714815 0.454715 0.295545 0.510759
+1 0.353459 0.194723 0.603042 0.175350 0.609220 0.426930 0.359638 0.446303
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094558_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094558_Color.jpg
new file mode 100644
index 0000000..92a6411
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094558_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094558_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094558_Color.txt
new file mode 100644
index 0000000..ff99710
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094558_Color.txt
@@ -0,0 +1,2 @@
+0 0.281419 0.178452 0.700689 0.122407 0.714815 0.456395 0.295545 0.512440
+1 0.335065 0.209078 0.568178 0.179372 0.578302 0.430478 0.345190 0.460184
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094607_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094607_Color.jpg
new file mode 100644
index 0000000..e914314
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094607_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094607_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094607_Color.txt
new file mode 100644
index 0000000..80f8f55
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094607_Color.txt
@@ -0,0 +1,2 @@
+0 0.282365 0.175938 0.701627 0.119894 0.715752 0.453882 0.296491 0.509925
+1 0.334730 0.193012 0.569811 0.201766 0.566825 0.455155 0.331744 0.446401
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094647_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094647_Color.jpg
new file mode 100644
index 0000000..bcde7ec
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094647_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094647_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094647_Color.txt
new file mode 100644
index 0000000..e0eb305
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094647_Color.txt
@@ -0,0 +1,2 @@
+0 0.280006 0.175105 0.699275 0.119060 0.713401 0.453034 0.294131 0.509079
+1 0.349167 0.186040 0.595058 0.192907 0.592849 0.442904 0.346958 0.436037
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094656_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094656_Color.jpg
new file mode 100644
index 0000000..7ed94ba
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094656_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/train/094656_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094656_Color.txt
new file mode 100644
index 0000000..f8a283b
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/train/094656_Color.txt
@@ -0,0 +1,2 @@
+0 0.280006 0.175105 0.699275 0.119060 0.713401 0.453034 0.294131 0.509079
+1 0.315828 0.253615 0.568866 0.167803 0.595321 0.414343 0.342282 0.500156
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/val.cache b/ailai_pc/yolo_obb_dataset/ailai_test1/val.cache
new file mode 100644
index 0000000..f9f0e0d
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/val.cache differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/val/094615_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/val/094615_Color.jpg
new file mode 100644
index 0000000..8337628
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/val/094615_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/val/094615_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/val/094615_Color.txt
new file mode 100644
index 0000000..e000e0b
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/val/094615_Color.txt
@@ -0,0 +1,2 @@
+0 0.280951 0.174271 0.700213 0.118227 0.714338 0.452201 0.295076 0.508245
+1 0.282802 0.194126 0.538176 0.192542 0.538667 0.442582 0.283293 0.444167
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/val/094633_Color.jpg b/ailai_pc/yolo_obb_dataset/ailai_test1/val/094633_Color.jpg
new file mode 100644
index 0000000..cc364ad
Binary files /dev/null and b/ailai_pc/yolo_obb_dataset/ailai_test1/val/094633_Color.jpg differ
diff --git a/ailai_pc/yolo_obb_dataset/ailai_test1/val/094633_Color.txt b/ailai_pc/yolo_obb_dataset/ailai_test1/val/094633_Color.txt
new file mode 100644
index 0000000..168d739
--- /dev/null
+++ b/ailai_pc/yolo_obb_dataset/ailai_test1/val/094633_Color.txt
@@ -0,0 +1,2 @@
+0 0.282365 0.175104 0.701627 0.119061 0.715752 0.453035 0.296490 0.509078
+1 0.342378 0.187129 0.589708 0.182524 0.591215 0.438357 0.343885 0.442962
diff --git a/main/README.md b/main/README.md
new file mode 100644
index 0000000..e53edcc
--- /dev/null
+++ b/main/README.md
@@ -0,0 +1,54 @@
+# RKNN 关键点推理与偏移量计算工具
+
+该工具通过使用RKNN模型对输入图像进行关键点检测,并根据检测结果计算相对于固定参考点的偏移量(单位:毫米)。此外,还提供了可视化选项来展示计算结果。
+
+## 目录结构
+
+├── calculate_offset.py # 主程序脚本
+├── point.rknn # RKNN 模型文件 (请确保正确路径)
+└── README.md # 说明文档
+
+## 配置
+
+在 `calculate_offset.py` 文件顶部的配置区中,您可以修改如下参数以适应您的需求:
+
+- **MODEL_PATH**: RKNN 模型文件路径。
+- **OUTPUT_DIR**: 输出目录路径。
+- **FIXED_REF_POINT**: 固定参考点坐标(像素)。
+- **SCALE_X**, **SCALE_Y**: 缩放因子,用于将像素坐标转换为毫米。
+- **IMG_SIZE**: 输入图像尺寸。
+
+## 安装依赖
+
+请确保安装了必要的 Python 库。可以通过 pip 安装:
+
+```bash
+pip install opencv-python numpy rknnlite
+```
+
+## 函数调用
+
+您也可以直接调用 calculate_offset_from_image 函数,以便集成到其他项目中:
+示例 1: 仅获取偏移量(不画图)
+**
+from calculate_offset import calculate_offset_from_image
+result = calculate_offset_from_image("your_image_path.jpg", visualize=False)
+if result['success']:
+ print(f"Offset: DeltaX={result['dx_mm']:+.2f} mm, DeltaY={result['dy_mm']:+.2f} mm")
+else:
+ print("Error:", result['message'])
+**
+示例 2: 获取偏移量并保存可视化图
+**
+from calculate_offset import calculate_offset_from_image
+result = calculate_offset_from_image("your_image_path.jpg", visualize=True)
+**
+
+该函数返回一个包含下列字段的字典:
+
+ success: 成功标志(True/False)
+ dx_mm: 水平偏移(毫米)
+ dy_mm: 垂直偏移(毫米)
+ cx: 中心点 x 坐标(像素)
+ cy: 中心点 y 坐标(像素)
+ message: 错误信息或成功提示
diff --git a/main/calculate_diff.py b/main/calculate_diff.py
new file mode 100644
index 0000000..98ff4bd
--- /dev/null
+++ b/main/calculate_diff.py
@@ -0,0 +1,230 @@
+import cv2
+import numpy as np
+import os
+from rknnlite.api import RKNNLite
+
+# ====================== 配置区 ======================
+MODEL_PATH = "point.rknn"
+OUTPUT_DIR = "./output_rknn"
+os.makedirs(OUTPUT_DIR, exist_ok=True)
+
+# 固定参考点(像素坐标)
+FIXED_REF_POINT = (535, 605)
+
+# mm/px 缩放因子(根据标定数据填写)
+width_mm = 70.0
+width_px = 42
+SCALE_X = width_mm / float(width_px)
+height_mm = 890.0
+height_px = 507
+SCALE_Y = height_mm / float(height_px)
+print(f"Scale factors: SCALE_X={SCALE_X:.3f} mm/px, SCALE_Y={SCALE_Y:.3f} mm/px")
+
+# 输入尺寸
+IMG_SIZE = (640, 640)
+
+
+def letterbox_resize(image, size, bg_color=114):
+ """保持比例缩放并填充到指定大小"""
+ target_w, target_h = size
+ h, w = image.shape[:2]
+ scale = min(target_w / w, target_h / h)
+ new_w, new_h = int(w * scale), int(h * scale)
+ resized = cv2.resize(image, (new_w, new_h))
+ canvas = np.full((target_h, target_w, 3), bg_color, dtype=np.uint8)
+ dx, dy = (target_w - new_w) // 2, (target_h - new_h) // 2
+ canvas[dy:dy + new_h, dx:dx + new_w] = resized
+ return canvas, scale, dx, dy
+
+
+def safe_sigmoid(x):
+ x = np.clip(x, -50, 50)
+ return 1.0 / (1.0 + np.exp(-x))
+
+
+def parse_pose_outputs(outputs, dx=0, dy=0, scale=1.0):
+ """
+ 解析 RKNN YOLO-Pose 关键点输出
+ outputs[3]: shape (1, 4, 3, 8400) -> [kpt_id, (x,y,conf), anchor]
+ """
+ kpt_output = np.array(outputs[3])[0] # (4, 3, 8400)
+ confs = kpt_output[:, 2, :] # 取每个关键点的 visible_conf
+ mean_conf_per_anchor = np.mean(confs, axis=0) # 每个 anchor 的平均可见性
+ best_anchor_idx = np.argmax(mean_conf_per_anchor)
+ kpt_data = kpt_output[:, :, best_anchor_idx] # (4, 3): x, y, vis_conf
+
+ keypoints = []
+ for i in range(4):
+ x_img = kpt_data[i, 0]
+ y_img = kpt_data[i, 1]
+ vis_conf_raw = kpt_data[i, 2]
+ vis_prob = safe_sigmoid(vis_conf_raw)
+
+ # 映射回原图坐标
+ x_orig = (x_img - dx) / scale
+ y_orig = (y_img - dy) / scale
+ keypoints.append([x_orig, y_orig, vis_prob])
+
+ return np.array(keypoints)
+
+
+def compute_offset(keypoints, fixed_point, scale_x, scale_y):
+ """
+ 计算中心点相对于固定参考点的偏移量(mm)
+ 中心点 = P0 和 P1 的中点
+ 返回: (center_x, center_y, dx_mm, dy_mm)
+ """
+ if len(keypoints) < 2:
+ return None
+
+ p1, p2 = keypoints[0], keypoints[1]
+ cx = (p1[0] + p2[0]) / 2.0
+ cy = (p1[1] + p2[1]) / 2.0
+
+ dx_px = cx - fixed_point[0]
+ dy_px = cy - fixed_point[1]
+ dx_mm = dx_px * scale_x
+ dy_mm = dy_px * scale_y
+
+ return cx, cy, dx_mm, dy_mm
+
+
+def visualize_result(image, keypoints, fixed_point, offset_info, save_path):
+ """
+ 可视化关键点、参考点、中心点、偏移箭头和文字
+ """
+ vis = image.copy()
+ colors = [(0, 0, 255), (255, 0, 0), (0, 255, 0), (255, 255, 0)]
+ cx, cy, dx_mm, dy_mm = offset_info
+ fx, fy = map(int, fixed_point)
+
+ # 绘制关键点
+ for i, (x, y, conf) in enumerate(keypoints):
+ if conf > 0.5:
+ cv2.circle(vis, (int(x), int(y)), 8, colors[i], -1)
+ cv2.putText(vis, f"P{i}", (int(x) + 10, int(y) - 10),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.8, colors[i], 2)
+
+ # 绘制中心点
+ cv2.circle(vis, (int(cx), int(cy)), 12, (0, 255, 0), 3)
+ cv2.putText(vis, "Center", (int(cx) + 20, int(cy)),
+ cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 3)
+
+ # 绘制参考点
+ cv2.circle(vis, (fx, fy), 15, (255, 255, 0), 3)
+ cv2.putText(vis, "Ref", (fx + 20, fy),
+ cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 0), 3)
+
+ # 绘制偏移箭头和文字
+ cv2.arrowedLine(vis, (fx, fy), (int(cx), int(cy)), (0, 255, 255), 3, tipLength=0.05)
+ cv2.putText(vis, f"DeltaX={dx_mm:+.1f}mm", (fx + 40, fy - 40),
+ cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 255), 3)
+ cv2.putText(vis, f"DeltaY={dy_mm:+.1f}mm", (fx + 40, fy + 40),
+ cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 255), 3)
+
+ cv2.imwrite(save_path, vis)
+
+
+def calculate_offset_from_image(image_path, visualize=False):
+ """
+ 主函数:输入图片路径,输出偏移量 (dx_mm, dy_mm)
+
+ 参数:
+ image_path (str): 输入图像路径
+ visualize (bool): 是否保存可视化结果
+
+ 返回:
+ dict: {
+ 'success': bool,
+ 'dx_mm': float or None,
+ 'dy_mm': float or None,
+ 'cx': float or None, # 中心点 x
+ 'cy': float or None, # 中心点 y
+ 'message': str
+ }
+ """
+ # 读取图像
+ orig = cv2.imread(image_path)
+ if orig is None:
+ return {
+ 'success': False,
+ 'dx_mm': None, 'dy_mm': None, 'cx': None, 'cy': None,
+ 'message': f'Failed to load image: {image_path}'
+ }
+
+ h0, w0 = orig.shape[:2]
+
+ # 预处理
+ img_resized, scale, dx, dy = letterbox_resize(orig, IMG_SIZE)
+ infer_img = np.expand_dims(img_resized[..., ::-1], 0).astype(np.uint8)
+
+ # 加载模型并推理
+ rknn = RKNNLite(verbose=False)
+ ret = rknn.load_rknn(MODEL_PATH)
+ if ret != 0:
+ return {
+ 'success': False,
+ 'dx_mm': None, 'dy_mm': None, 'cx': None, 'cy': None,
+ 'message': 'Failed to load RKNN model'
+ }
+
+ try:
+ rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_0)
+ outputs = rknn.inference([infer_img])
+ except Exception as e:
+ rknn.release()
+ return {
+ 'success': False,
+ 'dx_mm': None, 'dy_mm': None, 'cx': None, 'cy': None,
+ 'message': f'Inference error: {str(e)}'
+ }
+ finally:
+ rknn.release()
+
+ # 解析关键点
+ try:
+ keypoints = parse_pose_outputs(outputs, dx=dx, dy=dy, scale=scale)
+ except Exception as e:
+ return {
+ 'success': False,
+ 'dx_mm': None, 'dy_mm': None, 'cx': None, 'cy': None,
+ 'message': f'Parse keypoint error: {str(e)}'
+ }
+
+ # 计算偏移
+ offset_info = compute_offset(keypoints, FIXED_REF_POINT, SCALE_X, SCALE_Y)
+ if offset_info is None:
+ return {
+ 'success': False,
+ 'dx_mm': None, 'dy_mm': None, 'cx': None, 'cy': None,
+ 'message': 'Not enough keypoints to compute offset'
+ }
+
+ cx, cy, dx_mm, dy_mm = offset_info
+
+ # 可视化(可选)
+ if visualize:
+ vis_save_path = os.path.join(OUTPUT_DIR, f"result_{os.path.basename(image_path)}")
+ visualize_result(orig, keypoints, FIXED_REF_POINT, offset_info, vis_save_path)
+
+ return {
+ 'success': True,
+ 'dx_mm': dx_mm,
+ 'dy_mm': dy_mm,
+ 'cx': cx,
+ 'cy': cy,
+ 'message': 'Success'
+ }
+
+
+# ====================== 使用示例 ======================
+if __name__ == "__main__":
+ image_path = "11.jpg"
+
+ result = calculate_offset_from_image(image_path, visualize=True)
+
+ if result['success']:
+ print(f"Center point: ({result['cx']:.1f}, {result['cy']:.1f})")
+ print(f"Offset: DeltaX={result['dx_mm']:+.2f} mm, DeltaY={result['dy_mm']:+.2f} mm")
+ else:
+ print("Error:", result['message'])
\ No newline at end of file
diff --git a/main/point.rknn b/main/point.rknn
new file mode 100644
index 0000000..1ba05e9
Binary files /dev/null and b/main/point.rknn differ