飞道的博客

OpenCV+pyqt打开文件图片摄像头等

282人阅读  评论(0)

参考:OpenCV-Python入门教程7-PyQt编写GUI界面

前面一直都是使用命令行运行代码,不够人性化。这篇用Python编写一个GUI界面,使用PyQt5编写图像处理程序。包括:打开、关闭摄像头,捕获图片,读取本地图片,灰度化和Otsu自动阈值分割的功能。

使用Qt Designer来设计界面。而anaconda里自带了designer.exe,我使用的就是这个。designer.exe的路径:D:\ProgramData\Anaconda3\Library\bin\,如果是普通的Python环境,则需要自行安装

pip install pyqt5-tools

安装完成后,designer.exe应该在Python的安装目录下:xxx\Lib\site-packages\pyqt5_tools
生成一个简单的界面,后面还会用到

import sys
from PyQt5.QtWidgets import QApplication, QWidget

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = QWidget()
    window.setWindowTitle('Hello World!')
    window.show()
    sys.exit(app.exec_())

一、界面设计

在D:\ProgramData\Anaconda3\Library\bin\下打开designer.exe,会弹出创建新窗体的窗口,我们直接点击"创建"(英文版是create)


界面左侧是Qt的常用控件"Widget Box",右侧有一个控件属性窗口"Property Editor"。本例中我们只用到了"Push Button"控件和"Label"控件,可以在属性窗口调整它的大小150x150(可以根据自己的需求适当调大或者缩小):


控件上显示的文字"text"属性和控件的名字"objectName"属性需要修改,便于显示和代码调用,可以按照下面的表格命名:
前面设计好了界面,接下来就是实现"打开摄像头"到"阈值分割"这五个按钮的功能,也就是给每个按钮指定一个函数,逻辑代码写在这个函数里面。这个函数就称事件,Qt中称为槽连接

点击Designer工具栏的Edit Signals/Slots按钮,进入槽函数编辑界面,点击旁边的"Edit Widgets"可以恢复正常视图:

在弹出的配置窗口中,可以看到左侧是按钮的常用事件,我们选择点击事件”clicked()”,然后添加一个名为”btnOpenCamera_Clicked()”的槽函数:

重复上面的步骤,给五个按钮添加五个槽函数,最终结果如下:


Ctrl + S保存.ui文件。我们需要将ui转py代码。

打开cmd命令行,切换到ui文件的保存目录。Windows下有个小技巧,可以在目录的地址栏输入cmd,一步切换到当前目录:

执行这条指令

pyuic5 -o mainForm.py using_pyqt_create_ui.ui

生成mainForm.py文件,里面包含一个名为”Ui_MainWindow”的类。
二、编写逻辑代码

mainForm.py是根据ui文件生成的,也就是说,一旦ui文件有所改变,需要重新生成覆盖原来的文件。

新建一个mainEntry.py存放逻辑代码,代码虽然很长,但是很简单并不难懂。有些部分有所重复,并没有将其封装成一个函数(博主能力有限),感兴趣的可以试一下

import sys
import cv2
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QFileDialog, QMainWindow
from mainForm import Ui_MainWindow


