PyQt5与Opencv的小小融合
一些核心代码
1 | class myLabel(QLabel): |
实现大体思路
- 重新实现QLabel类,在类中重新实现了鼠标的点击、拖动、释放、以及绘画事件
- 在窗体上新建了一个label标签,然后载入图片
- label标签载入的图像是由Opencv实现的
鼠标画矩形的思路
- 新建一个矩形是否完成标志flag,默认是Flase,表示未完成
- 鼠标点击的时候,记录当前鼠标所在位置的坐标,flag标志置为True,表示开始画矩形了
- 鼠标拖动的时候,因为flag为True,所以记录当前鼠标所在位置的坐标
- 鼠标释放的时候,flag置为False,表示矩形画完了,准备画下一个了
代码讲解
Opencv图像的转换
1 | img = cv2.imread('xxx.jpg') |
这个就是Opencv和PyQt对象的转化了。
1 | img = cv2.imread('xxx.jpg') |
使用Opencv读取图像。
1 | height, width, bytesPerComponent = img.shape |
在OpenCV-Python绑定中,图像使用NumPy数组的属性(这就解释了为什么要更新numpy)来表示图像的尺寸和通道信息。此时如果我们输出img.shape,将得到(200, 360, 3)。最后的3表示这是一个RGB图像。
1 | cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img) |
将图像从一个颜色空间转换为另一个颜色空间。
Python中的函数要求是这样的:
1 | 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可以识别的啊!
1 | bytesPerLine = 3 * width |
QImage类提供了独立于硬件的图像表示形式,允许直接访问像素数据,并可用作绘画设备。Qt提供了四个类来处理图像数据:QImage,QPixmap,QBitmap和QPicture。QImage是为I/O设计和优化的,并且可以直接进行像素访问和操作,而QPixmap则是针对在屏幕上显示图像而设计和优化的。
QBitmap只是一个继承QPixmap的便利类,深度为1。最后,QPicture类是一个记录和重放QPainter命令的绘图设备。
因为QImage是一个QPaintDevice子类,QPainter可以用来直接绘制图像。在QImage上使用QPainter时,可以在当前GUI线程之外的另一个线程中执行绘制。QImage提供了一系列功能,可用于获取有关图像的各种信息。也有几个功能,使图像转换。
详见官网介绍:QImage Class | Qt GUI 5.10
1 | 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的官方介绍,限于篇幅这里不展开。
1 | pixmap = QPixmap.fromImage(QImg) |
这个很好理解,就是想QImage对象转换成QPixmap对象,便于下步我们将Label标签中设置图像。
1 | self.lb.setPixmap(pixmap) |
设置标签的图像信息。
1 | self.lb.setCursor(Qt.CrossCursor) |
设置鼠标在QLabel对象中的样式,只是为了画画好看些而已,没其它的意思。除了这个十字架的,还有其它很多样式,如下图:
鼠标事件
按照上文中我们介绍的思路,我们自定义了一个QLabel类myLabel,当然是继承了QLabel。然后我们用几个类变量记录鼠标的坐标和矩形是否完成的标志。
1 | def mousePressEvent(self,event): |
这里就是重载了鼠标产生的几个事件,是我们自定义的。分别记录了点击鼠标后初始的鼠标坐标,以及释放鼠标后的鼠标坐标。并在鼠标移动的时候更新UI。也就是我们上面所说的鼠标画矩形的思路。
画矩形
1 | def paintEvent(self, event): |
这个是关键点啊!
1 | super().paintEvent(event) |
调用父类的paintEvent(),这个是为了显示你设置的效果。否则会是一片空白。大家可以试试注释这句话,看看效果啊!
1 | rect =QRect(self.x0, self.y0, abs(self.x1-self.x0), abs(self.y1-self.y0)) |
QRect类使用整数精度在平面中定义一个矩形。矩形通常表示为左上角和大小。QRect的大小(宽度和高度)始终等同于构成其渲染基础的数学矩形。QRect可以用一组左,上,宽和高整数,或者从QPoint和QSize构成。以下代码创建两个相同的矩形。
1 | QRect(100, 200, 11, 16) |
构建一个QPainter对象,设置它的画笔,然后画一个矩形。貌似感觉好简单!^_^”
1 | pqscreen = QGuiApplication.primaryScreen() |
截屏的原理呢,主要还是运用QScreen类中的grabWindow方法。
1 | 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()函数从屏幕抓取像素,而不是从窗口抓取像素,即,如果有另一个窗口部分或全部覆盖抓取的像素,则也会从上面的窗口获取像素。鼠标光标一般不会被抓取。详见官网介绍:QScreen Class | Qt GUI 5.10
由于QScreen类无构造函数,所以我们使用QGuiApplication.primaryScreen()创建了一个Qscreen类对象。最后使用pixmap2.save(‘555.png’),保存具体的截图。
如果你想保存的图片没有红框,可以参考这里:
《pyqt5与opencv的小小融合》这篇文章中如何实现保存下来