disease_metrics.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import cv2
  2. import numpy as np
  3. from flask import current_app
  4. from skimage.morphology import skeletonize
  5. from app.constants import DiseaseGrade
  6. def compute_count(masks):
  7. """计算病害数量(通用)"""
  8. return masks.shape[0]
  9. def compute_perimeter(masks):
  10. """计算病害周长(通用)"""
  11. contours, _ = cv2.findContours(masks, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  12. total_perimeter = sum(cv2.arcLength(cnt, closed=True) for cnt in contours)
  13. return float(total_perimeter)
  14. def compute_area(masks):
  15. """计算病害面积(通用)"""
  16. return float(np.sum(masks))
  17. def compute_shape_complexity(perimeter, area):
  18. """计算形状复杂度(通用)"""
  19. if perimeter == 0: # 避免除零错误
  20. return 0.0
  21. return float(1 - (4 * np.pi * area) / (perimeter ** 2))
  22. def compute_texture_roughness(masks):
  23. """计算纹理粗糙度,使用 Laplacian 变换后的标准差(通用)"""
  24. # 将掩码转换为灰度图像(0 和 1 转换到 0 和 255)
  25. gray_mask = (masks * 255).astype(np.uint8)
  26. # 计算 Laplacian 变换结果
  27. laplacian = cv2.Laplacian(gray_mask, cv2.CV_64F)
  28. # 计算 Laplacian 值的标准差作为纹理粗糙度
  29. texture_roughness = np.std(laplacian)
  30. return float(texture_roughness)
  31. def compute_crack_width(masks):
  32. """计算裂缝宽度(仅针对裂缝)"""
  33. # 骨架化
  34. skeleton = skeletonize(masks.astype(bool))
  35. # 距离变换
  36. dist_transform = cv2.distanceTransform((masks * 255).astype(np.uint8), cv2.DIST_L2, 5)
  37. crack_widths = dist_transform[skeleton]
  38. return float(2 * np.median(crack_widths)) if crack_widths.size > 0 else 0.0
  39. def compute_avg_hue(masks, image):
  40. """计算平均色调(仅针对锈蚀)"""
  41. hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
  42. hue_values = hsv_image[masks == 1, 0] # 取病害区域的 hue 通道
  43. if hue_values.size == 0:
  44. return 0.0
  45. H_max = 179 # OpenCV HSV 色调最大值
  46. adjusted_hue = H_max - np.median(hue_values)
  47. return float(adjusted_hue)
  48. def min_max_normalize(value, min_value, max_value):
  49. """ 归一化函数,使数据在 0 到 1 之间 """
  50. if max_value == min_value:
  51. return 0 # 避免除零错误
  52. return (value - min_value) / (max_value - min_value)
  53. def evaluate_disease_severity(disease_count, disease_perimeter, disease_area, shape_complexity, texture_roughness,
  54. crack_width, avg_hue, media):
  55. """
  56. 根据病害的多个指标,计算病害严重性分数和病害等级(轻度、中度、重度、严重)。
  57. :param disease_count: 病害数量
  58. :param disease_perimeter: 病害周长
  59. :param disease_area: 病害面积
  60. :param shape_complexity: 形状复杂度
  61. :param texture_roughness: 纹理粗糙度
  62. :param crack_width: 裂缝宽度
  63. :param avg_hue: 平均色调
  64. :param media: 媒体
  65. :return: 病害严重性分数和病害等级(轻度、中度、重度、严重)
  66. """
  67. # 读取指标权重配置
  68. weights = current_app.config['DISEASE_INDEX_WEIGHTS']
  69. # 读取裂缝缩放因子配置
  70. scale_factor = current_app.config['CRACK_SCALA_FACTOR']
  71. # 动态计算各指标最大值
  72. max_disease_count = (media.resolution_width * media.resolution_height * disease_count) // disease_area \
  73. if disease_area else 0
  74. max_disease_perimeter = float((2 * (media.resolution_width + media.resolution_height)))
  75. max_disease_area = float(media.resolution_width * media.resolution_height)
  76. max_crack_width = float(min(media.resolution_width, media.resolution_height) * scale_factor)
  77. min_max_values = {
  78. 'disease_count': (0, max_disease_count),
  79. 'disease_perimeter': (0.0, max_disease_perimeter),
  80. 'disease_area': (0.0, max_disease_area),
  81. 'shape_complexity': (0.0, 1.0),
  82. 'texture_roughness': (0.0, 1020.0),
  83. 'crack_width': (0.0, max_crack_width),
  84. 'avg_hue': (0.0, 179.0)
  85. }
  86. current_app.logger.debug(f'【评估病害】部分动态指标最大值:{min_max_values}')
  87. # 构造一个参数字典,确保所有需要的键都在其中
  88. params = {
  89. 'disease_count': disease_count,
  90. 'disease_perimeter': disease_perimeter,
  91. 'disease_area': disease_area,
  92. 'shape_complexity': shape_complexity,
  93. 'texture_roughness': texture_roughness,
  94. 'crack_width': crack_width,
  95. 'avg_hue': avg_hue,
  96. }
  97. # 归一化
  98. normalized_values = {
  99. key: min_max_normalize(params[key], *min_max_values[key])
  100. for key in weights.keys()
  101. }
  102. current_app.logger.debug(f'【评估病害】归一化病害指标:{normalized_values}')
  103. # 计算加权总分
  104. weighted_score = sum(normalized_values[key] * weights[key] for key in weights)
  105. # 根据得分确定病害等级和描述
  106. if weighted_score >= 0.8:
  107. return weighted_score, DiseaseGrade.CRITICAL.value, '病害情况严重,需要立即采取修复措施。'
  108. elif weighted_score >= 0.5:
  109. return weighted_score, DiseaseGrade.SEVERE.value, '病害情况重度,应尽快安排修复。'
  110. elif weighted_score >= 0.2:
  111. return weighted_score, DiseaseGrade.MODERATE.value, '病害情况中等,应安排维护。'
  112. else:
  113. return weighted_score, DiseaseGrade.MILD.value, '病害情况轻微,定期观察即可。'