用光流法實現視訊中特徵點的跟蹤
阿新 • • 發佈:2019-01-27
在開始跟蹤前,首先要在初始幀中檢測特徵點,之後在下一幀中嘗試跟蹤這些點。你必須找到新的影象幀中這些點的位置。很明顯的,由於我們處理的是視訊序列,很有可能特徵點所在的物體已經移動過(運動也有可能是相機引起的)。因此,你必須在特徵點的先前位置附近進行搜尋,以找到下一幀中它的新位置。這正是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;
}
結果