OpenCV与PySide6、QT Designer的联合使用

一、一个简单的demo,用QT Designer创建一个QMainWindow,并且放置一个QLabel,用以显示从OpenCV读取到的图像文件。

1、打开QT Designer,新建QMainWindow,放置一个QLabel,命名为label_show:

 2、将label_show的样式表定义为下图,为其添加边框:border:2px solid #a6a6a6;

 显示效果如下:

3、保存ui文件,命名为demo_window.ui 

4、使用uic工具,将其转换为python的脚本,.py文件。

转换后的脚本命名为:demo_window_rc.py。如果不了解uic工具,方法见下面链接的第二种方法:pyside2加载ui文件的两种方式_from ui import infoui-CSDN博客。题外话,uic工具非常适合并不是把前端设计作为职业的人使用,比如像我这种,搞工控的,编程只是业余玩玩。不需要了解很多QT知识,而且所有的部件都在QT Designer的可视化界面内部署完成,很适合业余玩家。

 5、创建python脚本,调用并显示界面。

import cv2
from PySide6.QtCore import QObject, Signal
from PySide6.QtWidgets import QApplication, QMainWindow
from PySide6.QtGui import QPixmap, QImage
import sys
import demo_window_rc  # 导入需要显示的画面


class MainWindow(QMainWindow, demo_window_rc.Ui_MainWindow):  # 定义需要显示的画面类
    def __init__(self):
        super().__init__()


# #############################主程序###################################
if __name__ == '__main__':
    app = QApplication(sys.argv)

    # #######################项目级别的定义###################################
    class UI(QObject):  # 将项目定义为QObject,用来管理项目级别的信号和变量

        # ###########__init__###############
        def __init__(self):
            super().__init__()

    # ########################本项目的实例化###################################
    ui = UI()

    # ########################实例化画面#################################
    window1 = MainWindow()
    window1.show()  # 显示画面
    window1.setupUi(window1)  # 画面的本体初始化

    sys.exit(app.exec())

 至此,完成了基本的画面创建和调用。并且在主程序中定义了一个名为UI的QObject()类,项目级别的信号和变量都将在这个类中定义。运行效果:

 6、使用OpenCV读取图像并转换格式后在画面中显示。

# encoding: utf-8
import cv2
import numpy as np
from PySide6.QtCore import QObject, Signal
from PySide6.QtWidgets import QApplication, QMainWindow, QFileDialog
from PySide6.QtGui import QPixmap, QImage
import sys
import demo_window_rc  # 导入需要显示的画面


class MainWindow(QMainWindow, demo_window_rc.Ui_MainWindow):  # 定义需要显示的画面类
    def __init__(self):
        super().__init__()


def img2Widget(img, widget, autoScale=True):  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 转换 BGR 到 RGB

    # 转换图像到QT的QImage格式
    height, width, channels = img_rgb.shape  # 获取形状
    bytes_per_line = channels * width  # 每行字节数
    q_img = QImage(img_rgb.data, width, height, bytes_per_line, QImage.Format_RGB888)  # 转换成QImage格式

    pixmap = QPixmap.fromImage(q_img)  # 转换成QPixmap格式
    widget.setPixmap(pixmap)
    widget.setScaledContents(autoScale)  # 重新调整图像尺寸,适应小部件


# #############################主程序###################################
if __name__ == '__main__':
    app = QApplication(sys.argv)

    # #######################项目级别的定义###################################
    class UI(QObject):  # 将项目定义为QObject,用来管理项目级别的信号和变量
        # ###########__init__###############
        def __init__(self):
            super().__init__()


    # ########################本项目的实例化###################################
    ui = UI()

    # ########################实例化画面#################################
    window1 = MainWindow()  # 画面实例化

    window1.show()  # 显示画面
    window1.setupUi(window1)  # 画面初始化

    window1.label_show.img = cv2.imdecode(np.fromfile('../IMGS/泡泡.png', dtype=np.uint8), -1)  # 获取图像
    img2Widget(window1.label_show.img, window1.label_show)  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示

    sys.exit(app.exec())

 运行截图:

 至此,完成了基本的图像调用和显示。

二、在基础调用的框架上增加一些实用功能 

1、在画面上添加几个按钮,分别为:

“打开文件”:命名为btn_openImg,

“转换成灰度”:命名为btn_toGray

“转换成二值”:命名为btn_toBinary

"边缘检测":命名为btn_findEdge

“保存文件”:命名为btn_saveFile

1、使用文件选择对话框打开文件

# encoding: utf-8
import cv2
import numpy as np
from PySide6.QtCore import QObject, Signal
from PySide6.QtWidgets import QApplication, QMainWindow, QFileDialog
from PySide6.QtGui import QPixmap, QImage
import sys
import demo_window_rc  # 导入需要显示的画面


class MainWindow(QMainWindow, demo_window_rc.Ui_MainWindow):  # 定义需要显示的画面类
    def __init__(self):
        super().__init__()


def img2Widget(img, widget, autoScale=True):  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 转换 BGR 到 RGB

    # 转换图像到QT的QImage格式
    height, width, channels = img_rgb.shape  # 获取形状
    bytes_per_line = channels * width  # 每行字节数
    q_img = QImage(img_rgb.data, width, height, bytes_per_line, QImage.Format_RGB888)  # 转换成QImage格式

    pixmap = QPixmap.fromImage(q_img)  # 转换成QPixmap格式
    widget.setPixmap(pixmap)
    widget.setScaledContents(autoScale)  # 重新调整图像尺寸,适应小部件


