基于 PyQt5 和 OpenCV 的图像处理工具探索

PyQt5与OpenCV实现简易图像处理工具
该文章已生成可运行项目,

作为一名大二的人工智能专业学生,在学习图像处理相关知识的过程中,我接触到了一段非常实用且有趣的代码。这段代码利用 PyQt5 和 OpenCV 实现了一个简易的图像处理器,它不仅加深了我对图像处理算法的理解,还让我对 GUI(图形用户界面)开发有了初步的认识,今天就来和大家分享一下这段代码的内容以及用法。

一、代码总体功能概述

这段代码实现了一个功能较为全面的图像处理工具。它能够加载本地图片文件,并对图片进行一系列常见的处理操作,如灰度化、去噪、锐化、显示灰度直方图、离散傅里叶变换(DFT)以及离散余弦变换(DCT)。处理后的图像可以清晰地显示在界面上,同时还可以将结果保存到本地,方便我们对不同处理效果进行对比和分析,对于学习图像处理原理以及实践操作都非常有帮助。

二、代码结构分析与功能说明

(一)导入模块

import sys
import cv2
import numpy as np
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QLabel, QPushButton,
    QFileDialog, QMessageBox, QVBoxLayout, QHBoxLayout, QFrame
)
from PyQt5.QtGui import QPixmap, QImage
from PyQt5.QtCore import Qt
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] #使用黑体
plt.rcParams['axes.unicode_minus'] = False #解决负号显示问题

这里的代码导入了多个模块,包括系统模块 sys,用于图像处理的 cv2(OpenCV 库)和数值计算的 numpy。PyQt5 相关模块用于构建图形用户界面,涵盖了窗口、按钮、标签、对话框、消息框以及布局管理等界面元素。通过这些模块可以设计出直观、易用的操作界面。matplotlib 库虽然在这段代码中未直接用于绘制图像,但设置了其参数以确保中文和负号能够正确显示,避免编码问题导致的显示错误。

(二)主窗口类 ImageProcessor

class ImageProcessor(QMainWindow):
    """主窗口类,负责图像处理界面的创建和逻辑处理"""
    def __init__(self):
        super().__init__()
        self.setWindowTitle("PyQt图像处理器")  # 设置窗口标题
        self.resize(900, 600)  # 设置窗口大小

        # 存储图像数据的字典
        self.image_data = {
            'original': None,   # 原始图像
            'processed': None   # 处理后的图像
        }

        # 创建主窗口小部件并设置为主窗口的中央部件
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
      
        # 设置主布局为垂直布局
        main_layout = QVBoxLayout(main_widget)

        # 设置主窗口的样式(背景色、按钮样式等)
        main_widget.setStyleSheet("""
            QWidget {
                background-color: #f0f4f8;
            }
            QLabel {
                border: 2px solid #aaa;
                border-radius: 10px;
                background-color: white;
                padding: 5px;
                box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
            }
            QPushButton {
                font-size: 15px;
                padding: 8px 18px;
                min-width: 100px;
            }
        """)

        # 创建顶部布局:包含加载和保存按钮
        top_layout = QHBoxLayout()
        load_btn = QPushButton("📂 加载图片")  # 加载图片按钮
        save_btn = QPushButton("💾 保存图像")  # 保存图像按钮
      
        # 连接按钮的点击事件
        load_btn.clicked.connect(self.load_image)
        save_btn.clicked.connect(self.save_image)
      
        # 将按钮添加到顶部布局
        top_layout.addWidget(load_btn)
        top_layout.addWidget(save_btn)
        top_layout.addStretch()  # 添加弹性空间
        main_layout.addLayout(top_layout)
      
        # 添加水平分割线
        main_layout.addWidget(self._h_line())

        # 创建图像显示区域布局
        img_layout = QHBoxLayout()
        self.original_label = QLabel("原始图像")  # 原始图像标签
        self.processed_label = QLabel("处理后图像")  # 处理后图像标签

        # 设置图像标签的样式
        for label in (self.original_label, self.processed_label):
            label.setFixedSize(400, 400)  # 设置固定大小
            label.setAlignment(Qt.AlignmentFlag.AlignCenter)  # 设置居中对齐
            label.setStyleSheet("border: 1px solid #ddd;")
      
        # 将图像标签添加到布局
        img_layout.addWidget(self.original_label)
        img_layout.addWidget(self._v_line())  # 添加垂直分割线
        img_layout.addWidget(self.processed_label)
        img_layout.setSpacing(0)  # 设置布局间距为0
      
        main_layout.addLayout(img_layout)
        main_layout.addWidget(self._h_line())  # 添加水平分割线

        # 创建底部按钮布局(图像处理功能按钮)
        bottom_layout = QHBoxLayout()
      
        # 按钮配置列表:包含按钮文字和对应的处理模式
        button_config = [
            ("⚫灰度化", "gray"),
            ("🔍去噪", "denoise"),
            ("✨锐化", "sharpen"),
            ("📊直方图", "histogram"),
            ("🌀傅里叶变换", "dft"),
            ("🔷余弦变换", "dct")
        ]
      
        # 循环创建所有功能按钮
        for text, func in button_config:
            btn = QPushButton(text)  # 创建按钮
            # 使用lambda闭包绑定处理函数,保持func值
            btn.clicked.connect(lambda _, f=func: self.process(f))
            bottom_layout.addWidget(btn)  # 将按钮添加到布局
      
        bottom_layout.addStretch()  # 添加弹性空间
        main_layout.addLayout(bottom_layout)  # 将底部布局添加到主布局

