OpenCV-PyQT项目实战OpenCV 与PyQt的图像转换

Posted YouCans

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenCV-PyQT项目实战OpenCV 与PyQt的图像转换相关的知识,希望对你有一定的参考价值。

欢迎关注『OpenCV-PyQT项目实战 @ Youcans』系列,持续更新中
OpenCV-PyQT项目实战(1)安装与环境配置
OpenCV-PyQT项目实战(2)QtDesigner 和 PyUIC 快速入门
OpenCV-PyQT项目实战(3)信号与槽机制
OpenCV-PyQT项目实战(4)OpenCV 与PyQt的图像转换
OpenCV-PyQT项目实战(5)项目案例01

文章目录


OpenCV-PyQT项目实战(4)OpenCV 与PyQt的图像转换

本节介绍 OpenCV 与PyQt的图像格式及转换。

在OpenCV-PyQt的项目中,通常使用OpenCV读写和处理图像,使用PyQt进行显示和交互。但是,OpenCV与PyQt中的图像存储格式不同,需要进行转换。这里有不少坑,小心行驶。


1. OpenCV的图像格式

1.1 以Numpy数组表示数字图像

数字图像由像素点组成的矩阵来描述,以多维数据集来表示和处理。

OpenCV的Python API是基于Numpy库来存储和处理多维数组,图像的数据结构是ndarray多维数组。OpenCV中对图像的任何操作,本质上都是对Numpy多维数组的操作和运算。

OpenCV 中的二值图像和灰度图像用二维数组 (h, w) 表示,数组中的每个元素表示对应一个像素的灰度,每个像素的位深度为 8位。

OpenCV 中二值图像被作为特殊的灰度图像,每个像素点的值为 0(黑色)或 255(白色)。

OpenCV 中的彩色图像用三维数组 (h, w, ch=3) 表示,数组中的每个元素对应一个像素的某种颜色分量,每个像素的位深度为 24位。

OpenCV 使用 BGR 格式,色彩通道顺序为 B/G/R,因此 B 通道是 img[:, :, 0], G 通道是 img[:, :, 1], R 通道是 img[:, :, 2]。


1.2 图像的属性

OpenCV 中图像对象的数据结构是 ndarray 多维数组,因此 ndarray 数组的属性和操作方法也都适用于 OpenCV 的图像对象。例如:

  • img.ndim:查看图像的维数,彩色图像的维数为 3,灰度图像的维数为 2。

  • img.shape:查看图像的形状,即图像栅格的行数(高度)、列数(宽度)、通道数。

  • img.size:查看图像数组元素总数,灰度图像的数组元素总数为像素数量,彩色图像的数组元素总数为像素数量与通道数的乘积。


例程 1:OpenCV 图像数组的属性

    # 图像数组的属性
    imgFile = "../images/imgLena.tif"  # 读取文件的路径
    img1 = cv2.imread(imgFile, flags=1)  # flags=1 读取彩色图像(BGR)
    img2 = cv2.imread(imgFile, flags=0)  # flags=0 读取为灰度图像
    # cv2.imshow("Demo1", img1)  # 在窗口显示图像
    # key = cv2.waitKey(0)  # 等待按键命令

    # 维数(ndim), 形状(shape), 元素总数(size), 元素类型(dtype)
    print("Ndim of img1(BGR): , img2(Gray): ".format(img1.ndim, img2.ndim))  # number of rows, columns and channels
    print("Shape of img1(BGR): , img2(Gray): ".format(img1.shape, img2.shape))  # number of rows, columns and channels
    print("Size of img1(BGR): , img2(Gray): ".format(img1.size, img2.size))  # size = rows * columns * channels
    print("Dtype of img1(BGR): , img2(Gray): ".format(img1.dtype, img2.dtype))  # uint8

本例程的运行结果如下:

