上一节我们初步完成了主程序的结构设计,只要将每一个回调函数编写完成就可实现最终的功能了,本节来详细介绍这些回调函数的内容。由于设计到的回调函数较多,我们按功能将其分类,分别为摄像头图片的读取和显示、图像颜色控制、摄像头参数设置、视频文件保存、退出程序。
从笔记本自带的摄像头中读取图片,并将其显示在我们命名为DispLb的label中。这一过程涉及到的‘开始’和‘暂停’两个按钮,我们预期的功能是当鼠标单击‘开始’按钮时,程序就不断地从摄像头读取图像,并将其实时显示在DispLb中,而一旦我们点击‘暂停’按钮,就停止从摄像头读取图像,DispLb中显示的图像也不再改变。当然,还涉及到一些其它控件的‘enable’或‘disable’。具体涉及到的回调函数包括‘开始’按钮的回调函数:StartCamera();‘暂停’按钮的回调函数: StopCamera();另外,由于我们是通过time函数来控制图像的重复读取,因此还需要它的回调函数
TimerOutFun();将图片通过DispLb显示出来的函数:DispImg()。这些函数的内容如下所示。
def StartCamera(self):
self.ShowBt.setEnabled(False)
self.StopBt.setEnabled(True)
self.RecordBt.setEnabled(True)
self.GrayImgCkB.setEnabled(True)
if self.GrayImgCkB.isChecked()==0:
self.RedColorSld.setEnabled(True)
self.RedColorSpB.setEnabled(True)
self.GreenColorSld.setEnabled(True)
self.GreenColorSpB.setEnabled(True)
self.BlueColorSld.setEnabled(True)
self.BlueColorSpB.setEnabled(True)
self.ExpTimeSld.setEnabled(True)
self.ExpTimeSpB.setEnabled(True)
self.GainSld.setEnabled(True)
self.GainSpB.setEnabled(True)
self.BrightSld.setEnabled(True)
self.BrightSpB.setEnabled(True)
self.ContrastSld.setEnabled(True)
self.ContrastSpB.setEnabled(True)
self.RecordBt.setText('录像')
self.Timer.start(1)
self.timelb=time.clock()
StartCamera这个函数的主要功能就是它最后两行代码,self.Timer.start(1)
用来启动计时器,计时周期为1ms,即每隔1ms程序会自动调用一次TimerOutFun。我们将图像的具体读取和显示放到TimerOutFun中,这样就可以实现图像的实时读取和显示了。StartCamera的前面那些代码是用来启用相机参数设置和‘暂停’按钮等控件,同时,停用‘开始’按钮。
def StopCamera(self):
if self.StopBt.text()=='暂停':
self.StopBt.setText('继续')
self.RecordBt.setText('保存')
self.Timer.stop()
elif self.StopBt.text()=='继续':
self.StopBt.setText('暂停')
self.RecordBt.setText('录像')
self.Timer.start(1)
由于我们的‘暂停’和‘继续’功能是复用的同一个按钮,因此StopCamera函数要分成两种情况。如果我们点击时按钮显示的是‘暂停’,那么就停止计时器,同时将本按钮的显示改成‘继续’。另外,我们后面的存储按钮涉及两种功能,即暂停状态下保存图片,实时显示状态下保存录像,因此这里也需要更改‘保存’按钮的显示字符。
def TimerOutFun(self):
success,img=self.camera.read()
if success:
self.Image = self.ColorAdjust(img)
self.DispImg()
self.Image_num+=1
if self.RecordFlag:
self.video_writer.write(img)
if self.Image_num%10==9:
frame_rate=10/(time.clock()-self.timelb)
self.FmRateLCD.display(frame_rate)
self.timelb=time.clock()
#size=img.shape
self.ImgWidthLCD.display(self.camera.get(3))
self.ImgHeightLCD.display(self.camera.get(4))
else:
self.MsgTE.clear()
self.MsgTE.setPlainText('Image obtaining failed.')
我们将需要对每一帧图像都重复执行的操作放在函数TimerOutFun中,该函数的功能为:从摄像头读取图像,调用ColorAdjust函数来调整图片的颜色,调用DispImg函数来显示图像,保存视频,获取并显示摄像头帧频和图像尺寸。从摄像头读取图像可以通过调用OpenCV库的read函数实现。保存视频是通过OpenCV的VideoWriter函数实现的,获取并计算帧频则是通过time.clock()函数获取代码执行的时间实现。
def DispImg(self):
if self.GrayImgCkB.isChecked():
img = cv2.cvtColor(self.Image, cv2.COLOR_BGR2GRAY)
else:
img = cv2.cvtColor(self.Image, cv2.COLOR_BGR2RGB)
qimg = qimage2ndarray.array2qimage(img)
self.DispLb.setPixmap(QPixmap(qimg))
self.DispLb.show()
从摄像头获取的图像在DispLb中显示之前,需要对其格式进行转换。Label可接受的图像类型为QPixmap,且其颜色通道依次为R、G、B;而通过OpenCV的read函数读取的图像是二维矩阵格式的,且颜色通道依次为B、G、R,因此需要先调用cvtColor函数对矩阵的颜色进行调整,然后再调用array2qimage函数将其转为QImage格式,再通过QPixmap函数转为QPixmap格式进行显示。
涉及到图像显示颜色控制的主要是ColorFm中的CheckBox:GrayImgCkB和R、G、B三个颜色通道对应的Slider和SpinBox。相应的回调函数为SetGray、SetR、SetG、SetB、ColorAdjust。这些函数的代码如下所示。
def SetGray(self):
if self.GrayImgCkB.isChecked():
self.RedColorSld.setEnabled(False)
self.RedColorSpB.setEnabled(False)
self.GreenColorSld.setEnabled(False)
self.GreenColorSpB.setEnabled(False)
self.BlueColorSld.setEnabled(False)
self.BlueColorSpB.setEnabled(False)
else:
self.RedColorSld.setEnabled(True)
self.RedColorSpB.setEnabled(True)
self.GreenColorSld.setEnabled(True)
self.GreenColorSpB.setEnabled(True)
self.BlueColorSld.setEnabled(True)
self.BlueColorSpB.setEnabled(True)
SetGray函数所实现的功能是对三个颜色通道的slider和spinbox启用与否的控制,若为真,则将图像转为灰度图,自然就不存在三个颜色通道,因此相应的控件都要‘disable’ ,否则就要将它们‘enable’。至于实现灰度转换的代码则是在DispImg函数中实现的。
def SetR(self):
R=self.RedColorSld.value()
self.R=R/255
def SetG(self):
G=self.GreenColorSld.value()
self.G=G/255
def SetB(self):
B=self.BlueColorSld.value()
self.B=B/255
def ColorAdjust(self,img):
try:
B=img[:,:,0]
G=img[:,:,1]
R=img[:,:,2]
B=B*self.B
G=G*self.G
R=R*self.R
img1=img
img1[:,:,0]=B
img1[:,:,1]=G
img1[:,:,2]=R
return img1
except Exception as e:
self.MsgTE.setPlainText(str(e))
图像显示颜色的控制则是通过以上四个函数实现的。前三个函数分别定义三个颜色通道的强度缩小系数,第四个函数将函数对相应的颜色强度进行缩小。
摄像头的参数通常包括帧频、曝光时间、AOI、增益系数等,不同的摄像头通常会给出不同的可调节的参数和参数的调节范围。本例中,可供调节的参数包括曝光、增益、亮度、对比度四个,我们通过控制面板上的四组slider/spinbox来控制对应的参数,相应的回调函数分别为SetExposure、SetGain、SetBrightness、SetContrast,具体的函数代码如下所示。
def SetExposure(self):
try:
exposure_time_toset=self.ExpTimeSld.value()
self.camera.set(15,exposure_time_toset)
self.MsgTE.setPlainText('The exposure time is set to '+str(self.camera.get(15)))
except Exception as e:
self.MsgTE.setPlainText(str(e))
以SetExposure函数为例,代码内容为:首先通过self.ExpTimeSld.value()
获得要设置的曝光值,然后调用self.camera.set(15,exposure_time_toset)
函数来将曝光值赋给摄像头,改变其曝光参数。最后,通过self.camera.get(15)
函数获得设置完成后相机的实际曝光值,并通过MsgTE显示出来。除此之外,我们还使用了try…Except…结构,若设置的参数格式或范围不符合摄像头的要求,通常摄像头要么报错,要么忽视该该命令,曝光参数维持原值。因此如果我们在设置完曝光参数后,MsgTE中显示的值与我们的设置值一致,说明设置成功,如果不一致,则说明我们设置的值超出摄像头的参数范围。我们可以先将Slider和SpinBox的范围设置得比较大,例如-100到+100,然后反复设置参数并观察相机曝光参数的改变,从而试探出相机的曝光参数范围。对增益、亮度、对比度的设置与曝光类似,就不再一一介绍。
def SetGain(self):
gain_toset=self.GainSld.value()
try:
self.camera.set(14,gain_toset)
self.MsgTE.setPlainText('The gain is set to '+str(self.camera.get(14)))
except Exception as e:
self.MsgTE.setPlainText(str(e))
def SetBrightness(self):
brightness_toset=self.BrightSld.value()
try:
self.camera.set(10,brightness_toset)
self.MsgTE.setPlainText('The brightness is set to ' + str(self.camera.get(10)))
except Exception as e:
self.MsgTE.setPlainText(str(e))
def SetContrast(self):
contrast_toset=self.ContrastSld.value()
try:
self.camera.set(11,contrast_toset)
self.MsgTE.setPlainText('The contrast is set to ' + str(self.camera.get(11)))
except Exception as e:
self.MsgTE.setPlainText(str(e))
这部分包括两个功能,若图像显示被停止了,点击按钮后就保存图片;若图像在实时显示,点击按钮后就开始录像,再次点击停止录像。设计到的控件有两个,一个是设置保存文件路径的按钮FilePathBt,一个是保存图片或视频的RecordBt,前者的回调函数为SetFilePath(),后者的回调函数为RecordCamera()。具体的代码如下所示。
def SetFilePath(self):
dirname = QFileDialog.getExistingDirectory(self, "浏览", '.')
if dirname:
self.FilePathLE.setText(dirname)
self.RecordPath=dirname+'/'
本函数通过调用QFileDialog.getExistingDirectory函数弹出对话框,让用户自己选择路径,并将选择好的路径显示在FilePathLE中。默认的路径为‘D:/Python/PyQt/’,这个是在上一节介绍的PrepParameters()函数中设置好的。
def RecordCamera(self):
tag=self.RecordBt.text()
if tag=='保存':
try:
image_name=self.RecordPath+'image'+time.strftime('%Y%m%d%H%M%S',time.localtime(time.time()))+'.jpg'
print(image_name)
cv2.imwrite(image_name, self.Image)
self.MsgTE.clear()
self.MsgTE.setPlainText('Image saved.')
except Exception as e:
self.MsgTE.clear()
self.MsgTE.setPlainText(str(e))
elif tag=='录像':
self.RecordBt.setText('停止')
video_name = self.RecordPath + 'video' + time.strftime('%Y%m%d%H%M%S',time.localtime(time.time())) + '.avi'
fps = self.FmRateLCD.value()
size = (self.Image.shape[1],self.Image.shape[0])
fourcc = cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')
self.video_writer = cv2.VideoWriter(video_name, fourcc,self.camera.get(5), size)
self.RecordFlag=1
self.MsgTE.setPlainText('Video recording...')
self.StopBt.setEnabled(False)
self.ExitBt.setEnabled(False)
elif tag == '停止':
self.RecordBt.setText('录像')
self.video_writer.release()
self.RecordFlag = 0
self.MsgTE.setPlainText('Video saved.')
self.StopBt.setEnabled(True)
self.ExitBt.setEnabled(True)
本函数实现的功能正如之前所述,分成了保存图片和保存视频两种功能。如果按钮显示的是‘保存’,单击后就调用OpenCV的imwrite函数保存一幅图片,图片的名称会根据时间自动命名。如果按钮显示的是‘录像’,单击后就调用OpenCV的VideoWriter函数创建一个视频文件,文件名同样根据时间自动命名,同时将录像标签RecordFlag改为1,这样在TimerOutFun中每次读取图片后都会将其写入视频文件中。如果按钮显示的是‘停止’,单击后就停止录像,保存视频文件。还有一些关联控件的设置,大家可以自行理解。
退出按钮ExitBt的回调函数的代码如下所示,包括停止计时器、释放摄像头、退出几个功能。
def ExitApp(self):
self.Timer.Stop()
self.camera.release()
self.MsgTE.setPlainText('Exiting the application..')
QCoreApplication.quit()
至此,我们已经完成了程序所有代码的编写,点击运行该程序,就可以看到程序正确运行了! 运行的程序界面如下图所示。完整的代码文件可以从这里下载,回复即可获取提取码。
下一节将介绍如何将程序打包成可以在windows环境独立运行的exe文件。