# #############################主程序###################################
if __name__ == '__main__':
    app = QApplication(sys.argv)

    # #######################项目级别的定义###################################
    class UI(QObject):  # 将项目定义为QObject,用来管理项目级别的信号和变量
       
        # ###########__init__###############
        def __init__(self):
            super().__init__()


    # ########################本项目的实例化###################################
    ui = UI()

    # ########################实例化画面#################################
    window1 = MainWindow()  # 画面实例化

    window1.show()  # 显示画面
    window1.setupUi(window1)  # 画面初始化

    # ###########################信号的连接和槽函数####################################
    def btn_openImg_clicked():  # “打开文件”按钮点击的槽函数
        file_path, _ = QFileDialog.getOpenFileName(None, '请选择图片', '../IMGS',
                                                   "Image Files(*.jpg *.png *.bmp)")  # 打开文件选择框
        if file_path:
            window1.label_show.img = cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), -1)  # 获取图像
            img2Widget(window1.label_show.img, window1.label_show)  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示


    window1.btn_openImg.clicked.connect(btn_openImg_clicked)  # “打开文件”按钮点击的连接

    sys.exit(app.exec())

2、基本的图像处理操作

类似的,定义其他几个图像处理按钮的信号连接以及槽函数。

# encoding: utf-8
import cv2
import numpy as np
from PySide6.QtCore import QObject, Signal
from PySide6.QtWidgets import QApplication, QMainWindow, QFileDialog
from PySide6.QtGui import QPixmap, QImage
import sys
import demo_window_rc  # 导入需要显示的画面


class MainWindow(QMainWindow, demo_window_rc.Ui_MainWindow):  # 定义需要显示的画面类
    def __init__(self):
        super().__init__()


def img2Widget(img, widget, autoScale=True):  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 转换 BGR 到 RGB

    # 转换图像到QT的QImage格式
    height, width, channels = img_rgb.shape  # 获取形状
    bytes_per_line = channels * width  # 每行字节数
    q_img = QImage(img_rgb.data, width, height, bytes_per_line, QImage.Format_RGB888)  # 转换成QImage格式

    pixmap = QPixmap.fromImage(q_img)  # 转换成QPixmap格式
    widget.setPixmap(pixmap)
    widget.setScaledContents(autoScale)  # 重新调整图像尺寸,适应小部件


# #############################主程序###################################
if __name__ == '__main__':
    app = QApplication(sys.argv)

    # #######################项目级别的定义###################################
    class UI(QObject):  # 将项目定义为QObject,用来管理项目级别的信号和变量
       
        # ###########__init__###############
        def __init__(self):
            super().__init__()


    # ########################本项目的实例化###################################
    ui = UI()

    # ########################实例化画面#################################
    window1 = MainWindow()  # 画面实例化

    window1.show()  # 显示画面
    window1.setupUi(window1)  # 画面初始化

    # ###########################信号的连接和槽函数####################################
    def btn_openImg_clicked():  # “打开文件”按钮点击的槽函数
        file_path, _ = QFileDialog.getOpenFileName(None, '请选择图片', '../IMGS',
                                                   "Image Files(*.jpg *.png *.bmp)")  # 打开文件选择框
        if file_path:
            window1.label_show.img = cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), -1)  # 获取图像
            img2Widget(window1.label_show.img, window1.label_show)  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示


    window1.btn_openImg.clicked.connect(btn_openImg_clicked)  # “打开文件”按钮点击的连接


    def btn_toGray_clicked():  # “转换成灰度”按钮点击的槽函数
        if window1.label_show.img.size > 0:
            window1.label_show.gray_img = cv2.cvtColor(window1.label_show.img, cv2.COLOR_BGR2GRAY)
            img2Widget(window1.label_show.gray_img, window1.label_show)  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示


    window1.btn_toGray.clicked.connect(btn_toGray_clicked)  # “转换成灰度”按钮点击的连接


    def btn_toBinary_clicked():  # "转换成二值"按钮点击的槽函数
        thresh, output = cv2.threshold(window1.label_show.gray_img, 130, 255, cv2.THRESH_BINARY)
        window1.label_show.binary_img = cv2.GaussianBlur(output, (9, 9), 2)
        img2Widget(window1.label_show.binary_img, window1.label_show)  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示


    window1.btn_toBinary.clicked.connect(btn_toBinary_clicked)  # “转换成二值”按钮点击的连接


    def btn_findEdge_clicked():  # "边缘检测"按钮点击的槽函数
        window1.label_show.edge_img = cv2.Canny(window1.label_show.binary_img, 200, 255)
        img2Widget(window1.label_show.edge_img, window1.label_show)  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示


    window1.btn_findEdge.clicked.connect(btn_findEdge_clicked)  # “边缘检测”按钮点击的连接

    sys.exit(app.exec())

3、加入文件保存功能后的完整代码

# encoding: utf-8
import cv2
import numpy
import numpy as np
from PySide6.QtCore import QObject, Signal
from PySide6.QtWidgets import QApplication, QMainWindow, QFileDialog
from PySide6.QtGui import QPixmap, QImage
import sys
import demo_window_rc  # 导入需要显示的画面


class MainWindow(QMainWindow, demo_window_rc.Ui_MainWindow):  # 定义需要显示的画面类
    def __init__(self):
        super().__init__()


