在读研期间,由于导师与水环研究生水生物有项目交叉,我主要研究视频跟踪技术。用来提取鱼类的轨迹以及鱼类的微动作。其中鱼类的轨迹提取我已做了两部分工作,二维视频跟踪,提取鱼类的轨迹;另一部分工作是重建三维鱼类游动轨迹。鱼类微特征提取还没有动工(惭愧)。
----------
二维的视频跟踪
在做这项工作之前我们花费了很大的力气去获取实验数据。购买了三个汉邦高科的摄像头,水箱,摄像头支架等。搭建好实验装置。(由于主要说视频跟踪,具体与鱼相关的就带过)
注意:
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 16
VIBE::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结果:
局部搜索:
跟踪结果:
如有疑问,可以联系我。
等我的论文录用后,再深入讨论三维轨迹跟踪方法。
时间: 2024-10-11 12:41:32