深入探讨计算机视觉领域中OpenCV图像预处理技术与现代深度学习Transformer架构的完美融合, 为视觉AI应用提供高效、可靠的数据准备解决方案。

目录

  1. 1. 引言与技术背景
  2. 2. OpenCV图像预处理基础
  3. 3. Transformer模型对输入数据的要求
  4. 4. 图像预处理优化策略
  5. 5. 高级预处理技术
  6. 6. 性能优化与并行化
  7. 7. 实际应用案例
  8. 8. 未来发展趋势
  9. 9. 完整实现代码

1. 引言与技术背景

1.1 计算机视觉的发展历程

计算机视觉作为人工智能的重要分支,经历了从传统手工特征提取到深度学习自动特征学习的重大变革。在这一演进过程中, 图像预处理技术始终扮演着关键角色,为后续的模型训练和推理提供高质量的输入数据。随着Transformer架构在自然语言处理领域的巨大成功, 研究者们开始探索将其应用于计算机视觉任务,这催生了Vision Transformer (ViT)、DETR等一系列革命性模型。

然而,Transformer模型对输入数据的格式和质量要求与传统卷积神经网络存在显著差异。这种差异不仅体现在数据的组织形式上, 更体现在对数据预处理的精细化要求。OpenCV作为最成熟、应用最广泛的计算机视觉库,为解决这一挑战提供了强大的技术支撑。

1.2 OpenCV与Transformer的技术融合

OpenCV提供了丰富的图像处理函数和算法,涵盖了从基础的像素操作到复杂的计算机视觉算法。在Transformer模型的应用场景中, 这些功能可以被系统性地组织和优化,以满足现代深度学习模型对数据质量和格式的严格要求。

关键的技术融合点包括:图像尺寸标准化、色彩空间转换、噪声抑制、对比度增强、几何变换、数据增强等。 每一个环节都需要根据具体的Transformer架构特点进行精心设计和优化,以确保最终的模型性能达到最优状态。

2. OpenCV图像预处理基础

2.1 图像读取与格式处理

OpenCV支持多种图像格式的读取和处理,包括JPEG、PNG、TIFF、BMP等常见格式。在Transformer模型的应用中, 统一的图像格式处理是数据预处理流水线的第一步。cv2.imread()函数提供了灵活的读取选项, 可以指定读取模式(彩色、灰度、带透明度等)。

对于大规模数据集的处理,需要考虑内存效率和读取速度。OpenCV提供的图像解码器经过高度优化, 能够快速处理各种格式的图像文件。同时,通过合理的内存管理和批处理策略,可以显著提升数据加载的效率。

2.2 色彩空间转换与标准化

色彩空间的选择和转换对Transformer模型的性能有着重要影响。OpenCV支持多种色彩空间之间的转换, 包括RGB、BGR、HSV、LAB、YUV等。不同的色彩空间具有不同的特点和适用场景:

  • RGB/BGR:最常用的色彩空间,直接对应显示设备的三原色
  • HSV:更符合人类视觉感知,便于进行色彩相关的图像处理
  • LAB:设备无关的色彩空间,提供更准确的色彩表示
  • YUV:广泛用于视频编码和传输

在实际应用中,需要根据具体的Transformer模型架构和任务需求选择最适合的色彩空间。 例如,对于需要保持色彩一致性的任务,LAB色彩空间可能是更好的选择; 而对于需要处理光照变化的场景,HSV色彩空间的V通道提供了有价值的亮度信息。

2.3 图像几何变换

几何变换是图像预处理中的核心技术之一,包括缩放、旋转、平移、仿射变换、透视变换等。 对于Transformer模型,这些变换不仅用于数据标准化,还是数据增强的重要手段。

1

图像缩放

使用cv2.resize()函数进行图像尺寸调整,支持多种插值方法:双线性插值、双三次插值、最近邻插值等。 对于Transformer模型,通常需要将输入图像调整到固定尺寸。

2

仿射变换

通过cv2.getAffineTransform()和cv2.warpAffine()实现图像的仿射变换, 包括平移、旋转、缩放和剪切的组合变换。

3

透视变换

使用cv2.getPerspectiveTransform()和cv2.warpPerspective()进行透视校正, 特别适用于处理具有透视畸变的图像。

3. Transformer模型对输入数据的要求

3.1 Vision Transformer的输入格式

Vision Transformer (ViT)将图像划分为固定大小的patch(补丁),每个patch被当作序列中的一个token进行处理。 这种设计对输入图像的预处理提出了特殊要求:

  • 固定尺寸要求:输入图像必须具有固定的尺寸,以确保patch数量的一致性
  • Patch对齐:图像尺寸必须能够被patch大小整除
  • 归一化要求:像素值需要归一化到特定范围,通常是[0,1]或[-1,1]
  • 通道顺序:需要确保RGB通道的正确顺序

例如,对于标准的ViT-Base模型,输入图像通常被调整为224×224像素,然后划分为14×14个16×16的patch。 这要求预处理流水线能够精确地控制图像尺寸和像素值分布。

3.2 DETR目标检测模型的特殊需求

DETR (Detection Transformer)作为基于Transformer的端到端目标检测模型,对输入数据有着更加复杂的要求。 与传统的目标检测模型相比,DETR需要处理可变尺寸的输入图像,同时保持高效的计算性能。

关键的预处理要求包括:

  • 多尺度处理:支持不同尺寸的输入图像,通过padding或缩放策略统一处理
  • 长宽比保持:在调整图像尺寸时尽可能保持原始长宽比,避免物体变形
  • 边界填充:使用合适的填充策略处理尺寸不匹配的问题
  • 标注对齐:确保图像变换后标注信息的正确对应

3.3 数据增强与Transformer的兼容性

传统的数据增强技术在应用于Transformer模型时需要特别考虑模型的特点。Transformer模型对空间位置信息的处理方式 与卷积神经网络不同,这影响了数据增强策略的选择和实现。

有效的数据增强策略包括:RandomResizedCrop、ColorJitter、随机水平翻转等。 需要注意的是,一些几何变换可能会破坏patch之间的空间关系,因此需要谨慎使用。 同时,Transformer模型通常对颜色和亮度变化具有较好的鲁棒性,因此颜色空间的增强通常是安全且有效的。

4. 图像预处理优化策略

4.1 自适应图像增强技术

自适应图像增强技术能够根据图像的具体特征动态调整处理参数,以获得最佳的视觉效果和模型性能。 这种技术特别适用于处理光照条件变化较大的数据集。

1

自适应直方图均衡化

CLAHE(Contrast Limited Adaptive Histogram Equalization)技术可以有效改善图像的局部对比度, 特别适用于医学图像、监控图像等对比度较低的场景。通过限制对比度增强的幅度,避免了传统直方图均衡化可能产生的噪声放大问题。

2

自适应阈值处理

cv2.adaptiveThreshold()函数提供了基于局部邻域的自适应阈值化方法, 能够有效处理光照不均匀的图像,为后续的特征提取提供更稳定的输入。

3

多尺度图像融合

通过构建图像金字塔,在不同尺度上提取和融合特征信息, 为Transformer模型提供更丰富的多尺度特征表示。

4.2 噪声抑制与滤波优化

噪声抑制是图像预处理中的重要环节,特别是在处理低质量图像或传感器噪声较大的场景中。 OpenCV提供了多种滤波算法,每种算法都有其特定的适用场景和优化空间。

  • 高斯滤波:适用于去除高斯噪声,保持图像的整体平滑性
  • 双边滤波:在去除噪声的同时保持边缘信息,特别适用于自然图像
  • Non-local Means:基于图像自相似性的去噪算法,效果优异但计算复杂度较高
  • 形态学滤波:使用结构元素进行形态学操作,适用于二值图像和特定形状的噪声

在选择滤波算法时,需要平衡去噪效果与计算效率,同时考虑对后续Transformer模型性能的影响。 过度的滤波可能会损失重要的细节信息,而滤波不足则可能引入噪声干扰。

4.3 几何校正与畸变消除

几何校正技术用于消除由于相机镜头畸变、拍摄角度等因素引起的图像几何失真。 对于Transformer模型,几何校正不仅能提高图像质量,还能确保patch之间的空间关系的准确性。

主要的几何校正技术包括:镜头畸变校正、透视校正、图像配准等。 这些技术通过精确的数学变换,将失真的图像恢复到理想的几何状态,为模型提供更准确的空间信息。 在实际应用中,通常需要预先标定相机参数,然后使用这些参数进行实时的畸变校正。

