PyQt5番外篇:PyQt5与Opencv的小小融合
Posted 学点编程吧
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PyQt5番外篇:PyQt5与Opencv的小小融合相关的知识,希望对你有一定的参考价值。
今天来点特别的~!
正文
曾经有位学友像我咨询如何将PyQt5与Opencv融合,Opencv我之前并没有接触过,经过一番研究之后,实现了下面这个功能:
在一个显示图片窗口上,画一个矩形,最后我增加了一个功能,将矩形框内的图片保存在本地。效果如下:
2
Opencv的安装
开始是使用pip进行安装,输入:
pip install opencv-python。
如图:
为了验证安装是否成功,打开Python解释器,输入import cv2,提示DLLL load failed,一般出现这个问题有两种解决方式:
安装最新的Opencv,可以访问这个网址:https://www.lfd.uci.edu/~gohlke/pythonlibs/,选择合适opencv版本。其中的cp35、cp36对应的是Python是3.5还是3.6。
同时建议在安装前将numpy升级成最新的版本,否则Opencv也可能无法运行。
pip install --upgrade numpy
3
Opencv的名词解释
可能还有一些学友对于Opencv是什么东东还不是很熟悉,下面这个摘在百科百科。
OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows、android和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。
OpenCV用C++语言编写,它的主要接口也是C++语言,但是依然保留了大量的C语言接口。该库也有大量的Python, Java and MATLAB/OCTAVE (版本2.5)的接口。这些语言的API接口函数可以通过在线文档获得。如今也提供对于C#,Ch, Ruby的支持。
应用领域:人机互动、物体识别、图像分割、人脸识别、动作识别、运动跟踪、机器人、运动分析、机器视觉、结构分析、汽车安全驾驶
4
一些核心代码
在PyQt5中融合opencv、图像上画矩形、保存截图,其实这些代码在网上都有,即使PyQt的没有,适用于Qt编程的C++代码也是有的,但是描述的都过于简单,或者没有学习过C++的难以理解。下面我会尽可能用最简单的方法加以说明我的代码,要是感觉不对,欢迎吐槽,相互学习进步。要是合你的心意,欢迎给我点赞,赞赏更好,罒ω罒。
class myLabel(QLabel):
x0 = 0
y0 = 0
x1 = 0
y1 = 0
flag = False
def mousePressEvent(self,event):
self.flag = True
self.x0 = event.x()
self.y0 = event.y()
def mouseReleaseEvent(self,event):
self.flag = False
def mouseMoveEvent(self,event):
if self.flag:
self.x1 = event.x()
self.y1 = event.y()
self.update()
def paintEvent(self, event):
super().paintEvent(event)
rect =QRect(self.x0, self.y0, abs(self.x1-self.x0), abs(self.y1-self.y0))
painter = QPainter(self)
painter.setPen(QPen(Qt.red,4,Qt.SolidLine))
painter.drawRect(rect)
pqscreen = QGuiApplication.primaryScreen()
pixmap2 = pqscreen.grabWindow(self.winId(), self.x0, self.y0, abs(self.x1-self.x0), abs(self.y1-self.y0))
pixmap2.save('555.png')
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.resize(675, 300)
self.setWindowTitle('关注微信公众号:学点编程吧--opencv、PyQt5的小小融合')
self.lb = myLabel(self)
self.lb.setGeometry(QRect(140, 30, 511, 241))
img = cv2.imread('xxx.jpg')
height, width, bytesPerComponent = img.shape
bytesPerLine = 3 * width
cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img)
QImg = QImage(img.data, width, height, bytesPerLine, QImage.Format_RGB888)
pixmap = QPixmap.fromImage(QImg)
self.lb.setPixmap(pixmap)
self.lb.setCursor(Qt.CrossCursor)
self.show()
实现大体思路
重新实现QLabel类,在类中重新实现了鼠标的点击、拖动、释放、以及绘画事件;
在窗体上新建了一个label标签,然后载入图片;
label标签载入的图像是由Opencv实现的。
鼠标画矩形的思路
新建一个矩形是否完成标志flag,默认是Flase,表示未完成;
鼠标点击的时候,记录当前鼠标所在位置的坐标,flag标志置为True,表示开始画矩形了;
鼠标拖动的时候,因为flag为True,所以记录当前鼠标所在位置的坐标;
鼠标释放的时候,flag置为False,表示矩形画完了,准备画下一个了。
代码讲解
Opencv图像的转换
img = cv2.imread('xxx.jpg')
height, width, bytesPerComponent = img.shape
bytesPerLine = 3 * width
cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img)
QImg = QImage(img.data, width, height, bytesPerLine, QImage.Format_RGB888)
pixmap = QPixmap.fromImage(QImg)
这个就是Opencv和PyQt对象的转化了。
img = cv2.imread('xxx.jpg')
使用Opencv读取图像。
height, width, bytesPerComponent = img.shape
在OpenCV-Python绑定中,图像使用NumPy数组的属性(这就解释了为什么要更新numpy)来表示图像的尺寸和通道信息。此时如果我们输出img.shape,将得到(200, 360, 3)。最后的3表示这是一个RGB图像。
cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img)
将图像从一个颜色空间转换为另一个颜色空间。
Python中的函数要求是这样的:
Python:cv2.cvtColor(src,code [,dst [,dstCn]])→dst
参数:
src - 输入图像:8位无符号,16位无符号(CV_16UC …)或单精度浮点数。
dst - 输出与src相同大小和深度的图像。
code - 颜色空间转换代码(请参阅下面的说明)。
dstCn - 目标图像中的通道数量;如果参数是0,则通道的数量是从src和代码自动导出的。
该函数将输入图像从一个颜色空间转换为另一个颜色空间。在从RGB颜色空间转换到RGB颜色空间的情况下,通道的顺序应明确指定(RGB或BGR)。请注意,OpenCV中的默认颜色格式通常被称为RGB,但实际上是BGR(字节相反)。因此,标准(24位)彩色图像中的第一个字节将是一个8位蓝色分量,第二个字节将是绿色,而第三个字节将是红色。第四,五,六字节将是第二个像素(蓝色,然后是绿色,然后是红色),依此类推。
这里我们就是要求从Opencv的BGR图像转换成RGB图像了。为什么?因为要转换成PyQt5可以识别的啊!
bytesPerLine = 3 * width
QImg = QImage(img.data, width, height, bytesPerLine, QImage.Format_RGB888)
QImage类提供了独立于硬件的图像表示形式,允许直接访问像素数据,并可用作绘画设备。Qt提供了四个类来处理图像数据:QImage,QPixmap,QBitmap和QPicture。QImage是为I/O设计和优化的,并且可以直接进行像素访问和操作,而QPixmap则是针对在屏幕上显示图像而设计和优化的。 QBitmap只是一个继承QPixmap的便利类,深度为1。最后,QPicture类是一个记录和重放QPainter命令的绘图设备。
因为QImage是一个QPaintDevice子类,QPainter可以用来直接绘制图像。在QImage上使用QPainter时,可以在当前GUI线程之外的另一个线程中执行绘制。QImage提供了一系列功能,可用于获取有关图像的各种信息。也有几个功能,使图像转换。
详见官网介绍:https://doc.qt.io/qt-5/qimage.html
二维码直达:
QImg = QImage(img.data, width, height, bytesPerLine, QImage.Format_RGB888)
函数原型是:QImage(str, int, int, int, QImage.Format),用给定的宽度,高度和格式构造一个使用现有内存缓冲区数据的图像。宽度和高度必须以像素指定。bytesPerLine指定每行的字节数。
这里有个疑问:为什么bytesPerLine = 3 * width?
我的理解是:当1个像素占3个字节,此时图像为真彩色图像。
QImage.Format_RGB888表示的是图像存储使用8-8-8 24位RGB格式。当然还有更多的格式,详见QImage的官方介绍,限于篇幅这里不展开。
pixmap = QPixmap.fromImage(QImg)
这个很好理解,就是想QImage对象转换成QPixmap对象,便于下步我们将Label标签中设置图像。
self.lb.setPixmap(pixmap)
设置标签的图像信息。
self.lb.setCursor(Qt.CrossCursor)
设置鼠标在QLabel对象中的样式,只是为了画画好看些而已,没其它的意思。除了这个十字架的,还有其它很多样式,如下图:
鼠标事件
按照上文中我们介绍的思路,我们自定义了一个QLabel类myLabel,当然是继承了QLabel。然后我们用几个类变量记录鼠标的坐标和矩形是否完成的标志。
def mousePressEvent(self,event):
self.flag = True
self.x0 = event.x()
self.y0 = event.y()
def mouseReleaseEvent(self,event):
self.flag = False
def mouseMoveEvent(self,event):
if self.flag:
self.x1 = event.x()
self.y1 = event.y()
self.update()
这里就是重载了鼠标产生的几个事件,是我们自定义的。分别记录了点击鼠标后初始的鼠标坐标,以及释放鼠标后的鼠标坐标。并在鼠标移动的时候更新UI。也就是我们上面所说的鼠标画矩形的思路。
画矩形
def paintEvent(self, event):
super().paintEvent(event)
rect =QRect(self.x0, self.y0, abs(self.x1-self.x0), abs(self.y1-self.y0))
painter = QPainter(self)
painter.setPen(QPen(Qt.red,4,Qt.SolidLine))
painter.drawRect(rect)
pqscreen = QGuiApplication.primaryScreen()
pixmap2 = pqscreen.grabWindow(self.winId(), self.x0, self.y0, abs(self.x1-self.x0), abs(self.y1-self.y0))
pixmap2.save('555.png')
这个是关键点啊!
super().paintEvent(event)
调用父类的paintEvent(),这个是为了显示你设置的效果。否则会是一片空白。大家可以试试注释这句话,看看效果啊!
rect =QRect(self.x0, self.y0, abs(self.x1-self.x0), abs(self.y1-self.y0))
QRect类使用整数精度在平面中定义一个矩形。矩形通常表示为左上角和大小。QRect的大小(宽度和高度)始终等同于构成其渲染基础的数学矩形。QRect可以用一组左,上,宽和高整数,或者从QPoint和QSize构成。以下代码创建两个相同的矩形。
QRect(100, 200, 11, 16)
QRect(QPoint(100, 200), QSize(11, 16))
painter = QPainter(self)
painter.setPen(QPen(Qt.red,4,Qt.SolidLine))
painter.drawRect(rect)
构建一个QPainter对象,设置它的画笔,然后画一个矩形。貌似感觉好简单!^_^”
pqscreen = QGuiApplication.primaryScreen()
pixmap2 = pqscreen.grabWindow(self.winId(), self.x0, self.y0, abs(self.x1-self.x0), abs(self.y1-self.y0))
pixmap2.save('555.png')
截屏的原理呢,主要还是运用QScreen类中的grabWindow方法。
QScreen.grabWindow(WId window, int x = 0, int y = 0, int width = -1, int height = -1)
大致意思是创建并返回通过抓取由QRect(x,y,width,height)限制的给定窗口构造的像素图。
参数(x,y)指定窗口中的偏移量,而(宽度,高度)指定要复制的区域。如果宽度为负数,则该函数将所有内容复制到窗口的右边界。如果高度为负数,则该函数将所有内容复制到窗口的底部。
窗口系统标识符(WId)可以使用QWidget.winId()函数进行检索。grabWindow()函数从屏幕抓取像素,而不是从窗口抓取像素,即,如果有另一个窗口部分或全部覆盖抓取的像素,则也会从上面的窗口获取像素。鼠标光标一般不会被抓取。详见官网介绍:https://doc.qt.io/qt-5/qscreen.html
二维码直达:
由于QScreen类无构造函数,所以我们使用QGuiApplication.primaryScreen()创建了一个Qscreen类对象。最后使用pixmap2.save(‘555.png’),保存具体的截图。
5
最后
ok,今天的介绍暂时就到这里吧。下期我们再约。也算是第一篇和Opencv沾边的文章了吧,或许以后还会发些其它相关的。
苹果用户也能支持一下啦,长按二维码扫描吧!
看完本文有收获?请转发分享给更多人
猜你喜欢:
学点编程吧
长按左边二维码
感谢您的关注
以上是关于PyQt5番外篇:PyQt5与Opencv的小小融合的主要内容,如果未能解决你的问题,请参考以下文章
OpenCV-Python实战(番外篇)——OpenCV中绘制模拟时钟显示当前时间
Python-Matplotlib可视化(番外篇)——Matplotlib中的事件处理详解与实战
OpenCV-Python实战(番外篇)——利用 K-Means 聚类进行色彩量化
OpenCV-Python实战(番外篇)——OpenCV实现图像卡通化