Ndim of img1(BGR): 3, img2(Gray): 2
Shape of img1(BGR): (512, 512, 3), img2(Gray): (512, 512)
Size of img1(BGR): 786432, img2(Gray): 262144
Dtype of img1(BGR): uint8, img2(Gray): uint8

1.3 图像的数据类型

OpenCV库函数对于数据类型有严格要求,错误的数据类型将导致语法错误。

OpenCV中图像的数据类型的参数命名格式为:

CV_数字位数数字类型C通道数

例如,CV_8UC3表示3通道8位无符号整数格式的矩阵。

OpenCV数据类型与Numpy数据类型的对应关系如表2-1所示。图像处理中最常用的数据类型是8位无符号整数CV_8U,对应于np.uint8。


表2-1 OpenCV与Numpy数据类型的对照关系

数据类型OpenCVNumpy取值范围
8位无符号整数CV_8Uuint80~255
8位符号整数CV_8Sint8-128~127
16位无符号整数CV_16Uuint160~65 535
16位符号整数CV_16Sint16-32 768~32 767
32位符号整数CV_32Sint32-2 147 483 648~-2 147 483 647
32位单精度浮点数CV_32Ffloat32-FLT_MAX~FLT_MAX, INF, NAN
64位双精度浮点数CV_64Ffloat64-DBL_MAX~DBL_MAX, INF, NAN

使用ndarray.dtype可以获得Numpy数组的数据类型,使用ndarray.astype可以把数据类型转换成指定的Numpy数据类型。


1.4 OpenCV 图像转换

OpenCV 中的函数 cv.cvtColor() 用于将图像从一个颜色空间转换为另一个颜色空间。可以转换彩色图像的颜色通道顺序、将彩色图像转换为灰度图像,或将图像在RGB空间与其它色彩空间相互转换。

函数原型:

cv.cvtColor(src, code [, dst, dstCn=0]]) → dst

参数说明:

  • src:输入图像,ndarray 多维数组,格式为CV_8U、CV_16U或CV_32F
  • dst:输出图像,大小和深度与 src 相同
  • dstCn:输出图像的通道数,默认值为 0 表示由自动计算
  • code:颜色空间转换代码,详见 ColorConversionCodes
    • COLOR_BGR2RGB:BGR通道顺序转换为RGB
    • COLOR_BGR2GRAY:BGR彩色图像转换为灰度图像
    • COLOR_BGR2HSV:BGR图像转换为HSV图像

注意问题:
⑴ OpenCV使用RGB模型表示彩色图像时使用BGR格式,按B/G/R顺序存储为多维数组。而PIL、PyQt、Matplotlib等库使用的是R/G/B格式。
⑵ 灰度图像是单通道,在OpenCV、Matplotlib中都是Numpy二维数组。
⑶ 图像中各通道像素值的范围,由图像像素的位深度 depth决定。大多数图像和视频格式为8位无符号整数(CV_8U),取值范围 0~255。
⑷ 图像格式转换通常是线性变换,像素的位深度不影响变换结果;但在进行非线性计算或变换时,需要把输入图像归一化到适当的取值范围,才能得到正确的结果。例如CV_8U由于数据精度较低可能丢失部分信息,使用CV_16U或CV_32F就可以解决这个问题。
⑸ 将图像由GRAY转换为RGB 时,转换规则为:R=G=B=gray。
⑹ 函数cv.cvtColor提供了150多种转换类型,通过以下程序可以查询:

print([i for i in dir(cv) if i.startswith(‘COLOR_’)])


例程 2:OpenCV 图像的颜色空间转换


# 图像的颜色空间转换
import cv2 as cv
from matplotlib import pyplot as plt

if __name__ == '__main__':
    # 读取原始图像
    imgBGR = cv.imread("../images/Lena.tif", flags=1)  # 读取为彩色图像

    imgRGB = cv.cvtColor(imgBGR, cv.COLOR_BGR2RGB)  # BGR 转换为 RGB
    imgGRAY = cv.cvtColor(imgBGR, cv.COLOR_BGR2GRAY)  # BGR 转灰度图像
    imgHSV = cv.cvtColor(imgBGR, cv.COLOR_BGR2HSV)  # BGR 转 HSV 图像
    imgYCrCb = cv.cvtColor(imgBGR, cv.COLOR_BGR2YCrCb)  # BGR 转 YCrCb
    imgHLS = cv.cvtColor(imgBGR, cv.COLOR_BGR2HLS)  # BGR 转 HLS 图像
    imgXYZ = cv.cvtColor(imgBGR, cv.COLOR_BGR2XYZ)  # BGR 转 XYZ 图像
    imgLAB = cv.cvtColor(imgBGR, cv.COLOR_BGR2LAB)  # BGR 转 LAB 图像
    imgYUV = cv.cvtColor(imgBGR, cv.COLOR_BGR2YUV)  # BGR 转 YUV 图像

    # 调用matplotlib显示处理结果
    titles = ['BGR', 'RGB', 'GRAY', 'HSV', 'YCrCb', 'HLS', 'XYZ', 'LAB', 'YUV']
    images = [imgBGR, imgRGB, imgGRAY, imgHSV, imgYCrCb,
              imgHLS, imgXYZ, imgLAB, imgYUV]
    plt.figure(figsize=(10, 8))
    for i in range(9):
        plt.subplot(3, 3, i+1), plt.imshow(images[i], 'gray')
        plt.title(". ".format(i+1, titles[i]))
        plt.xticks([]), plt.yticks([])
    plt.tight_layout()
plt.show()


2. PyQt的图像类

2.1 QImage类

Qt提供了四个类来处理图像数据:QImage,QPixmap,QBitmap和QPicture。

  • QImage 是为I/O设计和优化的,并且可以直接进行像素访问和操作;
  • QPixmap 是针对在屏幕上显示图像而设计和优化的;
  • QBitmap 是一个继承QPixmap的便利类,深度为1;
  • QPicture类是一个记录和重放QPainter命令的绘图设备。

QImage类提供了独立于硬件的图像表示形式,允许直接访问像素数据,并可用作绘画设备。

QImage类支持Format枚举描述的几种图像格式:单色、8位、32位和alpha混合图像。

QImage提供了多种方式来读取图像文件,在创建QImage对象时可以加载图像文件,也可以在创建对象之后,使用load()或者loadFrameData()函数来加载图像。加载图像时,文件名可以是磁盘上的实际文件,也可以是嵌入到应用程序的资源。

QImage提供了获取图像信息的函数,以及实现图像转换的函数。

函数原型

[QImage](const uchar *data, int width, int height, int bytesPerLine, QImage::Format format, QImageCleanupFunction cleanupFunction = nullptr, void *cleanupInfo = nullptr)

用给定的宽度,高度和格式构造一个使用现有内存缓冲区数据的图像。宽度和高度必须以像素指定。bytesPerLine指定每行的字节数。详见官网介绍:QImage Class | Qt GUI 5.10

  • 几何尺寸信息: 函数size(), width(), height(), dotsPerMeterX()和dotsPerMeterY()提供了图像的尺寸和纵横比信息。rect()函数返回图像的外接矩形,vaild()函数用于判断一组坐标是否位于这个外接矩形内。
  • 色彩信息: 使用pixel()函数可以获得指定像素的颜色,该颜色是一个独立于文件格式的QRgb值。对于单色或者8位图像,colorCount()和colorTable() 函数提供存储数据的颜色分量的信息。hasAlphaChannel()函数用于检查是否有alpha通道。
  • 文本信息: text() 函数返回给定文本关键值的图像文本信息。函数textKeys()返回图像的文本关键值信息。
  • 底层信息: depth()函数返回图像的位深度信息。图像支持的深度信息把包括1(单色),8、16、24和32位。bitPlaneCount()返回图像的位平面数。

