1. 程式人生 > >用光流法實現視訊中特徵點的跟蹤

用光流法實現視訊中特徵點的跟蹤

在開始跟蹤前,首先要在初始幀中檢測特徵點,之後在下一幀中嘗試跟蹤這些點。你必須找到新的影象幀中這些點的位置。很明顯的,由於我們處理的是視訊序列,很有可能特徵點所在的物體已經移動過(運動也有可能是相機引起的)。因此,你必須在特徵點的先前位置附近進行搜尋,以找到下一幀中它的新位置。這正是cv::calcOpticalFlowPyrLK函式所實現的工作。你輸入兩個連續的影象幀以及第一幅影象中檢測到的特徵點陣列,該函式將返回一組新的特徵點為位置。為了跟蹤完整的序列,你需要在幀與幀之間重複這個過程,不可避免地你也會丟失其中一些點,於是被跟蹤的特徵點數目會減少。為了解決這個問題,我們可以不時地檢測新的特徵值。

// OpticalFlow.cpp : 定義控制檯應用程式的入口點。
//

#include "stdafx.h"
#include <opencv2\opencv.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>

using namespace std;
using namespace cv;

//幀處理基類  
class FrameProcessor
{
public:
    virtual void process(Mat &input,Mat &ouput)=0
; }; class FeatureTracker:public FrameProcessor { Mat gray;//當前灰度影象 Mat gray_prev;//之前灰度影象 //兩幅影象間跟蹤的特徵點 0->1 vector<Point2f>points[2]; //跟蹤的點的初始位置 vector<Point2f>initial; vector<Point2f>features;//檢測到的特徵 int max_count;//需要跟蹤的最大特徵數目 double qlevel;//特徵檢測中的質量等級
double minDist;//兩點之間的最小距離 vector<uchar>status;//檢測到的特徵的狀態 vector<float>err;//跟蹤過程中的錯誤 public: FeatureTracker():max_count(500),qlevel(0.01),minDist(10.){} void process(Mat &frame,Mat &output) { //轉換為灰度影象 cvtColor(frame,gray,CV_BGR2GRAY); frame.copyTo(output); //1.如果需要新增新的特徵點 if(addNewPoints()) { //進行檢測 detectFeaturePoints(); //新增檢測到的特徵到當前跟蹤的特徵中 points[0].insert(points[0].end(),features.begin(),features.end()); initial.insert(initial.end(),features.begin(),features.end()); } //對於序列中的第一幅影象 if(gray_prev.empty()) { gray.copyTo(gray_prev); } //2.跟蹤特徵點 calcOpticalFlowPyrLK( gray_prev,gray,//兩幅連續圖 points[0],//圖1中的輸入點座標 points[1],//圖2中的輸出點座標 status,//跟蹤成果 err);//跟蹤錯誤 //2.遍歷所有跟蹤的點進行篩選 int k=0; for(int i=0;i<points[1].size();i++) { //是否需要保留該點? if(acceptTrackedPoint(i)) { //進行保留 initial[k]=initial[i]; points[1][k++]=points[1][i]; } } //去除不成功的點 points[1].resize(k); initial.resize(k); //3.處理接收的跟蹤點 handleTrackedPoints(frame,output); //4.當前幀的點和影象變為前一幀的點和影象 swap(points[1],points[0]); swap(gray_prev,gray); } //檢測特徵點 void detectFeaturePoints() { //檢測特徵 goodFeaturesToTrack(gray,//影象 features,//檢測到的特徵 max_count,//特徵的最大數目 qlevel,//質量等級 minDist);//兩個特徵之間的最小距離 } //是否需要新增新的點 bool addNewPoints() { //如果點的數量太少 return points[0].size()<=10; } //決定哪些點應該跟蹤 bool acceptTrackedPoint(int i) { return status[i]&& //如果它移動了 (abs(points[0][i].x-points[1][i].x))+ (abs(points[0][i].y-points[1][i].y))>2; } //處理當前跟蹤的點 void handleTrackedPoints(Mat &frame,Mat &output) { //遍歷所有跟蹤點 for(int i=0;i<points[1].size();i++) { //繪製直線和圓 line(output, initial[i],//初始位置 points[1][i],//新位置 Scalar(255,255,255)); circle(output,points[1][i],3,Scalar(255,255,255),-1); } } }; class VideoProcessor { private: VideoCapture caputure; //輸出檔名 string Outputfile; int currentIndex; int digits; string extension; FrameProcessor *frameprocessor; //影象處理函式指標 void (*process)(Mat &,Mat &); bool callIt; string WindowNameInput; string WindowNameOutput; //延時 int delay; long fnumber; //第frameToStop停止 long frameToStop; //暫停標誌 bool stop; //影象序列作為輸入視訊流 vector<string> images; //迭代器 public: VideoProcessor():callIt(true),delay(0),fnumber(0),stop(false),digits(0),frameToStop(-1){} //設定影象處理函式 void setFrameProcessor(void (*process)(Mat &,Mat &)){ frameprocessor = 0; this->process = process; CallProcess (); } //開啟視訊 bool setInput(string filename){ fnumber = 0; //若已開啟,釋放重新開啟 caputure.release (); return caputure.open (filename); } //設定輸入視訊播放視窗 void displayInput(string wn){ WindowNameInput = wn; namedWindow (WindowNameInput); } //設定輸出視訊播放視窗 void displayOutput(string wn){ WindowNameOutput = wn; namedWindow (WindowNameOutput); } //銷燬視窗 void dontDisplay(){ destroyWindow (WindowNameInput); destroyWindow (WindowNameOutput); WindowNameInput.clear (); WindowNameOutput.clear (); } //啟動 void run(){ Mat frame; Mat output; if(!isOpened()) return; stop = false; while(!isStopped()){ //讀取下一幀 if(!readNextFrame(frame)) break; if(WindowNameInput.length ()!=0) imshow (WindowNameInput,frame); //處理該幀 if(callIt){ if(process) process(frame,output); else if(frameprocessor) frameprocessor->process (frame,output); } else{ output = frame; } if(WindowNameOutput.length ()!=0) imshow (WindowNameOutput,output); //按鍵暫停,繼續按鍵繼續 if(delay>=0&&waitKey (delay)>=0) waitKey(0); //到達指定暫停鍵,退出 if(frameToStop>=0&&getFrameNumber()==frameToStop) stopIt(); } } //暫停鍵置位 void stopIt(){ stop = true; } //查詢暫停標誌位 bool isStopped(){ return stop; } //返回視訊開啟標誌 bool isOpened(){ return caputure.isOpened ()||!images.empty (); } //設定延時 void setDelay(int d){ delay = d; } //讀取下一幀 bool readNextFrame(Mat &frame){ if(images.size ()==0) return caputure.read (frame); else{ if(itImg!=images.end()){ frame = imread (*itImg); itImg++; return frame.data?1:0; } else return false; } } void CallProcess(){ callIt = true; } void dontCallProcess(){ callIt = false; } //設定停止幀 void stopAtFrameNo(long frame){ frameToStop = frame; } // 獲得當前幀的位置 long getFrameNumber(){ long fnumber = static_cast<long>(caputure.get ((CV_CAP_PROP_POS_FRAMES))); return fnumber; } //獲取幀率 double getFrameRate(){ return caputure.get(CV_CAP_PROP_FPS); } vector<string>::const_iterator itImg; bool setInput (const vector<string> &imgs){ fnumber = 0; caputure.release (); images = imgs; itImg = images.begin (); return true; } void setFrameProcessor(FrameProcessor *frameprocessor){ process = 0; this->frameprocessor = frameprocessor; CallProcess (); } }; int main() { //建立視訊處理器例項 VideoProcessor processor; //建立特徵跟蹤器例項 FeatureTracker tracker; //開啟視訊檔案 processor.setInput("walk.avi"); //設定幀處理器物件 processor.setFrameProcessor(&tracker); //宣告顯示視窗 processor.displayOutput("Tracked Features"); //以原始幀率播放視訊 processor.setDelay(1000./processor.getFrameRate()); //開始處理過程 processor.run(); return 0; }

結果
這裡寫圖片描述

這裡寫圖片描述