第3章 學習圖形使用者界面
在第2章中,我們學習了OpenCV的基本類和結構,以及最重要的類Mat,還學習了如何讀取和儲存圖像及視訊,以及圖像在記憶體中的内部結構。我們現在已準備好使用OpenCV,但是,在大多數情況下,我們需要使用許多使用者界面來顯示圖像結果,并檢索使用者與圖像的互動。OpenCV為我們提供了一些基本的使用者界面,以便建立應用程式和原型。為了更好地了解使用者界面的工作原理,我們将在本章最後建立一個名為PhotoTool的小應用程式,在這個應用程式中,我們将學習如何使用濾鏡和顔色轉換。
本章介紹以下主題:
- OpenCV基本使用者界面
- OpenCV Qt界面
- 滑塊和按鈕
- 進階使用者界面:OpenGL
- 顔色轉換
- 基本濾波器
3.1 技術要求
本章需要熟悉基本的C++程式設計語言,所使用的所有代碼都可以從以下的GitHub連結下載下傳:
https://github.com/PacktPublishing/Learn-OpenCV-4-By-Building-Projects-Second-Edition/tree/master/Chapter_03。代碼可以在任何作業系統上執行,盡管它隻在Ubuntu上測試過。
3.2 OpenCV使用者界面介紹
OpenCV擁有自己的跨作業系統使用者界面,它使開發人員能夠建立自己的應用程式,而無須學習複雜的使用者界面庫。OpenCV使用者界面是基礎性的,但是它為計算機視覺開發人員提供了建立和管理軟體開發的基本功能。所有這些功能都是原生的,并針對實時應用進行了優化。
OpenCV提供兩種使用者界面選項:
- 基于原生使用者界面的基本界面,适用于Mac OS X的cocoa或carbon,以及适用于Linux或Windows使用者界面的GTK,這些界面在編譯OpenCV時被預設選擇。
- 基于Qt庫的略微更進階的界面,這是跨平台的界面。必須在編譯OpenCV之前,在CMake中手動啟用Qt選項。
在圖3-1中,可以看到左側的基本使用者界面視窗,而右側則是Qt使用者界面。

