python-PyQt项目实战案例:制作一个简单的图像处理工具

文章目录

1.设计UI

对于UI的设计可以通过qt designer直接绘制,也可以通过编写python代码实现。当然,一般情况下还是建议使用designer绘制,然后转换为py代码后再进行微调。

在qt designer中绘制ui,其中使用label控件用于图像的显示,整体布局如下:

设计好ui后将其保存,并导出为py文件。

2.编写功能代码

2.1 初始化ui界面及类成员参数

python 复制代码
class MyMainWindow(QMainWindow, Ui_MainWindow):  # 继承 QMainWindow 类和 Ui_MainWindow 界面类
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)  # 初始化父类
        self.setupUi(self)  # 继承 Ui_MainWindow 界面类

        self.setWindowTitle('图像处理工具箱V1.0')
        # 添加菜单项
        t = self.menu_2
        t.addAction('EqualHist')
        t.addAction('Gamma transform')
        t.addAction('Binary')
        t.addAction('Edge detect')
        t.addAction('Bilateral')

        t = self.menu_3
        t.addAction('About')
        t.addAction('Other')

        menu = self.menu_4
        self.actExit = QAction("Exit", self)
        self.actExit.triggered.connect(self.close)
        menu.addAction(self.actExit)

        # 添加可点击执行的菜单
        self.mTest = QAction("其他", self)
        self.mTest.triggered.connect(self.trigger_actHelp)
        t = self.menuBar()
        t.addAction(self.mTest)

        # 菜单栏
        self.actionopen.triggered.connect(self.openSlot)  # 连接并执行 openSlot 子程序
        self.action_save.triggered.connect(self.saveSlot)  # 连接并执行 saveSlot 子程序
        self.menu_3.triggered.connect(self.trigger_actHelp)  # 连接并执行 trigger_actHelp 子程序
        self.menu_4.triggered.connect(self.close)  # 连接并执行 close 子程序

        self.pushButton.clicked.connect(self.click_pushButton_1)    # # 按钮触发:导入图像
        self.pushButton_2.clicked.connect(self.click_pushButton_2)  # # 按钮触发:灰度显示
        self.pushButton_3.clicked.connect(self.click_pushButton_3)  # # 按钮触发:伽马变换
        self.pushButton_4.clicked.connect(self.click_pushButton_4)  # # 按钮触发:二值化
        self.pushButton_5.clicked.connect(self.click_pushButton_5)  # 点击 # 按钮触发:边缘检测
        self.pushButton_6.clicked.connect(self.click_pushButton_6)  # 点击 # 按钮触发:双边滤波
        self.pushButton_7.clicked.connect(self.saveSlot)  # 点击 # 按钮触发:保存图像

        # 初始化
        self.img1 = np.ndarray(())
        self.img2 = np.ndarray(())
        self.img1 = cv.imread("./images/image.png")  # OpenCV 读取图像
        self.refreshShow(self.img1, self.label_1)
        self.refreshShow(self.img1, self.label_2)
        return

2.2 添加菜单栏

python 复制代码
from PyQt5 import QtCore
from PyQt5.QtWidgets import QMainWindow, QWidget, QHBoxLayout, QPushButton, QApplication, QMenuBar, QLabel

class MainWin(QMainWindow):

    def __init__(self):
        super().__init__()
        m_bar = QMenuBar()
        f = m_bar.addMenu('File')
        f.addAction('New')

        t = m_bar.addMenu('Tool')
        t.addAction('Copy')
        t.addAction('Paste')

        sub = t.addMenu('Sub')
        sub1 = sub.addAction('sub1')
        sub.addAction('sub2')
        sub1.triggered.connect(self.sub1_trigger)

        self.setMenuBar(m_bar)

        self.label = QLabel()
        self.label.setText('label text')
        self.setCentralWidget(self.label)

    def copy_msg(self):
        print('Copy')

    def sub1_trigger(self):
        self.label.setText('sub1_trigger')


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    win = MainWin()
    win.show()
    sys.exit(app.exec())

2.3 建立信号/槽连接

python 复制代码
self.pushButton.clicked.connect(self.click_pushButton_1)    # # 按钮触发:导入图像
self.pushButton_2.clicked.connect(self.click_pushButton_2)  # # 按钮触发:灰度显示
self.pushButton_3.clicked.connect(self.click_pushButton_3)  # # 按钮触发:伽马变换
self.pushButton_4.clicked.connect(self.click_pushButton_4)  # # 按钮触发:二值化
self.pushButton_5.clicked.connect(self.click_pushButton_5)  # 点击 # 按钮触发:边缘检测
self.pushButton_6.clicked.connect(self.click_pushButton_6)  # 点击 # 按钮触发:双边滤波
self.pushButton_7.clicked.connect(self.saveSlot)  # 点击 # 按钮触发:保存图像

3.主要功能代码及效果

