[171118] PyQt5 滑動條控制 Canny 邊緣閾值並繪製 OpenCV 圖像
原創內容,禁止轉載
上周六雙十一挖的 PyQt 的坑,今天又是周六了,填一個坑吧。
在我的專欄 [171102] Python3 OpenCV3 圖像處理基礎(Python3 + Numpy + Matplotlib + OpenCV3 + ...)中的結尾部分,給出的 OpenCV 的例子是 Canny 邊緣檢測。其實呢,學過的都知道,邊緣檢測需要兩個閾值。怎樣控制閾值呢?可以硬編碼寫死在程序里,或者使用控制台接受一次性參數輸入,或者採用GUI界面編程通過控制項進行調控。
關於 Qt 的基礎知識,去搜索博客或者其他專欄吧,我就不做重複性工作了
今天主要內容有:
1. 安裝小demo測試n2. 介紹一下在 PyQt5 的 QWidget 控制項中顯示使用 OpenCV 讀取圖片n3. 使用 QSlider 滑動條控制參數,來進行Canny邊緣檢測。n
假設其他環境已經滿足(參考[171102] Python3 OpenCV3 圖像處理基礎(Python3 + Numpy + Matplotlib + OpenCV3 + ...))。
1. 安裝和測試
(1) 首先安裝 PyQt5:
sudo pip3 install PyQt5 n
(2) 給出最簡單的顯示窗口的小demo程序:
OK, 如果你的程序能夠運行,顯示出了窗口,那麼說明各種依賴已經安裝好了,可以繼續閱讀啦。(否則散了吧)
2. 在 PyQt5 的 QWidget 控制項中顯示使用 OpenCV 讀取圖片
總所周知的是, 在 Python 中使用 cv2.imread
讀取到的是 np.ndarray
數組,而 PyQt5.QtWidgets.QLabel
顯示的則是 PyQt5.QtGui.QPixmap
圖片。所以要想使用 QLabel
顯示 np.ndarray
,首先要進行數據類型轉換。同時注意到, OpenCV
圖像通道是 BGR
, 而 Qt
或matplotlib
或 pillow
等圖像通道則是 RGB
,所以一定要提前進行通道轉換,否則顏色顯示會 不正常
。
便捷函數:
添加完邊界函數後,修改 ImageView 類的初始化函數,ImageView.__init__()
,增加讀取圖片、設置圖片、縮放大小等操作,然後在次運行,就可以顯示圖片了。
SO EASY!
老師再也不用擔心我不會顯示圖片啦。
3. 使用 slider 控制參數,設置 Canny 閾值。
上面的 Demo 介紹的是顯示圖片,但是如果想要進行一些交互,則顯然是不夠的。需要其他控制項來輔助操作。如 QRadioButton
、 QSlider
等。在 OpenCV 中,通常需要對操作設置閾值,如大名鼎鼎 Canny 邊緣檢測,需要設置雙閾值來對邊緣進行滯後閾值非極大值抑制處理。怎樣控制閾值呢?可以硬編碼寫死在程序里,或者使用控制台接受一次性參數輸入,或者採用GUI界面編程通過控制項進行調控。這裡給出使用 QSlider
進行參數調節的方法。
首先,相信接觸過 Qt 編程的同學,都會對 信號-槽
有所耳聞。至於能不能耳熟能詳
,我就布吉島了(反正我不懂 ,我在這裡不懂裝懂瞎忽悠)
直觀上解釋,信號
是控制項
的某種事件觸發
的,而槽
則是觸發事件
後的調用的事件處理函數
。例如,上過高中的都知道 Pavlovs Dog
。對於小狗來說,它條件發射式地建立起特定聲音(信號)和分泌唾液(槽函數)之間的聯繫。當特定聲音來了,如 Pavlov
喊道"大黃大黃吃飯啦";然後小狗大黃接受到該信號後,觸發分泌唾液的槽函數;然後流了一地哈喇子。。。在 Qt 中,對某個控制項的特定事件信號綁定槽函數,當觸發控制項的特定信號時也可以實現類似的效果。
使用 QSlider
滑動條控制項,當滑動滑動條時,會產生valueChanged
事件(信號)。如果先前有對 valueChanged
信號綁定特定的槽函數如 valueChangedHandler
(如僅僅輸出當前滑動條的值),那麼滑動滑動條滑塊時,也就是改變滑動條的值時,就會調用 valueChangedHandler
事件處理函槽函數啦(也就是列印當前值)。
這是使用滑動條控制Canny邊緣檢測:
源碼如下:
#!/usr/bin/python3n# 2017.11.18 14:12:22 CSTn# 2017.11.18 16:09:32 CSTnfrom PyQt5.QtWidgets import *nfrom PyQt5.QtGui import *nfrom PyQt5.QtCore import *nimport numpy as npnimport cv2nimport os, sysnndef mat2qpixmap(img):n """ numpy.ndarray to qpixmapn """n height, width = img.shape[:2]n if img.ndim == 3:n rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)n elif img.ndim ==2:n #qimage = QImage(img.flatten(), width, height, QImage.Format_Indexed8)n rgb = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)n else:n raise Exception("Unstatistified image data format!")n qimage = QImage(rgb.flatten(), width, height, QImage.Format_RGB888)n qpixmap = QPixmap.fromImage(qimage)n return qpixmapn #qlabel.setPixmap(qpixmap)nnclass ImageView(QWidget):n """顯示 OpenCV 圖片的 QWidget 控制項n """n def __init__(self, winname = "ImageView"):n super().__init__()n self.setWindowTitle(winname)n self.imageLabel = QLabel(self)n self.imageLabel.setText(winname)n #self.resize(200,150) # 寬W, 高Hn self.show()nn def setPixmap(self, img):n #img = cv2.imread("test.png")n if img is not None:n H,W = img.shape[:2]n qpixmap = mat2qpixmap(img)n self.imageLabel.setPixmap(qpixmap)n self.resize(W,H) # 寬W, 高Hn self.imageLabel.resize(W,H) # 寬W, 高Hnnclass SliderCanny(QWidget):n """創建滑動條控制界面,設置Canny邊緣檢測上下閾值。n """n def __init__(self, img=None):n super().__init__()n if img is None:n raise Exception("The Image IS Empty !")n passn self.img = imgn self.gray = cv2.GaussianBlur(cv2.cvtColor(self.img, cv2.COLOR_BGR2GRAY), (3,3), 0)n self.setWindowTitle("Th")n self.ImageView = ImageView("Source")n self.CannyView = ImageView("Canny")n self.ImageView.setPixmap(self.img)n self.CannyView.setPixmap(self.img)nn self.mainLayout = QVBoxLayout()n self.setLayout(self.mainLayout)n self.addSliders()n self.show()nn def addSliders(self):n ## 創建水平滑動條n self.slider1 = QSlider(Qt.Horizontal)n self.slider2 = QSlider(Qt.Horizontal)nn ## 設置範圍和初值n self.slider1.setRange(10, 250)n self.slider2.setRange(10, 250)n self.slider1.setValue(50)n self.slider2.setValue(200)nn ## 添加到界面中n self.mainLayout.addWidget(self.slider1)n self.mainLayout.addWidget(self.slider2)nn ## 綁定信號槽n self.slider1.valueChanged.connect(self.doCanny)n self.slider2.valueChanged.connect(self.doCanny)nn def doCanny(self):n th1 = self.slider1.value()n th2 = self.slider2.value()n print(th1, th2 )n self.setWindowTitle("TH:{}~{}".format(th1, th2))nn ## Canny 邊緣檢測n cannyed = cv2.Canny(self.gray, th1, th2)n ## 創建彩色邊緣n mask = cannyed > 0 # 邊緣掩模n canvas = np.zeros_like(self.img) # 創建畫布n canvas[mask] = img[mask] # 賦值邊緣n ## 顯示結果n self.CannyView.setPixmap(canvas)nnif __name__ == "__main__":n qApp = QApplication([])n img = cv2.imread("test.png")n w2 = SliderCanny(img)n sys.exit(qApp.exec_())n
完結,手動撒花。
推薦閱讀:
※數字圖像處理專業如何快速入門?謝謝。
※斯坦福大學2017年春季_基於卷積神經網路的視覺識別課程視頻教程及ppt分享
※基於FPGA的中值濾波演算法實現