2025-05-27 15:46:31 +08:00

287 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)