Python OpenCV 使用 PyQt5 顯示影像圖片

本篇介紹如何使用 Python 與 PyQt5 來讀取並顯示 OpenCV 的影像圖片,在本篇將會學習到如何使用 PyQt5 寫一個簡單的視窗程式,
並且添加按鈕事件,把圖片影像顯示在視窗上,

本篇會學習到的內容有以下幾點,

  • 建立一個 PyQt5 的視窗程式
  • 新增 PyQt5 的按鈕事件
  • 將 OpenCV 的影像顯示在 PyQt5 視窗程式上
  • 新增 PyQt5 的滑鼠事件

本篇需要的基礎知識有以下幾點,

以下為我的環境,

  • Ubuntu 16.04
  • Python 3.5.2

Ubuntu 用 apt 安裝 PyQt5,

1
sudo apt-get install python3-pyqt5

接下來開始進入主題吧!

PyQt5 顯示 OpenCV 影像圖片的視窗程式

這邊將開始學習怎麼建立一個 PyQt5 的視窗程式,並且在視窗顯示的同時也將影像圖片也顯示出來,
首先,建立一個 PyQt5 的 Dialog 視窗程式,這部份在我之前的文章介紹過,所以先跳過不介紹,
之後在程式啟動時,使用 cv2.imread() 將影像圖片讀取進來,
再把 OpenCV 影像格式轉成 PyQt5 可以顯示的格式,也就是轉成 QImage RGB888,
最後再把轉換後的影像顯示在 QLabel 上,

python3-opencv-image-pyqt.py
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import cv2
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QDialog, QGridLayout, QLabel

class MyDialog(QDialog):
def __init__(self):
super().__init__()
self.initUI()
self.showImage()

def initUI(self):
self.resize(400, 300)
self.label = QLabel()

layout = QGridLayout(self)
layout.addWidget(self.label, 0, 0, 4, 4)

def showImage(self):
self.img = cv2.imread('lena.jpg', -1)
if self.img.size == 1:
return
height, width, channel = self.img.shape
bytesPerline = 3 * width
self.qImg = QImage(self.img.data, width, height, bytesPerline, QImage.Format_RGB888).rgbSwapped()
self.label.setPixmap(QPixmap.fromImage(self.qImg))

if __name__ == '__main__':
a = QApplication(sys.argv)
dialog = MyDialog()
dialog.show()
sys.exit(a.exec_())

將這個程式執行起來後,就可以看見 lena.jpg 這張圖片被讀取進來並顯示在視窗上啦,結果如下圖所示,

PyQt5 顯示 OpenCV 影像圖片的視窗程式(加入讀取圖片的按鈕事件)

上個範例已經學會最基本的顯示圖片的視窗程式囉!但是能不能讓使用者用開啟檔案對話框去選擇要顯示的圖片呢?

好!那接下來就來學習看看這功能怎麼作,以上個範例作為延伸來加入按鈕事件,讓使用者按下該按鈕後彈出開啟檔案對話框,
讓使用者去要顯示的影像圖片,如果選取的圖片路徑確定有效後,
便將該圖片利用 cv2.imread() 讀取進來,最後再顯示出來,
來看看程式怎麼寫吧!

python3-opencv-image-pyqt-2.py
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import cv2
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QDialog, QFileDialog, QGridLayout, QLabel, QPushButton

class MyDialog(QDialog):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.resize(400, 300)
self.label = QLabel()
self.btnOpen = QPushButton('Open Image', self)

layout = QGridLayout(self)
layout.addWidget(self.label, 0, 0, 4, 4)
layout.addWidget(self.btnOpen, 4, 0, 1, 1)

self.btnOpen.clicked.connect(self.openSlot)

def openSlot(self):
filename, _ = QFileDialog.getOpenFileName(self, 'Open Image', 'Image', '*.png *.jpg *.bmp')
if filename is '':
return
self.img = cv2.imread(filename, -1)
if self.img.size == 1:
return
self.showImage()

def showImage(self):
height, width, channel = self.img.shape
bytesPerline = 3 * width
self.qImg = QImage(self.img.data, width, height, bytesPerline, QImage.Format_RGB888).rgbSwapped()
self.label.setPixmap(QPixmap.fromImage(self.qImg))

