最近在做目标跟踪的相关程序的加速,整合借这个机会整理下TLD的源码理解,由于本人C++功底有限,难免会出现表述和理解错误,请大家批评指正
本帖子 长期更新 未完部分耐心等待
主要涉及两个函数:
tracker.RTLTrackerInit(last_gray, box);
其中 last gray 是frame第一帧经过灰度变换的图像,代码如下:
capture >> frame;
cvtColor(frame, last_gray, CV_RGB2GRAY);
Box 是我们标定的目标位置,TLD是单目标跟踪所以只能实现标记一个框
Rect box(248,198,27,14);
tracker.RTLTrackerTracking(last_gray, current_gray, pbox, status, true);
其中 pbox 是 box 调用 BoundingBox 的构造函数得到的
class BoundingBox : public cv::Rect
{
public:BoundingBox( ){}BoundingBox( cv::Rect r ) : cv::Rect( r ){}/* 和指定的box的重叠度 */float overlap;/* 使用的缩放组合的id( 0~21 ) */int scaleId;
};
status函数表示是否跟踪成功,如果跟踪成功则画框
if (status) {rectangle(frame, pbox, Scalar(0, 255, 0), 2, 8, 0);detections++;}
TrackerInit类
void RtLtTracker::RTLTrackerInit( const Mat &frame, const Rect userBox )
{/* 获得所有的扫描窗口 */AllScanWindowOfFrame( frame, userBox );/* 将这些窗口分类为好坏窗口,并得到好窗口的边界 */boxOperator.classifyScanningWindows( userBox, numClosestInit );/* 分配各种空间 *//*struct TempStruct {std::vector<std::vector<int> > patt;std::vector<float> conf;};*/f = vector<float>( boxOperator.allScanningWindows.size( ) );tmp.patt = vector<vector<int> >( boxOperator.allScanningWindows.size( ), vector<int>( 10, 0 ) );ate( classifier.patchSize, classifier.patchSize, CV_64F );/************************************************************ 源代码有保留上一帧信息的操作,应该是用来做BF跟踪器跟丢自检,这里先不加 ************************************************************//****????这里有个疑问是为什么不用初始rect作为bestWindows,而还是要用和Rect重合度最好的windows作为 bestWindows 因为这本来就是初始化阶段啊****/lastbox=boxOperator.bestWindow;lastconf=1;lastvalid=true;classifier.prepare( boxOperator.allScaledSizes );/* 生成数据 */patchGenerator = PatchGenerator(0, 0, noiseInit, true, 1 - scaleInit, 1 + scaleInit,-angleInit*CV_PI / 180, angleInit*CV_PI / 180,-angleInit*CV_PI / 180, angleInit*CV_PI / 180 );ate( ws + 1, ls + 1, CV_32F );ate( ws + 1, ls + 1, CV_64F );integral( frame, boxOperator.iisum, boxOperator.iisqsum );Scalar stdev, mean;meanStdDev( frame( boxOperator.bestWindow ), mean, stdev );classifier.varClassifierTh = powf( stdev.val[0], 2 ) * 0.atePositiveData(frame, classifier.numWrapsInit, boxOperator, patchGenerator );ateNegativeData(frame, boxOperator);/* 处理数据并训练分类器 */classifier.makeTrainAndTestDataThenTrain( );
}
对上述为啥不用userBox 而是 用 BestWindow 的疑问的解答:
初始化阶段的userBox和bestWindow本质是差不多的
下面我们逐句分析Tracker的代码:
getAllScanWindowOfFrame
输入1: frame 代表第一帧
输入2:userBox代表初始的矩阵框 Rect
void BoxOperator::getAllScanWindowOfFrame( const Mat &frame, const Rect &userBox )
{// 所有的缩放尺度,由尺度缩放系数构造,为[1.2^-10, 1.2^10],一共21种缩放尺度const float SCALES[] ={0.16151f, 0.19381f, 0.23257f, 0.27908f, 0.33490f,0.40188f, 0.48225f, 0.57870f, 0.69444f, 0.83333f,1.00000f, 1.20000f, 1.44000f, 1.72800f, 2.07360f,2.48832f, 2.98598f, 3.58318f, 4.29982f, 5.15978f,6.19174f };Size scaledSize;BoundingBox bbox;int cnt = 0;// 对于每一种缩放尺度for ( int s = 0; s < 21; s++ ){/*********************代表缩放后的宽度************************/int scaledWidth = (int)roundf( userBox.width * SCALES[s] );/*********************代表缩放后的高度************************/int scaledHeight = (int)roundf( userBox.height * SCALES[s] );/****************变换权值:宽度和高度较小值*********************/int shiftWeight = min( scaledWidth, scaledHeight );/* ***********筛选出不合适的padding size 跳过****************1) 不满足最小窗尺寸:minWindowSize = 152) 缩放后的宽度比原图像帧的宽度还大3) 缩放后的高度比原图像帧的长度还大**********************************************************/if ( shiftWeight < minWindowSize ||scaledWidth > ls ||scaledHeight > ws ){continue;}/************* 保留合适的scaledSize **********/scaledSize.width = scaledWidth;scaledSize.height = scaledHeight;/***********保存所有的ScaledSize*************/allScaledSizes.push_back( scaledSize );/***************设置扫描步长******************/int step = (int)roundf( shiftWeight * scanningShift );for ( int y=1; y < ws - scaledHeight; y += step ){for ( int x=1; x < ls - scaledWidth; x += step ){bbox.x = x;bbox.y = y;bbox.width = scaledWidth;bbox.height = scaledHeight;// 保存与初始输入窗口box的重叠度bbox.overlap = bbOverlap( bbox, BoundingBox( userBox ) );// 记录扫描窗口的idbbox.scaleId = cnt;// 把所有的窗口集中到一个容器内allScanningWindows.push_back( bbox );}}cnt++;}
}
classifyScanningWindows
输入1:
/* 在初始化分类器时需要保留的好窗口数量 */int numClosestInit = 10;
输入2:userBox 代表初始的矩阵框 Rect
void BoxOperator::classifyScanningWindows( const Rect &userBox, int closestNum )
{float maxOverlap = 0;for ( int i = 0; i < allScanningWindows.size( ); i++ ){if ( allScanningWindows[i].overlap > maxOverlap ){bestWindow = allScanningWindows[i];maxOverlap = bestWindow.overlap;}/**********goodOverlapth代表好窗口重叠度 = 0.6f ************/if ( allScanningWindows[i].overlap > goodOverlapTh ){goodBoxIndexes.push_back( i );}/**********badOverlapth代表坏窗口重叠度 = 0.2f ************/else if ( allScanningWindows[i].overlap < badOverlapTh ){badBoxIndexes.push_back( i );}}/* 如果筛选出来的好窗口数量大于给定数量,那么选取重合度大的留下来 在不要求排序的前提下选取给定条件下前N大的数 */// !!!!这段代码可以用作欣赏!!!!if ( goodBoxIndexes.size( ) > closestNum ){nth_element(goodBoxIndexes.begin( ),goodBoxIndexes.begin( ) + d( ),/*struct OComparator{OComparator( const std::vector<BoundingBox>& _grid ):grid( _grid ){}std::vector<BoundingBox> grid;bool operator()( int idx1, int idx2 ){return grid[idx1].overlap > grid[idx2].overlap;}};*/OComparator( allScanningWindows ) );size( closestNum );}getGoodBoxHull( );
}
代码段的最后其中涉及到getGoodBoxHull()
void BoxOperator::getGoodBoxHull( )
{int x1=INT_MAX, x2=0;int y1=INT_MAX, y2=0;int idx;/***这段代码的目的是:保证 x1 和y1 在 int 可以表示的范围内保证 x2 和 y2 是大于0的数***/for ( int i=0; i < goodBoxIndexes.size( ); i++ ){idx= goodBoxIndexes[i];x1 = min( allScanningWindows[idx].x, x1 );y1 = min( allScanningWindows[idx].y, y1 );x2 = max( allScanningWindows[idx].x + allScanningWindows[idx].width, x2 );y2 = max( allScanningWindows[idx].y + allScanningWindows[idx].height, y2 );}goodBoxHull.x = x1;goodBoxHull.y = y1;goodBoxHull.width = x2 - x1;goodBoxHull.height = y2 - y1;
}
f = vector<float>( boxOperator.allScanningWindows.size( ) );
tmp.patt = vector<vector<int> >( boxOperator.allScanningWindows.size( ), vector<int>( 10, 0 ) );
其中tmp的TempStruct为
struct TempStruct {std::vector<std::vector<int> > patt;std::vector<float> conf;
};
正样本的pathsize为 15*15
ate( classifier.patchSize, classifier.patchSize, CV_64F );
/***********保存所有的ScaledSize*************/allScaledSizes.push_back( scaledSize );
这行语句代表所有符合要求的21种尺度下的方框
void FernNNClassifier::prepare( const vector<Size> &scales )
{acum = 0;/* 初始化测试特征的位置 */// 随机森林中树的总数为 10// 每棵树的特征总数为 13 ,每棵树的判断节点个数,树上每一个特征作为一个决策点 int totalFeatures = totFerns * featureNumPerFern;fernsFeatures = vector<vector<Feature> >( scales.size( ), vector<Feature>( totalFeatures ) );RNG& rng = theRNG( );float x1f, x2f, y1f, y2f;int x1, x2, y1, y2;for ( int i=0; i < totalFeatures; i++ ){x1f = (float)rng;y1f = (float)rng;x2f = (float)rng;y2f = (float)rng;/* 利用随机数,随机定位出两个像素点,并作为特征 */// 其实就是2bit BP特征 随机找两个点比较亮度// 这篇博客里有写 .htmlfor ( int s=0; s < scales.size( ); s++ ){x1 = x1f * scales[s].width;y1 = y1f * scales[s].height;x2 = x2f * scales[s].width;y2 = y2f * scales[s].height;fernsFeatures[s][i] = Feature( x1, y1, x2, y2 );}}fernNegativeTh = 0.5f * totFerns;/* 初始化后验概率 */for ( int i = 0; i < totFerns; i++ ){posteriors.push_back( vector<float>( powf( 2.0f, featureNumPerFern ), 0 ) );positiveCounter.push_back( vector<int>( pow( 2.0, featureNumPerFern ), 0 ) );negativeCounter.push_back( vector<int>( pow( 2.0, featureNumPerFern ), 0 ) );}
}
在这里继续查看Feature类的定义
struct Feature{uchar x1, y1, x2, y2;Feature( ) : x1( 0 ), y1( 0 ), x2( 0 ), y2( 0 ) {}Feature( int _x1, int _y1, int _x2, int _y2 ): x1( (uchar)_x1 ), y1( (uchar)_y1 ), x2( (uchar)_x2 ), y2( (uchar)_y2 ){}bool operator ()( const cv::Mat& patch ) const{return patch.at<uchar>( y1, x1 ) > patch.at<uchar>( y2, x2 );}};
关于RNG的用法可以参考博客 :
重点是:就是要写成 rng.uniform(0.f, 1.f); 而不能写成rng.uniform( 0 , 1),因为输入为int型参数,会调用uniform(int,int),只能产生0。请大家注意使用
疑点是:为什么初始化成2的13次幂:
/************************* 初始化后验概率********************************/
//一共totFerns bit的特征 所以可能的情形有 2^totFerns 个 即0 到 2^totFerns - 1个
//.html中的作图 坐标范围有误 但是原理正确for ( int i = 0; i < totFerns; i++ ){posteriors.push_back( vector<float>( powf( 2.0f, featureNumPerFern ), 0 ) );positiveCounter.push_back( vector<int>( pow( 2.0, featureNumPerFern ), 0 ) );negativeCounter.push_back( vector<int>( pow( 2.0, featureNumPerFern ), 0 ) );}
patchGenerator = PatchGenerator(0, 0, noiseInit, true, 1 - scaleInit, 1 + scaleInit,-angleInit*CV_PI / 180, angleInit*CV_PI / 180,-angleInit*CV_PI / 180, angleInit*CV_PI / 180 );
ate( ws + 1, ls + 1, CV_32F );
ate( ws + 1, ls + 1, CV_64F );
integral( frame, boxOperator.iisum, boxOperator.iisqsum );
meanStdDev( frame( boxOperator.bestWindow ), mean, stdev );
classifier.varClassifierTh = powf( stdev.val[0], 2 ) * 0.5f;
void FernNNClassifier::generatePositiveData( const Mat& frame, int numWarps, const BoxOperator &bop, PatchGenerator patchGenerator )
{Scalar mean, stdev;/** 将frame图像bestBox区域的图像片归一化为均值为0的15*15大小的patch,* 存于positiveNNSample(用于最近邻分类器的正样本)中(最近邻的box的Pattern),* 该正样本只有一个。*/NormalOperation::getPattern( frame( bop.bestWindow ), positiveNNSample, patchSize, mean, stdev );/************************* 做仿射变换,并提取Fern特征 *************************/Mat img, wraped;/* 利用高斯滤波平滑图像 */GaussianBlur( frame, img, Size( 9, 9 ), 1.5 );wraped = img( dBoxHull );RNG& rng = theRNG( );/* 获取好窗口边界框的中心点 */Point2f dBoxHull.x + ( dBoxHull.width - 1 ) * 0.dBoxHull.y + ( dBoxHull.height - 1 )* 0.5f );vector<int> fern( totFerns );positiveFernSamples.clear( );Mat patch;int idx;for ( int i=0; i < numWarps; i++ ){if ( i > 0 ){/* 对图像进行仿射变换 */patchGenerator( frame, hullCenter, wraped, dBoxHull.size( ), rng );}for ( int b = 0; b < dBoxIndexes.size( ); b++ ){idx = dBoxIndexes[b];patch = img( bop.allScanningWindows[idx] );/* 获得输入patch的13位二进制码特征 */getFernsFeatures(patch, bop.allScanningWindows[idx].scaleId,fern );/* 标记为正样本 */positiveFernSamples.push_back( make_pair( fern, 1 ) );}}}
仿射变换介绍:具体详细解释见
获取13bit特征详见博客:.html
void FernNNClassifier::generateNegativeData( const Mat &frame, BoxOperator &bop )
{random_shuffle( bop.badBoxIndexes.begin( ), d( ) );/************************************************************* 利用积分图,计算每个badBox对应patch的方差,然后选取那些方差大的patch *************************************************************/int idx;vector<int> fern( totFerns );Mat patch;/* 将方差大于varClassifierTh/2的提取特征,标记为负样本0,* 放入集合分类器负样本集合negativeFernSamples中*/for ( int j = 0; j < bop.badBoxIndexes.size( ); j++ ){idx = bop.badBoxIndexes[j];if ( Var( bop.allScanningWindows[idx], bop.iisum, bop.iisqsum ) < varClassifierTh * 0.5f ){continue;}patch = frame( bop.allScanningWindows[idx] );getFernsFeatures( patch, bop.allScanningWindows[idx].scaleId, fern );negativeFernSamples.push_back( make_pair( fern, 0 ) );}/* 取 bad_patches 个归一化以后作为NN分类器的负样本 */Scalar tmp1, tmp2;negativeNNSamples = vector<Mat>( badPatchs );for ( int i = 0; i < badPatchs; i++ ){idx = bop.badBoxIndexes[i];patch = frame( bop.allScanningWindows[idx] );NormalOperation::getPattern( patch, negativeNNSamples[i], patchSize, tmp1, tmp2 );}
}
本文发布于:2024-02-03 22:39:08,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170697129151478.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |