C3D:視頻動作分類demo實現

C3D:視頻動作分類demo實現

C3D這個網絡是來自於learning Spatiotemporal feature with 3DConvolutional Networks這篇文章,我也對這篇文章仔細的研讀過,我覺得這個網絡具有兩個非常具有競爭力的優點:一是速度快;而是單純的使用未進過任何預處理的RGB特徵進行訓練就具有很不錯的準確率;我項目主頁上的代碼也運行過,而且按照C3D Guide 的步驟讓ucf101數據集上在已有的網絡上進行微調,微調後的測試準確率達到了80.169%,下面主要是簡單的介紹我實現C3D network的項目主頁上的視頻動作分類演示的demo。下面演示的視頻都是來自於ucf101數據中的。

1.ucf101數據集總共有101類,如下圖,我使用批量獲取文件夾名的方式獲得了這101類的名稱並保存在list.txt文件中如下:



下面是我再ucf101數據集中隨機選取的部分視頻,將這些視頻輸入到C3D網絡中,獲得了這些視頻的分類結果,並將結果保存在了相應的.prob文件中,如下:


文件夾的文件如下:


下面是相應的代碼:

#include<iostream>
#include<stdio.h>
#include<vector>
#include<fstream>
#include<opencv2/opencv.hpp>
#define video_index_max 101

/************************************************************************
函數功能:從已經獲取ucf101數據集所有類名稱的txt文件中讀入視頻類別名稱,共101類
輸入參數:filename是.txt文件路徑,videoname是保存101類的名稱
輸出參數:
************************************************************************/
void ReadVideoClass(const std::string& filename, std::vector<std::string>&videoname)
{
	std::fstream file0;
	file0.open(filename);
	if (!file0.is_open())
	{
		std::cerr << "fail to open the all name file" << std::endl;
		exit(0);
	}
	char temp0[1000];
	while (!file0.eof())
	{
		file0.getline(temp0, 1000);
		std::string line(temp0);
		videoname.push_back(line);
	}
	if (videoname.size() != video_index_max) //如果讀入的所有名稱數量小於101類,則輸入文件有誤
	{
		std::cerr << " video class file have error size" << std::endl;
		exit(0);
	}
	file0.close();
}


/************************************************************************
函數功能:獲取所有的從C3D網絡輸出並保存的.prob文件的路徑
輸入參數:filename是.txt文件路徑,videoname是保存101類的名稱
輸出參數:
************************************************************************/
void ReadAllTestResultPath(const std::string& filename, std::vector<std::string>&ResultPath)
{
	std::fstream file1;
	file1.open(filename);//讀取視頻文件路徑
	if (!file1.is_open())
	{
		std::cerr << "fail to open the test result file" << std::endl;
		exit(0);
	}
	char temp[1000];
	while (!file1.eof())
	{
		file1.getline(temp, 1000);
		std::string line(temp);
		//去除可能存在的空格,並添加後綴.prob
		std::string PostfixLine;//測試結果文件保存路徑
		std::string::size_type npos0 = line.find_last_of(" ");
		if (npos0 == line.size() - 1){
			PostfixLine = line.substr(0, npos0);
			PostfixLine += ".prob";
		}
		else
		PostfixLine = line + ".prob";
		std::cout << PostfixLine <<std::endl;

		ResultPath.push_back(PostfixLine);
	}
	file1.close();
}

/************************************************************************
函數功能:
輸入參數:
輸出參數:
************************************************************************/
void ReadLine(const std::string & path, std::string &Videopath, int &StartIndex, std::string& ground_truth)
{
	std::string line = path;
	StartIndex = atoi(line.substr(line.find_last_of("/") + 1, line.size() - 5).c_str());
	ground_truth = line.substr(line.find("output/") + strlen("output/"), line.find("/v_") - strlen("output/"));
	line.erase(0, 6); line.erase(line.find_last_of("/"), line.size());
	line.insert(0, "F:/ucf101"); line += ".avi";
	Videopath = line;

}