QImage对象可以按值传递,因为QImage类使用隐式数据共享。QImage对象也可以进行流式处理和比较。

因为QImage是一个QPaintDevice子类,所以可以使用QPainter直接绘制图像。在QImage上使用QPainter时,可以在当前GUI线程之外的其他线程中执行绘制。


例程3: 获取QImage的尺寸

from PyQt5 import QtGui
qimage = QtGui.QImage('C:/Users/wxscn/Desktop/test.jpg')
# 直接查询 rect 得不到长宽
rect = qimage.rect()
# 获取长宽的方法 1
w = rect.width()
h = rect.height()
# 获取长宽的方法 2
w_ = qimage.width()
h_ = qimage.height()
print(rect, (w, h), (w_, h_))
# 运行结果:PyQt5.QtCore.QRect(0, 0, 536, 868) (536, 868) (536, 868)


例程3: 获取QPixmap的尺寸

qpixmap = QtGui.QPixmap('C:/Users/wxscn/Desktop/test.jpg')
w = qpixmap.width()
h = qpixmap.height()
print((w, h))
# 运行结果:(536, 868)

例程4: 调整QPixmap的尺寸

qpixmap = qpixmap.scaled(w, h, QtCore.Qt.KeepAspectRatio)

例程5: 获取QImage的像素值

(r,g,b) = QtGui.QColor(qimage.pixel(x, y)).getRgb()[:-1]

2.2 QImage 图像格式

QImage中存储的每个像素由一个整数表示。整数的大小因格式而异。QImage支持Format枚举描述的几种图像格式。

  • 使用1位索引将单色图像存储到最多具有两种颜色的颜色表中。有两种不同类型的单色图像:大端(MSB优先)或小端(LSB优先)位顺序。

  • 使用8位索引将8位图像存储到颜色表中,即它们每像素具有一个字节。颜色表是一个QVector<QRgb>,QRgb typedef相当于一个无符号整数,包含格式为0xAARGGBB的ARGB四元组。

  • 32位图像没有颜色表;相反,每个像素都包含一个QRgb值。有三种不同类型的32位图像分别存储RGB(即0xffRRGGBB)、ARGB和预乘ARGB值。在预乘格式中,红色、绿色和蓝色通道乘以alpha分量除以255。

  • 可以使用format()函数检索图像的格式。使用convertToFormat()函数将图像转换为另一种格式。allGray()和isGrayscale()函数用于判断彩色图像是否可以安全地转换为灰度图像。

PyQt提供以下图像格式:

  • QImage.Format_Invalid (0): 无效图片。
  • QImage.Format_Mono (1): 每像素使用1位存储图像。字节首先用高位(MSB方式打包)。
  • QImage.Format_MonoLSB (2): 每像素使用1位存储图像。字节首先用低位(LSB方式打包)。
  • QImage.Format_Indexed8 (3): 使用8位索引将图像存储到颜色表中。
  • QImage.Format_RGB32 (4): 图像使用32位RGB格式(0xffRRGGBB)存储。
  • QImage.Format_RGB888 (13): 图像以24位RGB格式(8-8-8)存储。
  • QImage.Format_RGBA8888 (17): 图像使用32位字节顺序RGBA格式(8-8-8-8)存储。
  • QImage.Format_Grayscale8 (24): 图像使用8位灰度格式存储。(在Qt 5.5中添加)。
  • QImage.Format_Grayscale16 (28): 图像使用16位灰度格式存储。(在Qt 5.13中添加)。
  • QImage.Format_BGR888 (29): 图像使用24位BGR格式存储。(在Qt 5.14中添加)。

2.3 图像变换和图像属性修改

