標籤:
在讀研期間,由於導師與水環研究生水生物有項目交叉,我主要研究視頻跟蹤技術。用來提取魚類的軌跡以及魚類的微動作。其中魚類的軌跡提取我已做了兩部分工作,二維視頻跟蹤,提取魚類的軌跡;另一部分工作是重建三維魚類遊動軌跡。魚類微特徵提取還沒有動工(慚愧)。----------
二維的視頻跟蹤
在做這項工作之前我們花費了很大的力氣去擷取實驗資料。購買了三個漢邦高科的網路攝影機,水箱,網路攝影機支架等。搭建好實驗裝置。(由於主要說視頻跟蹤,具體與魚相關的就帶過)注意:1、網路攝影機要固定住,這樣拍攝的視訊框架才有固定的攝像機座標,最後才好轉換成統一的現實座標。2、擷取的視頻盡量減少運動背景的幹擾,熟悉前景檢測的人都應該知道。3、注意光線,不要太暗了,也不要太刺眼,有光圈亮點。
前景跟蹤演算法過程
首先都是查看文獻來著,大概看了20多篇文獻資料。其實起指導作用的文獻還是只有那麼幾篇。我就列出來:[1].Zhang Z.A flexible new technique for camera calibration[J].Transactions on Pattern Analysis and Machine Intelligence,2000(11):1330-1334.[2]Olivier Barnich, Marc Van Droogenbroeck. ViBe: A universal background subtraction algorithm for video sequences[J]. IEEE Transactions on Image Processing, 20(6):1709-1724, June 2011.[3]Robust Fragments-based Tracking using the Integral Histogram.還有很多文獻,就不一一列出來了。對我用C++來實現這些演算法的啟蒙文獻應該是文獻[3]:第[3]篇文獻:是一種改進的模板匹配方法,把模板和目標都分成多個字塊,然後分別匹配,這樣可以避免部分被遮擋就丟失目標的情況。(有源碼可以查看)文獻[1]很經典,我是直接用的,用來消除網路攝影機的扭曲形變。文獻[2]是我們方法的基礎,我直接將它的源碼移植過來了。也許你們會問我做了什麼,都是別人的東西,確實,是這樣的。
我的工作
1、主要是從前景檢測的結果中用一種更優的方法找出魚類所在的位置。能夠更加精確、即時的提取魚類軌跡。2、將軌跡做平滑處理,使用了基於平方的方法,效果很不錯。3、開發二維軌跡跟蹤軟體,確實只是小軟體,我只用了3個星期不到的時間做完,當然還有很多需要完善的地方。其實也有之前做三維魚類軌跡跟蹤系統的基礎。很多複用了之前的代碼。
二維估計跟蹤演算法:
原始幀->去失真->vibe->局部搜尋->二維軌跡
vibe演算法不是我的原創,是已經很成熟的演算法,故不詳細說明。貼出它的源碼吧,希望大家可以直接借鑒學習。
二次封裝之後的vibe.hxx:
#ifndef _VIBE_HXX_#define _VIBE_HXX_class VIBE {public: VIBE(); ~VIBE(); void initialize(); void update(); inline void setCurrentFrame( unsigned char* i ) { _image = i; } inline void setSegmentMap( unsigned char* i ) { _segMap = i; } inline void setFrameWidth( int w ) { _frameWidth = w; } inline void setFrameHeight( int h ) { _frameHeight = h; } inline void setFrameWidthStrip( int s ) { _frameWidthStrip = s; } inline bool isInitilized() { return _samples; }private: int getRandomSample(); int getRandomSubSample(); int getRandomNeightXCoordinate( int x ); int getRandomNeightYCoordinate( int y );private: int _frameWidth; int _frameHeight; int _frameWidthStrip; int _sphereRadius; int _pixelSamples; int _backgroundThreshold; int _subSampling; int _borderWidth; unsigned char* _image; unsigned char* _segMap; unsigned char** _samples;};#endif
#include "stdafx.h"#include <assert.h>#include<stdlib.h>#include <time.h>#include <math.h>#include "VIBE.hxx"#define N 25#define R 15#define ZMIN 3#define PHI 16VIBE::VIBE(){ _frameWidth = 0; _frameHeight = 0; _frameWidthStrip = 0; _sphereRadius = R; _pixelSamples = N; _backgroundThreshold = ZMIN; _subSampling = PHI; _image = 0; _segMap = 0; _samples = 0; _borderWidth = 0;}VIBE::~VIBE(){ if( _samples ) { for( int i = 0; i < _pixelSamples; i ++ ) if( _samples[ i ] ) { delete [] _samples[ i ]; _samples[ i ] = 0; } delete [] _samples; _samples = 0; }}void VIBE::initialize(){ assert( (_frameWidth < 1 || _frameHeight < 1 || _frameWidthStrip < 1) || "Please set frame info for initialize...\n"); srand((int)time(0)); _samples = new unsigned char*[ _pixelSamples ]; for( int i = 0; i < _pixelSamples; i ++ ) _samples[ i ] = new unsigned char[ _frameHeight * _frameWidthStrip ]; int tq = sqrtf( _pixelSamples ); _borderWidth = tq / 2; for( int y = _borderWidth; y < _frameHeight - _borderWidth; y ++ ) { for( int x = _borderWidth; x < _frameWidth - _borderWidth; x ++ ) { int c = 0; for( int i = -_borderWidth; i <= _borderWidth && c < _pixelSamples ; i ++ ) { for( int j = -_borderWidth; j <= _borderWidth && c < _pixelSamples; j ++ ) if( c < _pixelSamples - _backgroundThreshold ) *(_samples[ c++ ] + y * _frameWidthStrip + x) = *(_image + ( y + i ) * _frameWidthStrip + ( x + j )); else *(_samples[ c++ ] + y * _frameWidthStrip + x) = *(_image + y * _frameWidthStrip + x); } } }}void VIBE::update(){ for( int x = 0; x < _frameWidth; x ++ ) { for( int y = 0; y < _frameHeight; y ++ ) { int count = 0, index = 0, dist = 0; if( y < _borderWidth || x < _borderWidth || x >= _frameWidth - _borderWidth || y >= _frameHeight - _borderWidth ) { *(_segMap + y * _frameWidthStrip + x ) = 0; continue; } while( count < _backgroundThreshold && index < _pixelSamples ) { dist = abs( *(_image + y * _frameWidthStrip + x) - *(_samples[ index ] + y * _frameWidthStrip + x ) ); if( dist < _sphereRadius ) count ++; index ++; } if( count >= _backgroundThreshold ) { *(_segMap + y * _frameWidthStrip + x ) = 0; int rand = getRandomSubSample(); //if( rand == 0 ) if( rand < 6) { rand = getRandomSample(); *(_samples[ rand ] + y * _frameWidthStrip + x ) = *( _image + y * _frameWidthStrip + x ); } rand = getRandomSubSample(); //if( rand == 0 ) if( rand < 6 ) { int xg, yg; xg = getRandomNeightXCoordinate(x); yg = getRandomNeightYCoordinate(y); rand = getRandomSample(); *(_samples[ rand ] + yg * _frameWidthStrip + xg ) = *( _image + y * _frameWidthStrip + x ); } } else { *(_segMap + y * _frameWidthStrip + x ) = 255; } } }}int VIBE::getRandomSample(){ int val = _pixelSamples * 1.0 * rand() / RAND_MAX; if( val == _pixelSamples ) return val - 1; else return val;}int VIBE::getRandomSubSample(){ int val = _subSampling * 1.0 * rand() / RAND_MAX; if( val == _subSampling ) return val - 1; else return val;}int VIBE::getRandomNeightXCoordinate( int x ){ int val = 4 * 1.0 * rand() / RAND_MAX - 2; if( x + val >= _frameWidth || x + val < 0 ) return x; else return x + val;}int VIBE::getRandomNeightYCoordinate( int y ){ int val = 4 * 1.0 * rand() / RAND_MAX - 2; if( y + val >= _frameWidth || y + val < 0 ) return y; else return y + val;}
調用這個VIBE這個類的方法:
VIBE vibe; vibe.setFrameWidth(image->width); vibe.setFrameHeight(image->height); vibe.setFrameWidthStrip(segImage->widthStep); vibe.setSegmentMap( (unsigned char*)(segImage->imageData)); vibe.setCurrentFrame((unsigned char*)grayImage->imageData); vibe.initialize(); …… //更新vibe.setCurrentFrame( (unsigned char*)grayImage->imageData );vibe.update();
不會用多看看類的源碼吧。“多折騰”,我導師的原話。
局部搜尋以及平滑處理
設k為幀數,R為搜尋半徑,P(x,y)為在 (x,y)處的像素值。設第 幀的座標為(xk,yk) ,可以得到第k+1 幀的位置為 :
平滑處理過程:
設平滑視窗的寬度為W1,視窗中每一幀的權重為:
原理為每一位局當前位置距離的平方,最遠處為1^2,……,當前最大2^k
平滑結果:
效果展示:
VIBE結果:
局部搜尋:
跟蹤結果:
如有疑問,可以聯絡我。
等我的論文錄用後,再深入討論三維軌跡跟蹤方法。
軌跡跟蹤——二維軌跡跟蹤