3.3 OpenCV的基本圖形使用者界面
我們将使用OpenCV建立一個基本使用者界面。OpenCV使用者界面使我們能夠建立視窗,然後在裡面添加圖像,并移動、調整大小和銷毀所添加的圖像。使用者界面位于OpenCV的highui子產品中。在下面的代碼中,我們将學習如何建立和顯示兩個圖像,具體來說,可以在桌面上通過按鍵來顯示多個視窗,并使圖像移入這些視窗中。
不要擔心閱讀完整的代碼,我們會用小代碼塊來逐個解釋它:
我們來了解這段代碼:
- 為了便于使用圖形使用者界面,第一項必須完成的任務是導入OpenCV的highui子產品:
- 完成建立新視窗的準備工作之後,我們必須加載一些圖像:
- 要建立視窗,我們使用namedWindow函數。該函數有兩個參數。第一個參數是帶有視窗名稱的常量字元串,第二個參數是我們需要的标志,第二個參數是可選的:
- 在這個例子中,我們建立了兩個視窗:第一個叫作Lena,第二個叫作Photo。
對于Qt和原生界面,預設有三個标志:
- WINDOW_NORMAL:此标志允許使用者調整視窗的大小
- WINDOW_AUTOSIZE:如果設定了此标志,則視窗大小為自動調整以适應顯示圖像,但不能調整視窗的大小
- WINDOW_OPENGL:此标志啟用OpenGL支援
Qt有許多額外的标志:
- WINDOW_FREERATIO或WINDOW_KEEPRATIO:如果設定了WINDOW_FREERATIO,則調整圖像時不考慮其比例。如果設定了WINDOW_KEEPRATIO,則根據其比例調整圖像。
- WINDOW_GUI_NORMAL或WINDOW_GUI_EXPANDED:第一個标志提供沒有狀态欄和工具欄的基本界面。第二個标志使用狀态欄和工具欄來支援最進階的圖形使用者界面。
- 當建立多個視窗時,它們是疊加的,但我們可以使用moveWindow函數将視窗移動到桌面的任何區域,如下所示:
帶你讀《OpenCV 4計算機視覺項目實戰 (原書第2版)》之三:學習圖形使用者界面第3章 學習圖形使用者界面
-
在這段代碼中,我們将Lena視窗向左移動了10個像素,向上移動了10個像素,将Photo視窗向左移動了520個像素,向上移動了10個像素。
在使用imshow函數顯示此前加載的圖像之後,我們通過調用resizeWindow函數将Lena視窗的大小調整為512像素,該函數有三個參數:window name、width和height。
- 在利用waitKey函數等待按鍵按下之後,我們用destroyWindow函數删除這個視窗,其中,視窗的名稱是唯一需要的參數:
帶你讀《OpenCV 4計算機視覺項目實戰 (原書第2版)》之三:學習圖形使用者界面第3章 學習圖形使用者界面
- OpenCV可以通過一次調用删除所建立的所有視窗,該函數稱為destroyAllWindows。為了示範它是如何工作的,我們在樣本中建立10個視窗,然後等待按鍵按下。當使用者按下任意鍵時,它就會銷毀所有得視窗:
帶你讀《OpenCV 4計算機視覺項目實戰 (原書第2版)》之三:學習圖形使用者界面第3章 學習圖形使用者界面
在任何情況下,OpenCV都會在應用程式終止時自動銷毀所有視窗,是以在我們的應用程式結束時不必調用此函數。
所有這些代碼的結果可以在以下圖像中看到。首先,它顯示兩個視窗,如圖3-2所示。
按下任意鍵之後,應用程式繼續運作,并在不同的位置繪制出幾個視窗,如圖3-3所示。
隻需幾行代碼,我們就可以建立和操作視窗并顯示圖像。我們現在已經準備好進行使用者與圖像的互動,并添加使用者界面控件。
将滑塊和滑鼠事件添加到界面
滑鼠事件和滑塊控件在計算機視覺和OpenCV中非常有用。使用這些控件,可以直接與界面互動,并改變輸入圖像或變量的屬性。在本節中,我們将介紹用于基本互動的滑鼠事件和滑塊控件。為了便于正确了解,我們建立了以下代碼,通過這些代碼,我們将使用滑鼠事件在圖像中繪制綠色圓圈,并使用滑塊對圖像進行模糊處理:
我們來了解這段代碼。
首先,建立一個變量來儲存滑塊位置。我們需要儲存滑塊位置,以便從其他函數通路它:
現在,為滑塊和滑鼠事件定義回調函數,這是OpenCV的setMouseCallback函數和createTrackbar函數必需的:
在main函數中,加載一個圖像并建立一個名為Lena的新視窗:
現在建立滑塊。OpenCV的createTrackbar函數用于生成滑塊,其參數按順序如下所示:
- 跟蹤條名稱。
- 視窗名稱。
- 将作為值使用的整數指針。該參數是可選的,如果被設定,則滑塊會在建立時獲得該位置。
- 滑塊上的最大位置。
- 滑塊位置變化時的回調函數。
- 要發送到回調函數的使用者資料。它可用于在不使用全局變量的情況下将資料發送到回調函數。
對于這段代碼,我們為Lena視窗添加了trackbar,然後調用Lena跟蹤條對圖像進行模糊處理。跟蹤條的值存儲在将會作為指針傳遞的blurAmount整數中,并将跟蹤條的最大值設定為30。把onChange設定為回調函數,并将lena mat圖像作為使用者資料發送:
滑塊建立好以後,當使用者單擊滑鼠左鍵時,我們添加滑鼠事件來繪制圓形。這需要使用OpenCV的setMouseCallback函數,該函數有三個參數:
- 擷取滑鼠事件的視窗名稱。
- 當有任何滑鼠互動時調用的回調函數。
- 使用者資料:這是在觸發時将要發送給回調函數的任意資料。在這個例子中,我們将會發送整個Lena圖像。
使用以下代碼,可以向Lena視窗添加滑鼠回調,并将onMouse設定為回調函數,進而将lena mat圖像作為使用者資料進行傳遞:
為了完成主函數,需要使用與滑塊相同的參數來初始化圖像。要執行初始化,隻需調用onChange回調函數,并在使用destroyWindow關閉視窗之前等待事件,如下面的代碼所示:
滑塊回調函數使用滑塊值作為模糊量,将基本的模糊濾鏡應用于圖像:
該函數使用變量pos來檢查滑塊值是否為0。在這種情況下,我們不使用過濾器,因為它會生成執行錯誤,也不能用0像素模糊。檢查滑塊值後,我們建立一個名為imgBlur的空矩陣來存儲模糊結果。要檢索通過回調函數中的使用者資料發送的圖像,必須把void userData轉換為正确的圖像類型指針Mat。
現在我們有了正确的變量來應用模糊濾鏡。模糊函數将基本的中值濾波器應用于輸入圖像,在這個例子中是* img。對于輸出圖像,最後需要的參數是想要應用的模糊核心的大小(核心是用于計算核心和圖像之間卷積平均值的小矩陣)。在這個例子中使用的是pos大小的平方核心。最後,隻需用imshow函數更新圖像界面。
滑鼠事件的回調函數有五個輸入參數:第一個參數定義事件類型,第二個和第三個定義滑鼠位置,第四個參數定義滾輪動作,第五個參數定義使用者輸入資料。
滑鼠事件類型如下:
在這個例子中,我們隻處理單擊滑鼠左鍵所産生的事件,并且丢棄除EVENT_LBUTTONDOWN之外的任何事件。丢棄其他事件後,用滑塊回調擷取輸入圖像,并用OpenCV的circle函數擷取圖像中的圓:
3.4 Qt圖形使用者界面
Qt使用者界面為我們提供了更多控制和選項來處理圖像。
其界面分為以下三個主要區域:
- 工具欄
- 圖像區域
- 狀态欄
我們可以在圖3-4中看到這三個區域。圖像上面是工具欄,中間是圖像區域,底部是狀态欄。
工具欄從左到右具有以下按鈕:
- 用于平移的四個按鈕
- 縮放x1
- 縮放x30,顯示标簽
- 放大
- 縮小
- 儲存目前圖像
- 顯示屬性
可以在圖3-5中清楚地看到這些選項。
圖像區域顯示圖像,并且當我們在圖像上按下滑鼠右鍵時顯示上下文菜單。這個區域可以使用displayOverlay函數在該區域頂部顯示疊加消息,該函數接受三個參數:視窗名稱、要顯示的文本以及顯示疊加文本的時間(以毫秒為機關)。如果這個時間設定為0,則文本永遠不會消失:
我們可以在圖3-6中看到前面代碼的運作結果。你可以在圖像頂部看到一個小黑框,其中包含字元串“Overse 5secs”:
最後,狀态欄在視窗的底部顯示圖像中的像素值和坐标位置,如圖3-7所示。
我們可以在狀态欄中像疊加層一樣顯示消息,displayStatusBar函數可以更改狀态欄的消息。該函數具有與疊加函數相同的參數:視窗名稱、要顯示的文本和文本的顯示時間,如圖3-8所示。
将按鈕添加到使用者界面
在前面的部分中,我們學習了如何建立一般界面或者Qt界面,并使用滑鼠和滑塊與它們進行互動,但我們也可以建立不同類型的按鈕。
OpenCV Qt支援的按鈕類型如下:
- 按鈕
- 複選框
- 單選框
這些按鈕僅出現在控制台中。控制台是每個程式的獨立視窗,我們可以在其中附加按鈕和跟蹤條。要顯示控制台,我們可以按下最後一個工具欄按鈕,右鍵單擊Qt視窗的任何部分,并選擇“顯示屬性”視窗,或者用Ctrl + P快捷鍵。讓我們用按鈕建立一個基本的例子。代碼較長,我們首先解釋主函數,然後分别解釋每個回調函數,以便更好地了解所有内容。以下代碼向我們展示生成使用者界面的主函數代碼:
我們應用三種類型的濾鏡:模糊、Sobel過濾器以及将顔色轉換為灰色。所有這些都是可選的,使用者可以使用要建立的按鈕選擇每一種濾鏡。然後,為了獲得每個過濾器的狀态,我們建立了三個全局布爾變量:
在main函數中,在加載圖像并建立視窗之後,必須使用createButton函數來建立每個按鈕。
OpenCV中定義了三種按鈕類型:
- QT_CHECKBOX
- QT_RADIOBOX
- QT_PUSH_BUTTON
每個按鈕有五個參數,按順序如下所示:
- 按鈕名稱
- 回調函數
- 傳遞給回調函數的使用者變量資料的指針
- 按鈕類型
- 用于複選框和單選框按鈕類型的預設初始化狀态
然後,建立一個模糊複選框按鈕,兩個用于顔色轉換的單選框按鈕,以及一個用于sobel過濾器的按鈕,如下面的代碼所示:
這些是main函數中最重要的部分。我們将探讨回調(Callback)函數。每個回調函數都會更改其狀态變量以調用另一個名為applyFilters的函數,以便将激活的過濾器添加到輸入圖像:
applyFilters函數檢查每個過濾器的狀态變量:
要将顔色更改為灰色,我們使用cvtColor函數,該函數接受三個參數:輸入圖像、輸出圖像和顔色轉換類型。
最有用的顔色空間轉換如下:
- RGB或BGR到灰階(COLOR_RGB2GRAY, COLOR_BGR2GRAY)
- RGB或BGR到YcrCb (或YCC) (COLOR_RGB2YCrCb, COLOR_BGR2YCrCb)
- RGB或BGR到HSV (COLOR_RGB2HSV, COLOR_BGR2HSV)
- RGB或BGR到Luv (COLOR_RGB2Luv, COLOR_BGR2Luv)
- 灰階到RGB或BGR (COLOR_GRAY2RGB, COLOR_GRAY2BGR)
可以看到代碼很容易記憶。
模糊濾波器已經在前一節中做過描述,最後,如果applySobel變量為真,就應用sobel濾波器。sobel濾波器是使用sobel算子獲得的圖像導數,通常用于檢測圖像邊緣。OpenCV能夠生成具有核心大小的不同導數,但最常見的是用于計算x導數或y導數的3x3核心。
最重要的sobel參數如下:
- 輸入圖像
- 輸出圖像
- 輸出圖像深度(CV_8U,CV_16U,CV_32F,CV_64F)
- 導數x的階
- 導數y的階
- 核心大小(預設值為3)
要生成3×3核心和第一個x階導數,必須使用以下參數:
以下參數用于y階導數:
在這個例子中,我們同時使用x和y導數來重寫輸入。以下代碼段顯示如何通過在第四個和第五個參數中添加1來同時生成x和y導數:
同時應用x和y導數的結果看起來像應用于Lena圖檔的圖3-9。
3.5 OpenGL支援
OpenCV包括對OpenGL的支援。OpenGL是一個作為标準而內建在幾乎所有圖形卡中的圖形庫。OpenGL能夠把2D圖像繪制成複雜的3D場景。由于在許多任務中表現3D空間的重要性,OpenCV包括了對OpenGL的支援。要在OpenGL中允許支援視窗,必須在調用namedWindow建立視窗時設定WINDOW_OPENGL标志。
下面的代碼建立一個支援OpenGL的視窗,并繪制一個旋轉平面,我們将在其中顯示網絡攝像頭架構:
我們一起來了解這段代碼!
第一個任務是建立所需的全局變量,用來存儲捕獲的視訊幀并儲存幀,然後控制動畫角度平面和OpenGL紋理:
在主函數中,必須打開錄影機以檢索拍攝的幀:
如果攝相機正确打開,則使用WINDOW_OPENGL标志建立支援OpenGL的視窗:
在這個例子中,我們想在平面中繪制來自網絡攝像頭的圖像,是以,需要啟用OpenGL紋理:
現在,我們已準備好在視窗中用OpenGL進行繪制,但是需要像典型的OpenGL應用程式一樣設定繪制OpenGL回調。OpenCV提供了帶有兩個參數的setOpenGLDrawCallback函數,其參數是視窗名稱和回調函數:
在定義OpenCV視窗和回調函數之後,需要建立一個循環來加載紋理,并更新調用OpenGL繪圖回調的視窗内容,最後更新角度位置。要更新視窗内容,我們用OpenCV函數更新視窗,并用視窗名稱作為參數:
當使用者按下Q鍵時進入循環。在編譯示例應用程式之前,我們需要定義loadTexture函數和on_opengl回調繪制函數。loadTexture函數将Mat幀轉換為OpenGL紋理圖像,這樣就可以在每個回調繪圖中加載和使用。在将圖像作為紋理加載之前,必須確定在幀矩陣中有資料,即檢查資料變量對象是否為空:
如果幀矩陣中有資料,那麼可以建立OpenGL紋理綁定,并将OpenGL紋理參數設定為線性插值:
現在,必須定義像素如何存儲在矩陣中,以及如何使用OpenGL glTexImage2D函數生成像素。非常重要的是,要注意OpenGL預設使用RGB格式,而OpenCV預設使用BGR格式,是以必須在此函數中設定正确的格式:
現在,當我們在主循環中調用updateWindow時,隻需在每個回調上完成平面繪制。我們使用常見的OpenGL函數,然後加載辨別OpenGL矩陣以重置之前的所有更改:
我們還必須加載幀紋理:
在繪制平面之前,将所有變換應用到場景中。在這個例子中,我們将在1,1,1軸上旋轉平面:
現在,場景已被正确設定,可以繪制平面了,我們将繪制四邊形面(具有四個頂點的面),并用glBegin(GL_QUADS)來實作:
接下來,我們将繪制一個以0,0位置為中心的平面,其大小為2個機關。然後用glTextCoord2D和glVertex2D函數定義要使用的紋理坐标和頂點位置:
我們可以在圖3-10中看到結果。
3.6 總結
在這一章中,我們學習了如何使用OpenGL建立不同類型的使用者界面來顯示圖像或3D界面,學習了如何建立滑塊和按鈕或繪制3D。還學習了原生OpenCV的一些基本圖像處理過濾器,但也有新的開源替代品,它們能夠添加更多功能,比如cvui(
https://dovyski.github.io/cvui/)或OpenCVGUI(
https://damiles.github.io/OpenCVGUI/)。
在下一章中,我們将建構一個完整的照片工具應用程式,并将應用到目前為止學過的所有知識。我們将學習如何通過圖形使用者界面将多個過濾器應用于輸入圖像。