QImage支持许多用于创建新图像的功能:

  • createAlphaMask(): 从该图像的alpha缓冲区生成并返回一个1-bpp的蒙版
  • mirrored(): 生成一个径向图像
  • scaled(): 生成一个缩放图像
  • rgbSwapped(): 从RGB图像构造一个BGR图像
  • scaledToWidth(): 返回缩放到指定宽度的图像
  • caledToHeight(): 返回缩放到指定高度的图像
  • transformed():返回使用给定的转换矩阵和转换模式转换的图像

一些用于就地图像属性的函数:

  • setDotsPerMeterX(): 设置物理单位的水平像素数来定义纵横比。
  • setDotsPerMeterY(): 设置物理单位的垂直像素数来定义纵横比。
  • fill(): 用给定的像素值填充整个图像。
  • invertPixels(): 使用给定的InvertMode值反转图像中的所有像素值。
  • setColorTable(): 设置用于转换颜色索引的颜色表。仅单色和8位格式。
  • setColorCount(): 调整颜色表的大小。仅单色和8位格式。


3. OpenCV图像转换为PyQt图像类

3.1 OpenCV图像转换为PyQt图像

OpenCV 图像格式不同,转换为 QImage类的方法和参数也不同。

特别地,对于彩色图像,由于 OpenCV 使用 RGB 模型表示彩色图像时使用BGR格式,按B/G/R顺序存储为多维数组,而 PyQt、Matplotlib 等库使用的是 R/G/B 格式。因此,将OpenCV彩色图像转换为QImage时,还要进行颜色通道的顺序调换。

# (1) OpenCV 彩色图像(3通道8位图像)-> QImage 
row, col, pix = image.shape[0], image.shape[1], image.strides[0]
qImg = QImage(image.data, col, row, pix, QImage.Format_RGB888).rgbSwapped()

# (2) OpenCV 灰度图像(单通道8位图像)-> QImage 
row, col, pix = image.shape[0], image.shape[1], image.strides[0]
qImg = QImage(image.data, col, row, pix, QImage.Format_Indexed8)

# (3) OpenCV 灰度图像(单通道16位图像)-> QImage 
row, col, pix = image.shape[0], image.shape[1], image.strides[0]
qimage = QtGui.QImage(image.data, cols, rows, pix, QImage.Format_Grayscale16)

例程6:OpenCV图像转换为QImage类

def cvToQImage(image):  # OpenCV图像 转换为 PyQt图像
    # 8-bits unsigned, NO. OF CHANNELS=1
    row, col, pix = image.shape[0], image.shape[1], image.strides[0]
    channels = 1 if len(image.shape) == 2 else image.shape[2]
    if channels == 3:  # CV_8UC3
        qImg = QImage(image.data, col, row, pix, QImage.Format_RGB888)
        return qImg.rgbSwapped()
    elif channels == 1:
        qImg = QImage(image.data, col, row, pix, QImage.Format_Indexed8)
        return qImg
    else:
        QtCore.qDebug("ERROR: numpy.ndarray could not be converted to QImage. Channels = %d" % image.shape[2])
        return QImage()

3.2 PyQt图像转换为OpenCV图像

例程7:QPixmap类转换为OpenCV图像

def qPixmapToCV(qtpixmap):  # PyQt图像 转换为 OpenCV图像
    qImg = qPixmap.toImage()  # QPixmap 转换为 QImage
    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)  # 定义 OpenCV 图像
    image = image[..., :3]
    return image

3.3 Image/QImage/QPixmap 的转换

qimage = image.toqimage()  # Image 转换为 QImage
qimage = QtGui.QImage(qpixmap)  # QPixmap 转换为 QImage
qpixmap = image.toqpixmap()  # Image 转换为 QPixmap
qpixmap = QtGui.QPixmap(qimage)  # QImage 转换为 QPixmap


4. OpenCV-PyQt5 综合例程

例程 GUIdemo4.py 如下:

例程8:OpenCV-PyQt5 综合例程 GUIdemo4.py