class PyQtMainEntry(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)

        self.camera = cv2.VideoCapture(0)
        self.is_camera_opened = False  # 摄像头有没有打开标记

        # 定时器:30ms捕获一帧
        self._timer = QtCore.QTimer(self)
        self._timer.timeout.connect(self._queryFrame)
        self._timer.setInterval(30)

    def btnOpenCamera_Clicked(self):
        '''
        打开和关闭摄像头
        '''
        self.is_camera_opened = ~self.is_camera_opened
        if self.is_camera_opened:
            self.btnOpenCamera.setText("关闭摄像头")
            self._timer.start()
        else:
            self.btnOpenCamera.setText("打开摄像头")
            self._timer.stop()

    def btnCapture_Clicked(self):
        '''
        捕获图片
        '''
        # 摄像头未打开,不执行任何操作
        if not self.is_camera_opened:
            return

        self.captured = self.frame
        # 后面这几行代码几乎都一样,可以尝试封装成一个函数
        rows, cols, channels = self.captured.shape
        bytesPerLine = channels * cols
        # Qt显示图片时,需要先转换成QImgage类型
        QImg = QImage(self.captured.data, cols, rows, bytesPerLine, QImage.Format_RGB888)
        self.labelCapture.setPixmap(QPixmap.fromImage(QImg).scaled(
            self.labelCapture.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))

    def btnReadImage_Clicked(self):
        '''
        从本地读取图片 文件路径不能有中文
        '''
        # 打开文件选取对话框
        filename, _ = QFileDialog.getOpenFileName(self, '打开图片')
        if filename:
            self.captured = cv2.imread(str(filename))
            # OpenCV图像以BGR通道存储,显示时需要从BGR转到RGB
            self.captured = cv2.cvtColor(self.captured, cv2.COLOR_BGR2RGB)

            rows, cols, channels = self.captured.shape
            bytesPerLine = channels * cols
            QImg = QImage(self.captured.data, cols, rows, bytesPerLine, QImage.Format_RGB888)
            self.labelCapture.setPixmap(QPixmap.fromImage(QImg).scaled(
                self.labelCapture.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))

    def btnGray_Clicked(self):
        '''
        灰度化
        '''
        # 如果没有捕获图片,则不执行操作
        if not hasattr(self, "captured"):
            return
        self.cpatured = cv2.cvtColor(self.captured, cv2.COLOR_RGB2GRAY)
        rows, columns = self.cpatured.shape
        bytesPerLine = columns
        # 灰度图是单通道,所以需要用Format_Indexed8
        QImg = QImage(self.cpatured.data, columns, rows, bytesPerLine, QImage.Format_Indexed8)
        self.labelResult.setPixmap(QPixmap.fromImage(QImg).scaled(
            self.labelResult.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))

    def btnThreshold_Clicked(self):
        '''
        Otsu自动阈值分割
        '''
        if not hasattr(self, "captured"):
            return

        _, self.cpatured = cv2.threshold(
            self.cpatured, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        rows, columns = self.cpatured.shape
        bytesPerLine = columns
        # 阈值分割图也是单通道,也需要用Format_Indexed8
        QImg = QImage(self.cpatured.data, columns, rows, bytesPerLine, QImage.Format_Indexed8)
        self.labelResult.setPixmap(QPixmap.fromImage(QImg).scaled(
            self.labelResult.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))

    @QtCore.pyqtSlot()
    def _queryFrame(self):
        '''
        循环捕获图片
        '''
        ret, self.frame = self.camera.read()
        img_rows, img_cols, channels = self.frame.shape
        bytesPerLine = channels * img_cols

        cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB, self.frame)
        QImg = QImage(self.frame.data, img_cols, img_rows, bytesPerLine, QImage.Format_RGB888)
        self.labelCamera.setPixmap(QPixmap.fromImage(QImg).scaled(
            self.labelCamera.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = PyQtMainEntry()
    window.show()
    sys.exit(app.exec_())

下面是我参考上面改的:

上面的程序可以跑,但是容错率太低了,比如你直接点击阈值化它就报错了,所有我就改了一下,无非就是属性的权限改下。。。加了个保存灰度图片的功能

第一步:

先建一个souye.ui画面

第二步:把刚刚的souye.ui转化为souye.py

可参考上面的转换方式,或去其他博客也可,转为souye.py如下:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'souye.ui'
#
# Created by: PyQt5 UI code generator 5.9.2
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1079, 774)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(10, 10, 161, 151))
        self.label.setText("")
        self.label.setPixmap(QtGui.QPixmap("图片路径"))
        self.label.setScaledContents(True)
        self.label.setObjectName("label")
        self.frame = QtWidgets.QFrame(self.centralwidget)
        self.frame.setGeometry(QtCore.QRect(70, 200, 711, 491))
        self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
        self.frame.setObjectName("frame")
        self.label_2 = QtWidgets.QLabel(self.frame)
        self.label_2.setGeometry(QtCore.QRect(40, 30, 271, 421))
        self.label_2.setScaledContents(True)
        self.label_2.setObjectName("label_2")
        self.label_3 = QtWidgets.QLabel(self.frame)
        self.label_3.setGeometry(QtCore.QRect(420, 20, 251, 411))
        self.label_3.setScaledContents(True)
        self.label_3.setObjectName("label_3")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(880, 170, 93, 28))
        self.pushButton.setObjectName("pushButton")
        self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_2.setGeometry(QtCore.QRect(880, 330, 93, 28))
        self.pushButton_2.setObjectName("pushButton_2")
        self.pushButton_4 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_4.setGeometry(QtCore.QRect(880, 460, 93, 28))
        self.pushButton_4.setObjectName("pushButton_4")
        self.pushButton_5 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_5.setGeometry(QtCore.QRect(880, 240, 93, 28))
        self.pushButton_5.setObjectName("pushButton_5")
        self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_3.setGeometry(QtCore.QRect(872, 290, 111, 28))
        self.pushButton_3.setObjectName("pushButton_3")
        self.pushButton_6 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_6.setGeometry(QtCore.QRect(830, 520, 151, 28))
        self.pushButton_6.setObjectName("pushButton_6")
        self.btn_savehuidu = QtWidgets.QPushButton(self.centralwidget)
        self.btn_savehuidu.setGeometry(QtCore.QRect(880, 380, 93, 28))
        self.btn_savehuidu.setObjectName("btn_savehuidu")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1079, 26))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        self.pushButton.clicked.connect(MainWindow.openfile_click)
        self.pushButton_2.clicked.connect(MainWindow.gray)
        self.pushButton_4.clicked.connect(MainWindow.threshed_click)
        self.pushButton_5.clicked.connect(MainWindow.open_camera_click)
        self.pushButton_3.clicked.connect(MainWindow.camera_click)
        self.btn_savehuidu.clicked.connect(MainWindow.save_huidu_click)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.label_2.setText(_translate("MainWindow", "原图"))
        self.label_3.setText(_translate("MainWindow", "效果图"))
        self.pushButton.setText(_translate("MainWindow", "打开图片"))
        self.pushButton_2.setText(_translate("MainWindow", "灰度"))
        self.pushButton_4.setText(_translate("MainWindow", "阈值化"))
        self.pushButton_5.setText(_translate("MainWindow", "打开摄像头"))
        self.pushButton_3.setText(_translate("MainWindow", "捕获视频图片"))
        self.pushButton_6.setText(_translate("MainWindow", "打开另一个页面"))
        self.btn_savehuidu.setText(_translate("MainWindow", "保存灰度图"))



