前几篇博客分享搭建人脸识别与情绪判断的环境和源码,但是没有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