PyQt5番外篇:PyQt5与Opencv的小小融合

Posted 学点编程吧

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PyQt5番外篇:PyQt5与Opencv的小小融合相关的知识,希望对你有一定的参考价值。


今天来点特别的~!



正文


1

曾经有位学友像我咨询如何将PyQt5与Opencv融合,Opencv我之前并没有接触过,经过一番研究之后,实现了下面这个功能:


在一个显示图片窗口上,画一个矩形,最后我增加了一个功能,将矩形框内的图片保存在本地。效果如下:


PyQt5番外篇(1):PyQt5与Opencv的小小融合


2

Opencv的安装

开始是使用pip进行安装,输入:

pip install opencv-python。

如图:

PyQt5番外篇(1):PyQt5与Opencv的小小融合

为了验证安装是否成功,打开Python解释器,输入import cv2,提示DLLL load failed,一般出现这个问题有两种解决方式:

  1. 安装最新的Opencv,可以访问这个网址:https://www.lfd.uci.edu/~gohlke/pythonlibs/,选择合适opencv版本。其中的cp35、cp36对应的是Python是3.5还是3.6。


PyQt5番外篇(1):PyQt5与Opencv的小小融合


同时建议在安装前将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实现的。

鼠标画矩形的思路
  1. 新建一个矩形是否完成标志flag,默认是Flase,表示未完成;

  2. 鼠标点击的时候,记录当前鼠标所在位置的坐标,flag标志置为True,表示开始画矩形了;

  3. 鼠标拖动的时候,因为flag为True,所以记录当前鼠标所在位置的坐标;

  4. 鼠标释放的时候,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

二维码直达:

PyQt5番外篇(1):PyQt5与Opencv的小小融合

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对象中的样式,只是为了画画好看些而已,没其它的意思。除了这个十字架的,还有其它很多样式,如下图:

PyQt5番外篇(1):PyQt5与Opencv的小小融合

鼠标事件

按照上文中我们介绍的思路,我们自定义了一个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

二维码直达:

PyQt5番外篇(1):PyQt5与Opencv的小小融合

由于QScreen类无构造函数,所以我们使用QGuiApplication.primaryScreen()创建了一个Qscreen类对象。最后使用pixmap2.save(‘555.png’),保存具体的截图。

5

最后

ok,今天的介绍暂时就到这里吧。下期我们再约。也算是第一篇和Opencv沾边的文章了吧,或许以后还会发些其它相关的。

苹果用户也能支持一下啦,长按二维码扫描吧!


PyQt5番外篇(1):PyQt5与Opencv的小小融合

看完本文有收获?请转发分享给更多人




猜你喜欢:


学点编程吧

长按左边二维码

感谢您的关注


以上是关于PyQt5番外篇:PyQt5与Opencv的小小融合的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV-Python实战(番外篇)——OpenCV中绘制模拟时钟显示当前时间

Python-Matplotlib可视化(番外篇)——Matplotlib中的事件处理详解与实战

OpenCV-Python实战(番外篇)——利用 K-Means 聚类进行色彩量化

OpenCV-Python实战(番外篇)——OpenCV实现图像卡通化

OpenCV-Python实战(番外篇)——利用 SVM 算法识别手写数字

OpenCV-Python实战(番外篇)——OpenCVNumPy和Matplotlib直方图比较