5. 高级预处理技术

5.1 频域处理与特征增强

频域处理技术通过傅里叶变换等方法在频率域对图像进行处理,能够实现一些空域难以实现的效果。 对于Transformer模型,频域处理可以用于增强特定频率成分,抑制噪声,或者进行特殊的图像增强。

关键的频域处理技术包括:

  • 低通滤波:去除高频噪声,保留图像的主要结构信息
  • 高通滤波:增强图像的边缘和细节信息
  • 带通滤波:保留特定频率范围的信息,用于特征增强
  • 同态滤波:同时处理照明和反射成分,改善图像的动态范围

在实际应用中,频域处理通常与空域处理相结合,形成复合的预处理流水线。 需要注意的是,频域处理可能会引入一些人工伪影,因此需要仔细调整参数和验证效果。

5.2 多尺度特征提取与融合

多尺度特征提取是计算机视觉中的重要技术,通过在不同尺度上分析图像,可以获得更全面的特征表示。 对于Transformer模型,多尺度特征可以提供丰富的上下文信息,提高模型的性能。

1

图像金字塔构建

使用高斯金字塔或拉普拉斯金字塔构建多尺度图像表示, 每一层代表不同的分辨率级别,为模型提供多尺度的视觉信息。

2

尺度空间特征检测

使用SIFT、SURF等尺度不变特征检测算法, 在多个尺度上检测关键点和提取特征描述符。

3

特征融合策略

将不同尺度的特征进行有效融合,可以采用简单的拼接、加权平均, 或者更复杂的注意力机制进行特征整合。

5.3 智能裁剪与内容感知处理

智能裁剪技术能够根据图像内容自动确定最佳的裁剪区域,保留最重要的视觉信息。 这对于处理不同长宽比的输入图像特别有用,可以在保持重要内容的同时满足模型的输入要求。

内容感知处理技术包括:基于显著性的裁剪、基于对象检测的智能裁剪、基于人脸检测的人像裁剪等。 这些技术使用计算机视觉算法分析图像内容,自动识别重要区域,然后进行相应的处理。 相比简单的中心裁剪或随机裁剪,智能裁剪能够显著提高模型的性能和用户体验。

6. 性能优化与并行化

6.1 内存管理与缓存优化

在处理大规模图像数据时,内存管理是一个关键的性能瓶颈。OpenCV提供了多种内存管理策略, 可以有效减少内存分配和释放的开销,提高处理速度。

  • 内存池技术:预分配大块内存,减少频繁的内存分配操作
  • 就地操作:尽可能使用就地操作,避免不必要的内存拷贝
  • 数据类型优化:选择合适的数据类型,平衡精度和内存使用
  • 缓存友好的访问模式:优化数据访问顺序,提高缓存命中率

此外,合理的批处理策略也能显著提高内存使用效率。通过将多个图像组织成批次进行处理, 可以减少函数调用开销,提高并行度,同时更好地利用现代处理器的向量化指令。

6.2 GPU加速与OpenCL优化

OpenCV提供了丰富的GPU加速功能,通过CUDA和OpenCL接口,可以将计算密集型的图像处理操作 转移到GPU上执行,获得显著的性能提升。

GPU加速的主要优势包括:

  • 并行计算能力:GPU拥有大量的计算核心,适合并行图像处理
  • 高内存带宽:GPU内存带宽远高于CPU,适合处理大尺寸图像
  • 专用指令集:现代GPU提供了专门的图像处理指令
  • 异步执行:可以与CPU计算重叠,提高整体吞吐量

在实际应用中,需要考虑数据传输的开销。对于小尺寸图像或简单操作, GPU加速可能不会带来明显的性能提升,甚至可能因为数据传输开销而降低性能。 因此,需要根据具体的应用场景选择合适的加速策略。

6.3 多线程处理与流水线优化

多线程处理是提高图像预处理性能的重要手段。OpenCV支持多种并行化策略, 包括基于OpenMP的自动并行化和手动的线程管理。

1

数据并行

将大图像分割成多个子区域,在不同线程中并行处理,最后合并结果。 这种方法适用于像素级的操作,如滤波、增强等。

2

任务并行

将不同的处理步骤分配到不同的线程中执行,形成处理流水线。 这种方法可以充分利用多核处理器的计算能力。

3

批处理优化

将多个图像组织成批次,利用SIMD指令和缓存局部性, 提高处理效率。现代处理器的向量化指令可以同时处理多个像素。

7. 实际应用案例

7.1 医学图像分析

在医学图像分析领域,OpenCV预处理技术与Transformer模型的结合展现出巨大的潜力。 医学图像通常具有特殊的成像特点,如对比度低、噪声大、分辨率要求高等,这对预处理技术提出了更高的要求。

典型的应用场景包括:

  • X光影像分析:使用CLAHE增强对比度,结合Transformer进行病灶检测
  • CT/MRI图像分割:多尺度处理结合Vision Transformer进行精确分割
  • 病理图像分析:颜色归一化结合Transformer进行细胞分类
  • 眼底图像筛查:几何校正结合DETR进行视网膜病变检测

在这些应用中,预处理的质量直接影响诊断的准确性。因此,需要特别注意保持图像的医学意义, 避免过度处理导致重要信息的丢失。同时,还需要考虑不同设备、不同协议产生的图像差异, 设计鲁棒的预处理流程。

7.2 自动驾驶视觉感知

自动驾驶系统对视觉感知的实时性和准确性要求极高。在这个领域, OpenCV预处理技术需要与Transformer模型紧密配合,处理各种复杂的道路场景。

关键的技术挑战包括:

  • 多相机融合:处理来自多个相机的图像,进行几何校正和视角统一
  • 动态光照适应:应对日夜变化、天气变化等光照条件的影响
  • 实时处理要求:在保证精度的前提下满足实时性要求
  • 恶劣天气处理:雨雪、雾霾等天气条件下的图像增强

DETR等基于Transformer的目标检测模型在处理复杂场景时表现出色, 但需要高质量的预处理来确保检测精度。特别是在处理远距离小目标、部分遮挡目标时, 预处理的质量往往决定了检测的成败。

7.3 工业质量检测

在工业质量检测领域,Vision Transformer模型结合精心设计的OpenCV预处理流程, 能够实现高精度的缺陷检测和质量评估。工业图像通常具有高分辨率、对精度要求极高的特点。

典型的预处理流程包括:几何校正消除透视畸变、光照归一化确保一致的检测条件、 多尺度增强突出细微缺陷、背景抑制突出感兴趣区域等。这些技术的组合使用, 能够显著提高Transformer模型在工业检测任务中的性能,实现超越人眼的检测精度和速度。

8. 未来发展趋势

8.1 端到端学习与自适应预处理

未来的发展趋势是将图像预处理融入到深度学习模型的训练过程中,实现端到端的优化。 这种方法能够自动学习最适合特定任务的预处理参数,减少人工调优的工作量。

可学习的预处理模块包括:可微分的图像变换、神经网络控制的滤波参数、 自适应的数据增强策略等。这些技术将使得预处理过程更加智能化和自动化, 同时保持与Transformer模型的最佳兼容性。

8.2 实时处理与边缘计算

随着边缘计算设备性能的提升,在资源受限的环境中部署Transformer模型成为可能。 这要求预处理算法具有更高的效率和更低的计算复杂度。

未来的优化方向包括:算法压缩和量化、专用硬件加速、动态精度调整等。 这些技术将使得复杂的预处理算法能够在移动设备、嵌入式系统等平台上高效运行, 为更广泛的应用场景提供支持。

8.3 多模态融合与跨域适应

未来的Transformer模型将更多地涉及多模态数据的处理,包括图像、文本、音频等。 这要求预处理技术具备跨模态的处理能力,能够统一不同类型数据的表示格式。

同时,跨域适应技术将使得在一个领域训练的模型能够快速适应新的应用场景, 减少重新训练的成本。预处理技术在这个过程中发挥关键作用, 通过领域自适应的预处理策略,缩小不同领域数据的差异。

9. 完整实现代码

基于PyQt6的OpenCV图像预处理工具

以下是一个完整的PyQt6应用程序,实现了本文讨论的各种OpenCV图像预处理技术, 专门为Transformer模型的输入准备而设计。该工具提供了直观的图形界面, 支持实时预览和批量处理功能。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
OpenCV图像预处理技术在Transformer模型输入准备阶段的优化策略与实现方法
完整的PyQt6图形界面应用程序

