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项目实战(6)项目案例02:滚动条应用
OpenCV-PyQT项目实战(7)项目案例03:鼠标框选
OpenCV-PyQT项目实战(8)项目案例04:鼠标定位
文章目录
- OpenCV-PyQT项目实战(4)OpenCV 与PyQt的图像转换
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数据类型的对照关系
数据类型 | OpenCV | Numpy | 取值范围 |
---|---|---|---|
8位无符号整数 | CV_8U | uint8 | 0~255 |
8位符号整数 | CV_8S | int8 | -128~127 |
16位无符号整数 | CV_16U | uint16 | 0~65 535 |
16位符号整数 | CV_16S | int16 | -32 768~32 767 |
32位符号整数 | CV_32S | int32 | -2 147 483 648~-2 147 483 647 |
32位单精度浮点数 | CV_32F | float32 | -FLT_MAX~FLT_MAX, INF, NAN |
64位双精度浮点数 | CV_64F | float64 | -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项目实战(10)项目案例06:键盘事件与视频抓拍