[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程序:

PyQt5 QWidget 窗口

OK, 如果你的程序能夠運行,顯示出了窗口,那麼說明各種依賴已經安裝好了,可以繼續閱讀啦。(否則散了吧)


2. 在 PyQt5 的 QWidget 控制項中顯示使用 OpenCV 讀取圖片

總所周知的是, 在 Python 中使用 cv2.imread 讀取到的是 np.ndarray 數組,而 PyQt5.QtWidgets.QLabel 顯示的則是 PyQt5.QtGui.QPixmap 圖片。所以要想使用 QLabel 顯示 np.ndarray,首先要進行數據類型轉換。同時注意到, OpenCV 圖像通道是 BGR, 而 Qtmatplotlibpillow 等圖像通道則是 RGB,所以一定要提前進行通道轉換,否則顏色顯示會 不正常

便捷函數:

添加完邊界函數後,修改 ImageView 類的初始化函數,ImageView.__init__() ,增加讀取圖片、設置圖片、縮放大小等操作,然後在次運行,就可以顯示圖片了。

SO EASY! 老師再也不用擔心我不會顯示圖片啦。


3. 使用 slider 控制參數,設置 Canny 閾值。

上面的 Demo 介紹的是顯示圖片,但是如果想要進行一些交互,則顯然是不夠的。需要其他控制項來輔助操作。如 QRadioButtonQSlider 等。在 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的中值濾波演算法實現

TAG:PyQt | OpenCV | 图像处理 |