作者: 丁林松
邮箱: cnsilan@163.com
创建日期: 2024年
版本: 1.0

这个应用程序演示了如何使用OpenCV进行图像预处理,
专门为Transformer模型(如Vision Transformer、DETR等)的输入准备而优化。
"""

import sys
import os
import cv2
import numpy as np
from typing import Optional, Tuple, List, Dict, Any
import json
import logging
from datetime import datetime
from dataclasses import dataclass, asdict
from pathlib import Path

from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QGridLayout, QTabWidget, QGroupBox, QLabel, QPushButton,
    QSlider, QSpinBox, QDoubleSpinBox, QComboBox, QCheckBox,
    QProgressBar, QTextEdit, QFileDialog, QMessageBox,
    QSplitter, QScrollArea, QFrame, QButtonGroup, QRadioButton,
    QTableWidget, QTableWidgetItem, QHeaderView, QMenuBar,
    QMenu, QStatusBar, QToolBar, QSizePolicy
)
from PyQt6.QtCore import (
    Qt, QThread, pyqtSignal, QTimer, QSize, QRect,
    QMutex, QWaitCondition, QRunnable, QThreadPool
)
from PyQt6.QtGui import (
    QPixmap, QImage, QPainter, QPen, QBrush, QColor,
    QFont, QIcon, QAction, QKeySequence, QPalette
)


# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('opencv_transformer_preprocessor.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)


@dataclass
class PreprocessConfig:
    """预处理配置数据类"""
    # 基础变换参数
    target_size: Tuple[int, int] = (224, 224)
    maintain_aspect_ratio: bool = True
    interpolation_method: int = cv2.INTER_LINEAR
    
    # 颜色空间
    color_space: str = "RGB"  # RGB, BGR, HSV, LAB, YUV
    normalize_type: str = "zero_one"  # zero_one, neg_one_one, imagenet
    
    # 增强参数
    brightness_factor: float = 1.0
    contrast_factor: float = 1.0
    saturation_factor: float = 1.0
    gamma_correction: float = 1.0
    
    # 滤波参数
    enable_gaussian_blur: bool = False
    gaussian_kernel_size: int = 5
    gaussian_sigma: float = 1.0
    
    enable_bilateral_filter: bool = False
    bilateral_d: int = 9
    bilateral_sigma_color: float = 75.0
    bilateral_sigma_space: float = 75.0
    
    enable_clahe: bool = False
    clahe_clip_limit: float = 2.0
    clahe_tile_grid_size: Tuple[int, int] = (8, 8)
    
    # 几何变换
    enable_rotation: bool = False
    rotation_angle: float = 0.0
    
    enable_perspective_correction: bool = False
    perspective_strength: float = 0.1
    
    # 数据增强
    enable_horizontal_flip: bool = False
    enable_vertical_flip: bool = False
    enable_random_crop: bool = False
    crop_scale_range: Tuple[float, float] = (0.8, 1.0)
    
    # 高级特征
    enable_multi_scale: bool = False
    scale_factors: List[float] = None
    
    def __post_init__(self):
        if self.scale_factors is None:
            self.scale_factors = [0.5, 1.0, 1.5]


class ImageProcessor:
    """核心图像处理类"""
    
    def __init__(self):
        self.config = PreprocessConfig()
        
    def process_image(self, image: np.ndarray, config: PreprocessConfig = None) -> Dict[str, Any]:
        """
        主要的图像处理函数
        
        Args:
            image: 输入图像 (H, W, C)
            config: 处理配置
            
        Returns:
            处理结果字典,包含处理后的图像和元数据
        """
        if config is None:
            config = self.config
            
        try:
            result = {
                'original_shape': image.shape,
                'processing_steps': [],
                'metadata': {}
            }
            
            processed_image = image.copy()
            
            # 1. 颜色空间转换
            processed_image = self._convert_color_space(processed_image, config)
            result['processing_steps'].append('color_space_conversion')
            
            # 2. 尺寸调整
            processed_image = self._resize_image(processed_image, config)
            result['processing_steps'].append('resize')
            
            # 3. 滤波处理
            if config.enable_gaussian_blur:
                processed_image = self._apply_gaussian_blur(processed_image, config)
                result['processing_steps'].append('gaussian_blur')
                
            if config.enable_bilateral_filter:
                processed_image = self._apply_bilateral_filter(processed_image, config)
                result['processing_steps'].append('bilateral_filter')
                
            if config.enable_clahe:
                processed_image = self._apply_clahe(processed_image, config)
                result['processing_steps'].append('clahe')
            
            # 4. 颜色增强
            processed_image = self._apply_color_enhancement(processed_image, config)
            result['processing_steps'].append('color_enhancement')
            
            # 5. 几何变换
            if config.enable_rotation:
                processed_image = self._apply_rotation(processed_image, config)
                result['processing_steps'].append('rotation')
                
            if config.enable_perspective_correction:
                processed_image = self._apply_perspective_correction(processed_image, config)
                result['processing_steps'].append('perspective_correction')
            
            # 6. 数据增强
            if config.enable_horizontal_flip:
                processed_image = cv2.flip(processed_image, 1)
                result['processing_steps'].append('horizontal_flip')
                
            if config.enable_vertical_flip:
                processed_image = cv2.flip(processed_image, 0)
                result['processing_steps'].append('vertical_flip')
            
            # 7. 归一化
            processed_image = self._normalize_image(processed_image, config)
            result['processing_steps'].append('normalization')
            
            # 8. 多尺度处理(如果启用)
            if config.enable_multi_scale:
                multi_scale_images = self._generate_multi_scale(processed_image, config)
                result['multi_scale_images'] = multi_scale_images
                result['processing_steps'].append('multi_scale')
            
            result['processed_image'] = processed_image
            result['final_shape'] = processed_image.shape
            
            return result
            
        except Exception as e:
            logger.error(f"图像处理过程中发生错误: {str(e)}")
            raise
    
    def _convert_color_space(self, image: np.ndarray, config: PreprocessConfig) -> np.ndarray:
        """颜色空间转换"""
        if config.color_space == "RGB" and len(image.shape) == 3:
            return cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        elif config.color_space == "HSV":
            return cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
        elif config.color_space == "LAB":
            return cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
        elif config.color_space == "YUV":
            return cv2.cvtColor(image, cv2.COLOR_BGR2YUV)
        return image
    
    def _resize_image(self, image: np.ndarray, config: PreprocessConfig) -> np.ndarray:
        """智能图像尺寸调整"""
        h, w = image.shape[:2]
        target_w, target_h = config.target_size
        
        if config.maintain_aspect_ratio:
            # 保持长宽比的智能缩放
            scale = min(target_w / w, target_h / h)
            new_w, new_h = int(w * scale), int(h * scale)
            
            # 缩放图像
            resized = cv2.resize(image, (new_w, new_h), interpolation=config.interpolation_method)
            
            # 创建目标尺寸的画布并居中放置
            if len(image.shape) == 3:
                canvas = np.zeros((target_h, target_w, image.shape[2]), dtype=image.dtype)
            else:
                canvas = np.zeros((target_h, target_w), dtype=image.dtype)
                
            y_offset = (target_h - new_h) // 2
            x_offset = (target_w - new_w) // 2
            
            if len(image.shape) == 3:
                canvas[y_offset:y_offset+new_h, x_offset:x_offset+new_w, :] = resized
            else:
                canvas[y_offset:y_offset+new_h, x_offset:x_offset+new_w] = resized
                
            return canvas
        else:
            return cv2.resize(image, config.target_size, interpolation=config.interpolation_method)
    
    def _apply_gaussian_blur(self, image: np.ndarray, config: PreprocessConfig) -> np.ndarray:
        """应用高斯模糊"""
        kernel_size = config.gaussian_kernel_size
        if kernel_size % 2 == 0:
            kernel_size += 1
        return cv2.GaussianBlur(image, (kernel_size, kernel_size), config.gaussian_sigma)
    
    def _apply_bilateral_filter(self, image: np.ndarray, config: PreprocessConfig) -> np.ndarray:
        """应用双边滤波"""
        return cv2.bilateralFilter(image, config.bilateral_d, 
                                 config.bilateral_sigma_color, 
                                 config.bilateral_sigma_space)
    
    def _apply_clahe(self, image: np.ndarray, config: PreprocessConfig) -> np.ndarray:
        """应用CLAHE(对比度限制自适应直方图均衡化)"""
        clahe = cv2.createCLAHE(clipLimit=config.clahe_clip_limit, 
                               tileGridSize=config.clahe_tile_grid_size)
        
        if len(image.shape) == 3:
            # 彩色图像:在LAB色彩空间的L通道应用CLAHE
            lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
            lab[:, :, 0] = clahe.apply(lab[:, :, 0])
            return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)
        else:
            # 灰度图像
            return clahe.apply(image)
    
    def _apply_color_enhancement(self, image: np.ndarray, config: PreprocessConfig) -> np.ndarray:
        """应用颜色增强"""
        if len(image.shape) != 3:
            return image
            
        # 亮度调整
        if config.brightness_factor != 1.0:
            image = cv2.convertScaleAbs(image, alpha=1, beta=(config.brightness_factor - 1) * 50)
        
        # 对比度调整
        if config.contrast_factor != 1.0:
            image = cv2.convertScaleAbs(image, alpha=config.contrast_factor, beta=0)
        
        # Gamma校正
        if config.gamma_correction != 1.0:
            gamma_table = np.array([((i / 255.0) ** (1.0 / config.gamma_correction)) * 255
                                  for i in np.arange(0, 256)]).astype("uint8")
            image = cv2.LUT(image, gamma_table)
        
        # 饱和度调整(HSV色彩空间)
        if config.saturation_factor != 1.0:
            hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV).astype(np.float32)
            hsv[:, :, 1] = hsv[:, :, 1] * config.saturation_factor
            hsv[:, :, 1] = np.clip(hsv[:, :, 1], 0, 255)
            image = cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2BGR)
        
        return image
    
    def _apply_rotation(self, image: np.ndarray, config: PreprocessConfig) -> np.ndarray:
        """应用旋转变换"""
        h, w = image.shape[:2]
        center = (w // 2, h // 2)
        
        rotation_matrix = cv2.getRotationMatrix2D(center, config.rotation_angle, 1.0)
        
        # 计算旋转后的边界框
        cos = np.abs(rotation_matrix[0, 0])
        sin = np.abs(rotation_matrix[0, 1])
        new_w = int((h * sin) + (w * cos))
        new_h = int((h * cos) + (w * sin))
        
        # 调整旋转中心
        rotation_matrix[0, 2] += (new_w / 2) - center[0]
        rotation_matrix[1, 2] += (new_h / 2) - center[1]
        
        return cv2.warpAffine(image, rotation_matrix, (new_w, new_h))
    
    def _apply_perspective_correction(self, image: np.ndarray, config: PreprocessConfig) -> np.ndarray:
        """应用透视校正"""
        h, w = image.shape[:2]
        strength = config.perspective_strength
        
        # 定义源点和目标点
        src_points = np.float32([[0, 0], [w, 0], [w, h], [0, h]])
        
        # 添加透视畸变
        offset = strength * min(w, h) * 0.1
        dst_points = np.float32([
            [offset, offset],
            [w - offset, offset],
            [w - offset, h - offset],
            [offset, h - offset]
        ])
        
        perspective_matrix = cv2.getPerspectiveTransform(src_points, dst_points)
        return cv2.warpPerspective(image, perspective_matrix, (w, h))
    
    def _normalize_image(self, image: np.ndarray, config: PreprocessConfig) -> np.ndarray:
        """图像归一化"""
        image_float = image.astype(np.float32)
        
        if config.normalize_type == "zero_one":
            return image_float / 255.0
        elif config.normalize_type == "neg_one_one":
            return (image_float / 255.0) * 2.0 - 1.0
        elif config.normalize_type == "imagenet":
            # ImageNet标准化
            mean = np.array([0.485, 0.456, 0.406]) * 255
            std = np.array([0.229, 0.224, 0.225]) * 255
            return (image_float - mean) / std
        
        return image_float
    
    def _generate_multi_scale(self, image: np.ndarray, config: PreprocessConfig) -> List[np.ndarray]:
        """生成多尺度图像"""
        multi_scale_images = []
        h, w = image.shape[:2]
        
        for scale in config.scale_factors:
            new_h, new_w = int(h * scale), int(w * scale)
            scaled_image = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_LINEAR)
            multi_scale_images.append(scaled_image)
        
        return multi_scale_images


class ProcessingWorker(QThread):
    """图像处理工作线程"""
    
    progress_updated = pyqtSignal(int)
    result_ready = pyqtSignal(dict)
    error_occurred = pyqtSignal(str)
    
    def __init__(self, image_paths: List[str], config: PreprocessConfig):
        super().__init__()
        self.image_paths = image_paths
        self.config = config
        self.processor = ImageProcessor()
        self._is_cancelled = False
    
    def run(self):
        """执行批量图像处理"""
        try:
            total_images = len(self.image_paths)
            results = []
            
            for i, image_path in enumerate(self.image_paths):
                if self._is_cancelled:
                    break
                
                # 读取图像
                image = cv2.imread(image_path)
                if image is None:
                    logger.warning(f"无法读取图像: {image_path}")
                    continue
                
                # 处理图像
                result = self.processor.process_image(image, self.config)
                result['source_path'] = image_path
                results.append(result)
                
                # 更新进度
                progress = int((i + 1) / total_images * 100)
                self.progress_updated.emit(progress)
            
            self.result_ready.emit({
                'results': results,
                'total_processed': len(results),
                'config': asdict(self.config)
            })
            
        except Exception as e:
            self.error_occurred.emit(str(e))
    
    def cancel(self):
        """取消处理"""
        self._is_cancelled = True


class ImageWidget(QLabel):
    """自定义图像显示组件"""
    
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setMinimumSize(400, 300)
        self.setStyleSheet("""
            QLabel {
                border: 2px dashed #5D5CDE;
                border-radius: 10px;
                background-color: #f8f9fa;
                color: #6c757d;
            }
        """)
        self.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.setText("点击加载图像或拖拽图像到此处")
        self.setAcceptDrops(True)
        self._pixmap = None
        
    def set_image(self, image: np.ndarray):
        """设置显示的图像"""
        if len(image.shape) == 3:
            h, w, ch = image.shape
            bytes_per_line = ch * w
            
            # 确保图像数据类型正确
            if image.dtype != np.uint8:
                image = (image * 255).astype(np.uint8) if image.max() <= 1.0 else image.astype(np.uint8)
            
            if ch == 3:
                qt_image = QImage(image.data, w, h, bytes_per_line, QImage.Format.Format_RGB888)
            else:
                qt_image = QImage(image.data, w, h, bytes_per_line, QImage.Format.Format_RGBA8888)
        else:
            h, w = image.shape
            bytes_per_line = w
            if image.dtype != np.uint8:
                image = (image * 255).astype(np.uint8) if image.max() <= 1.0 else image.astype(np.uint8)
            qt_image = QImage(image.data, w, h, bytes_per_line, QImage.Format.Format_Grayscale8)
        
        self._pixmap = QPixmap.fromImage(qt_image)
        self._update_display()
    
    def _update_display(self):
        """更新显示"""
        if self._pixmap:
            scaled_pixmap = self._pixmap.scaled(
                self.size(), 
                Qt.AspectRatioMode.KeepAspectRatio, 
                Qt.TransformationMode.SmoothTransformation
            )
            self.setPixmap(scaled_pixmap)
        
    def resizeEvent(self, event):
        """处理窗口大小变化"""
        super().resizeEvent(event)
        self._update_display()
    
    def dragEnterEvent(self, event):
        """拖拽进入事件"""
        if event.mimeData().hasUrls():
            event.accept()
        else:
            event.ignore()
    
    def dropEvent(self, event):
        """拖拽放置事件"""
        files = [u.toLocalFile() for u in event.mimeData().urls()]
        if files:
            self.parent().load_image(files[0])


class ControlPanel(QWidget):
    """控制面板"""
    
    config_changed = pyqtSignal(PreprocessConfig)
    
    def __init__(self, parent=None):
        super().__init__(parent)
        self.config = PreprocessConfig()
        self._setup_ui()
        self._connect_signals()
    
    def _setup_ui(self):
        """设置UI"""
        layout = QVBoxLayout(self)
        
        # 创建选项卡
        tab_widget = QTabWidget()
        
        # 基础设置选项卡
        basic_tab = self._create_basic_tab()
        tab_widget.addTab(basic_tab, "基础设置")
        
        # 滤波选项卡
        filter_tab = self._create_filter_tab()
        tab_widget.addTab(filter_tab, "滤波处理")
        
        # 增强选项卡
        enhancement_tab = self._create_enhancement_tab()
        tab_widget.addTab(enhancement_tab, "图像增强")
        
        # 变换选项卡
        transform_tab = self._create_transform_tab()
        tab_widget.addTab(transform_tab, "几何变换")
        
        # 高级选项卡
        advanced_tab = self._create_advanced_tab()
        tab_widget.addTab(advanced_tab, "高级选项")
        
        layout.addWidget(tab_widget)
        
        # 控制按钮
        button_layout = QHBoxLayout()
        
        self.process_btn = QPushButton("处理图像")
        self.process_btn.setStyleSheet("""
            QPushButton {
                background-color: #5D5CDE;
                color: white;
                border: none;
                padding: 10px 20px;
                border-radius: 5px;
                font-weight: bold;
            }
            QPushButton:hover {
                background-color: #4A4BC9;
            }
        """)
        
        self.reset_btn = QPushButton("重置参数")
        self.save_config_btn = QPushButton("保存配置")
        self.load_config_btn = QPushButton("加载配置")
        
        button_layout.addWidget(self.process_btn)
        button_layout.addWidget(self.reset_btn)
        button_layout.addWidget(self.save_config_btn)
        button_layout.addWidget(self.load_config_btn)
        
        layout.addLayout(button_layout)
    
    def _create_basic_tab(self) -> QWidget:
        """创建基础设置选项卡"""
        widget = QWidget()
        layout = QVBoxLayout(widget)
        
        # 尺寸设置
        size_group = QGroupBox("图像尺寸")
        size_layout = QGridLayout(size_group)
        
        size_layout.addWidget(QLabel("目标宽度:"), 0, 0)
        self.width_spinbox = QSpinBox()
        self.width_spinbox.setRange(32, 2048)
        self.width_spinbox.setValue(self.config.target_size[0])
        size_layout.addWidget(self.width_spinbox, 0, 1)
        
        size_layout.addWidget(QLabel("目标高度:"), 1, 0)
        self.height_spinbox = QSpinBox()
        self.height_spinbox.setRange(32, 2048)
        self.height_spinbox.setValue(self.config.target_size[1])
        size_layout.addWidget(self.height_spinbox, 1, 1)
        
        self.maintain_aspect_checkbox = QCheckBox("保持长宽比")
        self.maintain_aspect_checkbox.setChecked(self.config.maintain_aspect_ratio)
        size_layout.addWidget(self.maintain_aspect_checkbox, 2, 0, 1, 2)
        
        size_layout.addWidget(QLabel("插值方法:"), 3, 0)
        self.interpolation_combo = QComboBox()
        self.interpolation_combo.addItems([
            "最近邻", "双线性", "双三次", "面积", "兰索斯"
        ])
        self.interpolation_combo.setCurrentIndex(1)  # 双线性
        size_layout.addWidget(self.interpolation_combo, 3, 1)
        
        layout.addWidget(size_group)
        
        # 颜色空间设置
        color_group = QGroupBox("颜色空间")
        color_layout = QGridLayout(color_group)
        
        color_layout.addWidget(QLabel("颜色空间:"), 0, 0)
        self.color_space_combo = QComboBox()
        self.color_space_combo.addItems(["RGB", "BGR", "HSV", "LAB", "YUV"])
        color_layout.addWidget(self.color_space_combo, 0, 1)
        
        color_layout.addWidget(QLabel("归一化方式:"), 1, 0)
        self.normalize_combo = QComboBox()
        self.normalize_combo.addItems(["[0,1]", "[-1,1]", "ImageNet"])
        color_layout.addWidget(self.normalize_combo, 1, 1)
        
        layout.addWidget(color_group)
        layout.addStretch()
        
        return widget
    
    def _create_filter_tab(self) -> QWidget:
        """创建滤波选项卡"""
        widget = QWidget()
        layout = QVBoxLayout(widget)
        
        # 高斯滤波
        gaussian_group = QGroupBox("高斯滤波")
        gaussian_layout = QGridLayout(gaussian_group)
        
        self.gaussian_enable_checkbox = QCheckBox("启用高斯滤波")
        gaussian_layout.addWidget(self.gaussian_enable_checkbox, 0, 0, 1, 2)
        
        gaussian_layout.addWidget(QLabel("核大小:"), 1, 0)
        self.gaussian_kernel_spinbox = QSpinBox()
        self.gaussian_kernel_spinbox.setRange(3, 31)
        self.gaussian_kernel_spinbox.setSingleStep(2)
        self.gaussian_kernel_spinbox.setValue(5)
        gaussian_layout.addWidget(self.gaussian_kernel_spinbox, 1, 1)
        
        gaussian_layout.addWidget(QLabel("Sigma:"), 2, 0)
        self.gaussian_sigma_spinbox = QDoubleSpinBox()
        self.gaussian_sigma_spinbox.setRange(0.1, 10.0)
        self.gaussian_sigma_spinbox.setSingleStep(0.1)
        self.gaussian_sigma_spinbox.setValue(1.0)
        gaussian_layout.addWidget(self.gaussian_sigma_spinbox, 2, 1)
        
        layout.addWidget(gaussian_group)
        
        # 双边滤波
        bilateral_group = QGroupBox("双边滤波")
        bilateral_layout = QGridLayout(bilateral_group)
        
        self.bilateral_enable_checkbox = QCheckBox("启用双边滤波")
        bilateral_layout.addWidget(self.bilateral_enable_checkbox, 0, 0, 1, 2)
        
        bilateral_layout.addWidget(QLabel("邻域直径:"), 1, 0)
        self.bilateral_d_spinbox = QSpinBox()
        self.bilateral_d_spinbox.setRange(3, 31)
        self.bilateral_d_spinbox.setValue(9)
        bilateral_layout.addWidget(self.bilateral_d_spinbox, 1, 1)
        
        bilateral_layout.addWidget(QLabel("颜色Sigma:"), 2, 0)
        self.bilateral_sigma_color_spinbox = QDoubleSpinBox()
        self.bilateral_sigma_color_spinbox.setRange(1.0, 200.0)
        self.bilateral_sigma_color_spinbox.setValue(75.0)
        bilateral_layout.addWidget(self.bilateral_sigma_color_spinbox, 2, 1)
        
        bilateral_layout.addWidget(QLabel("空间Sigma:"), 3, 0)
        self.bilateral_sigma_space_spinbox = QDoubleSpinBox()
        self.bilateral_sigma_space_spinbox.setRange(1.0, 200.0)
        self.bilateral_sigma_space_spinbox.setValue(75.0)
        bilateral_layout.addWidget(self.bilateral_sigma_space_spinbox, 3, 1)
        
        layout.addWidget(bilateral_group)
        
        # CLAHE
        clahe_group = QGroupBox("CLAHE对比度增强")
        clahe_layout = QGridLayout(clahe_group)
        
        self.clahe_enable_checkbox = QCheckBox("启用CLAHE")
        clahe_layout.addWidget(self.clahe_enable_checkbox, 0, 0, 1, 2)
        
        clahe_layout.addWidget(QLabel("裁剪限制:"), 1, 0)
        self.clahe_clip_spinbox = QDoubleSpinBox()
        self.clahe_clip_spinbox.setRange(1.0, 10.0)
        self.clahe_clip_spinbox.setSingleStep(0.1)
        self.clahe_clip_spinbox.setValue(2.0)
        clahe_layout.addWidget(self.clahe_clip_spinbox, 1, 1)
        
        layout.addWidget(clahe_group)
        layout.addStretch()
        
        return widget
    
    def _create_enhancement_tab(self) -> QWidget:
        """创建增强选项卡"""
        widget = QWidget()
        layout = QVBoxLayout(widget)
        
        enhancement_group = QGroupBox("颜色增强")
        enhancement_layout = QGridLayout(enhancement_group)
        
        # 亮度
        enhancement_layout.addWidget(QLabel("亮度:"), 0, 0)
        self.brightness_slider = QSlider(Qt.Orientation.Horizontal)
        self.brightness_slider.setRange(0, 200)
        self.brightness_slider.setValue(100)
        enhancement_layout.addWidget(self.brightness_slider, 0, 1)
        self.brightness_label = QLabel("1.0")
        enhancement_layout.addWidget(self.brightness_label, 0, 2)
        
        # 对比度
        enhancement_layout.addWidget(QLabel("对比度:"), 1, 0)
        self.contrast_slider = QSlider(Qt.Orientation.Horizontal)
        self.contrast_slider.setRange(0, 200)
        self.contrast_slider.setValue(100)
        enhancement_layout.addWidget(self.contrast_slider, 1, 1)
        self.contrast_label = QLabel("1.0")
        enhancement_layout.addWidget(self.contrast_label, 1, 2)
        
        # 饱和度
        enhancement_layout.addWidget(QLabel("饱和度:"), 2, 0)
        self.saturation_slider = QSlider(Qt.Orientation.Horizontal)
        self.saturation_slider.setRange(0, 200)
        self.saturation_slider.setValue(100)
        enhancement_layout.addWidget(self.saturation_slider, 2, 1)
        self.saturation_label = QLabel("1.0")
        enhancement_layout.addWidget(self.saturation_label, 2, 2)
        
        # Gamma校正
        enhancement_layout.addWidget(QLabel("Gamma:"), 3, 0)
        self.gamma_slider = QSlider(Qt.Orientation.Horizontal)
        self.gamma_slider.setRange(10, 300)
        self.gamma_slider.setValue(100)
        enhancement_layout.addWidget(self.gamma_slider, 3, 1)
        self.gamma_label = QLabel("1.0")
        enhancement_layout.addWidget(self.gamma_label, 3, 2)
        
        layout.addWidget(enhancement_group)
        layout.addStretch()
        
        return widget
    
    def _create_transform_tab(self) -> QWidget:
        """创建变换选项卡"""
        widget = QWidget()
        layout = QVBoxLayout(widget)
        
        # 旋转
        rotation_group = QGroupBox("旋转变换")
        rotation_layout = QGridLayout(rotation_group)
        
        self.rotation_enable_checkbox = QCheckBox("启用旋转")
        rotation_layout.addWidget(self.rotation_enable_checkbox, 0, 0, 1, 2)
        
        rotation_layout.addWidget(QLabel("旋转角度:"), 1, 0)
        self.rotation_slider = QSlider(Qt.Orientation.Horizontal)
        self.rotation_slider.setRange(-180, 180)
        self.rotation_slider.setValue(0)
        rotation_layout.addWidget(self.rotation_slider, 1, 1)
        self.rotation_label = QLabel("0°")
        rotation_layout.addWidget(self.rotation_label, 1, 2)
        
        layout.addWidget(rotation_group)
        
        # 透视校正
        perspective_group = QGroupBox("透视校正")
        perspective_layout = QGridLayout(perspective_group)
        
        self.perspective_enable_checkbox = QCheckBox("启用透视校正")
        perspective_layout.addWidget(self.perspective_enable_checkbox, 0, 0, 1, 2)
        
        perspective_layout.addWidget(QLabel("校正强度:"), 1, 0)
        self.perspective_slider = QSlider(Qt.Orientation.Horizontal)
        self.perspective_slider.setRange(0, 100)
        self.perspective_slider.setValue(10)
        perspective_layout.addWidget(self.perspective_slider, 1, 1)
        self.perspective_label = QLabel("0.1")
        perspective_layout.addWidget(self.perspective_label, 1, 2)
        
        layout.addWidget(perspective_group)
        
        # 数据增强
        augmentation_group = QGroupBox("数据增强")
        augmentation_layout = QGridLayout(augmentation_group)
        
        self.horizontal_flip_checkbox = QCheckBox("水平翻转")
        augmentation_layout.addWidget(self.horizontal_flip_checkbox, 0, 0)
        
        self.vertical_flip_checkbox = QCheckBox("垂直翻转")
        augmentation_layout.addWidget(self.vertical_flip_checkbox, 0, 1)
        
        self.random_crop_checkbox = QCheckBox("随机裁剪")
        augmentation_layout.addWidget(self.random_crop_checkbox, 1, 0)
        
        layout.addWidget(augmentation_group)
        layout.addStretch()
        
        return widget
    
    def _create_advanced_tab(self) -> QWidget:
        """创建高级选项卡"""
        widget = QWidget()
        layout = QVBoxLayout(widget)
        
        # 多尺度处理
        multi_scale_group = QGroupBox("多尺度处理")
        multi_scale_layout = QGridLayout(multi_scale_group)
        
        self.multi_scale_checkbox = QCheckBox("启用多尺度处理")
        multi_scale_layout.addWidget(self.multi_scale_checkbox, 0, 0, 1, 2)
        
        multi_scale_layout.addWidget(QLabel("尺度因子:"), 1, 0)
        self.scale_factors_edit = QTextEdit()
        self.scale_factors_edit.setMaximumHeight(60)
        self.scale_factors_edit.setPlainText("0.5, 1.0, 1.5")
        multi_scale_layout.addWidget(self.scale_factors_edit, 1, 1)
        
        layout.addWidget(multi_scale_group)
        layout.addStretch()
        
        return widget
    
    def _connect_signals(self):
        """连接信号"""
        # 基础设置
        self.width_spinbox.valueChanged.connect(self._update_config)
        self.height_spinbox.valueChanged.connect(self._update_config)
        self.maintain_aspect_checkbox.toggled.connect(self._update_config)
        self.interpolation_combo.currentTextChanged.connect(self._update_config)
        self.color_space_combo.currentTextChanged.connect(self._update_config)
        self.normalize_combo.currentTextChanged.connect(self._update_config)
        
        # 滤波设置
        self.gaussian_enable_checkbox.toggled.connect(self._update_config)
        self.gaussian_kernel_spinbox.valueChanged.connect(self._update_config)
        self.gaussian_sigma_spinbox.valueChanged.connect(self._update_config)
        self.bilateral_enable_checkbox.toggled.connect(self._update_config)
        self.bilateral_d_spinbox.valueChanged.connect(self._update_config)
        self.bilateral_sigma_color_spinbox.valueChanged.connect(self._update_config)
        self.bilateral_sigma_space_spinbox.valueChanged.connect(self._update_config)
        self.clahe_enable_checkbox.toggled.connect(self._update_config)
        self.clahe_clip_spinbox.valueChanged.connect(self._update_config)
        
        # 增强设置
        self.brightness_slider.valueChanged.connect(self._update_brightness)
        self.contrast_slider.valueChanged.connect(self._update_contrast)
        self.saturation_slider.valueChanged.connect(self._update_saturation)
        self.gamma_slider.valueChanged.connect(self._update_gamma)
        
        # 变换设置
        self.rotation_enable_checkbox.toggled.connect(self._update_config)
        self.rotation_slider.valueChanged.connect(self._update_rotation)
        self.perspective_enable_checkbox.toggled.connect(self._update_config)
        self.perspective_slider.valueChanged.connect(self._update_perspective)
        self.horizontal_flip_checkbox.toggled.connect(self._update_config)
        self.vertical_flip_checkbox.toggled.connect(self._update_config)
        self.random_crop_checkbox.toggled.connect(self._update_config)
        
        # 高级设置
        self.multi_scale_checkbox.toggled.connect(self._update_config)
        self.scale_factors_edit.textChanged.connect(self._update_config)
        
        # 按钮
        self.reset_btn.clicked.connect(self._reset_config)
        self.save_config_btn.clicked.connect(self._save_config)
        self.load_config_btn.clicked.connect(self._load_config)
    
    def _update_brightness(self, value):
        """更新亮度显示"""
        factor = value / 100.0
        self.brightness_label.setText(f"{factor:.1f}")
        self._update_config()
    
    def _update_contrast(self, value):
        """更新对比度显示"""
        factor = value / 100.0
        self.contrast_label.setText(f"{factor:.1f}")
        self._update_config()
    
    def _update_saturation(self, value):
        """更新饱和度显示"""
        factor = value / 100.0
        self.saturation_label.setText(f"{factor:.1f}")
        self._update_config()
    
    def _update_gamma(self, value):
        """更新Gamma显示"""
        factor = value / 100.0
        self.gamma_label.setText(f"{factor:.1f}")
        self._update_config()
    
    def _update_rotation(self, value):
        """更新旋转角度显示"""
        self.rotation_label.setText(f"{value}°")
        self._update_config()
    
    def _update_perspective(self, value):
        """更新透视强度显示"""
        factor = value / 100.0
        self.perspective_label.setText(f"{factor:.1f}")
        self._update_config()
    
    def _update_config(self):
        """更新配置"""
        try:
            # 基础设置
            self.config.target_size = (self.width_spinbox.value(), self.height_spinbox.value())
            self.config.maintain_aspect_ratio = self.maintain_aspect_checkbox.isChecked()
            
            interpolation_map = {
                "最近邻": cv2.INTER_NEAREST,
                "双线性": cv2.INTER_LINEAR,
                "双三次": cv2.INTER_CUBIC,
                "面积": cv2.INTER_AREA,
                "兰索斯": cv2.INTER_LANCZOS4
            }
            self.config.interpolation_method = interpolation_map[self.interpolation_combo.currentText()]
            
            self.config.color_space = self.color_space_combo.currentText()
            
            normalize_map = {
                "[0,1]": "zero_one",
                "[-1,1]": "neg_one_one",
                "ImageNet": "imagenet"
            }
            self.config.normalize_type = normalize_map[self.normalize_combo.currentText()]
            
            # 滤波设置
            self.config.enable_gaussian_blur = self.gaussian_enable_checkbox.isChecked()
            self.config.gaussian_kernel_size = self.gaussian_kernel_spinbox.value()
            self.config.gaussian_sigma = self.gaussian_sigma_spinbox.value()
            
            self.config.enable_bilateral_filter = self.bilateral_enable_checkbox.isChecked()
            self.config.bilateral_d = self.bilateral_d_spinbox.value()
            self.config.bilateral_sigma_color = self.bilateral_sigma_color_spinbox.value()
            self.config.bilateral_sigma_space = self.bilateral_sigma_space_spinbox.value()
            
            self.config.enable_clahe = self.clahe_enable_checkbox.isChecked()
            self.config.clahe_clip_limit = self.clahe_clip_spinbox.value()
            
            # 增强设置
            self.config.brightness_factor = self.brightness_slider.value() / 100.0
            self.config.contrast_factor = self.contrast_slider.value() / 100.0
            self.config.saturation_factor = self.saturation_slider.value() / 100.0
            self.config.gamma_correction = self.gamma_slider.value() / 100.0
            
            # 变换设置
            self.config.enable_rotation = self.rotation_enable_checkbox.isChecked()
            self.config.rotation_angle = self.rotation_slider.value()
            
            self.config.enable_perspective_correction = self.perspective_enable_checkbox.isChecked()
            self.config.perspective_strength = self.perspective_slider.value() / 100.0
            
            self.config.enable_horizontal_flip = self.horizontal_flip_checkbox.isChecked()
            self.config.enable_vertical_flip = self.vertical_flip_checkbox.isChecked()
            self.config.enable_random_crop = self.random_crop_checkbox.isChecked()
            
            # 高级设置
            self.config.enable_multi_scale = self.multi_scale_checkbox.isChecked()
            
            try:
                scale_text = self.scale_factors_edit.toPlainText()
                scale_factors = [float(x.strip()) for x in scale_text.split(',') if x.strip()]
                self.config.scale_factors = scale_factors
            except:
                pass
            
            self.config_changed.emit(self.config)
            
        except Exception as e:
            logger.error(f"更新配置时发生错误: {str(e)}")
    
    def _reset_config(self):
        """重置配置"""
        self.config = PreprocessConfig()
        self._update_ui_from_config()
        self.config_changed.emit(self.config)
    
    def _save_config(self):
        """保存配置"""
        file_path, _ = QFileDialog.getSaveFileName(
            self, "保存配置", "", "JSON files (*.json)"
        )
        if file_path:
            try:
                with open(file_path, 'w', encoding='utf-8') as f:
                    json.dump(asdict(self.config), f, indent=2, ensure_ascii=False)
                QMessageBox.information(self, "成功", "配置保存成功!")
            except Exception as e:
                QMessageBox.critical(self, "错误", f"保存配置失败:{str(e)}")
    
    def _load_config(self):
        """加载配置"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "加载配置", "", "JSON files (*.json)"
        )
        if file_path:
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    config_dict = json.load(f)
                self.config = PreprocessConfig(**config_dict)
                self._update_ui_from_config()
                self.config_changed.emit(self.config)
                QMessageBox.information(self, "成功", "配置加载成功!")
            except Exception as e:
                QMessageBox.critical(self, "错误", f"加载配置失败:{str(e)}")
    
    def _update_ui_from_config(self):
        """从配置更新UI"""
        # 基础设置
        self.width_spinbox.setValue(self.config.target_size[0])
        self.height_spinbox.setValue(self.config.target_size[1])
        self.maintain_aspect_checkbox.setChecked(self.config.maintain_aspect_ratio)
        
        # 更新其他控件...
        # 这里省略了详细的UI更新代码,实际应用中需要完整实现


