基於Motion Vector的實時動作識別

 

論文:Real-time Action Recognition with Enhanced Motion Vector CNNs

Github: https://github.com/zbwglory/MV-release

 

2016 CVPR

 

論文基於雙流法(deep two-stream)的基本結構,提出了使用運動向量(Motion Vector)來代替光流(optical flow)。可以獲得比光流法更快的速度。但是單純的使用運動向量替換光流法會使得整體有7%精度的下降。對於精度降低的問題,提出了基於三步法(initialization transfer, supervision transfer ,their combination)的知識蒸餾,來使用光流的teacher網絡(OF-CNN)初始化,指導訓練student的運動向量網絡(MV-CNN)。

最終可以在UCF101數據集達到390.7FPS的速度,THUMOS14數據集達到403FPS的速度,相比傳統的雙流法加速27倍。

 

運動向量 VS 光流:

運動向量基於macro blocks輸出最終結果,而光流法是基於每一個pixel輸出結果。運動向量的噪聲更大,粒度更粗,光流法的粒度更細。運動向量的速度快,光流的速度很慢。

 

論文貢獻:

  1. 使用雙流模型,提出了基於CNN的識別方法,並且取得了state-of-the-art的效果。
  2. 首次提出使用運動向量來替代光流
  3. 提出了知識蒸餾的方法,大大的提升了準確性。

 

網絡結構:

並不是每一個圖片都會包含運動向量。假設圖片的集合爲group of pictures (GOP),那麼一個GOP裏面包含3種類型的幀圖片,I-frame, P-frame and B-frameI-frame是內部編碼的幀,不包含運動向量。P-frame表示預測的幀,包含運動向量。B-frame表示前後預測的幀,包含運動向量。

在實際的訓練中,不包含運動向量的I-frame會使得訓練精度降低,爲了解決這個問題,使用該幀的前面的包含運動向量的I-frame幀替代該幀的I-frame

 

詳細的網絡結構如下,

 

知識蒸餾三部曲:

Teacher Initialization

Teacher網絡和student網絡的結構一樣,使用Teacher網絡的權值來初始化student網絡。

Supervision Transfer

教師網絡的輸入爲基於光流法得到的圖片,學生網絡的輸入爲基於運動向量得到的圖片。兩者的輸入不同,但是需要達到輸出一樣的效果。

對教師網絡的最後一個全連接層除以Temp,得到soft的輸出,學生網絡的最後一個全連接層也做同樣的操作。最後通過softmax_crossentrop loss來使得教師網絡和學生網絡的最後一個全連接層的特徵分佈相似。

另外一個loss就是學生網絡和groundtruth的softmax_crossentrop loss。

最終的loss就是上面兩個loss的加權和。

 

Combination

將Teacher Initialization和Supervision Transfer結合起來。兩個同時使用對學生網絡進行訓練。

 

實驗結果:

 

訓練中的數據增強:

  1. 隨機crop,224× 224, 196× 196 , 168× 168
  2. 隨機進行像素的尺度縮放scale jittering1, 0.875, 0.75
  3. 隨機水平鏡像

 

程序指北:

CMakeLists

cmake_minimum_required(VERSION 2.8)                                             

project(draw_flow)
set(CMAKE_CXX_FLAGS   "-std=c++11")

FIND_PACKAGE(OpenCV REQUIRED)

include_directories(${OpenCV_INCLUDE_DIRS})
include_directories("/usr/local/include/")

LINK_DIRECTORIES("/usr/local/lib")
add_executable(draw_flow draw_flow.cpp)
target_link_libraries(draw_flow ${OpenCV_LIBS})

mpegflow代碼:

#include <opencv2/highgui/highgui.hpp>                                                                                                                                                                             
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/imgcodecs/imgcodecs.hpp>
#include <opencv2/opencv.hpp>


#include <stdio.h>
#include <iostream>
#include <fstream>
using namespace cv;
using namespace std;



static void convertFlowToImage(const Mat &flow_x, const Mat &flow_y, Mat &img_x, Mat &img_y,
       double lowerBound, double higherBound) {
    #define CAST(v, L, H) ((v) > (H) ? 255 : (v) < (L) ? 0 : cvRound(255*((v) - (L))/((H)-(L))))
    for (int i = 0; i < flow_x.rows; ++i) {
        for (int j = 0; j < flow_y.cols; ++j) {
            float x = flow_x.at<float>(i,j);
            float y = flow_y.at<float>(i,j);
            img_x.at<uchar>(i,j) = CAST(x, lowerBound, higherBound);
            img_y.at<uchar>(i,j) = CAST(y, lowerBound, higherBound);
        }
    }
    #undef CAST
}