第三步:继承souye.py的my_souye.py:

import sys
import cv2
from PyQt5.Qt import QMessageBox   #QMessage用
from PyQt5 import QtCore,QtGui,QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QFileDialog,QMainWindow
from souye import Ui_MainWindow
from my_classify import My_classify  #引入另一个画面的类,可以不用
class My_souye(QMainWindow,Ui_MainWindow):
    def __init__(self):
        super(My_souye,self).__init__()
        self.setupUi(self)

        self.camera=cv2.VideoCapture(0)
        self.is_camera_opened=False# 摄像头有没有打开
        #定时器,30ms捕获一帧
        self._timer=QtCore.QTimer(self)
        self._timer.timeout.connect(self._queryFrame)
        self._timer.setInterval(30)
    #打开摄像头
    def open_camera_click(self):
        '''
        打开和关闭摄像头
        '''
        self.is_camera_opened = ~self.is_camera_opened
        if self.is_camera_opened:
            self.pushButton_5.setText("关闭摄像头")
            self._timer.start()
        else:
            self.pushButton_5.setText("打开摄像头")
            self._timer.stop()

    #从视频中截取图片的function
    def camera_click(self):
        if not self.is_camera_opened:
            return
        #捕获视频
        temp=self.src
        img_rows, img_cols, channels = temp.shape
        bytePerLine = channels * img_cols
        # Qt显示图片时,需要先转换成QImgage类型
        QImg=QImage(temp.data,img_cols,img_rows,bytePerLine,QImage.Format_RGB888)
        self.label_3.setPixmap(QPixmap.fromImage(QImg).scaled(
            self.label_3.size(),Qt.KeepAspectRatio,Qt.SmoothTransformation
        ))


    #打开文件,并显示原图
    def openfile_click(self):
        filename,_=QFileDialog.getOpenFileName(self,'打开图片','D:/aphoto',"JPEG Files(*.jpg);;PNG Files(*.png)")
        if filename:
            self.src=cv2.imread(str(filename))   #原图,以后很多要用,所有要用self
            #Opencv图像以BGR存储,显示的时候要从BGR转到RGB
            temp=cv2.cvtColor(self.src,cv2.COLOR_BGR2RGB)
            rows,cols,channels=temp.shape
            bytePerLine=channels*cols
            QImg=QImage(temp.data,cols,rows,bytePerLine,QImage.Format_RGB888)
            self.label_2.setPixmap(QPixmap.fromImage(QImg).scaled(
                self.label_2.size(),Qt.KeepAspectRatio,Qt.SmoothTransformation
            ))
        else:
            QMessageBox.information(self,"打开失败","没有选择图片")

    #从打开的图变为灰度图
    def gray(self):
        #hasattr判断这个对象是否有这个变量或属性
        if not hasattr(self,"src"):
            QMessageBox.information(self, "灰度失败", "请先打开图片")
            return
        self.temp=cv2.cvtColor(self.src,cv2.COLOR_BGR2GRAY)
        rows,columns=self.temp.shape
        bytePerLine=columns
        # 灰度图是单通道,所以需要用Format_Indexed8
        QImg=QImage(self.temp.data,columns,rows,bytePerLine,QImage.Format_Indexed8)
        self.label_3.setPixmap(QPixmap.fromImage(QImg).scaled(
            self.label_3.size(),Qt.KeepAspectRatio,Qt.SmoothTransformation
        ))

        #保存灰度图
    def save_huidu_click(self):
        savefilename,_=QFileDialog.getSaveFileName(self,"保存图片",'Image','*.png *.jpg *.bmp')
        if savefilename and (hasattr(self, "temp")):
            cv2.imwrite(savefilename, self.temp)
            QMessageBox.information(self, "保存灰度图", "保存成功")
        else:
            QMessageBox.information(self, "保存灰度图失败", "请选择有效路径或生成灰度图先")
            return


        #阈值化
    def threshed_click(self):
        if not hasattr(self,"src"):
            QMessageBox.information(self, "阈值化失败", "请先打开图片")
            return
        temp = cv2.cvtColor(self.src, cv2.COLOR_BGR2GRAY)
        _,thresh_img=cv2.threshold(temp,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
        rows,columns=thresh_img.shape
        bytesPerLine=columns
        # 阈值分割图也是单通道,也需要用Format_Indexed8
        QImg=QImage(thresh_img.data,columns,rows,bytesPerLine,QImage.Format_Indexed8)
        self.label_3.setPixmap(QPixmap.fromImage(QImg).scaled(
            self.label_3.size(),Qt.KeepAspectRatio,Qt.SmoothTransformation
        ))

    #@QtCore.pyqtSlot()
    def _queryFrame(self):
        '''
        循环捕获图片
        '''
        ret,self.src=self.camera.read()
        img_rows,img_cols,channels=self.src.shape
        bytePerLine=channels*img_cols
        self.src=cv2.cvtColor(self.src,cv2.COLOR_BGR2RGB)
        QImg=QImage(self.src.data,img_cols,img_rows,bytePerLine,QImage.Format_RGB888)
        self.label_2.setPixmap(QPixmap.fromImage(QImg).scaled(
            self.label_2.size(),Qt.KeepAspectRatio,Qt.SmoothTransformation
        ))



if __name__=="__main__":
    app=QtWidgets.QApplication(sys.argv)
    ex=My_souye()
    ex.show()
    #另一个页面对象
    # child=My_classify()
    # btn=ex.pushButton_6
    # btn.clicked.connect(child.show)
    sys.exit(app.exec_())

上面就是我改的关键啦,比转载的容错率提升点了。
在Qt开发过程中,点击按钮弹出另一个窗口:
首先就是像上面一样,先定义classify.ui---->转为classify .py---->再建立一个my_classify.py里面有个:My_classify类:(这里只放my_classify.py代码)

import sys
import cv2
from classify import Ui_MainWindow
from PyQt5.QtWidgets import QFileDialog,QMainWindow
class My_classify(QMainWindow,Ui_MainWindow):
    def __init__(self):
        super(My_classify,self).__init__()
        self.setupUi(self)
    def openphoto_click(self):
        pass
    def classify_click(self):
        pass

然后在souye.ui上添加一个pushbutton_6按钮,再转为souye.py,再然后就是在my)souye.py上做些改动就可调用刚刚新建的classify.ui界面了:

  1. from my_classify import My_classify #引入另一个画面的类
  2. if name==“main”:
    app=QtWidgets.QApplication(sys.argv)
    ex=My_souye()
    ex.show()
    #另一个页面对象
    child=My_classify()
    #按钮绑定事件
    btn=ex.pushButton_6
    btn.clicked.connect(child.show)
    sys.exit(app.exec_())

这里就是在这创建子窗口的对象,然后把刚刚定义的pushButton_6可以说是绑定一个事件把,直接调个系统有的show槽函数,不用在ui定义pushButton_6的槽函数,非常方便!


转载:https://blog.csdn.net/QLBFA/article/details/105984012
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场