from ultralytics import YOLO import cv2 import os import numpy as np from PIL import ImageFont, ImageDraw, Image try: from .config import Config except: from config import Config import math from matplotlib import font_manager as fm, patheffects import matplotlib.pyplot as plt import matplotlib.pyplot as plt import os from typing import Dict, Tuple import matplotlib.image as mpimg class BackAcupointsDetector: def __init__(self): """ 初始化 BackAcupointsDetector 类,加载模型和字体。 这个类使用 YOLO 模型自动检测腹部穴位点。 """ # 初始化模型 self.model = YOLO(Config.BACK_MODEL_PATH) self.device = "cpu" # 默认使用 CPU # 穴位名称列表 self.all_acupoints_names = [ "肩中左俞", "肩外左俞", "秉风左", "天宗左", "膈俞左", "魂门左", "三焦左俞", "京门左", "关元左俞", "膀胱左俞", "白环左俞", "秩边左", "会阳左", "肩中右俞", "肩外右俞", "秉风右", "天宗右", "膈俞右", "魂门右", "三焦右俞", "京门右", "关元右俞", "膀胱右俞", "白环右俞", "秩边右", "会阳右", "督俞左", "心俞左", "厥阴左俞", "肺俞左", "风门左", "大杼左", "督俞右", "心俞右", "厥阴右俞", "肺俞右", "风门右", "大杼右", "膈关左", "譩譆左", "神堂左", "膏肓左", "魄户左", "附分左", "曲垣左", "膈关右", "譩譆右", "神堂右", "膏肓右", "魄户右", "附分右", "曲垣右", "胃俞左", "脾俞左", "胆俞左", "肝俞左", "胃俞右", "脾俞右", "胆俞右", "肝俞右", "大肠左俞", "气海左俞", "肾俞左", "大肠右俞", "气海右俞", "肾俞右", "小肠左俞", "中膂左俞", "小肠右俞", "中膂右俞", "胞肓左", "志室左", "肓门左", "胃仓左", "意舍左", "阳纲左", "胞肓右", "志室右", "肓门右", "胃仓右", "意舍右", "阳纲右", "崇骨", "大椎", "陶道", "身柱", "神道", "灵台", "至阳", "筋缩", "中枢", "脊中", "悬枢", "命门", "腰阳关" ] self.unique_list = [ "肩中左俞", "肩外左俞", "秉风左", "天宗左", "膈俞左", "魂门左", "三焦左俞", "京门左", "关元左俞", "肩中右俞", "肩外右俞", "秉风右", "天宗右", "膈俞右", "魂门右", "三焦右俞", "京门右", "关元右俞" ] # 获取字体路径 self.font_path = Config.get_font_path() if self.font_path is None: print("警告:未找到合适的字体文件,将使用系统默认字体") def calculate_upper_acupoints(self, coordinates): """计算上半部分的穴位点""" acupoint_list_zuo = ["肩中左俞", "肩外左俞", "秉风左", "天宗左", "膈俞左"] acupoint_list_you = ["肩中右俞", "肩外右俞", "秉风右", "天宗右", "膈俞右"] zuo1_average = self.calculate_average_coordinates(acupoint_list_zuo, coordinates) you1_average = self.calculate_average_coordinates(acupoint_list_you, coordinates) k1, b1 = self.calculate_k_and_b(zuo1_average, you1_average) k1_1 = self.calculate_perpendicular_slope(k1) if coordinates["膈俞左"][1] > coordinates["肩中左俞"][1]: distance = self.calculate_distance_in_direction(coordinates["肩中左俞"], coordinates["膈俞左"], k1_1) one_inch_row = distance / 8.5 distances = [1.25 * one_inch_row, 2.4 * one_inch_row, 3.5 * one_inch_row, 4.8 * one_inch_row, 5.7 * one_inch_row, 6.5 * one_inch_row] moved_points_1 = self.move_points_in_direction(coordinates["膈俞左"], coordinates["膈俞右"], k1_1, distances) self.add_points_to_coordinates(moved_points_1, coordinates, 26, 38) distances1 = [2.5 * one_inch_row, 3.7 * one_inch_row, 4.85 * one_inch_row, 5.95 * one_inch_row, 7.25 * one_inch_row, 8.05 * one_inch_row, 8.75 * one_inch_row] moved_points_11 = self.move_points_in_direction(coordinates["魂门左"], coordinates["魂门右"], k1_1, distances1) self.add_points_to_coordinates(moved_points_11, coordinates, 38, 52) def calculate_middle_acupoints(self, coordinates): """计算中间部分的穴位点""" acupoint_list_zuo_middle = ["膈俞左", "魂门左", "三焦左俞", "京门左"] acupoint_list_you_middle = ["膈俞右", "魂门右", "三焦右俞", "京门右"] zuo2_average = self.calculate_average_coordinates(acupoint_list_zuo_middle, coordinates) you2_average = self.calculate_average_coordinates(acupoint_list_you_middle, coordinates) k2, b2 = self.calculate_k_and_b(zuo2_average, you2_average) k2_1 = self.calculate_perpendicular_slope(k2) if coordinates["三焦左俞"][1] > coordinates["膈俞左"][1]: distance = self.calculate_distance_in_direction(coordinates["三焦左俞"], coordinates["膈俞左"], k2_1) one_inch_row = distance / 5.6 distances = [0.9 * one_inch_row, 1.7 * one_inch_row, 2.6 * one_inch_row, 3.6 * one_inch_row] moved_points_2 = self.move_points_in_direction(coordinates["三焦左俞"], coordinates["三焦右俞"], k2_1, distances) self.add_points_to_coordinates(moved_points_2, coordinates, 52, 60) def calculate_points_on_lines(self, coordinates): """ 计算线段上的穴位点坐标,并更新到coordinates字典中。 :param coordinates: 包含已知穴位点坐标的字典 """ # 定义需要计算的穴位点及其参数 points_to_calculate = [ {"name": "小肠左俞", "start": "关元左俞", "end": "膀胱左俞", "ratio": 3/7}, {"name": "中膂左俞", "start": "膀胱左俞", "end": "白环左俞", "ratio": 4/7}, {"name": "小肠右俞", "start": "关元右俞", "end": "膀胱右俞", "ratio": 3/7}, {"name": "中膂右俞", "start": "膀胱右俞", "end": "白环右俞", "ratio": 4/7} ] # 批量计算并更新coordinates for point_info in points_to_calculate: name = point_info["name"] start_point = coordinates[point_info["start"]] end_point = coordinates[point_info["end"]] ratio = point_info["ratio"] coordinates[name] = self.calculate_point_on_line(start_point, end_point, ratio) def calculate_lower_acupoints(self, coordinates): """计算最下面部分的穴位点""" acupoint_list_zuo_low = ["三焦左俞", "京门左", "关元左俞"] acupoint_list_you_low = ["三焦右俞", "京门右", "关元右俞"] zuo3_average = self.calculate_average_coordinates(acupoint_list_zuo_low, coordinates) you3_average = self.calculate_average_coordinates(acupoint_list_you_low, coordinates) k3, b3 = self.calculate_k_and_b(zuo3_average, you3_average) self.k3_1 = self.calculate_perpendicular_slope(k3) if coordinates["关元左俞"][1] > coordinates["三焦左俞"][1]: distance = self.calculate_distance_in_direction(coordinates["关元左俞"], coordinates["三焦左俞"], self.k3_1) one_inch_row = distance / 4.3 distances = [1.1 * one_inch_row, 2.2 * one_inch_row, 3.3 * one_inch_row] moved_points_3 = self.move_points_in_direction(coordinates["关元左俞"], coordinates["关元右俞"], self.k3_1, distances) self.add_points_to_coordinates(moved_points_3, coordinates, 60, 66) def calculate_right_lower_acupoints(self, coordinates): """计算中下右边部分的穴位点""" acupoint_list_zuo_youxia = ["魂门左", "京门左", "秩边左"] acupoint_list_you_youxia = ["魂门右", "京门右", "秩边右"] zuo4_average = self.calculate_average_coordinates(acupoint_list_zuo_youxia, coordinates) you4_average = self.calculate_average_coordinates(acupoint_list_you_youxia, coordinates) k4, b4 = self.calculate_k_and_b(zuo4_average, you4_average) k4_1 = self.calculate_perpendicular_slope(k4) print(k4_1) if coordinates["秩边左"][1] > coordinates["魂门左"][1]: distance = self.calculate_distance_in_direction(coordinates["秩边左"], coordinates["魂门左"], k4_1) one_inch_row = distance / 11 distances22 = [1.5 * one_inch_row, 6.5 * one_inch_row, 7.6 * one_inch_row, 8.4 * one_inch_row, 9.15 * one_inch_row, 10.05 * one_inch_row] moved_points_22 = self.move_points_in_direction(coordinates["秩边左"], coordinates["秩边右"], k4_1, distances22) elif coordinates["京门左"][1] > coordinates["魂门左"][1]: print("yes") distance = self.calculate_distance_in_direction(coordinates["关元左俞"], coordinates["三焦左俞"], self.k3_1) one_inch_row = distance / 4.3 distances22 = [-9 * one_inch_row, -4.5 * one_inch_row, -3.4 * one_inch_row, -2.6 * one_inch_row, -1.85 * one_inch_row, -0.95 * one_inch_row] moved_points_22 = self.move_points_in_direction(coordinates["魂门左"], coordinates["魂门右"], self.k3_1, distances22) self.add_points_to_coordinates(moved_points_22, coordinates, 70, 82) def calculate_middle_column_acupoints(self, coordinates): """计算中间一列穴位的坐标""" middle_acupoints = { "崇骨": ["肩中左俞", "肩中右俞"], "大椎": ["肩外左俞", "肩外右俞"], "陶道": ["大杼左", "大杼右"], "身柱": ["肺俞左", "肺俞右"], "神道": ["心俞左", "心俞右"], "灵台": ["督俞左", "督俞右"], "至阳": ["膈俞左", "膈俞右"], "筋缩": ["肝俞左", "肝俞右"], "中枢": ["胆俞左", "胆俞右"], "脊中": ["脾俞左", "脾俞右"], "悬枢": ["三焦左俞", "三焦右俞"], "命门": ["肾俞左", "肾俞右"], "腰阳关": ["大肠左俞", "大肠右俞"] } for name, points in middle_acupoints.items(): coordinates[name] = self.calculate_average_coordinates(points, coordinates) # 更新崇骨和大椎和肩中左俞和肩中右俞的位置 coordinates["崇骨"],coordinates["大椎"], coordinates["肩中左俞"], coordinates["肩中右俞"] = self.move_points_up(coordinates["崇骨"], coordinates["大椎"], coordinates["肩中左俞"],coordinates["肩中右俞"]) # 获取初始的由yolov11得到的27个关键点 def annotate_image(self, input_image_path): """ 对指定的图片进行穴位标注,并返回一个字典,key 为穴位拼音,value 为坐标。 :param input_image_path: 输入图片路径 :return: dict,包含穴位拼音和坐标的字典 """ # 存储穴位名称和对应坐标的字典 acupoint_coordinates = {} # 加载图片 img = cv2.imread(input_image_path) # 进行推理 results = self.model(img)[0] points = results.keypoints.xy[0] # 获取第一组点的 (x, y) 坐标 points = points[1:] # 将点和对应的名称保存到字典 for idx, point in enumerate(points): x, y = point x, y = x.item(), y.item() # 将 tensor 转换为数值 # 获取穴位名称 acupoint_name = self.all_acupoints_names[idx] # 将坐标添加到字典中 acupoint_coordinates[acupoint_name] = (x, y) return acupoint_coordinates # 将获得的26个关键点(去除大椎)统一往上移动一定的尺寸 def move_26points_up(self, coordinates): # 定义点组配置 point_groups = [ { "left_points": ["肩中左俞", "肩外左俞", "秉风左", "天宗左", "膈俞左", "魂门左"], "right_points": ["肩中右俞", "肩外右俞", "秉风右", "天宗右", "膈俞右", "魂门右"], "move_points": [ "肩中左俞", "肩外左俞", "秉风左", "天宗左", "膈俞左", "肩中右俞", "肩外右俞", "秉风右", "天宗右", "膈俞右" ], "ref_points": ["肩中左俞", "肩外左俞"] }, { "left_points": ["魂门左", "三焦左俞", "京门左", "关元左俞"], "right_points": ["魂门右", "三焦右俞", "京门右", "关元右俞"], "move_points": [ "魂门左", "三焦左俞", "京门左", "魂门右", "三焦右俞", "京门右" ], "ref_points": ["肩中左俞", "肩外左俞"] }, { "left_points": ["三焦左俞", "关元左俞", "膀胱左俞", "白环左俞", "秩边左"], "right_points": ["三焦右俞", "关元右俞", "膀胱右俞", "白环右俞", "秩边右"], "move_points": [ "关元左俞", "膀胱左俞", "白环左俞", "秩边左", "会阳左", "关元右俞", "膀胱右俞", "白环右俞", "秩边右", "会阳右" ], "ref_points": ["肩中左俞", "肩外左俞"] } ] for group in point_groups: # 计算左右平均坐标 left_avg = self.calculate_average_coordinates(group["left_points"], coordinates) right_avg = self.calculate_average_coordinates(group["right_points"], coordinates) # 计算斜率和垂直线斜率 k, _ = self.calculate_k_and_b(left_avg, right_avg) k_perpendicular = self.calculate_perpendicular_slope(k) # 计算移动距离 ref_point1, ref_point2 = group["ref_points"] distance = self.calculate_distance_in_direction( coordinates[ref_point1], coordinates[ref_point2], k_perpendicular ) / 2 # 移动坐标点 coordinates = self.move_coordinates_along_slope( coordinates, group["move_points"], k_perpendicular, distance ) return coordinates # 将所有的点往上在一定斜率上移动一定距离 def move_coordinates_along_slope(self, coordinates, points, slope, distance): """ 沿给定斜率移动点,方向规则: - k>0:向右上方移动(x增大,y减小) - k<0:向左上方移动(x减小,y减小) - k=0:垂直向上移动(x不变,y减小) - 垂直线:垂直向上移动(x不变,y减小) :param coordinates: 坐标字典 {'点名称': (x,y), ...} :param points: 要移动的点名称列表 :param slope: 移动方向的斜率 :param distance: 移动距离(应为正数) :return: 更新后的坐标字典 """ # 确保距离为正数 abs_distance = abs(distance) for point in points: if point in coordinates: x, y = coordinates[point] if math.isinf(slope): # 垂直线:垂直向上移动 (x不变,y减小) new_x = x new_y = y - abs_distance elif slope == 0: # 水平线:垂直向上移动 (x不变,y减小) new_x = x new_y = y - abs_distance else: # 计算单位方向向量 magnitude = math.sqrt(1 + slope**2) if slope > 0: # 正斜率:右上方向 (x增大,y减小) move_x = abs_distance / magnitude move_y = -abs_distance * slope / magnitude else: # 负斜率:左上方向 (x减小,y减小) move_x = -abs_distance / magnitude move_y = -abs_distance * abs(slope) / magnitude new_x = x + move_x new_y = y + move_y coordinates[point] = (new_x, new_y) return coordinates # 计算n个点之间的平均坐标 def calculate_average_coordinates(self, acupoint_list, acupoint_coordinates): """ 计算多个穴位坐标的平均值。 :param acupoint_pinyin_list: 需要计算平均值的穴位拼音列表 :param acupoint_coordinates: 包含所有穴位坐标的字典,key为穴位拼音,value为坐标元组 :return: 平均坐标 (x_avg, y_avg) """ # 筛选出指定穴位拼音对应的坐标 points = [] for name in acupoint_list: if name in acupoint_coordinates.keys(): points.append(acupoint_coordinates[name]) if not points: return None # 如果没有找到对应的坐标,返回None # 计算坐标的平均值 points_array = np.array(points) avg_x = np.mean(points_array[:, 0]) # 计算x坐标的平均值 avg_y = np.mean(points_array[:, 1]) # 计算y坐标的平均值 return (avg_x, avg_y) # 计算两点之间的斜率和截距 def calculate_k_and_b(self, point1, point2): """ 计算两点之间的斜率和截距。 :param point1: 第一个点 (x1, y1) :param point2: 第二个点 (x2, y2) :return: 斜率 k 和截距 b """ x1, y1 = point1 x2, y2 = point2 if x1 == x2: # 垂直线,斜率无穷大,截距无意义 return float('inf'), None elif y1 == y2: # 水平线,斜率为 0 return 0, y1 else: # 正常情况 k = (y2 - y1) / (x2 - x1) b = y1 - k * x1 return k, b # 计算垂直于当前直线的斜率 def calculate_perpendicular_slope(self, k): """计算垂直于当前直线的斜率""" if k == 0: return float('inf') elif math.isinf(k): return 0 else: return -1 / k # 计算两点在给定斜率方向上的距离 def calculate_distance_in_direction(self, point1, point2, k): """ 计算两个点在给定斜率方向上的距离。 :param point1: 第一个点 (x1, y1) :param point2: 第二个点 (x2, y2) :param k: 直线的斜率 :return: 两个点在直线方向上的距离 """ x1, y1 = point1 x2, y2 = point2 if math.isinf(k): # 垂直线,距离为 x 坐标的差值 return abs(y2 - y1) elif k == 0: # 水平线,距离为 y 坐标的差值 return abs(x2 - x1) else: # 正常情况 vector_p1p2 = (x2 - x1, y2 - y1) vector_v = (1, k) dot_product = vector_p1p2[0] * vector_v[0] + vector_p1p2[1] * vector_v[1] magnitude_v = math.sqrt(vector_v[0]**2 + vector_v[1]**2) distance = abs(dot_product) / magnitude_v return distance # 计算基于基准点在给定斜率方向上移动一定尺寸的距离 def move_points_in_direction(self, point1, point2, k, distances): """ 根据给定的两个点、斜率和多个移动距离,计算出沿直线方向上每个距离对应的点。 :param point1: 第一个点 (x1, y1) :param point2: 第二个点 (x2, y2) :param k: 直线的斜率 :param distances: 一个包含多个距离的列表,表示每个点沿直线方向上应该移动的距离 :return: 返回一个包含移动后点坐标的列表 """ x1, y1 = point1 x2, y2 = point2 if math.isinf(k): # 垂直线,沿 y 方向移动 unit_direction_vector = (0, 1) elif k == 0: # 水平线,沿 x 方向移动 unit_direction_vector = (1, 0) else: # 正常情况 magnitude_v = math.sqrt(1 + k ** 2) direction_sign = -1 if k > 0 else 1 unit_direction_vector = (direction_sign * 1 / magnitude_v, direction_sign * k / magnitude_v) # 用来存储结果 moved_points = [] # 对于每个给定的移动距离,计算新的点 for distance in distances: offset = (distance * unit_direction_vector[0], distance * unit_direction_vector[1]) new_point1 = (x1 + offset[0], y1 + offset[1]) new_point2 = (x2 + offset[0], y2 + offset[1]) moved_points.extend((new_point1, new_point2)) return moved_points # 根据两个点计算两点之间一定比例的点 def calculate_point_on_line(self, a, b, ratio): """ 计算从点 a 开始,距离为 ratio * ab 的点的坐标。 # font_path = 'C:/Windows/Fonts/simhei.ttf' tuple: 比例处的点的坐标 (x, y) """ x1, y1 = a x2, y2 = b # 计算两点之间的距离 distance = math.sqrt((x2 - x1)**2 + (y2 - y1)**2) # 计算目标距离 target_distance = distance * ratio # 计算目标点的坐标 dx = (x2 - x1) / distance * target_distance dy = (y2 - y1) / distance * target_distance x = x1 + dx y = y1 + dy return (x, y) # 重建崇骨和大椎的位置 def move_points_up(self,chonggu, dazhui, jianzhongzuo, jianzhongyou): """ 将崇骨和大椎两点沿着它们的直线向上移动两者距离的一半 参数: chonggu (tuple): 崇骨坐标 (x, y) dazhui (tuple): 大椎坐标 (x, y) 返回: tuple: 移动后的新崇骨坐标和新大椎坐标 (new_chonggu, new_dazhui) """ # 提取坐标 x1, y1 = chonggu x2, y2 = dazhui x3, y3 = jianzhongzuo x4, y4 = jianzhongyou # 计算两点之间的距离 distance = math.sqrt((x2 - x1)**2 + (y2 - y1)**2) move_distance = distance # 移动距离为两点距离的一半 move_distance1 = 0.45 * distance # 计算两点连线的方向向量 dx = x2 - x1 dy = y2 - y1 # 归一化方向向量(因为我们只需要方向) if distance > 0: dx /= distance dy /= distance else: # 如果两点重合,无法移动 return chonggu, dazhui # 因为y轴向上是减小,所以移动方向是减去移动距离乘以方向向量 # 崇骨的新坐标 new_x1 = x1 - move_distance * dx new_y1 = y1 - move_distance * dy # 大椎的新坐标 new_x2 = x2 - move_distance * dx new_y2 = y2 - move_distance * dy # 向下移动,y轴增大 # 肩中左俞的新坐标 new_x3 = x3 - move_distance1 * dx new_y3 = y3 + move_distance1 * dy # 肩中右俞的新坐标 new_x4 = x4 - move_distance1* dx new_y4 = y4 + move_distance1 * dy return (new_x1, new_y1), (new_x2, new_y2), (new_x3, new_y3), (new_x4, new_y4) # 画图 def plot_acupoints_on_image(self, input_image_path, coordinates, output_path): # 读取原始图像 orig_img = cv2.imread(input_image_path) if orig_img is None: raise FileNotFoundError(f"无法加载背景图:{input_image_path}") # 获取原始图像的尺寸 orig_h, orig_w = orig_img.shape[:2] # 定义目标分辨率(可以根据需求调整) # TARGET_W = 1280 # 目标宽度 TARGET_W = 1478 # 目标宽度 TARGET_H = int(orig_h * (TARGET_W / orig_w)) # 根据宽高比计算目标高度 # 将原始图像放大到目标尺寸 resized_img = cv2.resize(orig_img, (TARGET_W, TARGET_H), interpolation=cv2.INTER_LANCZOS4) # 计算缩放比例 scale_factor = TARGET_W / orig_w # 设置金色渐变颜色 golden_colors = [ (223, 87, 98), # 浅蓝紫色 (223, 87, 98), # 中等蓝紫色 (223, 87, 98), # 深蓝紫色 ] # 在放大后的图像上绘制穴位点 for name, (x, y) in coordinates.items(): if x == 0 and y == 0: continue # 将坐标缩放到目标尺寸 x, y = int(x * scale_factor), int(y * scale_factor) # 绘制金色穴位小球 radius = 5 # 小球半径 for r in range(radius, 0, -1): alpha = r / radius # alpha 从 1(中心)到 0(边缘) # 计算渐变颜色 color_index = int(alpha * (len(golden_colors) - 1)) color = golden_colors[color_index] # 绘制渐变圆 cv2.circle(resized_img, (x, y), r, color, -1, cv2.LINE_AA) # 添加高光效果 highlight_radius = int(radius * 0.45) # highlight_color = (255, 255, 255) # 白色高光 highlight_color = (0, 255, 185) # 白色高光 highlight_pos = (x, y) cv2.circle(resized_img, highlight_pos, highlight_radius, highlight_color, -1, cv2.LINE_AA) # 在放大后的图像上绘制文字 pil_img = Image.fromarray(cv2.cvtColor(resized_img, cv2.COLOR_BGR2RGB)) draw = ImageDraw.Draw(pil_img) # 直接在目标尺寸下设置字体大小 font_size = 12 font = ImageFont.truetype(self.font_path, font_size) for name, (x, y) in coordinates.items(): if x == 0 and y == 0: continue # 将坐标缩放到目标尺寸 x, y = int(x * scale_factor), int(y * scale_factor) if name in ["崇骨", "大椎", "陶道", "身柱", "神道", "灵台", "至阳", "筋缩", "中枢", "脊中", "悬枢", "命门","腰阳关"]: dx, dy = -font_size , -font_size * 1.8 elif name in [ "督俞左", "心俞左", "厥阴左俞", "肺俞左", "风门左", "大杼左", "膈俞左", "督俞右", "心俞右", "厥阴右俞", "肺俞右", "风门右", "大杼右", "膈俞右", "胃俞左", "脾俞左", "胆俞左", "肝俞左", "三焦左俞", "肾俞左", "胃俞右", "脾俞右", "胆俞右", "肝俞右", "三焦右俞", "肾俞右", "大肠左俞", "气海左俞", "大肠右俞", "气海右俞" ]: dx, dy = -font_size * 1.8, -font_size * 1.8 elif name == "白环左俞": dx, dy = -font_size * 1.8, font_size elif name == "白环右俞": dx, dy = -font_size * 1.8, font_size elif name == "膀胱左俞": dx, dy = -font_size * 0.2, -font_size * 1.8 elif name == "膀胱右俞": dx, dy = -font_size * 3, -font_size * 1.8 elif name in ["肩中左俞", "肩外左俞", "关元左俞","小肠左俞", "中膂左俞"]: dx, dy = -font_size * 5, -font_size * 0.8 elif "左" in name: dx, dy = -font_size * 4, -font_size * 0.8 else: dx, dy = font_size , -font_size * 0.8 # 绘制带白边的黑字 text_pos = (x + dx, y + dy) for offset in [(-1,-1), (-1,1), (1,-1), (1,1)]: draw.text((text_pos[0]+offset[0], text_pos[1]+offset[1]), name, font=font, fill=(255,255,255)) draw.text(text_pos, name, font=font, fill=(0,0,0)) # 转换回OpenCV格式并保存 final_img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR) cv2.imwrite(output_path, final_img) # 异常处理 def error_process(self, coordinate): """ 检查坐标数据是否有效。 :param coordinate: 包含穴位点坐标的字典 :return: 如果坐标有效返回 True,否则返回 None """ if not coordinate: # 判断 coordinates 是否为空 print("Coordinates are empty, skipping the calculation for this iteration...") return None # 遍历 unique_list 中的每个点 for point in self.unique_list: # 检查点是否存在于 coordinate 中 if point in coordinate: # 检查点的值是否为空或 (0, 0) if not coordinate[point] or coordinate[point] == (0, 0): print(f"Point '{point}' is empty or (0, 0), skipping the calculation for this iteration...") return None else: print(f"Point '{point}' is missing in the coordinates, skipping the calculation for this iteration...") return None # 如果所有检查都通过,返回 True print("All points are valid, proceeding with the calculation...") return True # 将移动后的点添加到coordinates字典中 def add_points_to_coordinates(self, moved_points, coordinates, start_index, end_index): """将移动后的点添加到coordinates字典中""" for i in range(0, len(moved_points), 2): point_left = moved_points[i] point_right = moved_points[i+1] name_left = self.all_acupoints_names[start_index:end_index][i//2] name_right = self.all_acupoints_names[start_index:end_index][i//2 + (end_index - start_index)//2] coordinates[name_left] = point_left coordinates[name_right] = point_right # 主函数 def process_image(self, input_image_path, output_coordinate_image_path): """ 处理单张图片,计算穴位点并保存结果。 :param input_image_path: 输入图片路径 :param output_coordinate_image_path: 输出坐标图片路径 """ try: # 确保输出文件夹存在 os.makedirs(os.path.dirname(output_coordinate_image_path), exist_ok=True) # 处理图片 print(f"Processing {input_image_path}...") # 调用 annotate_image 函数获取穴位坐标 coordinates = self.annotate_image(input_image_path) # 假设 error_process 函数已经定义 result = self.error_process(coordinates) if result is None: print("Pass this image due to error processing:Yolov11穴位产生为None或(0,0),人躺太上或者是躺太下了.") return None # 重构识别到的26个点 coordinates = self.move_26points_up(coordinates) # 计算各个部分的穴位点 self.calculate_upper_acupoints(coordinates) self.calculate_middle_acupoints(coordinates) self.calculate_points_on_lines(coordinates) self.calculate_lower_acupoints(coordinates) self.calculate_right_lower_acupoints(coordinates) # 计算中间一列穴位的坐标 self.calculate_middle_column_acupoints(coordinates) # 定义规则:{检查点: [受影响点]} rules = { # (检查点1, 检查点2): [所有受影响点] ("白环左俞", "白环右俞"): ["白环左俞", "白环右俞", "中膂左俞", "中膂右俞"], ("膀胱左俞", "膀胱右俞"): ["膀胱左俞", "膀胱右俞", "小肠左俞", "小肠右俞"], ("秩边左", "秩边右"): ["秩边左", "秩边右"], } for check_points, affected_points in rules.items(): if any(c < 0 for point in check_points for c in coordinates.get(point, (0, 0))): coordinates.update({p: (0, 0) for p in affected_points}) # 画图,保存图片 self.plot_acupoints_on_image(input_image_path, coordinates, output_coordinate_image_path) # back_acupoints_list = [{name: coords} for name, coords in coordinates.items()] return coordinates except Exception as e: print(f"Error processing {input_image_path}: {str(e)}") return None if __name__ == "__main__": """ single_picture """ input_image_path = Config.get_image_path("color_b.png") output_image_path = Config.get_output_path("color_b_back.png") # 初始化检测器 detector = BackAcupointsDetector() # 处理单张图片,并获取穴位点字典 acupoints = detector.process_image(input_image_path, output_image_path) print(acupoints) """ batch_picture """ # # 输入输出文件夹路径 # # input_folder = os.path.join(Config.IMAGE_DIR, "train", "images") # # output_folder = os.path.join(Config.IMAGE_DIR, "output_all") # # input_folder = "/home/kira/codes/datas/colors" # input_folder = "/home/kira/codes/IoT_img_process/output_classified/back" # # input_folder = "/home/kira/codes/datas/result_back_problem" # output_folder= "/home/kira/codes/datas/results4" # Config.ensure_dir(output_folder) # # 初始化检测器 # detector = BackAcupointsDetector() # for filename in os.listdir(input_folder): # if filename.lower().endswith(('.png', '.jpg', '.jpeg')): # input_image_path = os.path.join(input_folder, filename) # output_image_path = os.path.join(output_folder, filename) # try: # # 处理图片并获取穴位点 # acupoints = detector.process_image(input_image_path, output_image_path) # print(f"处理图片: {filename}") # if acupoints is None: # print("警告: 未检测到任何穴位点或处理失败") # continue # 跳过当前图片继续处理下一张 # if not isinstance(acupoints, dict): # print(f"警告: 返回的穴位点格式不正确,应为字典,实际得到: {type(acupoints)}") # continue # if not acupoints: # 空字典 # print("检测到空穴位点结果 (字典为空)") # else: # print("检测到的穴位点:") # for name, coords in acupoints.items(): # print(f"{name}: {coords}") # except Exception as e: # print(f"处理图片 {filename} 时发生错误: {str(e)}") # continue # 继续处理下一张图片 # print("-" * 40)