2013年10月9日 星期三

EmguCV Image Process: Process Video Sequences part 3


EmguCV Image Process: Process Video Sequences

參考OpenCV 2 Computer Vision Application Programming Cookbook第十章

介紹內容如下:

Reading video sequences

Processing the video frames

Writing video sequences

Tracking feature points in video

Extracting the foreground objects in video

承接上一篇所介紹的內容

一樣透過VideoProcessor這個類別

加入把處理過的影像寫檔的功能

在這個類別中加入以下這些欄位

//The OpenCV video witer object
VideoWriter writer;
//output filename
string outputFile;
//current index for output images
int currentIndex;
//number of digits in output image filename
int digits;
//extension of output images
string extension;

這裡新增一個VideoWriter的類別

這就是EmguCV用來寫檔的類別

其他欄位像是儲存寫檔的檔名

儲存成影像檔的index、副檔名等等

接著加入以下的方法

/// <summary>
/// set the output video file by default the same 
/// parameters than input video will be used
/// </summary>
/// <param name="filename">file name</param>
/// <param name="codec">video codec</param>
/// <param name="framerate">video FPS</param>
/// <param name="isColor">is color</param>
/// <returns>success or fail</returns>
public bool SetOutput(string filename, bool isColor)
{
    double framerate = GetFrameRate();//same as input
    Size size = GetFrameSize();

    this.outputFile = filename;
    this.extension = string.Empty;

    this.writer = new VideoWriter(this.outputFile, GetCodec(), (int)framerate, size.Width, size.Height, isColor);
    return true;
}

/// <summary>
/// set output as a series of image files
/// extension must be ".jpg", ".bmp" ...
/// </summary>
/// <param name="filename">prefix</param>
/// <param name="ext">image file extension</param>
/// <param name="numberOfDigits">number of digits</param>
/// <param name="startIndex">start index</param>
/// <returns>success or fail</returns>
public bool SetOutput(string filename, string ext)
{
    //filenames and their common extension
    this.outputFile = filename;
    this.extension = ext;
    //number of digits in the file numbering scheme
    this.digits = 3;
    //start numbering at this index
    this.currentIndex = 0;

    return true;
}

/// <summary>
/// Get the codec of input video 
/// </summary>
/// <returns>the codec</returns>
private int GetCodec()
{
    return CvInvoke.CV_FOURCC('X', 'V', 'I', 'D');
}

/// <summary>
/// Get the frame size of input video
/// </summary>
/// <returns>the frame size</returns>
private Size GetFrameSize()
{
    Size size = new Size()
    {
        Height = this.capture.Height,
        Width =this.capture.Width
    };
    return size;
}

/// <summary>
/// to write the output frame
/// could be video or image
/// </summary>
/// <param name="frame">output frame</param>
private void WriteNextFrame(IImage frame)
{
    if (!string.IsNullOrWhiteSpace(this.extension))
    {
        string imageFile = this.outputFile + this.currentIndex + this.extension;
        this.currentIndex++;
        frame.Save(imageFile);
    }
    else
    {
        if (frame.NumberOfChannels == 1)
        {
            this.writer.WriteFrame((Image<Gray, Byte>)frame);
        }
        else
        {
            this.writer.WriteFrame((Image<Bgr, Byte>)frame);
        }
    }

}
這裡分別加入兩個設定output的方法

採用多載的方式(相同名稱的方法SetOutput)

一個做為output為video的設定

一個做為output為image的設定

而output為video的部分

要取得codec,因此加入一個GetCodec的方法

這裡採用一般電腦都會有安裝的XVID這個codec

然後GetFrameSize取得capture所擷取的影像的大小

WriteNextFrame就判斷是要出輸出video還是image

然後在原本的Run這個方法中

加入寫檔的操作

/// <summary>
/// to grab (and process) the frame of the sequence
/// </summary>
public void Run()
{
    //current frame
    IImage frame;
    //output frame
    IImage output;
    //if no catpure device has been set
    if (!this.IsOpened)
    {
        return;
    }

    this.stop = false;
    while (!IsStopped)
    {
        //read next frame if any
        frame = this.capture.QueryFrame();
        if (frame == null)
        {
            break;
        }
        //display input frame
        if(!string.IsNullOrWhiteSpace(this.windowNameInput))
        {
            CvInvoke.cvShowImage(this.windowNameInput, frame.Ptr);
        }
        //calling the process function
        if (this.callIt)
        {
            //process the frame
            output = process(frame);
            //increment frame number
            this.fnumber++;
        }
        else
        {
            output = frame;
        }

        //write output sequence
        if (!string.IsNullOrWhiteSpace(this.outputFile))
        {
            WriteNextFrame(output);
        }

        //display output frame
        if (!string.IsNullOrWhiteSpace(this.windowNameOutput))
        {
            CvInvoke.cvShowImage(this.windowNameOutput, output.Ptr);
        }
        //introduce a delay
        if (this.delay >= 0 && CvInvoke.cvWaitKey(delay) >= 0)
        {
            StopIt();
        }
        //check if we should stop
        if (this.frameToStop >= 0 && GetFrameNumber() == this.frameToStop)
        {
            StopIt();
        }

    }
    this.writer.Dispose();
}

在output接收到處理過的frame後

判斷outputFile是否有值,有的話則進行WriteNextFrame的動作

並把處理後的frame(output)帶入即可


操作的方式也很簡單

如下

//Create instance
VideoProcessor processor = new VideoProcessor();
//Open the video file
processor.SetInput(@"E:\2012 Taipei Time Lapse.mov");
processor.SetFrameProcessor(Canny);
processor.SetOutput(@"E:\output.avi", false);
//processor.SetOutput(@"E:\output\output", ".jpg");
//Start the process
processor.Run();    

執行後就會開始讀取E:\2012 Taipei Time Lapse.mov這個影片檔

並且把這個檔案寫入E:\output.avi

若是下面那行註解的就是將影像一張一張存入E:\output\資料夾中

結果如下

這是存成圖片的結果

 這是轉錄成影片,由media player播放的開頭

 這是轉錄成影片,由media player播放的龍山寺

這是轉錄成影片,由media player播放的國父紀念館


接著下一篇將會介紹如何去追蹤影片中的特徵值(feature)


沒有留言:

張貼留言