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
上一篇介紹了如何透過Capture這個類別來讀入視訊檔案
這篇來介紹如何對視訊的每一張frame做一些影像處理
也就是所謂的視訊處理
如果每一次做不同的視訊處理
都要重複寫Capture這個類別的實作方法有點煩
所以這裡要重新包裝Capture這個類別
變成一個方便以後可再利用的類別
基本概念就是這個類別要能夠開啟視訊檔案
然後可以設定要用什麼樣的方法去做影像處理
這裡用到一個C#語言中很方便的Func這個委派類別
影像處理一般都是input一張影像就output一張影像
假設先設定我們要對每一張影像要做的方法
例如這裡要做Canny的邊緣偵測
方法如下:
IImage Canny(IImage image) { Image<Gray, byte> gray; if (image.NumberOfChannels == 3) { //Convert to gray gray = ((Image<Bgr, Byte>)image).Convert<Gray, Byte>(); } else { gray = (Image<Gray, Byte>)image; } //Compute Canny edges gray = gray.Canny(100, 200); //Invert the image gray._ThresholdBinaryInv(new Gray(128), new Gray(255)); return gray; }這裡的input值,與output值設定為image的基底介面
裡面的方法就是做Canny的邊緣偵測
所以其相對應的Func委派就是:Func<IImage, IImage>
不熟悉Func的人可到此學習
Func其實與Action這個委派是一樣的使用方式
只是Func有含回傳值,而Action是沒有回傳值的
那這邊開始包裝一下新的類別:VideoProcessor
public class VideoProcessor { //the OpenCV video capture object private Capture capture; //a bool to determine if the process //callback will be called private bool callIt; //the callback function to be called //for the processing of each frame private Func<IImage, IImage> process; //Input display window name private string windowNameInput; //Output display window name private string windowNameOutput; //delay between each frame processing int delay; //number of processed frames long fnumber; //stop at this frame number long frameToStop; //to stop the processing bool stop; //Construct public VideoProcessor() { callIt = true; delay = 0; fnumber = 0; stop = false; frameToStop = -1; } ...這個類別需要的一些欄位和建構子
這裡要設定callIt:是否要使用設定的方法來做影像處理
process:就是用來委派所需要的影像處理的方法
windowNameInput:設定原始畫面撥放的window的顯示名稱
windowNameOutput:設定處理畫面撥放的window的顯示名稱
delay:設定每張frame的延遲時間
fnumber:下一張要處理的frame number
frameToStop:設定處理到哪一張frame的時候停止
stop:是否要停止
接下來提供一些公開屬性和方法讓使用者設定
/// <summary> /// set the callback function that will be called /// for each frame /// </summary> /// <param name="process">the callback function</param> public void SetFrameProcessor(Func<IImage, IImage> process) { this.process = process; } /// <summary> /// set the name of the video file /// </summary> /// <param name="filename">the name of the video file</param> /// <returns>success or failure</returns> public bool SetInput(string filename) { this.fnumber = 0; //In case a resource was already //associated with the Capture instance if (this.capture != null) this.capture.Dispose(); try { this.capture = new Capture(filename); return true; } catch (Exception) { return false; } } /// <summary> /// to display the processed frames /// </summary> /// <param name="windowName">the name of the window</param> public void DisplayInput(string windowName) { this.windowNameInput = windowName; CvInvoke.cvNamedWindow(this.windowNameInput); } /// <summary> /// to display the processed frames /// </summary> /// <param name="windowName">the name of the window</param> public void DisplayOutput(string windowName) { this.windowNameOutput = windowName; CvInvoke.cvNamedWindow(this.windowNameOutput); } /// <summary> /// do not display the processed frames /// </summary> public void DontDisplay() { CvInvoke.cvDestroyWindow(this.windowNameInput); CvInvoke.cvDestroyWindow(this.windowNameOutput); } /// <summary> /// Stop the porcessing /// </summary> public void StopIt() { this.stop = true; } /// <summary> /// Is the process stopped? /// </summary> public bool IsStopped { get { return this.stop; } } /// <summary> /// Is a capture device opened? /// </summary> public bool IsOpened { get { return this.capture != null; } } /// <summary> /// set a delay between each frame /// 0 means wait at each frame /// negative means no delay /// </summary> /// <param name="delay">delay</param> public void SetDelay(int delay) { this.delay = delay; } /// <summary> /// process callback to be called /// </summary> public void CallProcess() { this.callIt = true; } /// <summary> /// do not call process callback /// </summary> public void DontCallProcess() { this.callIt = false; } /// <summary> /// stop at the frame number /// </summary> /// <param name="frame">the frame number</param> public void StopAtFrameNo(long frame) { this.frameToStop = frame; } /// <summary> /// return the frame number of the next frame /// </summary> /// <returns>the frame number of the next frame</returns> public double GetFrameNumber() { double frames = this.capture.GetCaptureProperty(Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_POS_FRAMES); return frames; } /// <summary> /// return the frame rate of the video file /// </summary> /// <returns>the frame rate of the video file</returns> public double GetFrameRate() { double frameRate = this.capture.GetCaptureProperty(Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_FPS); return frameRate; }提供設定屬性和設定影像處理的方法
SetFrameProcessor(Func<IImage, IImage> process)就是傳入一個方法
此方法必須是只有一個引數,並且為IImage,回傳值也為IImage
然後SetInput(string filename)設定處理的視訊檔案的完整路徑
DisplayInput(string windowName)設定原始畫面window的顯示名稱
DisplayOutput(string windowName)設定處理畫面window的顯示名稱
和一些設定停止、delay時間、取得frame number、取得frame rate的方法
再來是運作的方法:
/// <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; } //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(); } } }這裡利用一個while迴圈去判斷是否被使用者停止
然後不斷的去取得最新一張frame
然後用一個window去呈現原始畫面
用另一個window去呈現處理的畫面
這裡可以發現Func<IImage, IImage> process
就直接被當作是一個方法來用output = process(frame);
他就會去執行使用者所設定的那個方法!
所以這裡是彈性的
使用者可以自由地設定他要處理的方法
使用的方式如下:
//Create instance VideoProcessor processor = new VideoProcessor(); //Open the video file processor.SetInput(@"video.mov"); //Declare a window to display the video processor.DisplayInput("Current frame"); processor.DisplayOutput("Output frame"); //Play the video at the original frame rate processor.SetDelay((int)(1000/processor.GetFrameRate())); //Set the frame processor callback function processor.SetFrameProcessor(Canny); //Start the process processor.Run();先建立我們所包裝好的VideoProcessor這個類別
然後設定目標的視訊來源
設定播放原始畫面的window的顯示名稱
設定播放處理畫面的window的顯示名稱
設定延遲時間為1000/FPS (這樣才會符合原始視訊的fps)
設定剛剛所寫的Canny方法作為這個VideoProcessor的處理方法
然後run
執行結果如下:
執行時會跳出兩個新視窗
一個視窗Current frame撥放原始畫面
另一個視窗Output frame則撥放Canny邊緣偵測的畫面
透過這個VideoProcessor類別
可以輕鬆地更換你要執行的影像處理方法
完全不需要重複撰寫多餘的程式碼
只要利用SetFrameProcessor(Func<IImage, IImage> process)這個方法
就可以去更換
也來實作一個影像處理的方法來執行看看吧!!
下一篇介紹將此VideoProcessor類別加入寫檔的功能
沒有留言:
張貼留言