287 lines
10 KiB
Python
287 lines
10 KiB
Python
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)
|