前幾篇博客分享搭建人臉識別與情緒判斷的環境和源碼,但是沒有UI,界面很難看,一打開就是opencv彈出的一個視屏框。處女座的我看着非常難受,於是決定做一個UI,稍微規矩好看一點,再怎么說,這樣的話也算是一個小軟件,不再是運行源碼了。
上網到處查了一圈之后,發現這是一個空缺,好像沒有人在做這個,看到的唯一一個有點相似的是用wxpython制作一個視屏播放器。和這個顯示opencv的實時視屏還是有點差距的,但是也有指導作用。
使用版本:python-3.6.3(anaconda) opencv-3.4.1 wxpython-4.0.1
運行流程:
1、運行程序時,先顯示封面頁
2、用戶點擊【start】后開始opencv讀取視屏,dlib開始處理,並進行情緒判斷
3、用戶點擊【close】后,結束視屏,回到封面頁。等待再次點擊開始
一、實例化frame、添加控件
def __init__(self,parent,title): wx.Frame.__init__(self,parent,title=title,size=(600,600)) self.panel = wx.Panel(self) self.Center() # 封面圖片 self.image_cover = wx.Image(COVER, wx.BITMAP_TYPE_ANY).Scale(350,300) # 顯示圖片在panel上 self.bmp = wx.StaticBitmap(self.panel, -1, wx.Bitmap(self.image_cover)) start_button = wx.Button(self.panel,label='Start') close_button = wx.Button(self.panel,label='Close') self.Bind(wx.EVT_BUTTON,self.learning_face,start_button) self.Bind(wx.EVT_BUTTON,self.close_face,close_button)
這段初始化的程序,就添加了一個圖片,兩個按鈕,並為按鈕綁定了各自的動作函數。這里使用wx.Image方法讀取一個任意格式的照片,交給staticbitmap,開始在panel面板上顯示這個圖片。
二、基於GridBagSizer的界面布局
界面布局有很多方法可以使用,這里使用gridbagsizer進行界面布局。
它把一個面板抽象成一個網格,我們可以想象在excel中的樣子,格子的大小比例根據自己的設置進行放大縮小。但是總面積不變。 # 基於GridBagSizer的界面布局
# 先實例一個對象 self.grid_bag_sizer = wx.GridBagSizer(hgap=5,vgap=5) # 注意pos里面是先縱坐標后橫坐標 self.grid_bag_sizer.Add(self.bmp, pos=(0, 0), flag=wx.ALL | wx.EXPAND, span=(4, 4), border=5) self.grid_bag_sizer.Add(start_button, pos=(4, 1), flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, span=(1, 1), border=5) self.grid_bag_sizer.Add(close_button, pos=(4, 2), flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, span=(1, 1), border=5) self.grid_bag_sizer.AddGrowableCol(0,1) self.grid_bag_sizer.AddGrowableRow(0,1)
self.panel.SetSizer(self.grid_bag_sizer) # 界面自動調整窗口適應內容 self.grid_bag_sizer.Fit(self)
使用Add方法把控件添加到網格中,pos是坐標,span是這個控件需要跨越的行列數,這兩個參數基本上就可以確定一個控件的位置和大小了。
下面的fit方法就是確保這個控件會隨着面板的拖大拖小而進行相應的比例移動。
三、按鈕動作
既然控件的位置格式都擺放整齊了,下面就是他們的動作綁定了。
點擊開始,會開始opencv讀取視屏,然后把每一幀圖片交給dlib進行人臉識別與特征點的標定,然后在進行顯示。
原來是調用opencv的imshow方法來進行顯示的,具體這句話為:cv2.imshow("camera", im_rd),那么我們想讓這一幀顯示在wxpython的框架內怎辦呢?
把剛才的封面頁換成opencv的每一幀不就可以了嗎!
cv2.imshow("camera", im_rd),是循環顯示它的每一幀圖片,只是換了個方法而已,其實原理都是一樣 的。循環顯示每一幀圖片,就成了視屏;
所以歸根結底,這個問題就是顯示一個圖片的問題。
好像還是有點不對。我們顯示圖片的格式是JPG圖片,那么這個 im_rd 是什么格式呢?
還記得這一段碼:
# cap.read() # 返回兩個值: # 一個布爾值true/false,用來判斷讀取視頻是否成功/是否到視頻末尾 # 圖像對象,圖像的三維矩陣 flag, im_rd = self.cap.read()
他是一個三維的矩陣。
這時,我們需要用下面的方法對這一幀的數據進行轉化,顯示、
# 現將opencv截取的一幀圖片BGR轉換為RGB,然后將圖片顯示在UI的框架中 height,width = im_rd.shape[:2] image1 = cv2.cvtColor(im_rd, cv2.COLOR_BGR2RGB) pic = wx.Bitmap.FromBuffer(width,height,image1) # 顯示圖片在panel上 self.bmp.SetBitmap(pic) self.grid_bag_sizer.Fit(self)
這樣就可以將視屏的每一幀數據顯示在wxpython的panel面板上面。
四、簡單多線程
在我能夠點擊start開始在框架內部顯示視屏的時候,我想拖動一下這個框架,但是程序竟然卡死了!無響應???what??
查了資料才知道,我們的程序只有一個線程,這時程序正在while死循環里面進行視屏顯示,占用了這唯一的一個線程,如果我們進行UI操作的話,程序就會被崩潰。
解決辦法就是,創建一個新的線程,讓按鈕的動作函數在這個新的子線程中執行,主線程我們進行UI的操作,比如框架的拖動,放大縮小。
def learning_face(self,event): """使用多線程,子線程運行后台的程序,主線程更新前台的UI,這樣不會互相影響""" import _thread # 創建子線程,按鈕調用這個方法, _thread.start_new_thread(self._learning_face, (event,))
這時,我們點擊按鈕start后,開始執行這個方法,先創建一個子線程,然后在這個方法中調用之前那個方法。就是對之前的那個方法進行了進一步的封裝。
這樣就不會出現卡死的現象了。
完整程序代碼:https://gitee.com/Andrew_Qian/codes/acm80fr6ekjwgpz27bthd35