一些核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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图像的转换

1
2
3
4
5
6
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对象的转化了。

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

参数:

  1. src - 输入图像:8位无符号,16位无符号(CV_16UC …)或单精度浮点数。
  2. dst - 输出与src相同大小和深度的图像。
  3. code - 颜色空间转换代码(请参阅下面的说明)。
  4. dstCn - 目标图像中的通道数量;如果参数是0,则通道的数量是从src和代码自动导出的。

该函数将输入图像从一个颜色空间转换为另一个颜色空间。在从RGB颜色空间转换到RGB颜色空间的情况下,通道的顺序应明确指定(RGB或BGR)。请注意,OpenCV中的默认颜色格式通常被称为RGB,但实际上是BGR(字节相反)。因此,标准(24位)彩色图像中的第一个字节将是一个8位蓝色分量,第二个字节将是绿色,而第三个字节将是红色。第四,五,六字节将是第二个像素(蓝色,然后是绿色,然后是红色),依此类推。

这里我们就是要求从Opencv的BGR图像转换成RGB图像了。为什么?因为要转换成PyQt5可以识别的啊!

1
2
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提供了一系列功能,可用于获取有关图像的各种信息。也有几个功能,使图像转换。
详见官网介绍: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
2
3
4
5
6
7
8
9
10
11
12
13
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。也就是我们上面所说的鼠标画矩形的思路。

画矩形

1
2
3
4
5
6
7
8
9
10
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')

这个是关键点啊!

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
2
3
4
5
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对象,设置它的画笔,然后画一个矩形。貌似感觉好简单!^_^”

1
2
3
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方法。

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的小小融合》这篇文章中如何实现保存下来