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

[2] PyQt5使用QLabel显示OpenCV图像的自适应调节(只改变大小不改变比例)

[3] OpenCV-PyQT项目实战(7)项目案例03:鼠标框选

相关推荐
_.Switch8 分钟前
高效网络自动化:Python在网络基础中的应用
运维·开发语言·网络·python·数据分析·自动化
lanboAI15 分钟前
基于yolov8的驾驶员疲劳驾驶检测系统,支持图像、视频和摄像实时检测【pytorch框架、python源码】
pytorch·python·yolo
南巷逸清风19 分钟前
LeetCode 101.对称二叉树
c++·python·算法·leetcode
纪怽ぅ33 分钟前
浅谈——深度学习和马尔可夫决策过程
人工智能·python·深度学习·算法·机器学习
阿丁小哥36 分钟前
【Python各个击破】numpy
开发语言·python·numpy
仙草哥哥1 小时前
使用virtualenv/Anaconda/Miniconda创建python虚拟环境
python·conda·virtualenv
qq22951165021 小时前
python基于django线上视频学习系统设计与实现_j0189d4x
python·学习·django
易辰君1 小时前
【Python爬虫实战】深入理解Python异步编程:从协程基础到高效爬虫实现
爬虫·python
achaoyang2 小时前
【Python学习计算机知识储备】
开发语言·python·学习
卡卡_R-Python2 小时前
相关矩阵图——Python实现
开发语言·python