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

807 lines
35 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.

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)