int main(int argc, char** argv){
    // IO operation
    const char* keys =
        {                                                                                                                                                                                                          
            "{ f  | vidFile      | dump | filename of optical flow}"
            "{ x  | xFlowFile    | flow_x | filename of flow x component }"
            "{ y  | yFlowFile    | flow_y | filename of flow x component }"
            "{ b  | bound | 15 | specify the maximum of optical flow}"
        };



    //CommandLineParser cmd(argc, argv, keys);
    string vidFile = "dump.mvs0";//cmd.get<string>("vidFile");
    string xFlowFile = "v_ApplyEyeMakeup_g01_c01/flow_x";//cmd.get<string>("xFlowFile");
    string yFlowFile = "v_ApplyEyeMakeup_g01_c01/flow_y"; //cmd.get<string>("yFlowFile");
    //string imgFile = cmd.get<string>("imgFile");
    int bound = 20;//cmd.get<int>("bound");

    int video_width = 320;
    int video_height = 240;

    int frame_num = 0;
    Mat image, prev_image, prev_grey, grey, frame;

    ifstream fin;
    cout << vidFile << endl;
    fin.open(vidFile.data());
    if (!fin) {
        cout << "error in opening file";
        return -1;
    }


    int frame_prev = 0;
    while(!fin.eof()) {
        // Output optical flow
        int mv_per_frame = -1;
        fin >> mv_per_frame;
        if (mv_per_frame == -1)
            break;
        int forback, blockx,blocky,srcx,srcy,dstx,dsty,minx,miny;
        Mat flow_x(video_height,video_width,CV_32F,Scalar(0));
        Mat flow_y(video_height,video_width,CV_32F,Scalar(0));
        for (int i=0; i<mv_per_frame; i++) {
            fin >> frame_num >> forback >> blockx >> blocky >> srcx >> srcy >> dstx >> dsty >> minx >> miny;
            for (int x=0; x<blockx; x++) {
                for (int y=0; y<blocky; y++) {
                    if ((dstx-blockx/2+x < 0) || (dsty-blocky/2+y < 0) || (dstx-blockx/2+x > video_width-1) || (dsty-blocky/2+y > video_height-1) || (forback > 0))
                        continue;
                    flow_x.at<float>(dsty-blocky/2+y,dstx-blockx/2+x) = (float)minx;
                    flow_y.at<float>(dsty-blocky/2+y,dstx-blockx/2+x) = (float)miny;
                }
            }
        }
        frame_num = frame_num-1;

        cv::Mat imgX(flow_x.size(),CV_8UC1);
        cv::Mat imgY(flow_y.size(),CV_8UC1);
        convertFlowToImage(flow_x,flow_y, imgX, imgY, -bound, bound);
        char tmp[20];
        sprintf(tmp,"_%04d.jpg",int(frame_num));

        cv::Mat imgX_, imgY_, imgX_small, imgY_small;
        cv::resize(imgX,imgX_, cv::Size(340,256));
        cv::resize(imgY,imgY_, cv::Size(340,256));

        cv::imwrite(xFlowFile + tmp,imgX_);
        cv::imwrite(yFlowFile + tmp,imgY_);


        while (frame_prev < frame_num-1) {
            frame_prev ++ ;
            char tmp1[20];
            sprintf(tmp1,"_%04d.jpg",int(frame_prev));
            cout << tmp1 << endl;
            cv::imwrite(xFlowFile + tmp1,imgX_);
            cv::imwrite(yFlowFile + tmp1,imgY_);
        }
        frame_prev = frame_num;

    }
    return 0;
}

提取motion vector並轉化爲灰度圖片指令extract_mvs_sample.sh

tar xvf ffmpeg-2.7.2.tar
mkdir v_ApplyEyeMakeup_g01_c01

gcc -o ffmpeg-2.7.2/doc/examples/extract_mvs extract_mvs.c -L /usr/local/lib/ -lavcodec -lavdevice -lavfilter -lavformat –lavutil

ffmpeg-2.7.2/doc/examples/extract_mvs v_ApplyEyeMakeup_g01_c01.avi > dump.mvs0

./MV-code-release/build/draw_flow -f dump.mvs0 -x v_ApplyEyeMakeup_g01_c01/flow_x -y v_ApplyEyeMakeup_g01_c01/flow_y -b 20

dump.mvs0樣子:

運動向量的圖片形式:

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章