# OpenCVPyqt04.py
# Demo04 of GUI by PyQt5
# Copyright 2023 Youcans, XUPT
# Crated:2023-01-31

import sys
import cv2 as cv
import numpy as np
from PyQt5 import QtCore
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from uiDemo3 import Ui_MainWindow  # 导入 uiDemo5.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.pushButton_1.clicked.connect(self.click_pushButton_1)  # 点击 pushButton_1 触发
        self.pushButton_2.clicked.connect(self.click_pushButton_2)  # 点击 pushButton_2 触发
        self.pushButton_3.clicked.connect(self.close)  # 点击 pushButton_3 关闭窗口
        return

    def click_pushButton_1(self):  # 点击 pushButton_1 触发
        img = self.openSlot()  # 读取图像
        print("click_pushButton_1", img.shape)
        qImg = self.cvToQImage(img)  # OpenCV 转为 PyQt 图像格式
        self.label.setPixmap(QPixmap.fromImage(qImg))

        # qtPixmap = QPixmap.fromImage(qImg)
        # image = self.qPixmapToCV(qtPixmap)
        # print("image = self.qPixmapToCV(qtPixmap)", image.shape)
        return

    def click_pushButton_2(self):  # 点击 pushButton_2 触发
        img = self.openSlot()  # 读取图像
        gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)  # 转为灰度图像
        print("click_pushButton_2", img.shape, gray.shape)
        qImg = self.cvToQImage(gray)  # 灰度图像 转为 PyQt 图像格式
        self.label.setPixmap(QPixmap.fromImage(qImg))
        return

    def openSlot(self, flag=1):  # 读取图像文件
        # 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

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

    def cvToQImage(self, image):  # OpenCV图像 转换为 PyQt图像
        # 8-bits unsigned, NO. OF CHANNELS=1
        row, col, pix = image.shape[0], image.shape[1], image.strides[0]
        channels = 1 if len(image.shape)==2 else image.shape[2]
        if channels == 3:  # CV_8UC3
            qImg = QImage(image.data, col, row, pix, QImage.Format_RGB888)
            return qImg.rgbSwapped()
        elif channels == 1:
            qImg = QImage(image.data, col, row, pix, QImage.Format_Indexed8)
            return qImg
        else:
            QtCore.qDebug("ERROR: numpy.ndarray could not be converted to QImage. Channels = %d" % image.shape[2])
            return QImage()

    def qPixmapToCV(self, qPixmap):  # PyQt图像 转换为 OpenCV图像
        qImg = qPixmap.toImage()  # QPixmap 转换为 QImage
        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)  # 定义 OpenCV 图像
        image = image[..., :3]
        return image

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



检查一下应用程序 GUIdemo4 的各项功能:

  • 点击 “1# 按钮”,选择路径和图像文件,读取图像文件并在窗口显示彩色图像;

  • 点击 “2# 按钮”,选择路径和图像文件,读取图像文件并在窗口显示为灰度图像;

  • 点击 “3# 按钮”,或工具栏中的 “退出”,关闭图形窗口应用程序;

  • 点击工具栏中的 “帮助”,弹出信息提示框,点击 “OK” 可以关闭信息框;

【本节完】


版权声明:
原创作品,转载必须标注原文链接:https://blog.csdn.net/youcans/article/details/128845326
Copyright 2023 youcans, XUPT
Crated:2023-02-02

以上是关于OpenCV-PyQT项目实战OpenCV 与PyQt的图像转换的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV-PyQT项目实战OpenCV 与PyQt的图像转换

OpenCV-PyQT项目实战信号与槽机制

OpenCV-PyQT项目实战(10)项目案例06:键盘事件与视频抓拍

OpenCV-PyQT项目实战(10)项目案例06:键盘事件与视频抓拍

OpenCV-PyQT项目实战信号与槽机制

OpenCV-PyQT项目实战(11)项目案例07:摄像头操作与拍摄视频