TLD 目标跟踪源码理解

2023-12-23 03:40
文章标签 源码 目标 理解 跟踪 tld

本文主要是介绍TLD 目标跟踪源码理解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

最近在做目标跟踪的相关程序的加速,整合借这个机会整理下TLD的源码理解,由于本人C++功底有限,难免会出现表述和理解错误,请大家批评指正
本帖子 长期更新 未完部分耐心等待

TLD源码解读

主函数部分

主要涉及两个函数:

  • RTLTrackerInit
tracker.RTLTrackerInit(last_gray, box);

其中 last gray 是frame第一帧经过灰度变换的图像,代码如下:

capture >> frame;
cvtColor(frame, last_gray, CV_RGB2GRAY);

Box 是我们标定的目标位置,TLD是单目标跟踪所以只能实现标记一个框

Rect box(248,198,27,14);
  • RTLTrackerTracking
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函数解读

TrackerInit类

void RtLtTracker::RTLTrackerInit( const Mat &frame, const Rect userBox )
{/* 获得所有的扫描窗口 */boxOperator.getAllScanWindowOfFrame( frame, userBox );/* 将这些窗口分类为好坏窗口,并得到好窗口的边界 */boxOperator.classifyScanningWindows( userBox, numClosestInit );/* 分配各种空间 *//*struct TempStruct {std::vector<std::vector<int> > patt;std::vector<float> conf;};*/tmp.conf = vector<float>( boxOperator.allScanningWindows.size( ) );tmp.patt = vector<vector<int> >( boxOperator.allScanningWindows.size( ), vector<int>( 10, 0 ) );classifier.positiveNNSample.create( 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 );boxOperator.iisum.create( frame.rows + 1, frame.cols + 1, CV_32F );boxOperator.iisqsum.create( frame.rows + 1, frame.cols + 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.5f;classifier.generatePositiveData(frame, classifier.numWrapsInit, boxOperator, patchGenerator );classifier.generateNegativeData(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 > frame.cols ||scaledHeight > frame.rows ){continue;}/************* 保留合适的scaledSize **********/scaledSize.width  = scaledWidth;scaledSize.height = scaledHeight;/***********保存所有的ScaledSize*************/allScaledSizes.push_back( scaledSize );/***************设置扫描步长******************/int step = (int)roundf( shiftWeight * scanningShift );for ( int y=1; y < frame.rows - scaledHeight; y += step ){for ( int x=1; x < frame.cols - 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( ) + closestNum,goodBoxIndexes.end( ),/*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 ) );goodBoxIndexes.resize( 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;
}
  • tmp结构分配空间
tmp.conf = 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

classifier.positiveNNSample.create( classifier.patchSize, classifier.patchSize, CV_64F );
  • classifier.prepare(boxOperator.allScaledSizes)
    关于随机厥的详细用法可以参考:http://www.cnblogs.com/nsnow/p/4670640.html
    输入:
    boxOperator.allScaledSizes是在getAllScanWindowOfFrame函数中体现的
/***********保存所有的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特征 随机找两个点比较亮度//  这篇博客里有写 http://www.cnblogs.com/nsnow/p/4670640.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的用法可以参考博客 :http://blog.csdn.net/yang_xian521/article/details/6931385
重点是:就是要写成 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个
//http://www.cnblogs.com/nsnow/p/4670640.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 = 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 );
  • integral
    这个部分是用于计算图像帧的积分图
    具体细节参考博客:http://blog.csdn.net/iracer/article/details/49029239
    其中参数的含义 和 积分图的意义 如下图所示
    这里写图片描述
boxOperator.iisum.create( frame.rows + 1, frame.cols + 1, CV_32F );
boxOperator.iisqsum.create( frame.rows + 1, frame.cols + 1, CV_64F );
integral( frame, boxOperator.iisum, boxOperator.iisqsum );
  • meanStdDev
meanStdDev( frame( boxOperator.bestWindow ), mean, stdev );

这里写图片描述

  • varClassifierTh
    方差分类器的方差阈值
classifier.varClassifierTh = powf( stdev.val[0], 2 ) * 0.5f;
  • generatePositiveData
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( bop.goodBoxHull );RNG& rng = theRNG( );/* 获取好窗口边界框的中心点 */Point2f hullCenter(bop.goodBoxHull.x + ( bop.goodBoxHull.width - 1 ) * 0.5f,bop.goodBoxHull.y + ( bop.goodBoxHull.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, bop.goodBoxHull.size( ), rng );}for ( int b = 0; b < bop.goodBoxIndexes.size( ); b++ ){idx   = bop.goodBoxIndexes[b];patch = img( bop.allScanningWindows[idx] );/* 获得输入patch的13位二进制码特征 */getFernsFeatures(patch, bop.allScanningWindows[idx].scaleId,fern );/* 标记为正样本 */positiveFernSamples.push_back( make_pair( fern, 1 ) );}}}

仿射变换介绍:具体详细解释见 http://blog.csdn.net/carson2005/article/details/7540936
这里写图片描述

获取13bit特征详见博客:http://www.cnblogs.com/nsnow/p/4670640.html

  • generateNegativeData
void FernNNClassifier::generateNegativeData( const Mat &frame, BoxOperator &bop )
{random_shuffle( bop.badBoxIndexes.begin( ), bop.badBoxIndexes.end( ) );/************************************************************\* 利用积分图,计算每个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 ( bop.getVar( 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 );}
}

这篇关于TLD 目标跟踪源码理解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/526583

相关文章

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

烟火目标检测数据集 7800张 烟火检测 带标注 voc yolo

一个包含7800张带标注图像的数据集,专门用于烟火目标检测,是一个非常有价值的资源,尤其对于那些致力于公共安全、事件管理和烟花表演监控等领域的人士而言。下面是对此数据集的一个详细介绍: 数据集名称:烟火目标检测数据集 数据集规模: 图片数量:7800张类别:主要包含烟火类目标,可能还包括其他相关类别,如烟火发射装置、背景等。格式:图像文件通常为JPEG或PNG格式;标注文件可能为X

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

如何通俗理解注意力机制?

1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分