def img2Widget(img, widget, autoScale=True):  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 转换 BGR 到 RGB

    # 转换图像到QT的QImage格式
    height, width, channels = img_rgb.shape  # 获取形状
    bytes_per_line = channels * width  # 每行字节数
    q_img = QImage(img_rgb.data, width, height, bytes_per_line, QImage.Format_RGB888)  # 转换成QImage格式

    pixmap = QPixmap.fromImage(q_img)  # 转换成QPixmap格式
    widget.setPixmap(pixmap)
    widget.setScaledContents(autoScale)  # 重新调整图像尺寸,适应小部件


# #############################主程序###################################
if __name__ == '__main__':
    app = QApplication(sys.argv)

    # #######################项目级别的定义###################################
    class UI(QObject):  # 将项目定义为QObject,用来管理项目级别的信号和变量
        
        # ###########__init__###############
        def __init__(self):
            super().__init__()


    # ########################本项目的实例化###################################
    ui = UI()

    # ########################实例化画面#################################
    window1 = MainWindow()  # 画面实例化

    window1.show()  # 显示画面
    window1.setupUi(window1)  # 画面初始化
    window1.label_show.img = numpy.ndarray

    # ###########################信号的连接和槽函数####################################
    def btn_openImg_clicked():  # “打开文件”按钮点击的槽函数
        file_path, _ = QFileDialog.getOpenFileName(None, '请选择图片文件', '../IMGS',
                                                   "Image Files(*.jpg *.png *.bmp)")  # 打开文件选择框
        if file_path:
            window1.label_show.img = cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), -1)  # 获取图像
            img2Widget(window1.label_show.img, window1.label_show)  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示
            print(type(window1.label_show.img))


    window1.btn_openImg.clicked.connect(btn_openImg_clicked)  # “打开文件”按钮点击的连接


    def btn_toGray_clicked():  # “转换成灰度”按钮点击的槽函数
        window1.label_show.gray_img = cv2.cvtColor(window1.label_show.img, cv2.COLOR_BGR2GRAY)
        img2Widget(window1.label_show.gray_img, window1.label_show)  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示


    window1.btn_toGray.clicked.connect(btn_toGray_clicked)  # “转换成灰度”按钮点击的连接


    def btn_toBinary_clicked():  # "转换成二值"按钮点击的槽函数
        if window1.label_show.gray_img.size > 0:
            thresh, output = cv2.threshold(window1.label_show.gray_img, 130, 255, cv2.THRESH_BINARY)
            window1.label_show.binary_img = cv2.GaussianBlur(output, (9, 9), 2)
            img2Widget(window1.label_show.binary_img, window1.label_show)  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示


    window1.btn_toBinary.clicked.connect(btn_toBinary_clicked)  # “转换成二值”按钮点击的连接


    def btn_findEdge_clicked():  # "边缘检测"按钮点击的槽函数
        if window1.label_show.binary_img.size > 0:
            window1.label_show.edge_img = cv2.Canny(window1.label_show.binary_img, 200, 255)
            img2Widget(window1.label_show.edge_img, window1.label_show)  # 将OpenCV格式图像转换为PySide格式,并在小部件上显示

    window1.btn_findEdge.clicked.connect(btn_findEdge_clicked)  # “边缘检测”按钮点击的连接

    def btn_saveFile_clicked():  # "保存文件"按钮点击的槽函数
        file_path, _ = QFileDialog.getSaveFileName(None, '保存为', '../IMGS',
                                                   "Image Files(*.png)")  # 打开文件选择框
        cv2.imencode('.png', window1.label_show.edge_img)[1].tofile(file_path)    # 保存文件

    window1.btn_saveFile.clicked.connect(btn_saveFile_clicked)  # “保存文件”按钮点击的连接

    sys.exit(app.exec())

运行截图

4、增加相机采图功能

 .首先,获取实时图像并显示在QT的小部件上的代码如下:

import sys
import cv2
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout
from PySide6.QtCore import QTimer, Qt
from PySide6.QtGui import QImage, QPixmap

