文章目錄
-
- 前言
- 攝像頭的讀取和顯示
- 圖像顯示的顔色控制
- 攝像頭參數設定
- 儲存圖檔或視訊
- 退出程式
前言
上一節我們初步完成了主程式的結構設計,隻要将每一個回調函數編寫完成就可實作最終的功能了,本節來詳細介紹這些回調函數的内容。由于設計到的回調函數較多,我們按功能将其分類,分别為攝像頭圖檔的讀取和顯示、圖像顔色控制、攝像頭參數設定、視訊檔案儲存、退出程式。
攝像頭的讀取和顯示
從筆記本自帶的攝像頭中讀取圖檔,并将其顯示在我們命名為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檔案。