ImageProcessor 类的初始化函数中,完成了界面布局的设计以及相关事件的绑定。设置了窗口的标题和大小,定义了一个字典 image_data 用于存储原始图像和处理后的图像数据。通过一系列的布局管理代码,创建了顶部布局(包含加载和保存按钮)、图像显示区域布局(包含原始图像和处理后图像的标签)以及底部按钮布局(包含各种图像处理功能按钮)。同时,为各个控件设置了样式,使其界面更加美观和易于使用。

(三)辅助方法

    # 辅助方法:创建水平分割线
    def _h_line(self):
        line = QFrame()
        line.setFrameShape(QFrame.Shape.HLine)  # 设置为水平线
        line.setFrameShadow(QFrame.Shadow.Sunken)  # 设置阴影效果
        line.setStyleSheet("color: #ccc;")  # 设置颜色
        return line

    # 辅助方法:创建垂直分割线
    def _v_line(self):
        line = QFrame()
        line.setFrameShape(QFrame.Shape.VLine)  # 设置为垂直线
        line.setFrameShadow(QFrame.Shadow.Sunken)  # 设置阴影效果
        line.setStyleSheet("color: #ccc;")  # 设置颜色
        return line

这两个辅助方法分别用于生成水平和垂直的分割线。通过设置分割线的形状、阴影效果以及颜色样式,使其在界面中起到了很好的分隔和装饰作用,增强了界面的层次感和美观度。

(四)图像加载与显示相关函数

    def load_image(self):
        """加载图像文件并初始化显示"""
        # 打开文件选择对话框
        file, _ = QFileDialog.getOpenFileName(
            self, "选择图片", "", "图片文件 (*.png *.jpg *.bmp)"
        )
        if file:  # 如果选择了文件
            img = cv2.imread(file)  # 使用OpenCV读取图像
            if img is None:  # 如果读取失败
                QMessageBox.warning(self, "错误", "无法加载图像")
                return
          
            # 保存原始图像和初始处理图像
            self.image_data['original'] = img
            self.image_data['processed'] = img.copy()
          
            # 显示原始图像
            self.show_image(img, self.original_label)

load_image 函数用于加载本地图片文件。它利用 QFileDialog 打开文件选择对话框,让用户选择图片文件。选中文件后,使用 OpenCV 的 imread 函数读取图像数据,并将其存储到 image_data 字典的 “original” 键对应的值中,同时调用 show_image 函数将原始图像显示在界面的原始图像标签位置。在读取图像失败时,会弹出警告消息框提示用户。

    def show_image(self, img, label):
        """将OpenCV图像转换为QImage并显示在标签中"""
        # 确保图像是三通道格式
        if len(img.shape) == 2:  # 如果是灰度图
            rgb = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
        else:  # 如果是彩色图
            rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
      
        # 获取图像尺寸和通道数
        h, w, ch = rgb.shape
        bytes_per_line = ch * w  # 每行的字节数
      
        # 转换为QImage格式
        q_img = QImage(
            rgb.data, w, h, bytes_per_line, QImage.Format.Format_RGB888
        )
      
        # 设置标签的图像,保持宽高比
        label.setPixmap(
            QPixmap.fromImage(q_img).scaled(
                350, 350, Qt.AspectRatioMode.KeepAspectRatio
            )
        )

