基于opencv對高空拍攝視頻消抖處理方法
一、問題背景
無人機在拍攝視頻時,由于風向等影響因素,不可避免會出現(xiàn)位移和旋轉,導致拍攝出的畫面存在平移和旋轉的幀間變換, 即“抖動” 抖動會改變目標物體 (車輛、行人) 的坐標,給后續(xù)的檢測、跟蹤任務引入額外誤差,造成數(shù)據(jù)集不可用。
原效果
目標效果
理想的無抖動視頻中,對應于真實世界同一位置的背景點在不同幀中的坐標應保持一致,從而使車輛、行人等目標物體的坐標變化只由物體本身的運動導致,而不包含相機的運動 抖動可以由不同幀中對應背景點的坐標變換來描述
二、量化指標
抖動可以用相鄰幀之間的 x 方向平移像素 dx,y 方向平移像素 dy,旋轉角度 da,縮放比例 s 來描述,分別繪制出 4 個折線圖,根據(jù)折線圖的走勢可以判斷抖動的程度 理想的無抖動視頻中,dx、dy、da 幾乎始終為 0,s 幾乎始終為 1。
三、技術思路
我們最終實現(xiàn),將視頻的所有幀都對齊到第一幀,以達到視頻消抖問題,實現(xiàn)邏輯如下圖所示。
(1)首先對視頻進行抽第一幀與最后一幀,為什么抽取兩幀?這樣做的主要目的是,我們在做幀對齊時,使用幀中靜態(tài)物的關鍵點做對齊,如果特征點來源于動態(tài)物上,那么對齊后就會產生形變,我們選取第一幀與最后一幀,提取特征點,留下交集部分,則可以得到靜態(tài)特征點我們這里稱為特征模板,然后將特征模板應用到每一幀上,這樣可以做有效對齊。
(2)常用特征點檢測器:
SIFT: 04 年提出,廣泛應用于各種跟蹤和識別算法,表現(xiàn)能力強,但計算復雜度高。
SURF: 06 年提出,是 SIFT 的演進版本,保持強表現(xiàn)能力的同時大大減少了計算量。
BRISK: BRIEF 的演進版本,壓縮了特征的表示,提高了匹配速度。 ORB: 以速度著稱,是 SURF 的演進版本,多用于實時應用。
GFTT: 最早提出的 Harris 角點的改進版本,經常合稱為 Harris-Shi-Tomasi 角點。
SimpleBlob: 使用 blob 的概念來抽取圖像中的特征點,相對于角點的一種創(chuàng)新。 FAST: 相比其他方法特征點數(shù)量最多,但也容易得到距離過近的點,需要經過 NMS。
Star: 最初用于視覺測距,后來也成為一種通用的特征點檢測方法。
我們這里使用的是SURF特征點檢測器
第一幀特特征點提取??????
最后一幀特征點提取
(3)在上圖中,我們發(fā)現(xiàn)所提取的特征點中部分來自于車身,由于車是運動的,所以我們不能使用,我們用第一幀與最后一幀做靜態(tài)特幀點匹配,生成靜態(tài)特征模板,在下圖中,我們發(fā)現(xiàn)只有所有的特征點只選取在靜態(tài)物上
靜態(tài)特征點模板
(4)靜態(tài)特征模板匹配 ,我們這里使用Flann算法,匹配結果如下
特征匹配
(5)使用匹配成功的兩組特征點,估計兩幀之間的透視變換 (Perspective Transformation)。估計矩陣 H,其中 (x_i, y_i) 和 (x_i^′, y_i^′) 分別是兩幀的特征點。
第一幀
最后一幀對齊到第一幀
四、實現(xiàn)代碼
運行環(huán)境以及版本,安裝命令如下:
python版本:3.X
opencv-python:3.4.2.16
opencv-contrib-python:3.4.2.16
需要卸載之前的opencv-python版本 pip uninstall opencv-python pip uninstall opencv-contrib-python 安裝新的版本 pip install opencv_python==3.4.2.16 pip install opencv-contrib-python==3.4.2.16
代碼基于python實現(xiàn),如下所示:
import cv2 import numpy as np from tqdm import tqdm import argparse import os # get param parser = argparse.ArgumentParser(description='') parser.add_argument('-v', type=str, default='') # 指定輸入視頻路徑位置(參數(shù)必選) parser.add_argument('-o', type=str, default='') # 指定輸出視頻路徑位置(參數(shù)必選) parser.add_argument('-n', type=int, default=-1) # 指定處理的幀數(shù)(參數(shù)可選), 不設置使用視頻實際幀 # eg: python3 stable.py -v=video/01.mp4 -o=video/01_stable.mp4 -n=100 -p=6 args = parser.parse_args() input_path = args.v output_path = args.o number = args.n class Stable: # 處理視頻文件路徑 __input_path = None __output_path = None __number = number # surf 特征提取 __surf = { # surf算法 'surf': None, # 提取的特征點 'kp': None, # 描述符 'des': None, # 過濾后的特征模板 'template_kp': None } # capture __capture = { # 捕捉器 'cap': None, # 視頻大小 'size': None, # 視頻總幀 'frame_count': None, # 視頻幀率 'fps': None, # 視頻 'video': None, } # 配置 __config = { # 要保留的最佳特征的數(shù)量 'key_point_count': 5000, # Flann特征匹配 'index_params': dict(algorithm=0, trees=5), 'search_params': dict(checks=50), 'ratio': 0.5, } # 特征提取列表 __surf_list = [] def __init__(self): pass # 初始化capture def __init_capture(self): self.__capture['cap'] = cv2.VideoCapture(self.__video_path) self.__capture['size'] = (int(self.__capture['cap'].get(cv2.CAP_PROP_FRAME_WIDTH)), int(self.__capture['cap'].get(cv2.CAP_PROP_FRAME_HEIGHT))) self.__capture['fps'] = self.__capture['cap'].get(cv2.CAP_PROP_FPS) self.__capture['video'] = cv2.VideoWriter(self.__output_path, cv2.VideoWriter_fourcc(*"mp4v"), self.__capture['fps'], self.__capture['size']) self.__capture['frame_count'] = int(self.__capture['cap'].get(cv2.CAP_PROP_FRAME_COUNT)) if number == -1: self.__number = self.__capture['frame_count'] else: self.__number = min(self.__number, self.__capture['frame_count']) # 初始化surf def __init_surf(self): self.__capture['cap'].set(cv2.CAP_PROP_POS_FRAMES, 0) state, first_frame = self.__capture['cap'].read() self.__capture['cap'].set(cv2.CAP_PROP_POS_FRAMES, self.__capture['frame_count'] - 1) state, last_frame = self.__capture['cap'].read() self.__surf['surf'] = cv2.xfeatures2d.SURF_create(self.__config['key_point_count']) self.__surf['kp'], self.__surf['des'] = self.__surf['surf'].detectAndCompute(first_frame, None) kp, des = self.__surf['surf'].detectAndCompute(last_frame, None) # 快速臨近匹配 flann = cv2.FlannBasedMatcher(self.__config['index_params'], self.__config['search_params']) matches = flann.knnMatch(self.__surf['des'], des, k=2) good_match = [] for m, n in matches: if m.distance < self.__config['ratio'] * n.distance: good_match.append(m) self.__surf['template_kp'] = [] for f in good_match: self.__surf['template_kp'].append(self.__surf['kp'][f.queryIdx]) # 釋放 def __release(self): self.__capture['video'].release() self.__capture['cap'].release() # 處理 def __process(self): current_frame = 1 self.__capture['cap'].set(cv2.CAP_PROP_POS_FRAMES, 0) process_bar = tqdm(self.__number, position=current_frame) while current_frame <= self.__number: # 抽幀 success, frame = self.__capture['cap'].read() if not success: return # 計算 frame = self.detect_compute(frame) # 寫幀 self.__capture['video'].write(frame) current_frame += 1 process_bar.update(1) # 視頻穩(wěn)像 def stable(self, input_path, output_path, number): self.__video_path = input_path self.__output_path = output_path self.__number = number self.__init_capture() self.__init_surf() self.__process() self.__release() # 特征點提取 def detect_compute(self, frame): frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 計算特征點 kp, des = self.__surf['surf'].detectAndCompute(frame_gray, None) # 快速臨近匹配 flann = cv2.FlannBasedMatcher(self.__config['index_params'], self.__config['search_params']) matches = flann.knnMatch(self.__surf['des'], des, k=2) # 計算單應性矩陣 good_match = [] for m, n in matches: if m.distance < self.__config['ratio'] * n.distance: good_match.append(m) # 特征模版過濾 p1, p2 = [], [] for f in good_match: if self.__surf['kp'][f.queryIdx] in self.__surf['template_kp']: p1.append(self.__surf['kp'][f.queryIdx].pt) p2.append(kp[f.trainIdx].pt) # 單應性矩陣 H, _ = cv2.findHomography(np.float32(p2), np.float32(p1), cv2.RHO) # 透視變換 output_frame = cv2.warpPerspective(frame, H, self.__capture['size'], borderMode=cv2.BORDER_REPLICATE) return output_frame if __name__ == '__main__': if not os.path.exists(input_path): print(f'[ERROR] File "{input_path}" not found') exit(0) else: print(f'[INFO] Video "{input_path}" stable begin') s = Stable() s.stable(input_path, output_path, number) print('[INFO] Done.') exit(0)
參數(shù)說明:
-v 指定輸入視頻路徑位置(參數(shù)必選)
-o 指定輸出視頻路徑位置(參數(shù)必選)
-n 指定處理的幀數(shù)(參數(shù)可選), 不設置使用視頻實際幀
調用示例:
python3 stable.py -v=test.mp4 -o=test_stable.mp4
五、效果展示
我們消抖后的視頻道路完全沒有晃動,但是在邊界有馬賽克一樣的東西,那是因為圖片對齊后后出現(xiàn)黑邊,我們采用邊緣點重復來彌補黑邊。
消抖前
消抖后
六、效率優(yōu)化
目前的處理效率(原視頻尺寸3840*2160),我們可以看出主要時間是花費在特征點(key)提取上。
可以采用異步處理+GPU提高計算效率
處理效率
到此這篇關于基于opencv對高空拍攝視頻消抖處理的文章就介紹到這了,更多相關opencv視頻消抖內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
pycharm不在cmd中運行卻在python控制臺運行問題解決
這篇文章主要介紹了pycharm不在cmd中運行卻在python控制臺運行問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08詳解Python中表達式i += x與i = i + x是否等價
這篇文章主要介紹了關于Python中表達式i += x與i = i + x是否等價的相關資料,文中通過示例代碼介紹的很詳細,相信對大家具有一定的參考價值,有需要的朋友們下面來一起看看吧。2017-02-02