class VideoWidget(QWidget):
    def __init__(self):
        super().__init__()

        self.label = QLabel(self)
        self.label.setAlignment(Qt.AlignCenter)

        layout = QVBoxLayout(self)
        layout.addWidget(self.label)

        self.video_capture = cv2.VideoCapture(0)  # 使用摄像头索引0(通常是默认的摄像头)

        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update_frame)
        self.timer.start(1000 // 5)  # 每秒10帧

    def update_frame(self):
        ret, frame = self.video_capture.read()
        if ret:
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)  # OpenCV使用的颜色通道顺序是BGR,转换为RGB
            h, w, ch = frame.shape
            bytes_per_line = ch * w
            convert_to_qt_format = QImage(frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
            pixmap = QPixmap.fromImage(convert_to_qt_format)
            pixmap = pixmap.scaled(self.label.size(), Qt.KeepAspectRatio)
            self.label.setPixmap(pixmap)

    def closeEvent(self, event):
        self.video_capture.release()
        event.accept()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    widget = VideoWidget()
    widget.show()
    sys.exit(app.exec())

看得出来,其原理就是每经过间隔时间从摄像机返回的数据流中获取一帧的图像数据,并将其转换为QT格式显示。而将OpenCV格式转换为QT格式并在小部件显示的功能正是之前的自定义函数img2Widget()所实现的。所以,只要周期地获取一帧的图像数据并调用img2Widget()即可。

.具体实施过程

增加一个复选框,命名为:checkBox_video

增加一个组合框:“选择相机”,命名为:comboBox_cameras

增加一个消息框,命名为:label_message

代码:

# encoding: utf-8
import cv2
import numpy as np
from PySide6.QtCore import QObject, QTimer
from PySide6.QtWidgets import QApplication, QMainWindow, QFileDialog
from PySide6.QtGui import QPixmap, QImage
import sys
import demo_window_rc  # 导入需要显示的画面


class MainWindow(QMainWindow, demo_window_rc.Ui_MainWindow):  # 定义需要显示的画面类
    def __init__(self):
        super().__init__()


def find_camera(max_camera=3):  # 查找本地可以用的相机
    window1.label_message.setText('正在查找相机,请稍候……')
    ui.cameras = [x for x in range(max_camera) if cv2.VideoCapture(x).isOpened()]     # 所有的相机的列表
    window1.comboBox_cameras.addItems([f'相机{x}' for x in ui.cameras])          # 更新相机选择组合框的下拉列表
    window1.label_message.setText('')


def activated_camera(i):  # 查找相机并按照给定的序号激活相机
    ui.activated_camera = cv2.VideoCapture(i)  # 激活的相机 cv2.VideoCapture(i)


def window1_sup_setupUi():       # window1的补充初始化
    ui.video_play = False  # 是否实时播放视频
    find_camera()   # 查找相机
    activated_camera(0)   # 激活相机0
    ui.video_play = False    # 是否实时播放视频
    ui.timer_video = QTimer()   # 视频的帧刷新节拍定时器
    ui.timer_video.start(1000 // 5)  # 每秒5帧


def img2Widget(img, widget, autoScale=True):  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 转换 BGR 到 RGB

    # 转换图像到QT的QImage格式
    height, width, channels = img_rgb.shape  # 获取形状
    bytes_per_line = channels * width  # 每行字节数
    q_img = QImage(img_rgb.data, width, height, bytes_per_line, QImage.Format_RGB888)  # 转换成QImage格式

    pixmap = QPixmap.fromImage(q_img)  # 转换成QPixmap格式
    widget.setPixmap(pixmap)   # 设置图像到小部件
    widget.setScaledContents(autoScale)  # 重新调整图像尺寸,适应小部件


# #############################主程序###################################
if __name__ == '__main__':
    app = QApplication(sys.argv)

    # #######################项目级别的定义###################################
    class UI(QObject):  # 将项目定义为QObject,用来管理项目级别的信号和变量
        # ###########__init__###############
        def __init__(self):
            super().__init__()

    # ########################本项目的实例化###################################
    ui = UI()    # 项目实例化

    # ########################实例化画面#################################
    window1 = MainWindow()  # 画面实例化

    window1.show()  # 显示画面
    window1.setupUi(window1)  # 画面初始化
    window1_sup_setupUi()     # 画面的补充初始化
    # window1.label_show.img = numpy.ndarray

    # ###########################信号的连接和槽函数####################################

    def update_frame(camera):  # 刷新视频画面
        if ui.video_play:  # 是否实时播放视频
            ret, frame = camera.read()
            if ret:
                window1.label_show.img = frame  # 获取图像数据
                img2Widget(window1.label_show.img, window1.label_show)  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示

    ui.timer_video.timeout.connect(lambda: update_frame(ui.activated_camera))  # 视频播放的帧定时器超时后连接的槽函数(刷新显示)

    def btn_openImg_clicked():  # “打开文件”按钮点击的槽函数
        file_path, _ = QFileDialog.getOpenFileName(None, '请选择图片文件', '../IMGS',
                                                   "Image Files(*.jpg *.png *.bmp)")  # 打开文件选择框
        if file_path:
            window1.label_show.img = cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), -1)  # 获取图像
            img2Widget(window1.label_show.img, window1.label_show)  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示

    window1.btn_openImg.clicked.connect(btn_openImg_clicked)  # “打开文件”按钮点击的连接

    def btn_toGray_clicked():  # “转换成灰度”按钮点击的槽函数
        window1.label_show.gray_img = cv2.cvtColor(window1.label_show.img, cv2.COLOR_BGR2GRAY)
        img2Widget(window1.label_show.gray_img, window1.label_show)  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示

    window1.btn_toGray.clicked.connect(btn_toGray_clicked)  # “转换成灰度”按钮点击的连接

    def btn_toBinary_clicked():  # "转换成二值"按钮点击的槽函数
        if window1.label_show.gray_img.size > 0:
            thresh, output = cv2.threshold(window1.label_show.gray_img, 130, 255, cv2.THRESH_BINARY)
            window1.label_show.binary_img = cv2.GaussianBlur(output, (9, 9), 2)
            img2Widget(window1.label_show.binary_img, window1.label_show)  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示

    window1.btn_toBinary.clicked.connect(btn_toBinary_clicked)  # “转换成二值”按钮点击的连接


    def find_contour_diameters(binary_img):       # 查找并计算直径
        contours, _ = cv2.findContours(binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        diameters = []
        for contour in contours:
            if len(contour) >= 5:
                (x, y), radius = cv2.minEnclosingCircle(contour)
                diameter = radius * 2
                diameters.append(diameter)
        return diameters

    # Inside btn_findEdge_clicked function
    def btn_findEdge_clicked():  # "边缘检测"按钮点击的槽函数
        if window1.label_show.binary_img.size > 0:
            window1.label_show.edge_img = cv2.Canny(window1.label_show.binary_img, 200, 255)
            diameters = find_contour_diameters(window1.label_show.edge_img)
            print("Diameters of contours:", diameters)  # You can print or further process the diameters here
            img2Widget(window1.label_show.edge_img, window1.label_show)  # 将OpenCV格式图像转换为PySide格式,并在小部件上显示

    window1.btn_findEdge.clicked.connect(btn_findEdge_clicked)  # “边缘检测”按钮点击的连接

    def btn_saveFile_clicked():  # "保存文件"按钮点击的槽函数
        file_path, _ = QFileDialog.getSaveFileName(None, '保存为', '../IMGS',
                                                   "Image Files(*.png)")  # 打开文件选择框
        if file_path:
            cv2.imencode('.png', window1.label_show.edge_img)[1].tofile(file_path)    # 保存文件

    window1.btn_saveFile.clicked.connect(btn_saveFile_clicked)  # “保存文件”按钮点击的连接

    def window1_checkBox_video_stateChanged(state):    # “实时视频”复选框的槽函数
        ui.video_play = state

    window1.checkBox_video.stateChanged.connect(window1_checkBox_video_stateChanged)    # “实时视频”复选框的连接

    window1.comboBox_cameras.currentIndexChanged.connect(activated_camera)       # “选择相机”组合框的连接

    def clean_up():
        ui.activated_camera.release()   # 释放相机资源

    app.aboutToQuit.connect(clean_up)  # 退出系统之前的释放资源

    sys.exit(app.exec())

运行截图:

 三、改进了一版的实用程序,增加了一些实用功能

.主脚本:

# encoding: utf-8
import cv2
import numpy as np
from PySide6.QtCore import QObject, QTimer
from PySide6.QtWidgets import QApplication, QMainWindow, QFileDialog
from PySide6.QtGui import QPixmap, QImage
import sys
import demo_window_rc  # 导入需要显示的画面


# 定义需要显示的画面类
class MainWindow(QMainWindow, demo_window_rc.Ui_MainWindow):
    def __init__(self):
        super().__init__()


# 查找本地可以用的相机
def find_camera(max_camera=3):
    # window1.label_message.setText('正在查找相机,请稍候……')
    ui.cameras = [x for x in range(max_camera) if cv2.VideoCapture(x).isOpened()]  # 所有的相机的列表
    window1.comboBox_cameras.addItems([f'相机{x}' for x in ui.cameras])  # 更新相机选择组合框的下拉列表
    # window1.label_message.setText('')


# 按照给定的序号激活相机
def activated_camera(i):
    ui.activated_camera = cv2.VideoCapture(i)  # 激活的相机 cv2.VideoCapture(i)


# window1的补充初始化
def window1_sup_setupUi():
    find_camera()  # 查找相机
    activated_camera(0)  # 激活相机0
    ui.video_play = False  # 是否实时播放视频
    ui.timer_video = QTimer()  # 视频的帧刷新节拍定时器
    ui.timer_video.start(1000 // 5)  # 每秒5帧

    window1.value_threshol_black.setText(str(window1.slider_threshol_black.value()))  # 二值阈值
    window1.value_threshol_min.setText(str(window1.slider_threshol_min.value()))  # 微泡阈值
    window1.value_threshol_edge1.setText(str(window1.slider_threshol_edge1.value()))  # 轮廓参数1
    window1.value_threshol_edge2.setText(str(window1.slider_threshol_edge2.value()))  # 轮廓参数2
    ui.kernel1 = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]])  # 提高清晰度函数所需的核心

    window1.btn_capture.setProperty('videoPlay', False)  # 实时采图按钮的初始状态
    ui.diameters = []  # 泡泡尺寸表