show_image 函数是关键的辅助函数,用于将 OpenCV 处理后的图像数据转换为 QImage 格式,从而能够在 PyQt5 的标签控件中显示出来。它先判断图像的通道数,对灰度图像和彩色图像分别进行相应的颜色空间转换,使其符合 QImage 的格式要求。然后根据图像数据创建 QImage 对象,并将其转换为 QPixmap 格式设置到指定的标签控件上,同时保持图像的宽高比,确保图像在显示区域中完整且不失真地呈现。

(五)图像处理主函数 process

    def process(self, mode):
        """图像处理主函数,根据模式选择处理算法"""
        if 'original' not in self.image_data or self.image_data['original'] is None:
            # 如果没有加载原始图像,显示警告
            QMessageBox.warning(self, "提示", "请先加载图片")
            return

        img = self.image_data['original'].copy()  # 获取原始图像副本
        result = None  # 存储处理结果

        # 根据选择的模式执行相应的图像处理
        if mode == "gray":  # 灰度化处理
            result = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            result = cv2.cvtColor(result, cv2.COLOR_GRAY2BGR)  # 转回三通道
      
        elif mode == "denoise":  # 去噪处理
            # 使用非局部均值去噪算法
            result = cv2.fastNlMeansDenoisingColored(
                img, None, 10, 10, 7, 21
            )
      
        elif mode == "sharpen":  # 锐化处理
            # 定义锐化核
            kernel = np.array([[-1, -1, -1], 
                              [-1, 9, -1], 
                              [-1, -1, -1]])
            result = cv2.filter2D(img, -1, kernel)  # 应用锐化核
      
        elif mode == "histogram":  # 灰度直方图
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 转为灰度图
            hist = cv2.calcHist([gray], [0], None, [256], [0, 256])  # 计算直方图
          
            # 创建直方图可视化图像
            hist_img = np.zeros((256, 256), dtype=np.uint8)
            cv2.normalize(hist, hist, 0, 255, cv2.NORM_MINMAX)
          
            # 绘制直方图
            for i in range(256):
                cv2.line(
                    hist_img, (i, 255), (i, 255 - int(hist[i][0])), 255, 1
                )
            result = cv2.cvtColor(hist_img, cv2.COLOR_GRAY2BGR)
      
        elif mode == "dft":  # 离散傅里叶变换
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 转为灰度图
            dft = cv2.dft(np.float32(gray), flags=cv2.DFT_COMPLEX_OUTPUT)
            dft_shift = np.fft.fftshift(dft)  # 将低频部分移到中心
          
            # 计算幅度谱并归一化
            magnitude_spectrum = 20 * np.log(
                cv2.magnitude(dft_shift[:,:,0], dft_shift[:,:,1])
            )
            result = cv2.normalize(
                magnitude_spectrum, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U
            )
            result = cv2.cvtColor(result, cv2.COLOR_GRAY2BGR)
      
        elif mode == "dct":  # 离散余弦变换
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 转为灰度图
            gray_float = np.float32(gray)  # 转换为浮点类型
            dct = cv2.dct(gray_float)  # 执行DCT变换
          
            # 取绝对值并归一化
            result = cv2.normalize(
                np.abs(dct), None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U
            )
            result = cv2.cvtColor(result, cv2.COLOR_GRAY2BGR)

        if result is not None:  # 如果处理成功
            self.image_data['processed'] = result  # 保存处理结果
            self.show_image(result, self.processed_label)  # 显示处理后的图像

process 函数是图像处理工具的核心逻辑部分。根据传入的处理模式参数 mode,该函数会选择相应的图像处理算法对原始图像进行操作,并将处理结果存储到 image_data 字典的 “processed” 键对应的值中,同时调用 show_image 函数将结果显示在处理后图像标签位置。实现了灰度化、去噪、锐化、灰度直方图、离散傅里叶变换和离散余弦变换等常见的图像处理功能。

(六)保存图像函数 save_image

    def save_image(self):
        """保存处理后的图像到文件"""
        if 'processed' not in self.image_data or self.image_data['processed'] is None:
            # 如果没有处理后的图像,显示警告
            QMessageBox.warning(self, "提示", "没有可保存的图像")
            return
      
        # 打开文件保存对话框
        file, _ = QFileDialog.getSaveFileName(
            self, "保存图像", "", "PNG (*.png);;JPG (*.jpg)"
        )
        if file:  # 如果选择了保存路径
            cv2.imwrite(file, self.image_data['processed'])  # 保存图像
            QMessageBox.information(self, "成功", f"图像已保存:{file}")  # 显示成功消息