class MainWindow(QMainWindow):
    """主窗口"""
    
    def __init__(self):
        super().__init__()
        self.processor = ImageProcessor()
        self.current_image = None
        self.processed_image = None
        self.processing_worker = None
        
        self._setup_ui()
        self._setup_menubar()
        self._setup_statusbar()
        self._connect_signals()
        
        self.setWindowTitle("OpenCV Transformer预处理工具 v1.0 - by 丁林松")
        self.setGeometry(100, 100, 1400, 900)
        
        # 应用样式
        self._apply_styles()
    
    def _setup_ui(self):
        """设置用户界面"""
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # 主布局
        main_layout = QHBoxLayout(central_widget)
        
        # 左侧控制面板
        self.control_panel = ControlPanel()
        self.control_panel.setMaximumWidth(400)
        
        # 右侧图像显示区域
        image_splitter = QSplitter(Qt.Orientation.Vertical)
        
        # 原始图像
        original_group = QGroupBox("原始图像")
        original_layout = QVBoxLayout(original_group)
        self.original_image_widget = ImageWidget()
        original_layout.addWidget(self.original_image_widget)
        
        # 处理后图像
        processed_group = QGroupBox("处理后图像")
        processed_layout = QVBoxLayout(processed_group)
        self.processed_image_widget = ImageWidget()
        processed_layout.addWidget(self.processed_image_widget)
        
        image_splitter.addWidget(original_group)
        image_splitter.addWidget(processed_group)
        image_splitter.setSizes([400, 400])
        
        # 添加到主布局
        main_layout.addWidget(self.control_panel)
        main_layout.addWidget(image_splitter, 1)
        
        # 进度条
        self.progress_bar = QProgressBar()
        self.progress_bar.setVisible(False)
    
    def _setup_menubar(self):
        """设置菜单栏"""
        menubar = self.menuBar()
        
        # 文件菜单
        file_menu = menubar.addMenu("文件")
        
        open_action = QAction("打开图像", self)
        open_action.setShortcut(QKeySequence.StandardKey.Open)
        open_action.triggered.connect(self.open_image)
        file_menu.addAction(open_action)
        
        save_action = QAction("保存处理结果", self)
        save_action.setShortcut(QKeySequence.StandardKey.Save)
        save_action.triggered.connect(self.save_processed_image)
        file_menu.addAction(save_action)
        
        file_menu.addSeparator()
        
        batch_action = QAction("批量处理", self)
        batch_action.triggered.connect(self.batch_process)
        file_menu.addAction(batch_action)
        
        file_menu.addSeparator()
        
        exit_action = QAction("退出", self)
        exit_action.setShortcut(QKeySequence.StandardKey.Quit)
        exit_action.triggered.connect(self.close)
        file_menu.addAction(exit_action)
        
        # 帮助菜单
        help_menu = menubar.addMenu("帮助")
        
        about_action = QAction("关于", self)
        about_action.triggered.connect(self.show_about)
        help_menu.addAction(about_action)
    
    def _setup_statusbar(self):
        """设置状态栏"""
        self.status_bar = self.statusBar()
        self.status_bar.showMessage("就绪")
        
        # 添加进度条到状态栏
        self.status_bar.addPermanentWidget(self.progress_bar)
    
    def _connect_signals(self):
        """连接信号"""
        self.control_panel.config_changed.connect(self.on_config_changed)
        self.control_panel.process_btn.clicked.connect(self.process_current_image)
        self.original_image_widget.mousePressEvent = self.on_image_click
    
    def _apply_styles(self):
        """应用样式"""
        self.setStyleSheet("""
            QMainWindow {
                background-color: #f8f9fa;
            }
            QGroupBox {
                font-weight: bold;
                border: 2px solid #dee2e6;
                border-radius: 5px;
                margin: 5px 0;
                padding-top: 10px;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                left: 10px;
                padding: 0 5px 0 5px;
            }
            QTabWidget::pane {
                border: 1px solid #dee2e6;
                border-radius: 5px;
            }
            QTabBar::tab {
                background: #e9ecef;
                border: 1px solid #dee2e6;
                padding: 8px 12px;
                margin-right: 2px;
            }
            QTabBar::tab:selected {
                background: #5D5CDE;
                color: white;
            }
        """)
    
    def on_image_click(self, event):
        """图像点击事件"""
        self.open_image()
    
    def open_image(self):
        """打开图像"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择图像文件", "", 
            "图像文件 (*.png *.jpg *.jpeg *.bmp *.tiff *.gif)"
        )
        if file_path:
            self.load_image(file_path)
    
    def load_image(self, file_path: str):
        """加载图像"""
        try:
            image = cv2.imread(file_path)
            if image is None:
                QMessageBox.critical(self, "错误", "无法读取图像文件!")
                return
            
            self.current_image = image
            
            # 显示原始图像
            display_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
            self.original_image_widget.set_image(display_image)
            
            # 自动处理
            self.process_current_image()
            
            self.status_bar.showMessage(f"图像已加载: {file_path}")
            
        except Exception as e:
            QMessageBox.critical(self, "错误", f"加载图像失败:{str(e)}")
            logger.error(f"加载图像失败: {str(e)}")
    
    def on_config_changed(self, config: PreprocessConfig):
        """配置改变时的处理"""
        if self.current_image is not None:
            # 延迟处理,避免频繁更新
            if hasattr(self, '_process_timer'):
                self._process_timer.stop()
            
            self._process_timer = QTimer()
            self._process_timer.timeout.connect(self.process_current_image)
            self._process_timer.setSingleShot(True)
            self._process_timer.start(500)  # 500ms延迟
    
    def process_current_image(self):
        """处理当前图像"""
        if self.current_image is None:
            return
        
        try:
            self.status_bar.showMessage("正在处理图像...")
            self.control_panel.process_btn.setEnabled(False)
            
            # 处理图像
            result = self.processor.process_image(self.current_image, self.control_panel.config)
            self.processed_image = result['processed_image']
            
            # 显示处理后的图像
            if len(self.processed_image.shape) == 3:
                if self.processed_image.dtype == np.float32:
                    display_image = (self.processed_image * 255).astype(np.uint8)
                else:
                    display_image = self.processed_image
                    
                if self.control_panel.config.color_space == "RGB":
                    display_image = display_image
                else:
                    display_image = cv2.cvtColor(display_image, cv2.COLOR_BGR2RGB)
            else:
                display_image = self.processed_image
                
            self.processed_image_widget.set_image(display_image)
            
            # 更新状态
            steps_text = " -> ".join(result['processing_steps'])
            self.status_bar.showMessage(f"处理完成: {steps_text}")
            
        except Exception as e:
            QMessageBox.critical(self, "错误", f"图像处理失败:{str(e)}")
            logger.error(f"图像处理失败: {str(e)}")
            self.status_bar.showMessage("处理失败")
        finally:
            self.control_panel.process_btn.setEnabled(True)
    
    def save_processed_image(self):
        """保存处理后的图像"""
        if self.processed_image is None:
            QMessageBox.warning(self, "警告", "没有处理后的图像可保存!")
            return
        
        file_path, _ = QFileDialog.getSaveFileName(
            self, "保存处理后的图像", "", 
            "PNG files (*.png);;JPEG files (*.jpg);;所有文件 (*)"
        )
        
        if file_path:
            try:
                # 确保图像格式正确
                save_image = self.processed_image.copy()
                if save_image.dtype == np.float32:
                    save_image = (save_image * 255).astype(np.uint8)
                
                # 如果是RGB格式,转换为BGR用于保存
                if len(save_image.shape) == 3 and self.control_panel.config.color_space == "RGB":
                    save_image = cv2.cvtColor(save_image, cv2.COLOR_RGB2BGR)
                
                cv2.imwrite(file_path, save_image)
                QMessageBox.information(self, "成功", "图像保存成功!")
                
            except Exception as e:
                QMessageBox.critical(self, "错误", f"保存图像失败:{str(e)}")
                logger.error(f"保存图像失败: {str(e)}")
    
    def batch_process(self):
        """批量处理"""
        file_paths, _ = QFileDialog.getOpenFileNames(
            self, "选择要批量处理的图像", "", 
            "图像文件 (*.png *.jpg *.jpeg *.bmp *.tiff *.gif)"
        )
        
        if not file_paths:
            return
        
        save_dir = QFileDialog.getExistingDirectory(self, "选择保存目录")
        if not save_dir:
            return
        
        # 创建并启动处理线程
        self.processing_worker = ProcessingWorker(file_paths, self.control_panel.config)
        self.processing_worker.progress_updated.connect(self.update_progress)
        self.processing_worker.result_ready.connect(self.on_batch_complete)
        self.processing_worker.error_occurred.connect(self.on_batch_error)
        
        self.progress_bar.setVisible(True)
        self.progress_bar.setValue(0)
        self.status_bar.showMessage("正在批量处理...")
        
        self.processing_worker.start()
    
    def update_progress(self, value: int):
        """更新进度"""
        self.progress_bar.setValue(value)
    
    def on_batch_complete(self, result: dict):
        """批量处理完成"""
        self.progress_bar.setVisible(False)
        self.status_bar.showMessage(f"批量处理完成,共处理 {result['total_processed']} 张图像")
        
        QMessageBox.information(
            self, "完成", 
            f"批量处理完成!\n共处理 {result['total_processed']} 张图像"
        )
    
    def on_batch_error(self, error_msg: str):
        """批量处理错误"""
        self.progress_bar.setVisible(False)
        self.status_bar.showMessage("批量处理失败")
        QMessageBox.critical(self, "错误", f"批量处理失败:{error_msg}")
    
    def show_about(self):
        """显示关于对话框"""
        QMessageBox.about(
            self, "关于",
            """
            OpenCV Transformer预处理工具 v1.0
            作者:丁林松
            邮箱:cnsilan@163.com
            功能:专为Transformer模型设计的图像预处理工具
            技术栈:Python, PyQt6, OpenCV, NumPy
            
            这个工具实现了多种图像预处理技术,包括:
            
            智能图像缩放与尺寸标准化
            多种滤波算法(高斯、双边、CLAHE)
            颜色空间转换与增强
            几何变换与数据增强
            多尺度特征提取
            批量处理功能
            
            """
        )
    
    def closeEvent(self, event):
        """关闭事件"""
        if self.processing_worker and self.processing_worker.isRunning():
            reply = QMessageBox.question(
                self, "确认退出", 
                "正在进行批量处理,确定要退出吗?",
                QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
            )
            
            if reply == QMessageBox.StandardButton.Yes:
                self.processing_worker.cancel()
                self.processing_worker.wait(3000)  # 等待3秒
                event.accept()
            else:
                event.ignore()
        else:
            event.accept()


def copy_code(button):
    """复制代码到剪贴板"""
    code_block = button.parentElement.querySelector('pre code');
    if (code_block) {
        navigator.clipboard.writeText(code_block.textContent).then(() => {
            button.textContent = '已复制!';
            setTimeout(() => {
                button.textContent = '复制代码';
            }, 2000);
        });
    }


def main():
    """主函数"""
    app = QApplication(sys.argv)
    
    # 设置应用程序信息
    app.setApplicationName("OpenCV Transformer预处理工具")
    app.setApplicationVersion("1.0")
    app.setOrganizationName("丁林松工作室")
    app.setOrganizationDomain("cnsilan@163.com")
    
    # 创建并显示主窗口
    window = MainWindow()
    window.show()
    
    # 启动应用程序事件循环
    sys.exit(app.exec())


if __name__ == "__main__":
    main()

总结

本文深入探讨了OpenCV图像预处理技术在Transformer模型输入准备阶段的优化策略与实现方法。 通过系统性的技术分析和完整的代码实现,为计算机视觉工程师提供了一套完整的解决方案。

随着AI技术的不断发展,图像预处理技术也将继续演进。我们期待看到更多创新的预处理方法, 以及与Transformer架构更深层次的融合,为构建更强大、更智能的视觉AI系统奠定基础。

作者:丁林松    邮箱:cnsilan@163.com    创建时间:2024年

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