数据集相对目录：原图dataset\images，边缘图标签dataset\edge，根据以下代码重新写一个指标计算模块，不需要UI,算法相同代码写法精炼，使用三种方法处理上述数据集并打印过程与结果表格，指标有准确率，精确率，召回率，F1分数，并保存在文件中，# python 3.10
# conda install numba
# conda install numpy
# conda install opencv
# conda install pyqt
# conda install scikit-learn
# conda install scikit-image
# conda install scipy
# conda install pytorch torchvision torchaudio
# conda install mamba -n base
# mamba install cupy cudatoolkit=10.2
import sys
import os
import cv2
import numpy as np
import cupy as cp
from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QImage, QPixmap
from scipy import ndimage
from skimage.color import rgb2lab, rgb2hsv
from sklearn.mixture import GaussianMixture
import torch
import torch.nn.functional as F

class GPUImageProcessor:
    def __init__(self):
        self.scales = [1, 2, 4]
        self.weights = [0.5, 0.3, 0.2]
        self.n_components = 3
        self.prob_threshold = 0.3
        
        # 检查GPU是否可用
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
        self.use_gpu = torch.cuda.is_available()
        
        # 初始化CUDA上下文
        if self.use_gpu:
            cv2.cuda.setDevice(0)
    
    def to_gpu(self, img):
        if isinstance(img, np.ndarray):
            return cp.asarray(img)
        return img
    
    def to_cpu(self, img):
        if isinstance(img, cp.ndarray):
            return cp.asnumpy(img)
        return img
    
    def rgb_to_hsi_lab(self, img):
        if self.use_gpu:
            # 将图像转换为CUDA张量
            img_tensor = torch.from_numpy(img).float().to(self.device)
            img_tensor = img_tensor / 255.0
            
            # RGB到HSI的转换
            r, g, b = img_tensor[:,:,0], img_tensor[:,:,1], img_tensor[:,:,2]
            
            # 计算HSI
            num = 0.5 * ((r - g) + (r - b))
            den = torch.sqrt((r - g)**2 + (r - b)*(g - b) + 1e-6)
            theta = torch.acos(num / (den + 1e-6))
            
            h = theta.clone()
            h[b > g] = 2*np.pi - h[b > g]
            h = h / (2*np.pi)
            
            s = 1 - 3 * torch.min(img_tensor, dim=2)[0] / (torch.sum(img_tensor, dim=2) + 1e-6)
            i = torch.mean(img_tensor, dim=2)
            
            hsi = torch.stack([h, s, i], dim=2)
            
            # RGB到LAB的转换
            lab = rgb2lab(self.to_cpu(img)) # LAB转换仍使用CPU
            lab = self.to_gpu(lab)
            
            return self.to_cpu(hsi.cpu().numpy()), self.to_cpu(lab)
        else:
            return rgb2hsv(img), rgb2lab(img)
    
    def segment_probability(self, img):
        # 转移到GPU
        img_gpu = self.to_gpu(img)
        
        # HSI-LAB双色彩空间处理
        hsi, lab = self.rgb_to_hsi_lab(img)
        
        # 特征提取
        h_channel = cp.asarray(hsi[:,:,0])
        i_channel = cp.asarray(hsi[:,:,2])
        
        # 构建特征矩阵
        features = cp.column_stack((h_channel.ravel(), i_channel.ravel()))
        
        # 转回CPU进行GMM (因为sklearn不支持GPU)
        features_cpu = cp.asnumpy(features)
        gmm = GaussianMixture(n_components=self.n_components)
        labels = gmm.fit_predict(features_cpu)
        probs = gmm.predict_proba(features_cpu)
        
        # 重构分割图
        segmentation = labels.reshape(img.shape[:2])
        probability_map = np.max(probs, axis=1).reshape(img.shape[:2])
        
        return segmentation, probability_map
    
    def multi_scale_edge(self, img):
        if self.use_gpu:
            # 转换为灰度图并移至GPU
            if len(img.shape) == 3:
                gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            else:
                gray = img
            
            # 创建CUDA流
            stream = cv2.cuda_Stream()
            
            # 上传图像到GPU
            gpu_gray = cv2.cuda_GpuMat()
            gpu_gray.upload(gray)
            
            # 创建输出图像
            edges = cp.zeros_like(cp.asarray(gray), dtype=cp.float32)
            
            for scale, weight in zip(self.scales, self.weights):
                # GPU上的高斯模糊
                gpu_blurred = cv2.cuda.GaussianBlur(gpu_gray, (0, 0), scale, scale, stream=stream)
                
                # Sobel边缘检测
                gpu_grad_x = cv2.cuda.Sobel(gpu_blurred, cv2.CV_32F, 1, 0, scale)
                gpu_grad_y = cv2.cuda.Sobel(gpu_blurred, cv2.CV_32F, 0, 1, scale)
                
                # 下载到CPU进行处理
                grad_x = gpu_grad_x.download()
                grad_y = gpu_grad_y.download()
                
                # 计算梯度幅值
                edge_scale = cp.sqrt(cp.asarray(grad_x)**2 + cp.asarray(grad_y)**2)
                
                # 权重叠加
                edges += weight * edge_scale
            
            # 归一化
            edges = (edges - edges.min()) / (edges.max() - edges.min()) * 255
            
            return self.to_cpu(edges.astype(cp.uint8))
        else:
            # If GPU is not used, perform edge detection on the CPU
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            edges = np.zeros_like(gray, dtype=np.float32)
            
            valid_ksizes = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31]
            
            for scale, weight in zip(self.scales, self.weights):
                # Find the nearest valid ksize
                ksize = min(valid_ksizes, key=lambda x: abs(x - scale))
                
                blurred = cv2.GaussianBlur(gray, (0, 0), scale, scale)
                grad_x = cv2.Sobel(blurred, cv2.CV_32F, 1, 0, ksize=ksize)
                grad_y = cv2.Sobel(blurred, cv2.CV_32F, 0, 1, ksize=ksize)
                edge_scale = np.sqrt(grad_x**2 + grad_y**2)
                edges += weight * edge_scale
            
            edges = (edges - edges.min()) / (edges.max() - edges.min()) * 255
            return edges.astype(np.uint8)
    
    def joint_detection(self, img):
        if self.use_gpu:
            # 概率分割
            segmentation, prob_map = self.segment_probability(img)
            
            # 边缘检测
            edges = self.multi_scale_edge(img)
            
            # 联合处理
            prob_map_gpu = cp.asarray(prob_map)
            edges_gpu = cp.asarray(edges)
            
            joint_result = cp.zeros_like(edges_gpu)
            mask = prob_map_gpu > self.prob_threshold
            joint_result[mask] = edges_gpu[mask]
            
            return self.to_cpu(joint_result), segmentation, edges
        else:
            # 在CPU上进行处理
            segmentation, prob_map = self.segment_probability(img)
            edges = self.multi_scale_edge(img)
            joint_result = np.zeros_like(edges)
            mask = prob_map > self.prob_threshold
            joint_result[mask] = edges[mask]
            return joint_result, segmentation, edges

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.processor = GPUImageProcessor()
        self.initUI()
        
    def initUI(self):
        self.setWindowTitle('图像处理系统')
        self.setGeometry(100, 100, 1400, 800)
        
        # 主布局
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QHBoxLayout(central_widget)
        
        # 左侧控制面板
        control_panel = QVBoxLayout()
        
        # 模式选择
        mode_group = QGroupBox('处理模式')
        mode_layout = QVBoxLayout()
        self.mode_combo = QComboBox()
        self.mode_combo.addItems(['概率分布分区', '多尺度边缘检测', '联合模式'])
        self.mode_combo.currentIndexChanged.connect(self.change_mode)
        mode_layout.addWidget(self.mode_combo)
        mode_group.setLayout(mode_layout)
        control_panel.addWidget(mode_group)
        
        # 文件选择
        self.select_btn = QPushButton('选择图片文件夹')
        self.select_btn.clicked.connect(self.select_folder)
        control_panel.addWidget(self.select_btn)
        
        self.file_list = QListWidget()
        self.file_list.itemClicked.connect(self.preview_image)
        control_panel.addWidget(self.file_list)
        
        # 参数调整
        param_group = QGroupBox('参数调整')
        param_layout = QFormLayout()
        
        # 分割参数
        self.segment_params = QWidget()
        segment_layout = QFormLayout()
        
        self.cluster_slider = QSlider(Qt.Horizontal)
        self.cluster_slider.setRange(2, 10)
        self.cluster_slider.setValue(3)
        self.cluster_slider.valueChanged.connect(self.update_preview)
        segment_layout.addRow('聚类数:', self.cluster_slider)
        
        self.cluster_spinbox = QSpinBox()
        self.cluster_spinbox.setRange(2, 10)
        self.cluster_spinbox.setValue(3)
        self.cluster_spinbox.valueChanged.connect(self.cluster_slider.setValue)
        self.cluster_slider.valueChanged.connect(self.cluster_spinbox.setValue)
        segment_layout.addRow('', self.cluster_spinbox)
        
        self.prob_slider = QSlider(Qt.Horizontal)
        self.prob_slider.setRange(1, 100)
        self.prob_slider.setValue(30)
        self.prob_slider.valueChanged.connect(self.update_preview)
        segment_layout.addRow('概率阈值:', self.prob_slider)
        
        self.prob_spinbox = QSpinBox()
        self.prob_spinbox.setRange(1, 100)
        self.prob_spinbox.setValue(30)
        self.prob_spinbox.valueChanged.connect(self.prob_slider.setValue)
        self.prob_slider.valueChanged.connect(self.prob_spinbox.setValue)
        segment_layout.addRow('', self.prob_spinbox)
        
        self.segment_params.setLayout(segment_layout)
        
        # 边缘检测参数
        self.edge_params = QWidget()
        edge_layout = QFormLayout()
        
        self.scale_weights = []
        for i, scale in enumerate(self.processor.scales):
            weight_slider = QSlider(Qt.Horizontal)
            weight_slider.setRange(0, 100)
            weight_slider.setValue(int(self.processor.weights[i] * 100))
            weight_slider.valueChanged.connect(self.update_preview)
            edge_layout.addRow(f'尺度 {scale} 权重:', weight_slider)
            
            weight_spinbox = QSpinBox()
            weight_spinbox.setRange(0, 100)
            weight_spinbox.setValue(int(self.processor.weights[i] * 100))
            weight_spinbox.valueChanged.connect(weight_slider.setValue)
            weight_slider.valueChanged.connect(weight_spinbox.setValue)
            edge_layout.addRow('', weight_spinbox)
            
            self.scale_weights.append((weight_slider, weight_spinbox))
            
        self.edge_params.setLayout(edge_layout)
        
        param_layout.addWidget(self.segment_params)
        param_layout.addWidget(self.edge_params)
        param_group.setLayout(param_layout)
        control_panel.addWidget(param_group)
        
        # 处理和保存按钮
        self.process_btn = QPushButton('处理图片')
        self.process_btn.clicked.connect(self.process_image)
        control_panel.addWidget(self.process_btn)
        
        self.save_btn = QPushButton('保存结果')
        self.save_btn.clicked.connect(self.save_result)
        control_panel.addWidget(self.save_btn)
        
        # 恢复默认值按钮
        self.reset_btn = QPushButton('恢复默认值')
        self.reset_btn.clicked.connect(self.reset_defaults)
        control_panel.addWidget(self.reset_btn)
        
        layout.addLayout(control_panel, 1)
        
        # 图像显示区域
        display_layout = QVBoxLayout()
        
        self.original_label = QLabel()
        self.original_label.setAlignment(Qt.AlignCenter)
        display_layout.addWidget(QLabel('原始图像:'))
        display_layout.addWidget(self.original_label)
        
        self.result_label = QLabel()
        self.result_label.setAlignment(Qt.AlignCenter)
        display_layout.addWidget(QLabel('处理结果:'))
        display_layout.addWidget(self.result_label)
        
        layout.addLayout(display_layout, 4)
        
        # 初始化变量
        self.current_image_path = None
        self.result_image = None
        self.current_mode = 0
        self.change_mode(0)
        
        # 长按调节定时器
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.adjust_parameter)
        self.adjust_direction = 0
        self.adjust_target = None
    
    def change_mode(self, index):
        self.current_mode = index
        self.segment_params.setVisible(index in [0, 2])
        self.edge_params.setVisible(index in [1, 2])
        if self.current_image_path:
            self.update_preview()
    
    def select_folder(self):
        folder = QFileDialog.getExistingDirectory(self, '选择图片文件夹')
        if folder:
            self.file_list.clear()
            for file in os.listdir(folder):
                if file.lower().endswith(('.png', '.jpg', '.jpeg', '.webp')):
                    self.file_list.addItem(os.path.join(folder, file))
    
    def preview_image(self, item):
        self.current_image_path = item.text()
        self.update_preview()
    
    def update_preview(self):
        if not self.current_image_path:
            return
            
        # 显示原始图像
        pixmap = QPixmap(self.current_image_path)
        pixmap = pixmap.scaled(500, 500, Qt.KeepAspectRatio)
        self.original_label.setPixmap(pixmap)
        
        # 更新参数
        self.processor.n_components = self.cluster_slider.value()
        self.processor.prob_threshold = self.prob_slider.value() / 100
        self.processor.weights = [s.value() / 100 for s, _ in self.scale_weights]
        
        # 处理图像
        img = cv2.imread(self.current_image_path)
        
        if self.current_mode == 0:  # 分割模式
            segmentation, _ = self.processor.segment_probability(img)
            self.result_image = (segmentation * (255 // self.processor.n_components)).astype(np.uint8)
        elif self.current_mode == 1:  # 边缘检测模式
            self.result_image = self.processor.multi_scale_edge(img)
        else:  # 联合模式
            self.result_image, _, _ = self.processor.joint_detection(img)
        
        # 检查 self.result_image 是否为 None
        if self.result_image is None:
            QMessageBox.warning(self, '错误', '图像处理失败，结果为空')
            return
        
        # 显示结果
        height, width = self.result_image.shape
        qimg = QImage(self.result_image.data, width, height, width, QImage.Format_Grayscale8)
        pixmap = QPixmap.fromImage(qimg)
        pixmap = pixmap.scaled(500, 500, Qt.KeepAspectRatio)
        self.result_label.setPixmap(pixmap)
    
    def process_image(self):
        if self.current_image_path:
            self.update_preview()
    
    def save_result(self):
        if self.result_image is not None:
            file_name, _ = QFileDialog.getSaveFileName(self, '保存结果', '', 
                'Images (*.png *.jpg *.jpeg *.webp)')
            if file_name:
                success, encoded_img = cv2.imencode(os.path.splitext(file_name)[1], 
                                                  self.result_image)
                if success:
                    try:
                        with open(file_name, 'wb') as f:
                            encoded_img.tofile(f)
                        QMessageBox.information(self, '提示', '保存成功!')
                    except Exception as e:
                        QMessageBox.warning(self, '错误', f'保存失败:{str(e)}')
                else:
                    QMessageBox.warning(self, '错误', '图像编码失败')
    
    def reset_defaults(self):
        self.cluster_slider.setValue(3)
        self.prob_slider.setValue(30)
        for slider, spinbox in self.scale_weights:
            slider.setValue(50)
        self.update_preview()
    
    def adjust_parameter(self):
        if self.adjust_target == 'cluster':
            self.cluster_slider.setValue(self.cluster_slider.value() + self.adjust_direction)
        elif self.adjust_target == 'prob':
            self.prob_slider.setValue(self.prob_slider.value() + self.adjust_direction)
        elif self.adjust_target == 'scale':
            for slider, spinbox in self.scale_weights:
                slider.setValue(slider.value() + self.adjust_direction)
    
    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.timer.start(50)
            self.adjust_direction = 1
            self.adjust_target = 'cluster'
        elif event.button() == Qt.RightButton:
            self.timer.start(50)
            self.adjust_direction = -1
            self.adjust_target = 'cluster'
    
    def mouseReleaseEvent(self, event):
        self.timer.stop()
        self.adjust_direction = 0
        self.adjust_target = None

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())