| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- import cv2
- import numpy as np
- from flask import current_app
- from skimage.morphology import skeletonize
- from app.constants import DiseaseGrade
- def compute_count(masks):
- """计算病害数量(通用)"""
- return masks.shape[0]
- def compute_perimeter(masks):
- """计算病害周长(通用)"""
- contours, _ = cv2.findContours(masks, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
- total_perimeter = sum(cv2.arcLength(cnt, closed=True) for cnt in contours)
- return float(total_perimeter)
- def compute_area(masks):
- """计算病害面积(通用)"""
- return float(np.sum(masks))
- def compute_shape_complexity(perimeter, area):
- """计算形状复杂度(通用)"""
- if perimeter == 0: # 避免除零错误
- return 0.0
- return float(1 - (4 * np.pi * area) / (perimeter ** 2))
- def compute_texture_roughness(masks):
- """计算纹理粗糙度,使用 Laplacian 变换后的标准差(通用)"""
- # 将掩码转换为灰度图像(0 和 1 转换到 0 和 255)
- gray_mask = (masks * 255).astype(np.uint8)
- # 计算 Laplacian 变换结果
- laplacian = cv2.Laplacian(gray_mask, cv2.CV_64F)
- # 计算 Laplacian 值的标准差作为纹理粗糙度
- texture_roughness = np.std(laplacian)
- return float(texture_roughness)
- def compute_crack_width(masks):
- """计算裂缝宽度(仅针对裂缝)"""
- # 骨架化
- skeleton = skeletonize(masks.astype(bool))
- # 距离变换
- dist_transform = cv2.distanceTransform((masks * 255).astype(np.uint8), cv2.DIST_L2, 5)
- crack_widths = dist_transform[skeleton]
- return float(2 * np.median(crack_widths)) if crack_widths.size > 0 else 0.0
- def compute_avg_hue(masks, image):
- """计算平均色调(仅针对锈蚀)"""
- hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
- hue_values = hsv_image[masks == 1, 0] # 取病害区域的 hue 通道
- if hue_values.size == 0:
- return 0.0
- H_max = 179 # OpenCV HSV 色调最大值
- adjusted_hue = H_max - np.median(hue_values)
- return float(adjusted_hue)
- def min_max_normalize(value, min_value, max_value):
- """ 归一化函数,使数据在 0 到 1 之间 """
- if max_value == min_value:
- return 0 # 避免除零错误
- return (value - min_value) / (max_value - min_value)
- def evaluate_disease_severity(disease_count, disease_perimeter, disease_area, shape_complexity, texture_roughness,
- crack_width, avg_hue, media):
- """
- 根据病害的多个指标,计算病害严重性分数和病害等级(轻度、中度、重度、严重)。
- :param disease_count: 病害数量
- :param disease_perimeter: 病害周长
- :param disease_area: 病害面积
- :param shape_complexity: 形状复杂度
- :param texture_roughness: 纹理粗糙度
- :param crack_width: 裂缝宽度
- :param avg_hue: 平均色调
- :param media: 媒体
- :return: 病害严重性分数和病害等级(轻度、中度、重度、严重)
- """
- # 读取指标权重配置
- weights = current_app.config['DISEASE_INDEX_WEIGHTS']
- # 读取裂缝缩放因子配置
- scale_factor = current_app.config['CRACK_SCALA_FACTOR']
- # 动态计算各指标最大值
- max_disease_count = (media.resolution_width * media.resolution_height * disease_count) // disease_area \
- if disease_area else 0
- max_disease_perimeter = float((2 * (media.resolution_width + media.resolution_height)))
- max_disease_area = float(media.resolution_width * media.resolution_height)
- max_crack_width = float(min(media.resolution_width, media.resolution_height) * scale_factor)
- min_max_values = {
- 'disease_count': (0, max_disease_count),
- 'disease_perimeter': (0.0, max_disease_perimeter),
- 'disease_area': (0.0, max_disease_area),
- 'shape_complexity': (0.0, 1.0),
- 'texture_roughness': (0.0, 1020.0),
- 'crack_width': (0.0, max_crack_width),
- 'avg_hue': (0.0, 179.0)
- }
- current_app.logger.debug(f'【评估病害】部分动态指标最大值:{min_max_values}')
- # 构造一个参数字典,确保所有需要的键都在其中
- params = {
- 'disease_count': disease_count,
- 'disease_perimeter': disease_perimeter,
- 'disease_area': disease_area,
- 'shape_complexity': shape_complexity,
- 'texture_roughness': texture_roughness,
- 'crack_width': crack_width,
- 'avg_hue': avg_hue,
- }
- # 归一化
- normalized_values = {
- key: min_max_normalize(params[key], *min_max_values[key])
- for key in weights.keys()
- }
- current_app.logger.debug(f'【评估病害】归一化病害指标:{normalized_values}')
- # 计算加权总分
- weighted_score = sum(normalized_values[key] * weights[key] for key in weights)
- # 根据得分确定病害等级和描述
- if weighted_score >= 0.8:
- return weighted_score, DiseaseGrade.CRITICAL.value, '病害情况严重,需要立即采取修复措施。'
- elif weighted_score >= 0.5:
- return weighted_score, DiseaseGrade.SEVERE.value, '病害情况重度,应尽快安排修复。'
- elif weighted_score >= 0.2:
- return weighted_score, DiseaseGrade.MODERATE.value, '病害情况中等,应安排维护。'
- else:
- return weighted_score, DiseaseGrade.MILD.value, '病害情况轻微,定期观察即可。'
|