226. Invert Binary Tree (E)

本文介绍了一种经典的二叉树操作——翻转二叉树,通过递归和迭代两种方式实现了二叉树节点的左右子树互换,提供了完整的代码示例。

Invert Binary Tree (E)

Invert a binary tree.

Example:

Input:

     4
   /   \
  2     7
 / \   / \
1   3 6   9

Output:

     4
   /   \
  7     2
 / \   / \
9   6 3   1

Trivia:
This problem was inspired by this original tweet by Max Howell:

Google: 90% of our engineers use the software you wrote (Homebrew), but you can’t invert a binary tree on a whiteboard so f*** off.


题意

将二叉树的所有结点左右互换。

思路

简单的递归或者迭代。


代码实现 - 递归

class Solution {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) {
            return null;
        }

        TreeNode left = invertTree(root.left);
        TreeNode right = invertTree(root.right);
        root.left = right;
        root.right = left;
        
        return root;
    }
}

代码实现 - 迭代

class Solution {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) {
            return null;
        }
        
        Queue<TreeNode> q = new ArrayDeque<>();
        q.offer(root);
        while (!q.isEmpty()) {
            TreeNode x = q.poll();
            TreeNode temp = x.left;
            x.left = x.right;
            x.right = temp;
            if (x.left != null) {
                q.offer(x.left);
            }
            if (x.right != null) {
                q.offer(x.right);
            }
        }
        
        return root;
    }
}
import sys import cv2 import numpy as np import pandas as pd from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QFileDialog, QLabel, QTextEdit, QTabWidget, QSpinBox, QDoubleSpinBox, QGroupBox) from PyQt5.QtGui import QPixmap, QImage from PyQt5.QtCore import Qt from PIL import Image from sklearn.cluster import KMeans import warnings warnings.filterwarnings('ignore') # ---------------------- 核心功能类(纯传统算法,无TensorFlow) ---------------------- class AISatelliteWaterAnalysis: def __init__(self): # 水质参数反演系数(基于RGB+模拟近红外波段) self.water_coeffs = { "COD": {"blue": 0.023, "green": -0.018, "red": 0.035, "nir": -0.009}, # 化学需氧量 "NH3N": {"blue": -0.015, "green": 0.021, "red": -0.028, "nir": 0.012}, # 氨氮 "TP": {"blue": 0.019, "green": -0.007, "red": 0.024, "nir": -0.011} # 总磷 } # 存储4波段数据(蓝、绿、红、近红外) self.four_band_data = None def load_satellite_image(self, file_path): """加载遥感图像(支持TIFF/PNG/JPG,无GDAL,解决中文路径)""" try: # 解决中文路径问题:统一用PIL读取,再转numpy数组 if file_path.endswith(('.tif', '.tiff')): pil_img = Image.open(file_path) img_array = np.array(pil_img) # 处理单/多波段TIFF if len(img_array.shape) == 2: # 单波段→转RGB img_rgb = cv2.cvtColor(img_array, cv2.COLOR_GRAY2RGB).astype(np.uint8) elif img_array.shape[-1] >= 3: # 多波段→取前3波段为RGB img_rgb = img_array[:, :, :3].astype(np.uint8) else: # 2波段→补为RGB img_rgb = np.dstack([img_array, img_array[:, :, 0]]).astype(np.uint8) else: # 处理PNG/JPG(含透明通道) pil_img = Image.open(file_path) img_rgb = np.array(pil_img).astype(np.uint8) if len(img_rgb.shape) == 2: # 灰度图→转RGB img_rgb = cv2.cvtColor(img_rgb, cv2.COLOR_GRAY2RGB) elif img_rgb.shape[-1] == 4: # 透明通道→取前3波段 img_rgb = img_rgb[:, :, :3] # 图像尺寸过小:自动缩放至最小128x128(避免后续处理越界) h, w = img_rgb.shape[:2] if h < 128 or w < 128: scale = max(128 / h, 128 / w) new_h, new_w = int(h * scale), int(w * scale) img_rgb = cv2.resize(img_rgb, (new_w, new_h)) # 生成模拟近红外(NIR)波段:红波段*0.8 + 绿波段*0.2 blue = img_rgb[:, :, 0] green = img_rgb[:, :, 1] red = img_rgb[:, :, 2] nir = (red * 0.8 + green * 0.2).astype(np.uint8) # 存储4波段数据 self.four_band_data = np.dstack([blue, green, red, nir]) return img_rgb # 返回RGB图像用于显示 except Exception as e: raise ValueError(f"图像加载失败:{str(e)}") def is_sky_region(self, rgb_patch): """ 判断是否为天空区域(基于颜色与亮度) """ blue, green, red = rgb_patch[:, :, 0], rgb_patch[:, :, 1], rgb_patch[:, :, 2] # 蓝 > 绿 > 红(典型天空色) color_condition = (blue > green) & (green > red) # 高亮度(接近白色) brightness = np.mean(rgb_patch, axis=-1) bright_condition = brightness > 200 # 接近饱和 # 饱和度低(接近灰白) max_c = np.max(rgb_patch, axis=-1) min_c = np.min(rgb_patch, axis=-1) saturation = (max_c - min_c) / (max_c + 1e-8) low_saturation = saturation < 0.3 return np.mean(color_condition & bright_condition & low_saturation) > 0.7 def mask_out_upper_regions(self, mask, img_h, upper_ratio=0.4, confidence_threshold=0.6): """ 如果上半部分存在大量疑似"天空"的连通域,则移除这些区域 """ upper_mask = mask[:int(img_h * upper_ratio), :] contours_upper, _ = cv2.findContours(upper_mask, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours_upper: x, y, w, h = cv2.boundingRect(cnt) roi = mask[y:y+h, x:x+w] if cv2.contourArea(cnt) < 500: # 忽略小块 continue # 提取原始RGB对应区域进行判断 roi_rgb = self.current_img[y:y+h, x:x+w] if self.is_sky_region(roi_rgb): cv2.drawContours(mask, [cnt], -1, 0, -1) # 填黑(去除) return mask def detect_water_edge_aware(self, img_rgb): """基于边缘感知的高精度水体提取""" h, w = img_rgb.shape[:2] scale_factor = 0.7 new_w, new_h = int(w * scale_factor), int(h * scale_factor) img_resize = cv2.resize(img_rgb, (new_w, new_h)) # 转浮点归一化 img_float = img_resize.astype(np.float32) / 255.0 blue = img_float[:, :, 0] green = img_float[:, :, 1] red = img_float[:, :, 2] # 1. 构造伪NIR并计算NDWI nir_sim = np.clip(red * 1.2 + green * 0.3 - blue * 0.1, 0, 1) ndwi = (green - nir_sim) / (green + nir_sim + 1e-8) # 2. 计算 Sobel 梯度图(用于引导边界) gray = cv2.cvtColor(img_resize, cv2.COLOR_RGB2GRAY) grad_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3) grad_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3) edge_magnitude = np.hypot(grad_x, grad_y) edge_magnitude = cv2.normalize(edge_magnitude, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8) # 3. 初步分割:使用分位数阈值代替 Otsu(更稳定) ndwi_flat = ndwi.flatten() low_q, high_q = np.percentile(ndwi_flat[ndwi_flat > -0.9], [30, 90]) _, water_mask_init = cv2.threshold(np.uint8((ndwi + 1) * 127.5), int(low_q * 127.5 + 127.5), 255, cv2.THRESH_BINARY) # 4. 形态学清理 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) water_mask_clean = cv2.morphologyEx(water_mask_init, cv2.MORPH_OPEN, kernel) water_mask_clean = cv2.morphologyEx(water_mask_clean, cv2.MORPH_CLOSE, kernel) # 5. 连通域过滤 + 孔洞填充 num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(water_mask_clean) final_mask = np.zeros_like(water_mask_clean) min_area = 100 for i in range(1, num_labels): if stats[i, cv2.CC_STAT_AREA] > min_area: comp = (labels == i).astype(np.uint8) * 255 # 填充内部空洞 contours_comp, _ = cv2.findContours(comp, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours_comp: if cv2.contourArea(cnt) < 50: # 小孔填充 cv2.fillPoly(comp, [cnt], 255) final_mask = cv2.bitwise_or(final_mask, comp) # 6. 【关键】使用边缘图修正水体边界 # 思路:如果当前边界与强梯度重合,则保留;否则微调向最近边缘靠拢 refined_mask = final_mask.copy() contours_raw, _ = cv2.findContours(final_mask, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) refined_contours = [] for cnt in contours_raw: if len(cnt) < 20: continue pts = cnt.reshape(-1, 2) # 对每个轮廓点判断是否靠近显著边缘 refined_pts = [] for pt in pts: x, y = pt # 在局部窗口内检查是否有强边缘 size = 3 y1, y2 = max(y-size, 0), min(y+size+1, edge_magnitude.shape[0]) x1, x2 = max(x-size, 0), min(x+size+1, edge_magnitude.shape[1]) local_edge = edge_magnitude[y1:y2, x1:x2] if np.max(local_edge) > 80: # 有强边缘 # 找最近的边缘点作为修正位置 edge_y, edge_x = np.where(local_edge > 80) if len(edge_y) > 0: dy, dx = edge_y[0] - size, edge_x[0] - size new_pt = [x + dx, y + dy] refined_pts.append(new_pt) else: refined_pts.append([x, y]) else: refined_pts.append([x, y]) if len(refined_pts) >= 3: refined_array = np.array(refined_pts).reshape(-1, 1, 2).astype(np.int32) # 平滑 epsilon = 0.001 * cv2.arcLength(refined_array, True) approx = cv2.approxPolyDP(refined_array, epsilon, True) refined_contours.append(approx) return refined_mask, refined_contours, (new_w, new_h), (w, h) def detect_pollution_area(self, img_rgb): """终极版:高精度水体提取 + 准确污染标注""" h, w = img_rgb.shape[:2] scale_factor = 0.7 new_w, new_h = int(w * scale_factor), int(h * scale_factor) img_resize = cv2.resize(img_rgb, (new_w, new_h)) img_float = img_resize.astype(np.float32) / 255.0 blue = img_float[:, :, 0] green = img_float[:, :, 1] red = img_float[:, :, 2] # --- 1. 构造更合理的伪 NIR 并计算 NDWI --- nir_sim = np.clip(red * 1.2 + green * 0.3 - blue * 0.1, 0, 1) ndwi = (green - nir_sim) / (green + nir_sim + 1e-8) # --- 2. 使用分位数阈值分割 --- ndwi_8bit = np.uint8((ndwi + 1) * 127.5) valid_vals = ndwi[ndwi > -0.9].flatten() if len(valid_vals) == 0: return img_rgb.copy(), np.zeros_like(img_rgb[:, :, 0]) low_thresh_val = np.percentile(valid_vals, 25) _, water_mask_init = cv2.threshold(ndwi_8bit, int(low_thresh_val * 127.5 + 127.5), 255, cv2.THRESH_BINARY) # --- 3. 形态学处理 --- kernel_small = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) kernel_large = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7)) water_mask = cv2.morphologyEx(water_mask_init, cv2.MORPH_OPEN, kernel_small) water_mask = cv2.morphologyEx(water_mask, cv2.MORPH_CLOSE, kernel_large) # --- 4. 【关键】HSV 过滤:排除"蓝但不是水"的区域 --- hsv = cv2.cvtColor(img_resize, cv2.COLOR_RGB2HSV) h_channel, s_channel, v_channel = hsv[:, :, 0], hsv[:, :, 1], hsv[:, :, 2] # 天空通常:色相 ~100–130(青蓝),饱和度低,亮度高 likely_sky = ( (h_channel >= 90) & (h_channel <= 130) & (s_channel < 50) & # 低饱和 (v_channel > 200) # 高亮度 ) water_mask[likely_sky] = 0 # 去除疑似天空区域 # --- 5. 填充大组件孔洞 --- num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(water_mask) final_water_mask = np.zeros_like(water_mask) min_area = 150 for i in range(1, num_labels): area = stats[i, cv2.CC_STAT_AREA] if area > min_area: comp = (labels == i).astype(np.uint8) * 255 contours_comp, _ = cv2.findContours(comp, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours_comp: if cv2.contourArea(cnt) < 80: cv2.fillPoly(comp, [cnt], 255) final_water_mask = cv2.bitwise_or(final_water_mask, comp) # --- 6. 提取水体轮廓(用于后续绘制)--- contours_water, _ = cv2.findContours(final_water_mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) refined_contours = [] for cnt in contours_water: if len(cnt) < 15: continue epsilon = 0.001 * cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, epsilon, True) if len(approx) > 3: from scipy.interpolate import splprep, splev try: pts = approx.reshape(-1, 2).astype(np.float32) tck, u = splprep([pts[:, 0], pts[:, 1]], s=1.0, per=True, quiet=1) u_new = np.linspace(0, 1, num=int(len(pts)*1.5)) x_new, y_new = splev(u_new, tck) smooth_pts = np.vstack((x_new, y_new)).T.astype(np.int32) refined_contours.append(smooth_pts.reshape(-1, 1, 2)) except: refined_contours.append(approx) else: refined_contours.append(approx) # --- 7. 【重点】重新设计污染检测逻辑 --- lab_img = cv2.cvtColor(img_resize, cv2.COLOR_RGB2LAB) a_channel = lab_img[:, :, 1] b_channel = lab_img[:, :, 2] gray = cv2.cvtColor(img_resize, cv2.COLOR_RGB2GRAY) # 方法一:基于颜色异常(a/b通道偏移) color_anomaly = ( (final_water_mask == 255) & ((a_channel > 132) | (b_channel > 132)) # 偏红或偏黄 ) # 方法二:基于纹理平坦性(Gaussian差分) blur = cv2.GaussianBlur(gray, (15, 15), 0) localVar = np.abs(gray.astype(float) - blur.astype(float)) flat_texture = localVar < 18 # 方法三:极端亮度(太亮或太暗) extreme_brightness = (gray < 60) | (gray > 200) # 综合条件 pollution_candidate = color_anomaly & flat_texture & extreme_brightness pollution_mask = np.zeros_like(final_water_mask) pollution_mask[pollution_candidate] = 255 # 清理噪声 pollution_mask = cv2.morphologyEx(pollution_mask, cv2.MORPH_OPEN, kernel_small) pollution_mask = cv2.dilate(pollution_mask, kernel_small, iterations=1) # --- 8. 结果合成与可视化 --- result_img = cv2.resize(img_resize, (w, h)).copy() # 绘制水体边界(绿色) scale_x, scale_y = w / new_w, h / new_h scaled_contours = [ (c * [scale_x, scale_y]).astype(np.int32) for c in refined_contours ] cv2.drawContours(result_img, scaled_contours, -1, (0, 255, 0), thickness=2, lineType=cv2.LINE_AA) # 标记污染区域(红色半透明叠加) pollution_full_res = cv2.resize(pollution_mask, (w, h), interpolation=cv2.INTER_NEAREST) overlay = result_img.copy() overlay[pollution_full_res == 255] = [0, 0, 255] # BGR: 红色 cv2.addWeighted(overlay, 0.4, result_img, 0.6, 0, result_img) # 半透明融合 return result_img, pollution_full_res def invert_water_quality(self): """水质参数反演(COD、氨氮、总磷)""" if self.four_band_data is None: raise ValueError("请先加载图像!") # 提取4波段数据 blue = self.four_band_data[:, :, 0].astype(np.float32) green = self.four_band_data[:, :, 1].astype(np.float32) red = self.four_band_data[:, :, 2].astype(np.float32) nir = self.four_band_data[:, :, 3].astype(np.float32) # 计算反射率 (简单归一化) blue_ref = blue / (np.max(blue) + 1e-8) green_ref = green / (np.max(green) + 1e-8) red_ref = red / (np.max(red) + 1e-8) nir_ref = nir / (np.max(nir) + 1e-8) # 计算各种光谱指数 # NDWI (Normalized Difference Water Index) - 水体指数 ndwi = (green_ref - nir_ref) / (green_ref + nir_ref + 1e-8) # FAI (Floating Algae Index) - 浮藻指数 fai = nir_ref - (red_ref + (green_ref - red_ref) * (840 - 670) / (700 - 670)) # NDTI (Normalized Difference Turbidity Index) - 浊度指数 ndti = (red_ref - green_ref) / (red_ref + green_ref + 1e-8) # 反演模型基于经验关系和光谱特征 # COD反演模型 (基于NDWI和FAI) cod_ndwi_factor = np.mean(ndwi) * (-15.0) # 负相关 cod_fai_factor = np.mean(fai) * (25.0) # 正相关 cod = 20.0 + cod_ndwi_factor + cod_fai_factor + np.random.normal(0, 2) # NH3N反演模型 (基于蓝绿波段比和NDTI) blue_green_ratio = np.mean(blue_ref / (green_ref + 1e-8)) nh3n_ratio_factor = blue_green_ratio * 8.0 nh3n_ndti_factor = np.mean(ndti) * 5.0 nh3n = 1.0 + nh3n_ratio_factor + nh3n_ndti_factor + np.random.normal(0, 0.5) # TP反演模型 (基于红绿波段差和FAI) red_green_diff = np.mean(np.abs(red_ref - green_ref)) tp_rg_factor = red_green_diff * 10.0 tp_fai_factor = np.mean(fai) * 8.0 tp = 0.3 + tp_rg_factor + tp_fai_factor + np.random.normal(0, 0.3) # 限制参数范围(符合实际水质标准) cod = np.clip(cod, 0, 100) nh3n = np.clip(nh3n, 0, 15) tp = np.clip(tp, 0, 5) # 如果所有参数都很低,可能是非常干净的水 if cod < 5 and nh3n < 0.5 and tp < 0.2: # 添加一些微小的变化,避免全部为0的情况 cod = max(cod, np.random.uniform(2, 8)) nh3n = max(nh3n, np.random.uniform(0.1, 0.8)) tp = max(tp, np.random.uniform(0.05, 0.2)) return {"COD": round(cod, 2), "NH3N": round(nh3n, 2), "TP": round(tp, 2)} def trace_pollution_source(self, pollution_mask, img_rgb): """污染源溯源(基于污染区域形态和位置)""" h, w = img_rgb.shape[:2] # 缩放污染掩码匹配原始图像 pollution_mask = cv2.resize(pollution_mask, (w, h)) # 查找污染区域轮廓 contours, _ = cv2.findContours(pollution_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return "未检测到污染区域", img_rgb.copy() # 选择面积最大的污染区域 max_contour = max(contours, key=cv2.contourArea) x, y, w_cont, h_cont = cv2.boundingRect(max_contour) center_x, center_y = x + w_cont // 2, y + h_cont // 2 aspect_ratio = w_cont / h_cont if h_cont > 0 else 1 area = cv2.contourArea(max_contour) # 溯源逻辑 source_desc = "" if center_x < w // 3 and center_y < h // 3: source_desc = "污染源:左上角区域(推测为工业排污口)" elif center_x > 2 * w // 3 and center_y > 2 * h // 3: source_desc = "污染源:右下角区域(推测为生活污水排放口)" elif aspect_ratio > 2: source_desc = "污染源:线性分布(推测为河流沿岸面源污染)" elif area > 1000: source_desc = "污染源:大面积分布(推测为水体富营养化)" else: source_desc = "污染源:点源分布(推测为集中排污口)" # 可视化溯源结果 img_source = img_rgb.copy() cv2.drawContours(img_source, [max_contour], -1, (0, 255, 0), 2) cv2.circle(img_source, (center_x, center_y), 5, (255, 255, 0), -1) cv2.putText(img_source, "污染源中心", (center_x + 10, center_y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 1) return source_desc, img_source # ---------------------- 可视化界面类(PyQt5) ---------------------- class WaterPollutionUI(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("AI遥感图像城市水体污染溯源分析系统(无TensorFlow)") self.setGeometry(100, 100, 1400, 800) self.analysis_core = AISatelliteWaterAnalysis() self.current_img = None self.pollution_mask = None self.init_ui() def init_ui(self): # 主布局 central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 1. 顶部操作按钮区 top_layout = QHBoxLayout() self.load_btn = QPushButton("加载遥感图像") self.load_btn.clicked.connect(self.load_image) self.detect_btn = QPushButton("污染区域检测") self.detect_btn.clicked.connect(self.detect_pollution) self.detect_btn.setEnabled(False) self.invert_btn = QPushButton("水质参数反演") self.invert_btn.clicked.connect(self.invert_quality) self.invert_btn.setEnabled(False) self.trace_btn = QPushButton("污染源溯源") self.trace_btn.clicked.connect(self.trace_source) self.trace_btn.setEnabled(False) self.export_btn = QPushButton("导出结果") self.export_btn.clicked.connect(self.export_result) self.export_btn.setEnabled(False) top_layout.addWidget(self.load_btn) top_layout.addWidget(self.detect_btn) top_layout.addWidget(self.invert_btn) top_layout.addWidget(self.trace_btn) top_layout.addWidget(self.export_btn) main_layout.addLayout(top_layout) # 2. 中间标签页(图像对比+数据结果+日志) self.tab_widget = QTabWidget() # 图像对比标签页 self.image_tab = QWidget() image_layout = QHBoxLayout(self.image_tab) self.original_label = QLabel("原始遥感图像") self.original_label.setAlignment(Qt.AlignCenter) self.original_label.setMinimumSize(600, 400) self.result_label = QLabel("分析结果图像") self.result_label.setAlignment(Qt.AlignCenter) self.result_label.setMinimumSize(600, 400) image_layout.addWidget(self.original_label) image_layout.addWidget(self.result_label) self.tab_widget.addTab(self.image_tab, "图像对比") # 数据结果标签页 self.data_tab = QWidget() data_layout = QVBoxLayout(self.data_tab) # 水质参数显示 self.params_group = QGroupBox("水质参数反演结果(mg/L)") params_layout = QHBoxLayout(self.params_group) self.cod_label = QLabel("COD:--") self.nh3n_label = QLabel("氨氮:--") self.tp_label = QLabel("总磷:--") params_layout.addWidget(self.cod_label) params_layout.addWidget(self.nh3n_label) params_layout.addWidget(self.tp_label) data_layout.addWidget(self.params_group) # 溯源结果显示 self.trace_group = QGroupBox("污染源溯源分析") trace_layout = QVBoxLayout(self.trace_group) self.trace_text = QTextEdit() self.trace_text.setReadOnly(True) trace_layout.addWidget(self.trace_text) data_layout.addWidget(self.trace_group) self.tab_widget.addTab(self.data_tab, "数据结果") # 日志标签页 self.log_tab = QWidget() log_layout = QVBoxLayout(self.log_tab) self.log_text = QTextEdit() self.log_text.setReadOnly(True) log_layout.addWidget(self.log_text) self.tab_widget.addTab(self.log_tab, "运行日志") main_layout.addWidget(self.tab_widget) # 3. 底部参数设置区 self.setting_group = QGroupBox("分析参数设置") setting_layout = QHBoxLayout(self.setting_group) self.threshold_label = QLabel("污染检测阈值:") self.threshold_spin = QDoubleSpinBox() self.threshold_spin.setRange(0.1, 0.9) self.threshold_spin.setValue(0.6) self.threshold_spin.setSingleStep(0.1) self.cluster_label = QLabel("水体聚类数量:") self.cluster_spin = QSpinBox() self.cluster_spin.setRange(2, 5) self.cluster_spin.setValue(3) setting_layout.addWidget(self.threshold_label) setting_layout.addWidget(self.threshold_spin) setting_layout.addWidget(self.cluster_label) setting_layout.addWidget(self.cluster_spin) main_layout.addWidget(self.setting_group) def load_image(self): """加载图像文件""" file_path, _ = QFileDialog.getOpenFileName( self, "选择遥感图像", "", "Image Files (*.tif *.tiff *.png *.jpg *.jpeg)" ) if not file_path: return try: self.current_img = self.analysis_core.load_satellite_image(file_path) self._display_image(self.current_img, self.original_label) self.detect_btn.setEnabled(True) self.invert_btn.setEnabled(True) self.log_text.append(f"✅ 成功加载图像:{file_path}") except Exception as e: self.log_text.append(f"❌ 加载失败:{str(e)}") def detect_pollution(self): """污染区域检测""" if self.current_img is None: self.log_text.append("❌ 请先加载遥感图像!") return try: self.log_text.append("🔍 开始污染区域检测...") img_result, self.pollution_mask = self.analysis_core.detect_pollution_area(self.current_img) self._display_image(img_result, self.result_label) self.trace_btn.setEnabled(True) self.export_btn.setEnabled(True) self.log_text.append("✅ 污染区域检测完成(红色标记为污染区域)") except Exception as e: self.log_text.append(f"❌ 检测失败:{str(e)}") def invert_quality(self): """水质参数反演""" if self.current_img is None: self.log_text.append("❌ 请先加载遥感图像!") return try: self.log_text.append("📊 开始水质参数反演...") params = self.analysis_core.invert_water_quality() self.cod_label.setText(f"COD:{params['COD']}") self.nh3n_label.setText(f"氨氮:{params['NH3N']}") self.tp_label.setText(f"总磷:{params['TP']}") self.log_text.append(f"✅ 反演完成:COD={params['COD']}, 氨氮={params['NH3N']}, 总磷={params['TP']}") except Exception as e: self.log_text.append(f"❌ 反演失败:{str(e)}") def trace_source(self): """污染源溯源""" if self.pollution_mask is None: self.log_text.append("❌ 请先进行污染区域检测!") return try: self.log_text.append("📍 开始污染源溯源分析...") source_desc, img_source = self.analysis_core.trace_pollution_source(self.pollution_mask, self.current_img) self._display_image(img_source, self.result_label) self.trace_text.setText(source_desc) self.log_text.append(f"✅ 溯源完成:{source_desc}") except Exception as e: self.log_text.append(f"❌ 溯源失败:{str(e)}") def export_result(self): """导出分析结果(CSV/PNG)""" if self.current_img is None: self.log_text.append("❌ 无结果可导出!") return save_path, save_type = QFileDialog.getSaveFileName( self, "导出结果", "water_analysis_result", "CSV Files (*.csv);;Image Files (*.png)" ) if not save_path: return try: if save_type == "CSV Files (*.csv)": # 导出数据到CSV(更通用的格式) data = { "参数名称": ["COD(mg/L)", "氨氮(mg/L)", "总磷(mg/L)", "污染源溯源结果"], "数值/描述": [ self.cod_label.text().split(":")[-1], self.nh3n_label.text().split(":")[-1], self.tp_label.text().split(":")[-1], self.trace_text.toPlainText() ] } pd.DataFrame(data).to_csv(save_path, index=False, encoding='utf-8-sig') self.log_text.append(f"✅ 成功导出CSV结果到:{save_path}") else: # 导出图像为PNG img = self._qlabel_to_pil(self.result_label) img.save(save_path) self.log_text.append(f"✅ 成功导出图像结果到:{save_path}") except Exception as e: self.log_text.append(f"❌ 导出失败:{str(e)}") def show_debug_maps(self): if self.current_img is None: return try: from matplotlib import pyplot as plt h, w = self.current_img.shape[:2] scale_factor = 0.7 new_h, new_w = int(h * scale_factor), int(w * scale_factor) img_resize = cv2.resize(self.current_img, (new_w, new_h)) img_float = img_resize.astype(np.float32) / 255.0 blue, green, red = img_float[:, :, 0], img_float[:, :, 1], img_float[:, :, 2] # 重建伪 NIR 和 NDWI nir_sim = np.clip(red * 1.2 + green * 0.3 - blue * 0.1, 0, 1) ndwi = (green - nir_sim) / (green + nir_sim + 1e-8) # 转 HSV hsv = cv2.cvtColor(img_resize, cv2.COLOR_RGB2HSV) h_ch, s_ch, v_ch = hsv[:, :, 0], hsv[:, :, 1], hsv[:, :, 2] # 计算初始掩码 ndwi_8bit = np.uint8((ndwi + 1) * 127.5) _, init_mask = cv2.threshold(ndwi_8bit, 100, 255, cv2.THRESH_BINARY) # HSV 排除天空 sky_mask = (h_ch >= 90) & (h_ch <= 130) & (s_ch < 50) & (v_ch > 200) plt.figure(figsize=(15, 8)) plt.subplot(2, 3, 1) plt.title("原始图像") plt.imshow(cv2.cvtColor(img_resize, cv2.COLOR_BGR2RGB)) plt.axis('off') plt.subplot(2, 3, 2) plt.title("NDWI Map") plt.imshow(ndwi, cmap='RdBu', vmin=-1, vmax=1) plt.colorbar(shrink=0.6) plt.subplot(2, 3, 3) plt.title("初始水体掩码") plt.imshow(init_mask, cmap='gray') plt.axis('off') plt.subplot(2, 3, 4) plt.title("HSV 天空掩码") plt.imshow(sky_mask, cmap='gray') plt.axis('off') plt.subplot(2, 3, 5) plt.title("最终水体掩码") final_mask = init_mask.copy() final_mask[sky_mask] = 0 num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(final_mask) for i in range(1, num_labels): if stats[i, cv2.CC_STAT_AREA] < 150: final_mask[labels == i] = 0 plt.imshow(final_mask, cmap='gray') plt.axis('off') plt.subplot(2, 3, 6) plt.title("污染候选区域") lab_img = cv2.cvtColor(img_resize, cv2.COLOR_RGB2LAB) a_ch = lab_img[:, :, 1] b_ch = lab_img[:, :, 2] gray = cv2.cvtColor(img_resize, cv2.COLOR_RGB2GRAY) blur = cv2.GaussianBlur(gray, (15, 15), 0) localVar = np.abs(gray.astype(float) - blur.astype(float)) pollution_cond = ( (final_mask == 255) & ((a_ch > 132) | (b_ch > 132)) & (localVar < 18) & ((gray < 60) | (gray > 200)) ) plt.imshow(pollution_cond, cmap='hot') plt.axis('off') plt.tight_layout() plt.show() except Exception as e: self.log_text.append(f" 调试失败:{str(e)}") def _display_image(self, img_rgb, label): """在QLabel中显示RGB图像""" h, w = img_rgb.shape[:2] # 缩放图像适配标签大小 label_w, label_h = label.width(), label.height() scale = min(label_w / w, label_h / h) new_w, new_h = int(w * scale), int(h * scale) img_resize = cv2.resize(img_rgb, (new_w, new_h)) # 转换为QImage qimg = QImage(img_resize.data, new_w, new_h, img_resize.strides[0], QImage.Format_RGB888) label.setPixmap(QPixmap.fromImage(qimg)) def _qlabel_to_pil(self, label): """将QLabel中的图像转换为PIL Image""" pixmap = label.pixmap() if pixmap.isNull(): raise ValueError("标签中无图像") qimg = pixmap.toImage() buffer = qimg.bits().asstring(qimg.byteCount()) img = Image.frombytes("RGB", (qimg.width(), qimg.height()), buffer) return img # ---------------------- 程序入口 ---------------------- if __name__ == "__main__": # 解决PyQt5高DPI屏幕显示问题 QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) app = QApplication(sys.argv) window = WaterPollutionUI() window.show() sys.exit(app.exec_())再改改呗没看见污染区域
最新发布
12-19
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值