import os import json import matplotlib.pyplot as plt from PIL import ImageFont, ImageDraw, Image from matplotlib import font_manager as fm, patheffects try: from .abdomen_data import AbdomenData from .config import Config except: from abdomen_data import AbdomenData from config import Config import matplotlib.patheffects as path_effects import cv2 import numpy as np class AbdominalAcupointsDetector: def __init__(self): """ 初始化 AbdominalAcupointsDetector 类。 """ self.processor = AbdomenData() self.vertices = None self.navel = None self.dimensions = None self.is_data_loaded = False self.font_path = Config.get_font_path() def load_data(self, json_data): """ 加载并处理输入数据。 参数: json_data (dict): 包含腹部顶点、脐点和尺寸信息的 JSON 数据。 """ self.processor.load_data(json_data) self.vertices = self.processor.get_vertices() self.navel = self.processor.get_navel_point() self.dimensions = self.processor.get_dimensions() self.is_data_loaded = True return self def calculate_line_equation(self, point1, point2): """ 计算通过两个点的直线的斜率和截距。 参数: point1 (tuple): 第一个点 (x1, y1)。 point2 (tuple): 第二个点 (x2, y2)。 返回: tuple: (斜率, 截距)。如果斜率不存在(垂直线),返回 None 和 x 截距。 """ x1, y1 = point1 x2, y2 = point2 k = (y2 - y1) / (x2 - x1) b = y1 - k * x1 return k, b def calculate_points(self, navel_point, k, one_inch): """ 计算 navel 点在直线上的正方向和反方向分别移动两寸和四寸后的坐标。 参数: navel_point (tuple): navel 点的坐标 (x, y)。 k (float): 直线的斜率。 one_inch (float): 一寸的实际距离。 返回: list: 包含正反方向移动两寸和四寸的坐标点列表。 """ x1, y1 = navel_point points = [navel_point] distances = [2, 4] # 两寸和四寸的距离 for distance in distances: dx = distance * one_inch / (1 + k ** 2) ** 0.5 dy = distance * one_inch * k / (1 + k ** 2) ** 0.5 point_positive = (x1 + dx, y1 + dy) point_negative = (x1 - dx, y1 - dy) points.append(point_positive) points.append(point_negative) return points def calculate_acupoints(self, points, k, one_inch): """ 计算基于已知点和移动规则得到的穴位坐标。 参数: points (list): 已知的点坐标 [(x1, y1), (x2, y2), ...]。 k (float): 原始斜率 k。 one_inch (float): 1寸对应的实际距离。 返回: dict: 每个穴位的坐标。 """ if k == 0: direction_x = 0 direction_y = 1 # 沿着 y 轴正方向移动 else: # 斜率不为 0,计算反方向的斜率和方向向量 new_k = -1 / k direction_x = 1 / (1 + new_k ** 2) ** 0.5 direction_y = new_k * direction_x # 如果方向是负的,翻转方向 if direction_y < 0: direction_x, direction_y = -direction_x, -direction_y acupoints = { "神阙": points[0], "天枢右": points[1], "天枢左": points[2], } point1 = points[0] acupoints.update({ "气海": (point1[0] + 1.5 * one_inch * direction_x, point1[1] + 1.5 * one_inch * direction_y), "石门": (point1[0] + 2 * one_inch * direction_x, point1[1] + 2 * one_inch * direction_y), "关元": (point1[0] + 3 * one_inch * direction_x, point1[1] + 3 * one_inch * direction_y), "水分": (point1[0] - 2 * one_inch * direction_x, point1[1] - 2 * one_inch * direction_y), }) point2 = points[1] acupoints.update({ "外陵右": (point2[0] + 1 * one_inch * direction_x, point2[1] + 1 * one_inch * direction_y), "滑肉右": (point2[0] - 2 * one_inch * direction_x, point2[1] - 2 * one_inch * direction_y), }) point3 = points[2] acupoints.update({ "外陵左": (point3[0] + 1 * one_inch * direction_x, point3[1] + 1 * one_inch * direction_y), "滑肉左": (point3[0] - 2 * one_inch * direction_x, point3[1] - 2 * one_inch * direction_y), }) acupoints["大横左"] = points[4] acupoints["大横右"] = points[3] return acupoints def plot_acupoints_on_image(self, image_path, acupoints, output_path): """ 在指定图片上绘制穴位点,并标注名称,保存结果。 参数: image_path (str): 图片文件路径。 acupoints (dict): 包含穴位坐标的字典 {"穴位名称": (x, y), ...}。 output_path (str): 保存结果图片的路径。 """ # 读取图片 image = cv2.imread(image_path) if image is None: raise FileNotFoundError(f"无法加载背景图:{image_path}") # 将图像分辨率增大到原来的三倍 scale_factor = 2 image = cv2.resize(image, None, fx=scale_factor, fy=scale_factor, interpolation=cv2.INTER_LINEAR) # 调整穴位点坐标 acupoints = {name: (int(x * scale_factor), int(y * scale_factor)) for name, (x, y) in acupoints.items()} # 定义蓝紫色渐变颜色BGR purple_colors = [ (223, 87, 98), # 浅蓝紫色 (223, 87, 98), # 中等蓝紫色 (223, 87, 98), ] # 绘制穴位点 for name, (x, y) in acupoints.items(): if x == 0 and y == 0: continue # 绘制蓝紫色带光圈的穴位点 radius = 4 # 穴位点半径 for r in range(radius, 0, -1): alpha = r / radius # alpha 从 1(中心)到 0(边缘) # 计算渐变颜色 color_index = int(alpha * (len(purple_colors) - 1)) color = purple_colors[color_index] # 绘制渐变圆 cv2.circle(image, (x, y), r, color, -1, cv2.LINE_AA) # 添加高光效果 highlight_radius = int(radius * 0.6) # highlight_color = (200, 200, 200) # 白色高光 highlight_color = (0, 255, 185) # 白色高光 highlight_pos = (x, y) cv2.circle(image, highlight_pos, highlight_radius, highlight_color, -1, cv2.LINE_AA) # 使用 PIL 绘制文本 image_pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) draw = ImageDraw.Draw(image_pil) font = ImageFont.truetype(self.font_path, 8 * scale_factor) # 字体大小也放大三倍 for name, (x, y) in acupoints.items(): if x == 0 and y == 0: continue if name in ["滑肉左", "大横左", "天枢左"]: x_offset, y_offset = -15 * scale_factor, -15 * scale_factor elif name in ["滑肉右", "大横右","天枢右"]: x_offset, y_offset = -15 * scale_factor, -15 * scale_factor elif name == "外陵左": x_offset, y_offset = -35 * scale_factor, -5 * scale_factor elif name == "关元": x_offset, y_offset = -20 * scale_factor, -5 * scale_factor elif name in ["石门","外陵右"]: x_offset, y_offset = 5 * scale_factor, -5 * scale_factor else: x_offset, y_offset = -10 * scale_factor, -15 * scale_factor # 绘制带白边的黑字 text_pos = (x + x_offset, y + y_offset) 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 格式 image = cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGB2BGR) # 保存结果 cv2.imwrite(output_path, image) print(f"结果已保存到:{output_path}") def process_image(self, image_path, output_path): """ 主函数:计算点斜式方程、斜截式方程,并计算 navel 点在正反方向移动两寸后的坐标。 参数: image_path (str): 输入图片路径 output_path (str): 输出图片路径 返回: dict: 包含穴位坐标的字典 {"穴位名称": (x, y), ...} """ if not self.is_data_loaded: raise ValueError("请先使用 load_data() 方法加载数据") one_inch = self.dimensions['length'] / 11 zuo1, you1 = self.vertices[0], self.vertices[1] k, b = self.calculate_line_equation(zuo1, you1) points = self.calculate_points(self.navel, k, one_inch) acupoints = self.calculate_acupoints(points, k, one_inch) for name, coord in acupoints.items(): print(f"{name}: {coord}") self.plot_acupoints_on_image(image_path, acupoints, output_path) return acupoints if __name__ == "__main__": sample_json = { "shapes": [ { "label": "fubu", "points": [ [278, 241], [400, 255], [396, 301], [262, 294] ], "shape_type": "polygon" }, { "label": "duqiyan", "points": [[341, 276]], "shape_type": "point" } ] } # 创建类实例并加载数据 detector = AbdominalAcupointsDetector() detector.load_data(sample_json) # 计算并获取穴位 output_path = Config.get_output_path("abdomen_acupoints.png") image_path = Config.get_image_path("color.png") acupoints = detector.process_image(image_path, output_path)