【原創文章】歡迎正常授權轉載(聯繫作者)
【反對惡意複製粘貼,如有發現必維權】
【微信公衆號原文傳送門】
系列文章鏈接
1. 問題總體描述及三種方案;
2. 方案1詳解(附代碼);
這篇文章將詳細介紹方案2的實現(代碼獲取見文章末尾)。
老規矩先看看下載好的代碼文件構成。
其中 “ssd” 文件夾中是SSD檢測的關鍵文件,關於這部分之前寫文章了,裏面詳細介紹瞭如何訓練一個屬於自己的SSD300,有代碼、有預訓練的權值文件,不清楚的請移步這裏。
需求分析
先簡單做一個需求分析,看看我們要怎樣實現。
首先,要解決的問題:電腦性能太好(如果你有的話,土豪交朋友嗎?),導致使用幀循環的方法時,視頻會被“加速”播放,我們想讓它按正常的速度播放。
之後,我們先了解一個概念FPS(每秒幀數),對應的可以計算出一幀應該顯示幾秒。例如:
(FPS: Frames Per Second)
FPS = 20
(TPF: times Per Frame,這個是我自己造的,哈哈哈哈)
TPF = 1 / 20 (單位:s)
只要控制 讀取圖像–>檢測–>顯示 的節奏,讓顯示圖像的時間與視頻的 FPS 對應,那麼看起來視頻就是正常播放的啦。
通過 計時器(QTimer) 可以很好的實現這個需求,計時器時間一到就會發送 “超時”信號 給對應的槽函數(用於檢測顯示),槽函數收到信號後就開始執行,槽函數執行結束後等待再一次被調用。
最後一個需求是要通過界面的按鈕來控制 開始 和 結束 ,這個就很簡單,構造函數裏實例化計時器,然後在對應的槽函數裏 開始 或 停止 計時器就可以了。
代碼分析
下面開始詳細介紹代碼。主要介紹下面幾個函數,其他的函數在上篇文章中已經講過或者比較簡單,就不介紹了。(偷個懶,嘿嘿嘿)
1. 構造函數
def __init__(self, parent=None):
"""
...上一篇文章已經介紹過...
...這裏就不寫了...
...這裏說點不一樣的...
"""
# 視頻文件路徑
self.camera_index = None # 用於保存視頻文件路徑
self.FPS = None # 用於保存視頻文件FPS
# 初始化計時器
self.timer = QTimer(self) # 更新計時器
self.timer.timeout.connect(self.timer_update) # 超時信號連接對應的槽函數
在構造函數中初始化必要的變量,同時實例化一個 QTimer計時器 ,並將 “超時”信號 與對應的槽函數綁定起來。代碼中self.timer_update爲計時器超時信號的槽函數,這裏作爲參數傳入不可以加’()’,後面會詳細說。
2. ‘開始’點擊槽函數
def on_pushButton_start_clicked(self):
"""
Slot documentation goes here.
"""
# TODO: not implemented yet
# 獲取數據流
self.cap = cv2.VideoCapture(self.camera_index)
if self.cap.isOpened():
# 獲取視頻的FPS
# FPS ---- 每秒多少幀
self.FPS = self.cap.get(cv2.CAP_PROP_FPS)
if isinstance(self.FPS, float): # 正常獲取的FPS是float
self.FPS = int(self.FPS) # 如果正確獲取FPS就保存在變量
else:
self.FPS = 20 # 沒正確獲取則設爲 20幀/s
# 計時器開始計時
# 計時器的參數爲 ms 爲了正常速度播放,計時器的參數計算爲 1/FPS * 1000 = 1000/FPS
self.timer.start(int(1000/self.FPS))
# 鎖定開始按鈕
self.pushButton_start.setEnabled(False)
else:
QMessageBox.warning(self, '數據流打開警告', '數據流打開錯誤!\n請重新嘗試。')
該函數的主要功能是:打開視頻數據流;獲取視頻流的FPS;計時器開始計時。需要注意的是計時器的時間設置問題。
函數self.timer.start(時間,單位:ms)的參數與 1/FPS 之間還需要乘以 1000 ,同時這個值還應該考慮到圖像預處理以及檢測的時間,適當的減小這個值,如果不考慮的話可能會出現“慢速播放”。
3. ‘結束’點擊槽函數
該函數比較簡單,主要功能是停止計時器的功能,同時爲下一次檢測做準備。
def on_pushButton_end_clicked(self):
"""
Slot documentation goes here.
"""
# TODO: not implemented yet
# 重設
self.resst_detector() # 詳細代碼在下方
# 清除顯示
self.textEdit.clear()
def resst_detector(self):
"""
重設檢測器,爲下一次檢測準備
:return:
"""
# 釋放攝像頭
if hasattr(self, 'cap'):
self.cap.release()
del self.cap
# 釋放‘開始’按鈕
self.pushButton_start.setEnabled(True)
# 顯示空白圖片
self.show_img(self.img_none)
# 停止計時器
self.timer.stop()
4. 計時器槽函數
def timer_update(self):
"""
計時器槽函數
:return:
"""
if self.cap.isOpened():
# 讀取圖像
ret, self.img_scr = self.cap.read()
# 如果視頻讀取完畢
if not ret:
# 計時器停止計時
self.timer.stop()
# 對話框提示
QMessageBox.information(self, '播放提示', '視頻已播放完畢!')
# 釋放攝像頭
if hasattr(self, 'cap'):
self.cap.release()
del self.cap
# 釋放‘開始’按鈕
self.pushButton_start.setEnabled(True)
# 預處理圖片
# 轉爲RGB
self.img_scr = cv2.cvtColor(self.img_scr, cv2.COLOR_BGR2RGB)
# 檢測
self.preds = self.ssd.Predict(self.img_scr)
# 過濾
self.preds = self.filter(self.preds, inclued_class=self.include_class)
self.img_scr = self.draw_img(self.img_scr, self.preds)
h, w = self.img_scr.shape[:2]
self.text = self.decode_preds(self.preds, w=w, h=h)
self.textEdit.setText(self.text)
# 顯示圖像
self.show_img(self.img_scr)
# 響應UI
QApplication.processEvents()
else:
self.textEdit.setText('數據流未打開!!!\n請檢查')
self.resst_detector() # 沒有打開就重設一下
看過上篇文章的是不是很熟悉,沒錯。基本就是幀循環裏面的東西。
5. ‘文件打開’槽函數
爲了方便更換被檢測視頻,創建了一個按鈕用於打開文件對話框選擇文件,基本功能就是爲了實現給變量self.camera_index賦值。
def on_pushButton_open_clicked(self):
"""
Slot documentation goes here.
"""
# TODO: not implemented yet
# 打開文件對話框
path = QFileDialog.getOpenFileName(self, '打開待檢測視頻', './', '*.avi;;*.mp4;;AllFile(*.*)', '')
if path[0] != '': # 點‘取消’,path[0]的值會爲‘’
path = os.path.normpath(os.path.abspath(path[0]))
self.camera_index = path
self.textEdit.setText('{}已選中!'.format(path))
else:
self.textEdit.setText('當前未選中任何文件')
預告:方案3應該下週整理完畢並更新
關注下方公衆號,回覆關鍵字即可獲取下載地址。
-
本文配套源代碼下載地址::
回覆“SSD界面2”獲取。