PyQt5番外篇(1):PyQt5與Opencv的小小融合
曾經有位學友像我諮詢如何將PyQt5與Opencv融合,Opencv我之前並沒有接觸過,經過一番研究之後,實現了下面這個功能:
在一個顯示圖片窗口上,畫一個矩形,最後我增加了一個功能,將矩形框內的圖片保存在本地。效果如下:
Opencv的安裝
開始是使用pip進行安裝,輸入:pip install opencv-python。
如圖:
為了驗證安裝是否成功,打開Python解釋器,輸入import cv2,提示DLLL load failed,一般出現這個問題有兩種解決方式:
- 請安裝對應的Visual C++ Redistributable for Visual Studio 2015,下載地址:Visual C++ Redistributable for Visual Studio 2015
- 安裝最新的Opencv,可以訪問這個網址:https://www.lfd.uci.edu/~gohlke/pythonlibs/,選擇合適opencv版本。其中的cp35、cp36對應的是Python是3.5還是3.6。
同時建議在安裝前將numpy升級成最新的版本,否則Opencv也可能無法運行。
pip install --upgrade numpy
上述涉及的文件,我都放到網盤中了,建議通過網盤下載,在網站上下載的話速度太慢了。關注微信公眾號:學點編程吧,發送:cvfiles,可以獲取相應的鏈接。
Opencv的名詞解釋
可能還有一些學友對於Opencv是什麼東東還不是很熟悉,下面這個摘在百科百科。
OpenCV是一個基於BSD許可(開源)發行的跨平台計算機視覺庫,可以運行在Linux、Windows、Android和Mac OS操作系統上。它輕量級而且高效——由一系列 C 函數和少量 C++ 類構成,同時提供了Python、Ruby、MATLAB等語言的介面,實現了圖像處理和計算機視覺方面的很多通用演算法。
OpenCV用C++語言編寫,它的主要介面也是C++語言,但是依然保留了大量的C語言介面。該庫也有大量的Python, Java and MATLAB/OCTAVE (版本2.5)的介面。這些語言的API介面函數可以通過在線文檔獲得。如今也提供對於C#,Ch, Ruby的支持。
應用領域:人機互動、物體識別、圖像分割、人臉識別、動作識別、運動跟蹤、機器人、運動分析、機器視覺、結構分析、汽車安全駕駛
一些核心代碼
在PyQt5中融合opencv、圖像上畫矩形、保存截圖,其實這些代碼在網上都有,即使PyQt的沒有,適用於Qt編程的C++代碼也是有的,但是描述的都過於簡單,或者沒有學習過C++的難以理解。下面我會儘可能用最簡單的方法加以說明我的代碼,要是感覺不對,歡迎吐槽,相互學習進步。要是合你的心意,歡迎給我點贊,讚賞更好,罒ω罒。
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圖像的轉換
img = cv2.imread("xxx.jpg")height, width, bytesPerComponent = img.shapebytesPerLine = 3 * widthcv2.cvtColor(img, cv2.COLOR_BGR2RGB, img)QImg = QImage(img.data, width, height, bytesPerLine, QImage.Format_RGB888)pixmap = QPixmap.fromImage(QImg)
這個就是Opencv和PyQt對象的轉化了。
img = cv2.imread("xxx.jpg")
使用Opencv讀取圖像。
height, width, bytesPerComponent = img.shape
在OpenCV-Python綁定中,圖像使用NumPy數組的屬性(這就解釋了為什麼要更新numpy)來表示圖像的尺寸和通道信息。此時如果我們輸出img.shape,將得到(200, 360, 3)。最後的3表示這是一個RGB圖像。
cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img)
將圖像從一個顏色空間轉換為另一個顏色空間。
Python中的函數要求是這樣的:
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可以識別的啊!
bytesPerLine = 3 * widthQImg = 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
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的官方介紹,限於篇幅這裡不展開。
pixmap = QPixmap.fromImage(QImg)
這個很好理解,就是想QImage對象轉換成QPixmap對象,便於下步我們將Label標籤中設置圖像。
self.lb.setPixmap(pixmap)
設置標籤的圖像信息。
self.lb.setCursor(Qt.CrossCursor)
設置滑鼠在QLabel對象中的樣式,只是為了畫畫好看些而已,沒其它的意思。除了這個十字架的,還有其它很多樣式,如下圖:
滑鼠事件
按照上文中我們介紹的思路,我們自定義了一個QLabel類myLabel,當然是繼承了QLabel。然後我們用幾個類變數記錄滑鼠的坐標和矩形是否完成的標誌。
def mousePressEvent(self,event): self.flag = True self.x0 = event.x() self.y0 = event.y()def mouseReleaseEvent(self,event): self.flag = Falsedef mouseMoveEvent(self,event): if self.flag: self.x1 = event.x() self.y1 = event.y() self.update()
這裡就是重載了滑鼠產生的幾個事件,是我們自定義的。分別記錄了點擊滑鼠後初始的滑鼠坐標,以及釋放滑鼠後的滑鼠坐標。並在滑鼠移動的時候更新UI。也就是我們上面所說的滑鼠畫矩形的思路。
畫矩形
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")
這個是關鍵點啊!
super().paintEvent(event)
調用父類的paintEvent(),這個是為了顯示你設置的效果。否則會是一片空白。大家可以試試注釋這句話,看看效果啊!
rect =QRect(self.x0, self.y0, abs(self.x1-self.x0), abs(self.y1-self.y0))
QRect類使用整數精度在平面中定義一個矩形。矩形通常表示為左上角和大小。QRect的大小(寬度和高度)始終等同於構成其渲染基礎的數學矩形。QRect可以用一組左,上,寬和高整數,或者從QPoint和QSize構成。以下代碼創建兩個相同的矩形。
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對象,設置它的畫筆,然後畫一個矩形。貌似感覺好簡單!^_^」
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方法。
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』),保存具體的截圖。
最後
ok,今天的介紹暫時就到這裡吧。下期我們再約。也算是第一篇和Opencv沾邊的文章了吧,或許以後還會發些其它相關的。
如果你想要本次教程中的相關源碼,請關注微信公眾號:學點編程吧,發送pyqt5cv,會自動得到相應的百度網盤下載鏈接。
推薦閱讀:
※Python:圖片轉字元畫
※快收藏了!GitHub 上最火最值得看的 Python 開源項目
※抓取單博主的所有微博及其評論
※學習筆記四:改善Python程序的91個建議
※碎片化學習Python的又一神作:termux