807 lines
35 KiB
Python
807 lines
35 KiB
Python
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) |