python 复制代码
# _*_ coding:utf-8 _*_
import sys
import cv2 as cv
import numpy as np
from PyQt5.QtCore import qDebug
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from ImageProV1 import Ui_MainWindow  # 导入 ImageProV1.py 中的 Ui_MainWindow 界面类


class MyMainWindow(QMainWindow, Ui_MainWindow):  # 继承 QMainWindow 类和 Ui_MainWindow 界面类
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)  # 初始化父类
        self.setupUi(self)  # 继承 Ui_MainWindow 界面类

        self.setWindowTitle('图像处理工具箱V1.0')
        # 添加菜单项
        t = self.menu_2
        t.addAction('EqualHist')
        t.addAction('Gamma transform')
        t.addAction('Binary')
        t.addAction('Edge detect')
        t.addAction('Bilateral')

        t = self.menu_3
        t.addAction('About')
        t.addAction('Other')

        menu = self.menu_4
        self.actExit = QAction("Exit", self)
        self.actExit.triggered.connect(self.close)
        menu.addAction(self.actExit)

        # 添加可点击执行的菜单
        self.mTest = QAction("其他", self)
        self.mTest.triggered.connect(self.trigger_actHelp)
        t = self.menuBar()
        t.addAction(self.mTest)

        # 菜单栏
        self.actionopen.triggered.connect(self.openSlot)  # 连接并执行 openSlot 子程序
        self.action_save.triggered.connect(self.saveSlot)  # 连接并执行 saveSlot 子程序
        self.menu_3.triggered.connect(self.trigger_actHelp)  # 连接并执行 trigger_actHelp 子程序
        self.menu_4.triggered.connect(self.close)  # 连接并执行 close 子程序

        # 通过 connect 建立信号/槽连接,点击按钮事件发射 triggered 信号,执行相应的子程序 click_pushButton
        self.pushButton.clicked.connect(self.click_pushButton_1)    # # 按钮触发:导入图像
        self.pushButton_2.clicked.connect(self.click_pushButton_2)  # # 按钮触发:灰度显示
        self.pushButton_3.clicked.connect(self.click_pushButton_3)  # # 按钮触发:伽马变换
        self.pushButton_4.clicked.connect(self.click_pushButton_4)  # # 按钮触发:二值化
        self.pushButton_5.clicked.connect(self.click_pushButton_5)  # 点击 # 按钮触发:边缘检测
        self.pushButton_6.clicked.connect(self.click_pushButton_6)  # 点击 # 按钮触发:双边滤波
        self.pushButton_7.clicked.connect(self.saveSlot)  # 点击 # 按钮触发:保存图像

        # 初始化
        self.img1 = np.ndarray(())
        self.img2 = np.ndarray(())
        self.img1 = cv.imread("./images/image.png")  # OpenCV 读取图像
        self.refreshShow(self.img1, self.label_1)
        self.refreshShow(self.img1, self.label_2)
        return

    def click_pushButton_1(self):  # 点击 pushButton_1 触发
        try:
            self.img1 = self.openSlot()  # 读取图像
            self.img2 = self.img1.copy()
            print("click_pushButton_1", self.img1.shape)
            self.refreshShow(self.img1, self.label_1)  # 刷新显示
        except:
            print('open file failed.')

        return

    def click_pushButton_2(self):  # 点击 pushButton_2 触发
        print("pushButton_2")
        self.img2 = cv.cvtColor(self.img1, cv.COLOR_BGR2GRAY)  # 图片格式转换:BGR -> Gray
        self.img2 = cv.equalizeHist(self.img2, None)
        self.refreshShow(self.img2, self.label_2)  # 刷新显示
        return

    def click_pushButton_3(self):  # 点击 pushButton_3 伽马变换
        print("pushButton_3")
        self.img2 = self.adjust_gamma(self.img1)
        self.refreshShow(self.img2, self.label_2)  # 刷新显示
        return

    def click_pushButton_4(self):  # 点击 pushButton_3 触发
        print("pushButton_4")
        temp = cv.cvtColor(self.img1, cv.COLOR_BGR2GRAY)  # 图片格式转换:BGR -> Gray
        _, self.img2 = cv.threshold(temp, 0, 255, cv.THRESH_OTSU + cv.THRESH_BINARY)
        self.refreshShow(self.img2, self.label_2)  # 刷新显示
        return

    def click_pushButton_5(self):
        print("pushButton_5")
        temp = cv.cvtColor(self.img1, cv.COLOR_BGR2GRAY)  # 图片格式转换:BGR -> Gray
        self.img2 = cv.Canny(self.img1, 10, 60)
        self.refreshShow(self.img2, self.label_2)  # 刷新显示
        return

    def click_pushButton_6(self):
        print("pushButton_6")
        self.img2 = cv.bilateralFilter(self.img1, 11, 20, 20, None)
        self.refreshShow(self.img2, self.label_2)  # 刷新显示
        return

    # 默认gamma值为1.0,默认不变化
    def adjust_gamma(self, image, gamma=1.5):
        brighter_image = np.array(np.power((image / 255), gamma) * 255, dtype=np.uint8)
        return brighter_image

    def refreshShow(self, img, label):
        print('shape: ', img.shape, label)
        qImg = self.cvToQImage(img)  # OpenCV 转为 PyQt 图像格式
        label.setScaledContents(True)  # 需要在图片显示之前进行设置
        # 当窗口大小改变时,调整图像大小以适应 QLabel,同时保持长宽比例
        # qImg = qImg.scaled(label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)

        label.setPixmap((QPixmap.fromImage(qImg)))  # 加载 PyQt 图像
        return

    def openSlot(self, flag=1):  # 读取图像文件
        try:
            # OpenCV 读取图像文件
            fileName, _ = QFileDialog.getOpenFileName(self, "Open Image", "../images/", "*.png *.jpg *.tif")
            if flag == 0 or flag == "gray":
                img = cv.imread(fileName, cv.IMREAD_GRAYSCALE)  # 读取灰度图像
            else:
                img = cv.imread(fileName, cv.IMREAD_COLOR)  # 读取彩色图像
            print(fileName, img.shape)
            return img
        except:
            return None

    def saveSlot(self):  # 保存图像文件
        # 选择存储文件 dialog
        try:
            fileName, tmp = QFileDialog.getSaveFileName(self, "Save Image", "../images/", '*.png; *.jpg; *.tif')
            if self.img2.size == 1:
                return
            # OpenCV 写入图像文件
            ret = cv.imwrite(fileName, self.img2)
            if ret:
                print(fileName, self.img2.shape)
        except:
            print('save failed.')
        return

    def cvToQImage(self, image):
        if image.dtype == np.uint8:
            channels = 1 if len(image.shape) == 2 else image.shape[2]
        if channels == 3:  # CV_8UC3
            qImg = QImage(image, image.shape[1], image.shape[0], image.strides[0], QImage.Format_RGB888)
            return qImg.rgbSwapped()
        elif channels == 1:
            qImg = QImage(image, image.shape[1], image.shape[0], image.strides[0], QImage.Format_Indexed8)
            return qImg
        else:
            qDebug("ERROR: numpy.ndarray could not be converted to QImage. Channels = %d" % image.shape[2])
            return QImage()

    def qPixmapToCV(self, qPixmap):
        qImg = qPixmap.toImage()
        shape = (qImg.height(), qImg.bytesPerLine() * 8 // qImg.depth())
        shape += (4,)
        ptr = qImg.bits()
        ptr.setsize(qImg.byteCount())
        image = np.array(ptr, dtype=np.uint8).reshape(shape)
        image = image[..., :3]
        return image

    def trigger_actHelp(self):  # 动作 actHelp 触发
        QMessageBox.about(self, "About", """数字图像处理工具箱 v1.0""")
        return

if __name__ == '__main__':
    app = QApplication(sys.argv)  # 在 QApplication 方法中使用,创建应用程序对象
    myWin = MyMainWindow()  # 实例化 MyMainWindow 类,创建主窗口
    myWin.show()            # 在桌面显示控件 myWin
    sys.exit(app.exec_())   # 结束进程,退出程序

4.设置图像自动调节长宽尺寸但不改变长宽比例

python 复制代码
# 当窗口大小改变时,调整图像大小以适应 QLabel,同时保持长宽比例  
scaledImage = self.image.scaled(self.label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)  
self.label.setPixmap(scaledImage)     

参考文献

1\] [python-在PyCharm中使用PyQt5](https://blog.csdn.net/qq_44924694/article/details/142316356) \[2\] [PyQt5使用QLabel显示OpenCV图像的自适应调节(只改变大小不改变比例)](https://blog.csdn.net/schuberta/article/details/140816815) \[3\] [OpenCV-PyQT项目实战(7)项目案例03:鼠标框选](https://blog.csdn.net/youcans/article/details/129028023)

相关推荐
思则变1 小时前
[Pytest] [Part 2]增加 log功能
开发语言·python·pytest
叶子爱分享1 小时前
计算机视觉与图像处理的关系
图像处理·人工智能·计算机视觉
漫谈网络1 小时前
WebSocket 在前后端的完整使用流程
javascript·python·websocket
try2find3 小时前
安装llama-cpp-python踩坑记
开发语言·python·llama
博观而约取4 小时前
Django ORM 1. 创建模型(Model)
数据库·python·django
精灵vector5 小时前
构建专家级SQL Agent交互
python·aigc·ai编程
Zonda要好好学习5 小时前
Python入门Day2
开发语言·python
Vertira6 小时前
pdf 合并 python实现(已解决)
前端·python·pdf
太凉6 小时前
Python之 sorted() 函数的基本语法
python
项目題供诗6 小时前
黑马python(二十四)
开发语言·python