if __name__ == '__main__':
a = QApplication(sys.argv)
dialog = MyDialog()
dialog.show()
sys.exit(a.exec_())

把程式執行起來,這次我們選別的 fruits.jpg 圖片來顯示看看,結果如下圖所示,

PyQt5 顯示 OpenCV 影像圖片的視窗程式(加入影像處理的按鈕事件)

到目前為止已經學會怎麼用開啟檔案對話框來選取想要顯示的圖片了,
那是不是可以再加一個按鈕事件來作一些影像處理呢?

我們就以影像模糊化這個功能為例,對於 OpenCV 圖片模糊化不熟悉的可以回去看我之前的文章
這邊就直接新增好 processSlot 按鈕事件後,並在按鈕事件裡對影像使用 cv2.blur() 模糊化處理,
之後再將處理後的影像更新在視窗上,這樣就完成了囉!
來看看程式怎麼寫吧!

python3-opencv-image-pyqt-3.py
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
53
54
55
56
57
58
59
60
61
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import cv2
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QDialog, QFileDialog, QGridLayout, QLabel, QPushButton

class MyDialog(QDialog):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.resize(400, 300)
self.label = QLabel()
self.btnOpen = QPushButton('Open Image', self)
self.btnProcess = QPushButton('Blur Image', self)
self.btnSave = QPushButton('Save Image', self)
self.btnSave.setEnabled(False)

layout = QGridLayout(self)
layout.addWidget(self.label, 0, 0, 4, 4)
layout.addWidget(self.btnOpen, 4, 0, 1, 1)
layout.addWidget(self.btnProcess, 4, 1, 1, 1)
layout.addWidget(self.btnSave, 4, 2, 1, 1)

self.btnOpen.clicked.connect(self.openSlot)
self.btnProcess.clicked.connect(self.processSlot)
self.btnSave.clicked.connect(self.saveSlot)

def openSlot(self):
filename, _ = QFileDialog.getOpenFileName(self, 'Open Image', 'Image', '*.png *.jpg *.bmp')
if filename is '':
return
self.img = cv2.imread(filename, -1)
if self.img.size == 1:
return
self.showImage()
self.btnSave.setEnabled(True)

def saveSlot(self):
filename, _ = QFileDialog.getSaveFileName(self, 'Save Image', 'Image', '*.png *.jpg *.bmp')
if filename is '':
return
cv2.imwrite(filename, self.img)

def processSlot(self):
self.img = cv2.blur(self.img, (7, 7))
self.showImage()

def showImage(self):
height, width, channel = self.img.shape
bytesPerline = 3 * width
self.qImg = QImage(self.img.data, width, height, bytesPerline, QImage.Format_RGB888).rgbSwapped()
self.label.setPixmap(QPixmap.fromImage(self.qImg))

if __name__ == '__main__':
a = QApplication(sys.argv)
dialog = MyDialog()
dialog.show()
sys.exit(a.exec_())

把程式執行起來,這次我們一樣選擇水果的圖片來試看看,最後模糊化的結果如下圖顯示,

PyQt5 顯示 OpenCV 影像圖片的視窗程式(加入滑鼠滾輪事件來處理縮放圖片)

對圖片作影像處理時,特別常用到縮放大小、裁剪圖片,
在圖形gui上常常使用滑鼠來完成這些操作,
所以滑鼠事件也是學習的重點之一,這個範例就來示範用滑鼠來縮放大小,
對於 OpenCV 縮放大小不熟悉的可以回去看我之前的文章
這邊我們直接建立一個ImageLabel類別並繼承QLabel
並且在ImageLabel上覆寫wheelEventmouseMoveEventmousePressEvent
並且在裡面實作我們自己對圖片縮放大小的邏輯,
mouseMoveEventmousePressEvent 在本範例並沒有使用到,這會在另一篇文章介紹,
這邊先關注wheelEvent,也就是滑鼠的滾輪事件,透過一些方法計算出滾輪滾動一格/一步numSteps時,進而對圖片縮放,
-1則縮小,1則放大,最後藉由cv2.resize()來完成圖片縮放,再顯示到畫面上,
來看看程式怎麼寫吧!

