(1.華南理工大學 廣州學院,廣州 510800; 2.廣東經傳多贏投資咨詢有限公司,廣州 510800)
隨著通信和多媒體技術的迅速發(fā)展,數字視頻數據在互聯網上的傳播越來越廣,檢索和瀏覽海量的數字視頻數據成為一個十分困擾的問題,采用傳統(tǒng)的人工描述方法存在很大的弊端[1-2]。目前,國外的Google視頻分析云可以通過HTTP協議去提供restful風格視頻分析API[3]。國內的騰訊優(yōu)圖天眼利用計算機視覺以及深度學習技術去實現高精度且實時的人物重識別任務[4],北京大學彭宇新教授及其研究團隊提出了視覺注意力驅動的圖像視頻分類與檢索研究、監(jiān)控視頻語義感知和服務系統(tǒng)研發(fā)及應用示范[5-7]。
在現有的視頻檢索技術上,缺少結構化的描述,從而使得檢索時難以用精確的語言來表示它的特征[8]。為此,本文通過對視頻關鍵幀的識別、特征向量的提取,從而把無結構化的視頻幀圖像中得出有用的信息以便于檢索。用戶在使用本系統(tǒng)的時候,只需要投入較少的人力即可大大地提高視頻的檢索效率[9-11]。
本系統(tǒng)主要由4大模塊組成,分為鏡頭分割引擎、相似幀定位檢索引擎、目標檢索引擎以及人臉檢索引擎,它們共同構成了后端系統(tǒng)。整個后端系統(tǒng),模塊間只需縱向地對上層的網關接口層負責,而無需橫向地和同級的模塊進行交互。模塊與上層的網關接口之間使用基于Protobuf3的GRPC框架進行通信。底層的數據庫引擎使用PostgreSQL數據庫、文件存儲文件系統(tǒng)為ext4。在宏觀角度上,本系統(tǒng)采用了B/S架構,與用戶的交互主要發(fā)生在Web瀏覽器上。前端UI的交互界面在MVVM架構上使用ES6和VueJS進行開發(fā)。系統(tǒng)架構如圖1所示。
圖1 系統(tǒng)架構圖
本系統(tǒng)的開發(fā)語言主要基于C++語言,Web處理層基于Go語言,前端使用ES6語言,還有部分涉及傳統(tǒng)機器學習的功能則使用Python開發(fā)。多語言的使用可以方便去發(fā)揮他們不同的長處,更好地去利用已有的資源進行系統(tǒng)開發(fā),但多語言的使用會使得模塊間的通信調用變得復雜化。因此本系統(tǒng)利用Kong作為API網關、GRPC作為RPC框架、以HTTP2為底層的Protobuf數據傳輸格式去搭建本系統(tǒng)的微服務架構。這樣既可以幫助開發(fā)者屏蔽跨語言模塊間遠程調用的底層的復雜度,同時大大地降低系統(tǒng)模塊間的耦合度,提高系統(tǒng)的可擴展性[12-14]。
在系統(tǒng)的運行中,涉及到很多需要長時間計算的任務,例如視頻幀切割、關鍵幀探測、人臉分類器訓練等。采用傳統(tǒng)的同步方式去處理任務,必然會導致系統(tǒng)的整體卡頓,從而影響用戶體驗。為了高效的利用硬件資源而又兼顧系統(tǒng)的穩(wěn)定,在ZeroMQ的基礎上設計了系統(tǒng)內統(tǒng)一的任務隊列,如圖2所示。在隊列的角度來看,用戶的遠程調用請求即是生產者生產的內容,調度器會將其放入到等待任務隊列中去等待。當系統(tǒng)模塊處理完任務后,會繼續(xù)去執(zhí)行等待隊列中的任務。通過任務隊列,系統(tǒng)就可以實現異步任務處理的功能。
圖2 隊列處理流程圖
本系統(tǒng)的前端UI界面使用HTML5與CSS3,其中的前端邏輯層使用ES6語言編寫,設計模式是基于Vue.js的MVVM設計模式,利用Vue.js 和vue-router去創(chuàng)建單頁應用,其中vue-router組件負責提供前端頁面的路由功能。在這基礎上,實現了前端頁面路由無刷新跳轉,提示用戶體驗。由于前端UI項目經常需要處理復雜的狀態(tài)管理,當應用遇到多個組件共享狀態(tài)時,傳統(tǒng)的傳參數據流動的方式會破壞視圖對狀態(tài)的單一依賴性,導致多層嵌套的組件的開發(fā)將會非常繁瑣,并且對于兄弟組件間的狀態(tài)傳遞無能為力,因此引入Vuex作為前端UI狀態(tài)管理的組件,如圖3所示。
圖3 基于Vuex的數據流動圖
在以上基礎上,就可以用數據去驅動前端的運作。在底層的數據傳輸上,對于實時性要求低的數據請求,一般使用Restful風格的HTTP接口。對于實時性要求比較高或需要服務端主動推送數據的接口,本系統(tǒng)獨立封裝一套Websocket協議。在前端組件的頁面路由分布上,安排如圖4所示:
圖4 前端路由分布
為了提高系統(tǒng)的運行性能,在系統(tǒng)的核心功能實現上,使用C++ 11語言進行開發(fā)。而在一些較為靈活的網絡中間層處理上,則使用Go語言開發(fā),前端界面則使用ES6開發(fā)。此外為了能夠充分地利用一些已有的開源機器學習庫,部分功能使用Python開發(fā)。通過多語言的混合式開發(fā),能夠充分地發(fā)揮不同語言的優(yōu)點并提高開發(fā)效率。
視頻都是由一系列連續(xù)的圖像組成,所以本系統(tǒng)視頻處理最終還是要歸結于圖像處理。因此,要實現后續(xù)的功能拓展類工作,必須先要對視頻進行幀分割。本系統(tǒng)的分割工作主要依賴OpenCV庫。
其中視頻幀分割模塊的頭文件定義如下:
/* @brief 視頻幀分割
* @param video_path 輸入視頻路徑
* @param out_folder 輸出視頻幀圖像文件的文件夾路徑
* @return 返回幀圖像文件列表
*/
std::list
視頻幀分割的流程如下:
1) 根據輸入路徑構建VideoCapture實例
2) 從二進制流中讀取幀數據
3) 把幀數據轉碼為JPG格式并按照系統(tǒng)的約定規(guī)則存放到相應的路徑
4) 把分割的記錄數據存入PostgreSQL數據庫
視頻關鍵幀提取模塊是在OpenCV的直方圖計算相關靜態(tài)方法上使用C++去實現的。對于一個已分割的視頻,可以從數據庫中讀取出相關的分割結果,然后把視頻序列以動態(tài)數組的形式送入提取器,提取器再調用系統(tǒng)中的shot_detector::shot_bound方法去調用。本方法在頭文件中的定義如下:
/* @brief 通過對比當前幀和前一幀去判斷是否檢測到了一個鏡頭,檢測鏡頭返回true,否則返回false
* @param curr_frame 當前幀矩陣
* @param prev_frame 前一幀矩陣
* @param threshold 閥值,默認為0.7
* @return 如果兩個圖像矩陣不屬于同一個鏡頭返回true,否則返回false */
bool shot_bound(const cv::Mat& curr_frame, const cv::Mat& prev_frame, double threshold = 0.7);
在具體的實現上,相鄰兩幀的圖像矩陣以引用指針的形式傳入方法中,然后使用cv::resize方法進行一定的縮放,以防止單點計算量過大,接著使用cv::cvtColor進行灰度圖的轉換,再使用cv::calcHist進行直方圖計算,最后規(guī)一化后會利用cv::compareHist進行相似度比較。當相似度超過threshold指定的閾值后,則認為此相鄰的兩幀是處于同一個鏡頭的。經測量,本系統(tǒng)選擇了0.7作為默認的閾值。若發(fā)現shot_detector::shot_bound方法返回的對比結果為false,則說明這兩個相鄰的幀是處于不同的鏡頭當中,系統(tǒng)會把后一幀記為一個新鏡頭的起點,即把視頻幀實體的is_shot_frame字段設為true。
視頻關鍵幀非對稱相似檢索模塊的實現由C++語言開發(fā),其中依賴的庫有OpenCV、VLFeat和Yael庫。利用OpenCV的圖像矩陣和顏色空間的相關方法進行關鍵幀的基礎圖像解析。由于OpenCV自帶的SIFT功能沒有充分利用到CPU的浮點運算指令,本系統(tǒng)選用了VLFeat庫去提取SIFT特征向量,它可以利用諸如AVX等向量運算指令集。Yael主要用于建立Fisher Vector。
2.3.1 SIFT特征向量提取
對于一個輸入的關鍵幀圖片路徑,系統(tǒng)提供sift_feat:: get_keypoints_and_descriptors方法去從圖像文件中計算得出關鍵點和描述算子,如果獲取成功則返回true,否則返回false。
/* 根據圖像去獲取其關鍵點和描述算子。如果獲取成功,則返回true。
* @param image_path 圖像路徑
* @param divide_512 如果為真,則描述算子元素會處于0~1之間
* @param keypoints 關鍵點包含x,y,s,o信息。向量中每一個元素都是作為一個關鍵點
* @param descriptors 向量中每一個元素都是作為相對于關鍵點的描述算子。
* @param num_descriptor 輸出的描述子數量
*/
static bool get_keypoints_and_descriptors(
const char *image_path, bool divide_512,
std::vector
std::vector
其中的提取流程如下:
1) 利用cv::imread讀取圖形文件為圖形矩陣數據;
2) 把cv::Mat類型的數據轉換為std::uint32類型的一維的像素向量;
3) 用函數vl_sift_new()初始化SIFT過濾器對象;
4) 用函數vl_sift_first_octave()及vl_sift_process_next()遍歷縮放空間的每一階,直到返回VL_ERR_EOF為止;
5) 對于縮放空間的每一階,用函數vl_sift_detect()來獲取關鍵點;
6) 對每個關鍵點,用函數vl_sift_calc_keypoint_orientations()來獲取該點的方向;
7) 對關鍵點的每個方向,用函數vl_sift_calc_keypoint_descriptor()來獲取該方向的描述;
8) 最后,用函數vl_sift_delete()來釋放資源。
2.3.2 GMM聚類參數訓練
在構建全局索引前,需要利用INRIA提供的假日數據集去進行GMM聚類參數的訓練。假日數據集是一組圖像,主要包含一些國外景點的假日照片。其中圖片包含著多種的變化,例如旋轉、視點和光照變化、模糊等。數據集包括非常多種高分辨率的場景類型(自然、人造、水和火效果等)。數據集包含500個圖像組,每個圖像組代表不同的場景或對象。每個組的第一個圖像是查詢圖像,其余為該圖像的變化。
2.3.3 全局索引建立
本系統(tǒng)的索引建立在gmm參數訓練后,利用yael的fisher 向量轉換功能實現。其中的流程如下:
1)遍歷數據庫中的視頻幀表,根據is_shot和img_path字段去獲取關鍵幀相應的圖像,然后利用vfleat進行逐個關鍵幀建立siftb特征文件
2)加載上一步建立的gmm參數
3)利用yael的fisher方法去進行fisher 向量建立
4)合并多個fisher向量為一個矩陣并進行序列化保存
當系統(tǒng)接收到一個非對稱相似視頻幀檢索請求時,會把請求圖片轉換為fisher向量化的sift特征,然后和全局索引進行遍歷對比。對比得分進行排序并返回給用戶。
在使用人臉檢索功能前,需要進行人臉預標注庫的建立。
其中人物的實體結構體定義如下:
struct person {
std::uint32 person_id;
std::string name; //人物姓名
std::string description; //描述
std::uin32 created_at; //創(chuàng)建時間
std::uint32 modified_at; //修改時間
}
人臉實體結構體定義如下:
struct person_face {
std::uint32 person_face_id;
std::uint32 person_id; //人物ID
std::string img_path; //人物的單人照片路徑
std::uin32 created_at; //創(chuàng)建時間
std::uint32 modified_at; //修改時間
}
2.4.1 基于的HOG和SVM的人臉探測的實現
為了提高人臉探測的速度,本系統(tǒng)在OpenCV上,使用基于HOG(Histogram of Oriented Gradient)特征的人臉探測方法。構建一個基于HOG的人臉探測器,實際上就是利用人臉數據的HOG特征去訓練一個SVM分類器。其中的構建流程如下:
1)利用加州理工學院互聯網人臉數據集(Caltech Web Faces)的13436張各種不同角度的36×36的人臉裁剪照片作為正樣本。對于負樣本,我們采用多尺度非人臉場景中隨機裁剪36×36的圖片,其中負樣本的數量為85000個。然后以cell等于 4為參數,利用OpenCV的HOGDescriptor去提取樣本的HOG特征。
2)利用OpenCV的CvSVM::CvTermCriteria定義迭代,終止條件為當迭代滿1000次或誤差小于FLT_EPSILON。利用CvSVM::CvSVMParams去指定SVM分類器的核函數為線性函數、松弛因子為0.01。最后使用CvSVM::train方法進行訓練迭代并使用save方法進行svm模型保存。
2.4.2 基于Dlib的臉部編碼的實現
本系統(tǒng)的臉部編碼是基于Dlib庫并使用Python語言實現,其編碼流程如下:
1)利用dlib.deserialize方法加載預訓練的FaceNet模型;
2)使用dlib.compute_face_descriptor方法計算臉部的特征向量。
2.4.3 基于KNN人臉分類器的實現
當要對某一個視頻進行人臉檢索前,需要提前對其中包含的人臉特征值提取然后加載到knn中并以person_id作為標簽去訓練一個KNN分類器。訓練過程依賴于scikit-learn,過程如下:
1)從數據庫中加載預標注人臉庫,以X作為人臉特征矩陣,Y作為person_id的標簽矩陣
2)以參數algorithm=knn_algo、weights='distance',然后利用sklearn.KNeighborsClassifier方法進行KNN分類器的訓練
本系統(tǒng)的目標檢測引擎主要在視頻關鍵幀上匹配出已出現過的常見物體,物體檢測是基于darknet引擎。在深度神經網絡模型上,選用已預先訓練好的yolov3.weights模型。由于darknet本身是基于C語言編寫的,其在面向對象方面會比較弱,所以本系統(tǒng)使用C++將其進行封裝。
其中的封裝中,提供了如下方法:
·static Darknet* get_current()
說明:返回利用單例模式返回darknet的實例
·Darknet()
說明:darknet的構造方法,構造返回darknet實例
·~Darknet()
說明:darknet的析構方法,清理darknet所占用的資源
·void initialize(int gpu_id = 0)
說明:初始化方法,其中可以指定是否啟用GPU去進行加速運算,同時它也會負責加載模型文件
·void run()
說明:運行darknet監(jiān)聽線程。使得整個的檢測工作可以使用異步方式去工作,從而防止系統(tǒng)因長時間運算而導致的停機狀態(tài)
·void process(cv::Mat& image, process_func_ptr process_func = nullptr)
說明:圖像物體檢測方法。其中它會把OpenCV類型的圖像矩陣送進去隊列中去等待處理。消費者對象會根據指定的算法去進行物體的檢測
表1 系統(tǒng)運行軟件版本需求
系統(tǒng)首次啟動時,需要完成以下流程的開啟:
1)導入系統(tǒng)數據庫表;
2)開啟PostgreSQL數據庫;
3)需要在HOME目錄下新建vrs_storage目錄;
4)修改系統(tǒng)根目錄下的config.yaml文件進行配置。
為了驗證本系統(tǒng)的性能,對CNN于2018年12月20-26日發(fā)布在youtube上的新聞短片分別進行了非對稱相似幀檢索、視頻人物檢索及物體檢索等檢索實驗。各檢索結果及數據分析如下:
3.3.1 視頻非對稱相似幀檢索:
視頻來源:CNN
鏈接:youtube
視頻:Who's been naughty and nice in 2018 politics | With Chris Cillizza
檢索圖片的來源: 視頻《Who's been naughty and nice in 2018 politics | With Chris Cillizza》的第164秒
檢索結果數:100
檢索結果如圖5所示,檢索信息如表2所示。從表2可知,近距離全屏拍攝檢索的耗時最長,主要是因為近距離全屏拍攝時,待檢索照片像素較高,數據量大,圖像預處理耗時較長,但在檢索的100個結果中,對應的圖片排在第1位。相反,雖然遠距離全屏拍攝的耗時最短,但檢索結果卻排在第8位,說明待檢索照片圖像預處理耗時較短,但同時會在檢索過程中跟其他類似的圖片有較高的匹配度。整體上,視頻非對稱相似幀檢索都能在預期的時間上檢索出所需要的圖片。
圖5 視頻非對稱相似幀檢索結果圖
檢索結果檢索類型 檢索結果位置耗時/s近距離整屏拍攝14.2遠距離半屏拍攝13.54遠距離全屏拍攝83.28
3.3.2 人臉檢索和物體檢索實驗數據
數據: 2018年12月20日-2018年12月26日 CNN發(fā)布于youtube上的新聞短片;
總時長:29 815秒;
總幀數:29 815;
關鍵幀數:2 535.
檢索結果如表3和表4的所示。
表3 物體檢索信息表
表4 人臉檢索信息表
由表3可知,人類檢索結果的數據及準確率最高,狗的準確率最低。由于人類的檢測算法相對較成熟,而對于一些特征及背景較復雜物體,則會出現相對較大的誤檢索。由表4可知,檢索準確率取決于人臉的特征值及人臉訓練度,特征值或訓練度高的,則檢索準確率也會相應的增高。
本文探索了如何結合傳統(tǒng)的圖像處理算法和深度學習去構建一個視頻檢索系統(tǒng)。通過B/S架構可以讓用戶直接使用Web瀏覽器進行檢索,也可以方便的部署到云端、充分利用云計算服務商提供的相對低廉的機器成本、快速伸縮和多容器備災等特性。另一方面通過模塊間的耦合度底且模塊與上層的通信使用基于HTTP2的Protobuf協議的GRPC實現,實現了模塊間分布式部署的可能。在后續(xù)的改進上,可以嘗試利用深度哈希算法進行檢索工作,這樣就可以利用深度神經網絡里面的隱藏層自動地提取特征信息,另外,還可以利用語音識別等技術,生成語義性更好的文本關鍵詞或標簽等信息。