当用户对图像处理结果满意并点击 “保存图像” 按钮时,save_image 函数会被触发。它通过 QFileDialog 打开文件保存对话框,让用户选择保存路径和文件格式(支持 PNG 和 JPG)。确定保存路径后,使用 OpenCV 的 imwrite 函数将处理后的图像保存到本地文件中,并弹出消息框提示用户保存成功。

三、代码用法详解

(一)运行环境准备

  • 首先需要确保已经安装了 Python 环境,在命令行中输入 python --version 可以查看已安装的 Python 版本。

  • 安装必要的库,可以通过 pip 命令安装:pip install opencv-python numpy pyqt5 matplotlib。这些库分别提供了图像处理函数、数值计算支持、图形用户界面框架以及绘图功能,是代码正常运行的基础。

(二)启动程序

将上述代码保存为一个 Python 文件(如 image_processor.py),然后在命令行中导航到文件所在目录,运行命令 python image_processor.py,即可启动图像处理工具,出现主界面。

(三)加载图像

  • 点击界面上方左侧的 “加载图片” 按钮,在弹出的文件选择对话框中,浏览并选中想要处理的本地图片文件(支持 png、jpg、bmp 格式)。

  • 选中文件后,点击 “打开” 按钮,程序会读取图像并在界面左侧的原始图像显示区域显示该图像,同时原始图像数据会被存储起来,为后续的处理操作做好准备。

(四)图像处理操作

  • 在界面下方的功能按钮区域,有多个图像处理功能按钮可供选择。

  • 根据需要处理的效果,点击相应的按钮,例如点击 “灰度化” 按钮,程序会对原始图像进行灰度化处理,并将处理后的图像显示在右侧的处理后图像显示区域。可以看到图像由彩色变为灰度效果,直观地展现了灰度化操作的结果。

  • 同样地,可以尝试点击其他按钮,如 “去噪”“锐化” 等,观察不同处理算法对图像产生的影响。每个处理操作都是对原始图像的独立操作,处理后的结果显示在右侧,方便与原始图像进行对比分析。

(五)保存处理后的图像

  • 当对处理后的图像效果满意时,点击界面上方右侧的 “保存图像” 按钮。

  • 在弹出的文件保存对话框中,选择保存的位置和文件名,以及文件格式(PNG 或 JPG),然后点击 “保存” 按钮,程序会将处理后的图像保存到指定的路径下,并弹出消息框提示保存成功,此时可以在文件保存位置查看保存好的图像文件。

四、总结与展望

通过这段代码的学习和实践,我收获颇丰。在图像处理方面,深入理解了灰度化、去噪、锐化、直方图、DFT 和 DCT 等常见算法的原理和实现方法,不再只是停留在理论知识的层面,而是能够实际看到它们对图像产生的效果,这有助于我更好地掌握图像处理的核心技术,为今后学习更复杂的图像处理和计算机视觉算法打下了坚实的基础。

在 GUI 开发方面,初次接触 PyQt5,了解了如何使用它来构建一个简单但功能实用的图形用户界面。从界面布局的设计到各个控件的使用,以及事件的绑定等,都有了一定的认识。这让我意识到,除了掌握核心算法外,能够设计出友好、便捷的用户界面也是将技术应用到实际产品中的重要一环。

不过,这段代码也有一定的局限性。例如,目前只实现了较为基础的图像处理功能,在面对更复杂的图像处理需求(如图像分割、目标检测等)时,可能需要进一步扩展和优化。而且在界面设计上还可以更加精细化,增加一些例如进度条、图像信息显示等功能,提升用户体验。

在未来的学习和实践中,我希望能够在此基础上继续深入探索图像处理领域,学习更多的算法并尝试将其集成到这个工具中,同时不断完善 GUI 的设计,使其成为一个更加强大、实用且易用的图像处理软件,为自己和他人的人工智能学习之旅提供更有力的辅助工具。

希望这篇博客能够帮助大家了解这段代码的内容和用法,如果有任何问题或建议,欢迎在评论区交流讨论!

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值