python3-opencv-image-pyqt-4.py
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import cv2
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QDialog, QFileDialog, QGridLayout, QLabel, QPushButton

class ImageLabel(QLabel):
scale = 1.0
def showImage(self, img):
height, width, channel = img.shape
bytesPerline = 3 * width
self.qImg = QImage(img.data, width, height, bytesPerline, QImage.Format_RGB888).rgbSwapped()
self.setPixmap(QPixmap.fromImage(self.qImg))

def mousePressEvent(self,event):
self.x = event.x()
self.y = event.y()
#print(str(self.x) + ' ' + str(self.y))

def mouseMoveEvent(self, event):
self.x = event.x()
self.y = event.y()
#print(str(self.x) + ' ' + str(self.y))

def wheelEvent(self, event):
numDegrees = event.angleDelta() / 8
numSteps = numDegrees / 15
#print(numSteps.y())
height, width, _ = self.img.shape
if numSteps.y() == -1:
if (self.scale >= 0.1):
self.scale -= 0.05
else:
if (self.scale <= 2.0):
self.scale += 0.05
#print(self.scale)
height2 = int(height * self.scale)
width2 = int(width * self.scale)
img2 = cv2.resize(self.img, (width2, height2), interpolation=cv2.INTER_AREA)
self.showImage(img2)

class MyDialog(QDialog):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.resize(400, 300)
self.label = ImageLabel()
#self.label.setMouseTracking(True)
self.btnOpen = QPushButton('Open Image', self)
self.btnProcess = QPushButton('Blur Image', self)
self.btnSave = QPushButton('Save Image', self)
self.btnSave.setEnabled(False)

layout = QGridLayout(self)
layout.addWidget(self.label, 0, 0, 4, 4)
layout.addWidget(self.btnOpen, 4, 0, 1, 1)
layout.addWidget(self.btnProcess, 4, 1, 1, 1)
layout.addWidget(self.btnSave, 4, 2, 1, 1)

self.btnOpen.clicked.connect(self.openSlot)
self.btnProcess.clicked.connect(self.processSlot)
self.btnSave.clicked.connect(self.saveSlot)

def openSlot(self):
filename, _ = QFileDialog.getOpenFileName(self, 'Open Image', 'Image', '*.png *.jpg *.bmp')
if filename is '':
return
self.label.img = cv2.imread(filename, -1)
if self.label.img.size == 1:
return
self.label.showImage(self.label.img)
height, width, _ = self.label.img.shape
self.label.setFixedSize(width, height)
self.btnSave.setEnabled(True)

def saveSlot(self):
filename, _ = QFileDialog.getSaveFileName(self, 'Save Image', 'Image', '*.png *.jpg *.bmp')
if filename is '':
return
cv2.imwrite(filename, self.label.img)

def processSlot(self):
self.label.img = cv2.blur(self.label.img, (7, 7))
self.label.showImage(self.label.img)

if __name__ == '__main__':
a = QApplication(sys.argv)
dialog = MyDialog()
dialog.show()
sys.exit(a.exec_())

把程式執行起來,這次我們一樣選擇水果的圖片來試看看,用滑鼠滾輪來放大縮小,
會發現圖片會隨著滑鼠的滾輪轉動而放大或鎖小,放大後的結果如下圖顯示,

參考
python - Show an OpenCV image with PyQt5 - Stack Overflow
https://stackoverflow.com/questions/57204782/show-an-opencv-image-with-pyqt5
QWheelEvent — Qt for Python
https://doc.qt.io/qtforpython/PySide2/QtGui/QWheelEvent.html
PyQt5番外篇(1):PyQt5与Opencv的小小融合 - 知乎
https://zhuanlan.zhihu.com/p/31810054

其它相關文章推薦
Python OpenCV 彩色轉灰階(RGB/BGR to GRAY)
Python OpenCV 彩色轉YCbCr(RGB/BGR to YCbCr)
Python OpenCV 灰階轉彩色(Gray to RGB/BGR)
Python OpenCV 影像二值化 Image Thresholding
Python OpenCV 影像平滑模糊化 blur
Python OpenCV 影像邊緣偵測 Canny Edge Detection
Python OpenCV resize 圖片縮放
Python OpenCV 畫矩形 rectangle
Python OpenCV 畫多邊形 polylines