# 将OpenCV格式的图像转换为PySide格式,并在小部件上显示
def img2Widget(img, widget, autoScale=True):
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 转换 BGR 到 RGB

    # 转换图像到QT的QImage格式
    height, width, channels = img_rgb.shape  # 获取形状
    bytes_per_line = channels * width  # 每行字节数
    q_img = QImage(img_rgb.data, width, height, bytes_per_line, QImage.Format_RGB888)  # 转换成QImage格式

    pixmap = QPixmap.fromImage(q_img)  # 转换成QPixmap格式
    widget.setPixmap(pixmap)  # 设置图像到小部件
    widget.setScaledContents(autoScale)  # 重新调整图像尺寸,适应小部件


# #############################主程序###################################
if __name__ == '__main__':
    app = QApplication(sys.argv)

    # #######################项目级别的定义###################################
    class UI(QObject):  # 将项目定义为QObject,用来管理项目级别的信号和变量
        # ###########__init__###############
        def __init__(self):
            super().__init__()


    # ########################本项目的实例化###################################
    ui = UI()  # 项目实例化

    # ########################实例化画面#################################
    window1 = MainWindow()  # 画面实例化

    window1.show()  # 显示画面
    window1.setupUi(window1)  # 画面初始化
    window1_sup_setupUi()  # 画面的补充初始化

    # ###########################信号的连接和槽函数####################################
    # 刷新视频画面
    def update_frame(camera):
        if ui.video_play:  # 是否实时播放视频
            ret, frame = camera.read()  # 相机的返回数据
            if ret:
                blurred = cv2.GaussianBlur(frame, (3, 3), 0)  # 高斯模糊,滤除噪点
                ui.img = cv2.filter2D(blurred, -1, ui.kernel1)  # 提高清晰度并获取图像数据
                img2Widget(ui.img, window1.label_show)  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示


    # 视频播放的帧定时器超时后连接的槽函数(刷新显示)
    ui.timer_video.timeout.connect(lambda: update_frame(ui.activated_camera))

    # “打开文件”按钮点击的槽函数
    def btn_openImg_clicked():
        file_path, _ = QFileDialog.getOpenFileName(None, '请选择图片文件', '../IMGS',
                                                   "Image Files(*.jpg *.png *.bmp)")  # 打开文件选择框
        if file_path:
            ui.img = cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), -1)  # 获取图像
            img2Widget(ui.img, window1.label_show)  # 将OpenCV格式的图像转换为PySide格式,并在小部件上显示


    # “打开文件”按钮点击的连接
    window1.btn_openImg.clicked.connect(btn_openImg_clicked)

    # 查找并计算直径
    def find_contour_diameters(binary_img):
        contours, _ = cv2.findContours(binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  # 查找轮廓
        diameters = []
        for contour in contours:
            if len(contour) >= 1:
                (x, y), radius = cv2.minEnclosingCircle(contour)
                diameter = radius * 2
                diameters.append(diameter)
        return diameters

    # "转换计算"按钮点击的槽函数
    def btn_handle_clicked():
        if ui.img.size > 0:
            ui.gray_img = cv2.cvtColor(ui.img, cv2.COLOR_BGR2GRAY)  # 将原始图转成灰度,用以进行后面的运算
            ui.gray_image_3ch = cv2.cvtColor(ui.gray_img, cv2.COLOR_GRAY2BGR)  # 保留一份显示效果为灰度,但色彩为三通道的图,用以作为最终效果的显示
            thresh, output = cv2.threshold(ui.gray_img, window1.slider_threshol_black.value(), 255,
                                           cv2.THRESH_BINARY)  # 转成黑白二值

            size_min = window1.slider_threshol_min.value()    # 定义最小泡泡
            if size_min % 2 == 0:
                size_min += 1

            ui.binary_img = cv2.GaussianBlur(output, (size_min, size_min), 2)  # 去除微小尺寸泡泡
            ui.edge_img = cv2.Canny(ui.binary_img, window1.slider_threshol_edge1.value(),
                                    window1.slider_threshol_edge2.value())  # 查找边缘并生成轮廓图
            ui.diameters = find_contour_diameters(ui.edge_img)  # 尺寸表输出
            print("Diameters of contours:", ui.diameters)

            contour_img = np.zeros_like(ui.gray_image_3ch)  # 创建0值掩模图,尺寸、格式与之前的灰色3通道图相同
            contour_img[ui.edge_img == 255] = [0, 255, 0]  # 按照轮廓图中的白色位置,将掩模图中对应位置变为绿色
            # if contour_img[0][0].shape[0] == 3:     # 取决于原始图是3通道还是4通道RGB
            #     contour_img[ui.edge_img == 255] = [0, 255, 0]  # 将轮廓图中的白色变为绿色
            # else:
            #     contour_img[ui.edge_img == 255] = [0, 255, 0, 255]  # 将轮廓图中的白色变为绿色

            mask_img = cv2.addWeighted(ui.gray_image_3ch, 0.7, contour_img, 1, 0)  # 将掩模图与灰色3通道图叠加输出
            img2Widget(mask_img, window1.label_show)
    # “转换计算”按钮点击的连接
    window1.btn_handle.clicked.connect(btn_handle_clicked)

    # "保存文件"按钮点击的槽函数
    def btn_saveFile_clicked():
        file_path, _ = QFileDialog.getSaveFileName(None, '保存为', '../IMGS',
                                                   "Image Files(*.png)")  # 打开文件选择框
        if file_path:
            cv2.imencode('.png', ui.edge_img)[1].tofile(file_path)  # 保存文件
    # “保存文件”按钮点击的连接
    window1.btn_saveFile.clicked.connect(btn_saveFile_clicked)

    # “实时/采图”按钮点击的槽函数
    def window1_btn_capture_clicked(state):
        ui.video_play = state  # 相机是否视频实时采图模式
        if window1.btn_capture.property('videoPlay'):  # 按钮的自定义特性:videoPlay
            window1.btn_capture.setProperty('videoPlay', False)
            ui.video_play = False
        else:
            window1.btn_capture.setProperty('videoPlay', True)
            ui.video_play = True
        window1.btn_capture.setStyleSheet(window1.btn_capture.styleSheet())  # 刷新按钮的显示
    # “实时/采图”按钮点击的连接
    window1.btn_capture.clicked.connect(window1_btn_capture_clicked)

    # “选择相机”选项框的连接
    window1.comboBox_cameras.currentIndexChanged.connect(activated_camera)

    # "黑白阈值"调节的槽函数
    def window1_threshol_black_valueChanged(var):
        window1.value_threshol_black.setText(str(var))
        btn_handle_clicked()
    # "黑白阈值"的连接
    window1.slider_threshol_black.valueChanged.connect(window1_threshol_black_valueChanged)

    # "去除杂斑"调节的槽函数
    def window1_threshol_min_valueChanged(var):
        window1.value_threshol_min.setText(str(var))
        btn_handle_clicked()
    # "去除杂斑"的连接
    window1.slider_threshol_min.valueChanged.connect(window1_threshol_min_valueChanged)

    # "轮廓参数1"调节的槽函数
    def window1_threshol_edge1_valueChanged(var):
        window1.value_threshol_edge1.setText(str(var))
        btn_handle_clicked()
    # "轮廓参数1"的连接
    window1.slider_threshol_edge1.valueChanged.connect(window1_threshol_edge1_valueChanged)

    # "轮廓参数2"调节的槽函数
    def window1_threshol_edge2_valueChanged(var):
        window1.value_threshol_edge2.setText(str(var))
        btn_handle_clicked()
    # "轮廓参数2"的连接
    window1.slider_threshol_edge2.valueChanged.connect(window1_threshol_edge2_valueChanged)

    # 退出前的清理
    def clean_up():
        ui.activated_camera.release()  # 释放相机资源


    app.aboutToQuit.connect(clean_up)  # 退出系统之前的释放资源

    sys.exit(app.exec())

 .使用uic工具转换而来的画面脚本

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

################################################################################
## Form generated from reading UI file 'demo_window.ui'
##
## Created by: Qt User Interface Compiler version 6.6.3
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################

from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
    QMetaObject, QObject, QPoint, QRect,
    QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
    QFont, QFontDatabase, QGradient, QIcon,
    QImage, QKeySequence, QLinearGradient, QPainter,
    QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QComboBox, QHBoxLayout, QLabel,
    QMainWindow, QMenuBar, QPushButton, QSizePolicy,
    QSlider, QStatusBar, QWidget)

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        if not MainWindow.objectName():
            MainWindow.setObjectName(u"MainWindow")
        MainWindow.resize(1186, 861)
        self.centralwidget = QWidget(MainWindow)
        self.centralwidget.setObjectName(u"centralwidget")
        self.label_show = QLabel(self.centralwidget)
        self.label_show.setObjectName(u"label_show")
        self.label_show.setGeometry(QRect(140, 40, 960, 540))
        self.label_show.setStyleSheet(u"border:2px solid #a6a6a6;")
        self.btn_openImg = QPushButton(self.centralwidget)
        self.btn_openImg.setObjectName(u"btn_openImg")
        self.btn_openImg.setGeometry(QRect(40, 220, 75, 46))
        self.btn_handle = QPushButton(self.centralwidget)
        self.btn_handle.setObjectName(u"btn_handle")
        self.btn_handle.setGeometry(QRect(40, 280, 75, 46))
        self.btn_saveFile = QPushButton(self.centralwidget)
        self.btn_saveFile.setObjectName(u"btn_saveFile")
        self.btn_saveFile.setGeometry(QRect(40, 340, 75, 46))
        self.comboBox_cameras = QComboBox(self.centralwidget)
        self.comboBox_cameras.setObjectName(u"comboBox_cameras")
        self.comboBox_cameras.setGeometry(QRect(40, 75, 75, 31))
        self.label = QLabel(self.centralwidget)
        self.label.setObjectName(u"label")
        self.label.setGeometry(QRect(53, 50, 51, 21))
        self.btn_capture = QPushButton(self.centralwidget)
        self.btn_capture.setObjectName(u"btn_capture")
        self.btn_capture.setGeometry(QRect(40, 120, 75, 46))
        self.btn_capture.setStyleSheet(u"QPushButton[videoPlay=\"true\"] {\n"
"\n"
"/* \u91c7\u56fe\u65f6\u7684\u6837\u5f0f */\n"
"\n"
"background-color:#007800;\n"
"\n"
"}\n"
"\n"
"")
        self.value_threshol_black = QLabel(self.centralwidget)
        self.value_threshol_black.setObjectName(u"value_threshol_black")
        self.value_threshol_black.setGeometry(QRect(347, 780, 21, 16))
        self.value_threshol_black.setAlignment(Qt.AlignCenter)
        self.label_3 = QLabel(self.centralwidget)
        self.label_3.setObjectName(u"label_3")
        self.label_3.setGeometry(QRect(335, 600, 51, 16))
        self.label_3.setAlignment(Qt.AlignCenter)
        self.lamp_video = QLabel(self.centralwidget)
        self.lamp_video.setObjectName(u"lamp_video")
        self.lamp_video.setGeometry(QRect(150, 50, 20, 20))
        self.lamp_video.setStyleSheet(u"QLabel:{\n"
"/* \u901a\u5e38\u7684\u6837\u5f0f */\n"
"background-color:#00000000;\n"
"border-radius:10px;\n"
"}\n"
"\n"
"QLabel:video {\n"
"/* \u91c7\u56fe\u65f6\u7684\u6837\u5f0f */\n"
"background-color:#007800;\n"
"}\n"
"\n"
"")
        self.label_4 = QLabel(self.centralwidget)
        self.label_4.setObjectName(u"label_4")
        self.label_4.setGeometry(QRect(404, 600, 51, 16))
        self.label_4.setAlignment(Qt.AlignCenter)
        self.value_threshol_min = QLabel(self.centralwidget)
        self.value_threshol_min.setObjectName(u"value_threshol_min")
        self.value_threshol_min.setGeometry(QRect(415, 780, 31, 16))
        self.value_threshol_min.setAlignment(Qt.AlignCenter)
        self.value_threshol_edge2 = QLabel(self.centralwidget)
        self.value_threshol_edge2.setObjectName(u"value_threshol_edge2")
        self.value_threshol_edge2.setGeometry(QRect(564, 780, 31, 16))
        self.value_threshol_edge2.setAlignment(Qt.AlignCenter)
        self.value_threshol_edge1 = QLabel(self.centralwidget)
        self.value_threshol_edge1.setObjectName(u"value_threshol_edge1")
        self.value_threshol_edge1.setGeometry(QRect(493, 780, 21, 16))
        self.value_threshol_edge1.setAlignment(Qt.AlignCenter)
        self.label_5 = QLabel(self.centralwidget)
        self.label_5.setObjectName(u"label_5")
        self.label_5.setGeometry(QRect(480, 600, 57, 16))
        self.label_5.setAlignment(Qt.AlignCenter)
        self.label_6 = QLabel(self.centralwidget)
        self.label_6.setObjectName(u"label_6")
        self.label_6.setGeometry(QRect(550, 600, 57, 16))
        self.label_6.setAlignment(Qt.AlignCenter)
        self.layoutWidget = QWidget(self.centralwidget)
        self.layoutWidget.setObjectName(u"layoutWidget")
        self.layoutWidget.setGeometry(QRect(300, 625, 341, 141))
        self.horizontalLayout = QHBoxLayout(self.layoutWidget)
        self.horizontalLayout.setObjectName(u"horizontalLayout")
        self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
        self.slider_threshol_black = QSlider(self.layoutWidget)
        self.slider_threshol_black.setObjectName(u"slider_threshol_black")
        self.slider_threshol_black.setMaximum(255)
        self.slider_threshol_black.setValue(150)
        self.slider_threshol_black.setOrientation(Qt.Vertical)

        self.horizontalLayout.addWidget(self.slider_threshol_black)

        self.slider_threshol_min = QSlider(self.layoutWidget)
        self.slider_threshol_min.setObjectName(u"slider_threshol_min")
        self.slider_threshol_min.setMinimum(1)
        self.slider_threshol_min.setMaximum(51)
        self.slider_threshol_min.setSingleStep(2)
        self.slider_threshol_min.setValue(9)
        self.slider_threshol_min.setOrientation(Qt.Vertical)

        self.horizontalLayout.addWidget(self.slider_threshol_min)

        self.slider_threshol_edge1 = QSlider(self.layoutWidget)
        self.slider_threshol_edge1.setObjectName(u"slider_threshol_edge1")
        self.slider_threshol_edge1.setMaximum(255)
        self.slider_threshol_edge1.setValue(200)
        self.slider_threshol_edge1.setOrientation(Qt.Vertical)

        self.horizontalLayout.addWidget(self.slider_threshol_edge1)

        self.slider_threshol_edge2 = QSlider(self.layoutWidget)
        self.slider_threshol_edge2.setObjectName(u"slider_threshol_edge2")
        self.slider_threshol_edge2.setMinimum(1)
        self.slider_threshol_edge2.setMaximum(255)
        self.slider_threshol_edge2.setSingleStep(1)
        self.slider_threshol_edge2.setValue(255)
        self.slider_threshol_edge2.setOrientation(Qt.Vertical)

        self.horizontalLayout.addWidget(self.slider_threshol_edge2)

        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QMenuBar(MainWindow)
        self.menubar.setObjectName(u"menubar")
        self.menubar.setGeometry(QRect(0, 0, 1186, 21))
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QStatusBar(MainWindow)
        self.statusbar.setObjectName(u"statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)

        QMetaObject.connectSlotsByName(MainWindow)
    # setupUi

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None))
        self.label_show.setText("")
        self.btn_openImg.setText(QCoreApplication.translate("MainWindow", u"\u6253\u5f00\u6587\u4ef6", None))
        self.btn_handle.setText(QCoreApplication.translate("MainWindow", u"\u8f6c\u6362\u8ba1\u7b97", None))
        self.btn_saveFile.setText(QCoreApplication.translate("MainWindow", u"\u4fdd\u5b58\u6587\u4ef6", None))
        self.label.setText(QCoreApplication.translate("MainWindow", u"\u9009\u62e9\u76f8\u673a", None))
        self.btn_capture.setText(QCoreApplication.translate("MainWindow", u"\u5b9e\u65f6\u91c7\u56fe", None))
        self.value_threshol_black.setText(QCoreApplication.translate("MainWindow", u"150", None))
        self.label_3.setText(QCoreApplication.translate("MainWindow", u"\u9ed1\u767d\u9608\u503c", None))
        self.lamp_video.setText("")
        self.label_4.setText(QCoreApplication.translate("MainWindow", u"\u53bb\u9664\u6742\u6591", None))
        self.value_threshol_min.setText(QCoreApplication.translate("MainWindow", u"9", None))
        self.value_threshol_edge2.setText(QCoreApplication.translate("MainWindow", u"255", None))
        self.value_threshol_edge1.setText(QCoreApplication.translate("MainWindow", u"200", None))
        self.label_5.setText(QCoreApplication.translate("MainWindow", u"\u8f6e\u5ed3\u53c2\u65701", None))
        self.label_6.setText(QCoreApplication.translate("MainWindow", u"\u8f6e\u5ed3\u53c2\u65702", None))
    # retranslateUi

 持续更新中。。。

  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深蓝海拓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值