/************************************************************************
函數功能:從.prob文件中讀入C3D網絡的輸出結果
輸入參數:.prob文件路徑
輸出參數:101類的預測輸出結果 blob
************************************************************************/
bool read_blob(const std::string &probfile, std::vector<float>&blob)
{
	/*讀取模型提取的特徵文件中的參數*/
	std::ifstream fin(probfile, std::ios::binary);
	if (!fin.is_open()){ std::cerr << probfile << "file not exist " << std::endl; return 0; }
	int num=0, channel=0, length=0, height=0, width=0;
	fin.read((char*)&num, sizeof(int));
	fin.read((char*)&channel, sizeof(int)); if (channel != video_index_max) return 0;
	fin.read((char*)&length, sizeof(int));fin.read((char*)&height, sizeof(int));
	fin.read((char*)&width, sizeof(int));
	float ptr = 0;
	for (int i = 0; i < channel; i++)
	{
		fin.read((char*)&ptr, sizeof(float));
		blob.push_back(ptr);
	}
	return 1;
}


/************************************************************************
函數功能:從數組vec中找出最大值,返回最大值和最大值對應的序號
輸入參數:數組vec,數組大小num,引用返回的數組最大值rate
輸出參數:最大值對應的序號
************************************************************************/
int find_sec_max(std::vector<float>vec,int num,float &rate)
{
	int index = -1;
	if (vec.size() != num)
		return -1;
	float maxc = 0;
	for (int i = 0; i < vec.size();i++)
	{
		if (vec[i]>maxc)
		{
			maxc = vec[i]; index = i;
		}
	}
	rate = maxc;
	return index;
}

int main()
{
	using namespace std;
	using namespace cv;
	std::vector<std::string>videoname;
	ReadVideoClass("output\\list.txt", videoname);
	std::vector<std::string>ResultPath;
	ReadAllTestResultPath("output\\last-test-output.lst", ResultPath);
	
	VideoWriter writer("VideoTest.avi", CV_FOURCC('M', 'J', 'P', 'G'), 25.0, Size(320,240));
	vector<string>TestResultAll;
	vector<float>TestResultAllRate;
	vector<string>GroundTrueAll;
	for (size_t i = 0; i < ResultPath.size(); i++)
	{
		std::string Videopath; int StartIndex; std::string ground_truth;
		ReadLine(ResultPath[i], Videopath, StartIndex, ground_truth);
		std::vector<float>blob;
		int flag = read_blob(ResultPath[i], blob);
		if (!flag) { cerr << "blob read failed" << endl; exit(0); }
		float maxrate = 0;
		int TestResult = find_sec_max(blob, video_index_max, maxrate);
		VideoCapture capture;
		capture.open(Videopath);
		int count = StartIndex;
		int countcopy = count;
		Mat frame;
		while (countcopy--)//每次寫視頻時,都跳過前count幀開始寫
		{
			capture >> frame;
		}
		while (count<StartIndex+16)//每個視頻段的大小都是16幀,
		{	
			capture >> frame;//先讀入至frame中
			writer << frame;//將frame保存到writer中
			TestResultAll.push_back(videoname[TestResult]);//每一幀都賦予一個檢測結果(string類型),並將檢測結果保存在TestResultAll中
			TestResultAllRate.push_back(maxrate);//每一幀每一幀都賦予一個檢測概率(float類型),並將檢測概率值保存在TestResultAllRate中
			GroundTrueAll.push_back(ground_truth);//每一幀都有一個ground truth 
			count++;
		}
	}
	//writer.release();
	VideoCapture writercapture; writercapture.open("VideoTest.avi");
	VideoWriter VideoTest("VideoTestAll.avi", CV_FOURCC('M', 'J', 'P', 'G'), 25.0, Size(320, 240));
	
	int count = 0;
	while (1)
	{
		Mat frame;
		writercapture >> frame;
		if (!frame.data)
			break;
		string result = TestResultAll[count];
		float ratio = TestResultAllRate[count];
		char stemp[200];
		sprintf(stemp, "( %.3f )", ratio);
		string truth = GroundTrueAll[count];
		cv::putText(frame, "test result: "+result+stemp, cv::Point(5, 25), CV_FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 255));
		cv::putText(frame, "ground truth: "+truth, cv::Point(5, 48), CV_FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 255));
		imshow("frame", frame);
		waitKey(30);
		VideoTest << frame;
		count++;
	}
	VideoTest.release();
	return 0;

}
下面是VideoTestAll.avi的視頻演示效果,由於無法上傳視頻,所以截了幾張圖片:



下面是提出C3D這個網絡的原文learning Spatiotemporal feature with 3DConvolutional Networks的介紹,摘自一位很牛的博主的一篇筆記:

http://blog.csdn.net/wzmsltw/article/details/61192243

這篇文章提出的網絡的卷積神經網絡(CNN)近年被廣泛應用於計算機視覺中,包括分類、檢測、分割等任務。這些任務一般都是針對圖像進行的,使用的是二維卷積(即卷積核的維度爲二維)。而對於基於視頻分析的問題,2D convolution不能很好得捕獲時序上的信息。因此3D convolution就被提出來了。3D convolution 最早應該是在[1]中被提出並用於行爲識別的,本篇文章則主要介紹下面這篇文章 C3D,C3D network是作爲一個通用的網絡提出的,文章中將其用於行爲識別,場景識別,視頻相似度分析等領域。

可以訪問C3D network的項目主頁或是github獲得其項目代碼及模型,項目基於caffe實現。最近作者還更新了殘差網絡結構的新C3D模型,但是還沒有放出對應的論文,暫時不做討論。

2D 與 3D 卷積操作

首先簡要介紹一下2D與3D卷積之間的區別。a)和b)分別爲2D卷積用於單通道圖像和多通道圖像的情況(此處多通道圖像可以指同一張圖片的3個顏色通道,也指多張堆疊在一起的圖片,即一小段視頻),對於一個濾波器,輸出爲一張二維的特徵圖,多通道的信息被完全壓縮了。而c)中的3D卷積的輸出仍然爲3D的特徵圖。

現在考慮一個視頻段輸入,其大小爲 clhw ,其中c爲圖像通道(一般爲3),l爲視頻序列的長度,h和w分別爲視頻的寬與高。進行一次kernel size爲333,stride爲1,padding=True,濾波器個數爲K的3D 卷積後,輸出的大小爲Klhw。池化同理。

3D 卷積核參數的選擇

作者還對卷積核的尺寸進行了實驗研究,結果表面333大小的卷積核效果最好。

C3D network 結構

基於3D卷積操作,作者設計瞭如上圖所示的C3D network結構。共有8次卷積操作,4次池化操作。其中卷積核的大小均爲333,步長爲111。池化核的大小爲222,步長爲222,但第一層池化除外,其大小和步長均爲122。這是爲了不過早縮減時序上的長度。最終網絡在經過兩次全連接層和softmax層後就得到了最終的輸出結果。網絡的輸入尺寸爲316112112,即一次輸入16幀圖像。

實驗結果

接下來介紹一下C3D的實驗結果,作者將C3D在行爲識別、動作相似度標註、場景與物體識別這三個方向的數據庫上進行了測試,均取得了不錯的效果。注意以下結果均爲當時情況下的比較(2015年),從那時候到現在這些數據庫不知道又被刷了多少遍了。。

行爲識別-Action Recognition

行爲識別用的數據庫是UCF101,C3D+SVM的結果達到了85.2%。UCF101這個數據庫目前爲止(2017年3月)看到最高的結果已經達到了96%左右。

動作相似度標註-Action Similarity Labeling

動作相似度標註問題的任務是判斷給出的兩段視頻是否屬於相同的動作。文章中使用的數據庫爲ASLAN。C3D的效果超過了當時的state of the art 不少。

場景識別-Scene Recognition

場景識別問題主要使用了Maryland和YUPENN,也都達到了不錯的效果。

運行時間分析

下表中是C3D與其他一些算法的速度比較。其中iDT是行爲識別領域的非深度學習方法中效果最好的方法,可以見我之前的博文iDT算法介紹。Brox指Brox提出的光流計算方法[3].

這幾部分我都跑過相關的實驗,其中光流計算(GPU版本)現在的速度可以達到20-25fps,我使用的光流計算代碼的github地址爲gpu_flow。表中C3D的速度應該是在視頻幀無重疊的情況下獲得的。將一段16幀的視頻作爲一個輸入,則C3D一秒可以處理約42個輸入(顯卡爲1080, batch size選爲50),換算成無重疊情況下的fps爲672。可見C3D的速度還是非常快的。

總結

C3D使用3D CNN構造了一個效果不錯的網絡結構,對於基於視頻的問題均可以用來提取特徵。可以將其全連接層去掉,將前面的卷積層放入自己的模型中,就像使用預訓練好的VGG模型一樣。

發佈了48 篇原創文章 · 獲贊 146 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章