OpenCV中CascadeClassifier类实现多尺度检测源码解析

级联分类器检测类CascadeClassifier,在2.4.5版本中使用Adaboost的方法+LBP、HOG、HAAR进行目标检测,加载的是使用traincascade进行训练的分类器

class CV_EXPORTS_W CascadeClassifier

{

public:

CV_WRAP CascadeClassifier(); // 无参数构造函数,new自动调用该函数分配初试内存

CV_WRAP CascadeClassifier( const string& filename ); // 带参数构造函数,参数为XML的绝对名称

virtual ~CascadeClassifier(); // 析构函数,无需关心

CV_WRAP virtual bool empty() const; // 是否导入参数,只创建了该对象而没有加载或者加载失败时都是空的

CV_WRAP bool load( const string& filename ); // 加载分类器,参数为XML的绝对名称,函数内部调用read读取新格式的分类器,读取成功后直接返回,读取失败后调用cvLoad读取旧格式的分类器,读取成功返回true,否则返回false

virtual bool read( const FileNode& node );   // load内部调用read解析XML中的内容,也可以自己创建节点然后调用Read即可,但是该函数只能读取新格式的分类器,不能读取旧格式的分类器

// 多尺度检测函数

CV_WRAP virtual void detectMultiScale( const Mat& image,        // 图像,cvarrtoMat实现IplImage转换为Mat,必须为8位,内部可自行转换为灰度图像

CV_OUT vector<Rect>& objects,    // 输出矩形,注意vector不是线程安全的

double scaleFactor=1.1,          // 缩放比例,必须大于1

int minNeighbors=3,              // 合并窗口时最小neighbor,每个候选矩阵至少包含的附近元素个数

int flags=0,                     // 检测标记,只对旧格式的分类器有效,与cvHaarDetectObjects的参数flags相同,默认为0,可能的取值为CV_HAAR_DO_CANNY_PRUNING(CANNY边缘检测)、CV_HAAR_SCALE_IMAGE(缩放图像)、CV_HAAR_FIND_BIGGEST_OBJECT(寻找最大的目标)、CV_HAAR_DO_ROUGH_SEARCH(做粗略搜索);如果寻找最大的目标就不能缩放图像,也不能CANNY边缘检测

Size minSize=Size(),             // 最小检测目标

Size maxSize=Size() );           // 最大检测目标

// 最好不要在这里设置最大最小,可能会影响合并的效果,因此可以在检测完毕后自行判断结果是否满足要求

CV_WRAP virtual void detectMultiScale( const Mat& image,

CV_OUT vector<Rect>& objects,

vector<int>& rejectLevels,

vector<double>& levelWeights,

double scaleFactor=1.1,

int minNeighbors=3, int flags=0,

Size minSize=Size(),

Size maxSize=Size(),

bool outputRejectLevels=false );

// 上述参数多了rejectLevels和levelWeights以及outputRejectLevels参数,只有在outputRejectLevels为true的时候才可能输出前两个参数

// 还有就是在使用旧分类器的时候必须设置flags为CV_HAAR_SCALE_IMAGE,可以通过haarcascade_frontalface_alt.xml检测人脸尝试

bool isOldFormatCascade() const;        // 是否是旧格式的分类器

virtual Size getOriginalWindowSize() const;    // 初始检测窗口大小,也就是训练的窗口

int getFeatureType() const; // 获取特征类型

bool setImage( const Mat& );    // 设置图像,计算图像的积分图

virtual int runAt( Ptr<FeatureEvaluator>& feval, Point pt, double& weight ); // 计算某检测窗口是否为目标

// 保存强分类器数据

class Data

{

public:

struct CV_EXPORTS DTreeNode // 节点

{

int featureIdx; // 对应的特征编号

float threshold; // for ordered features only 节点阈值

int left; // 左子树

int right; // 右子树

};

struct CV_EXPORTS DTree // 弱分类器

{

int nodeCount; // 弱分类器中节点个数

};

struct CV_EXPORTS Stage // 强分类器

{

int first; // 在classifier中的起始位置

int ntrees; // 该强分类器中的弱分类器数

float threshold; // 强分类器阈值

};

bool read(const FileNode &node); // 读取强分类器

bool isStumpBased;    // 是否只有树桩

int stageType;      // BOOST,boostType:GAB、RAB等

int featureType;    // HAAR、HOG、LBP

int ncategories;    // maxCatCount,LBP为256,其余为0

Size origWinSize;

vector<Stage> stages;

vector<DTree> classifiers;

vector<DTreeNode> nodes;

vector<float> leaves;

vector<int> subsets;

};

Data data;

Ptr<FeatureEvaluator> featureEvaluator;

Ptr<CvHaarClassifierCascade> oldCascade;

// 关于mask这块参考《OpenCV目标检测之MaskGenerator》

public:

class CV_EXPORTS MaskGenerator

{

public:

virtual ~MaskGenerator() {}

virtual cv::Mat generateMask(const cv::Mat& src)=0;

virtual void initializeMask(const cv::Mat& /*src*/) {};

};

void setMaskGenerator(Ptr<MaskGenerator> maskGenerator);

Ptr<MaskGenerator> getMaskGenerator();

void setFaceDetectionMaskGenerator();

protected:

Ptr<MaskGenerator> maskGenerator;

}

注意:当在不同的分类器之间切换的时候,需要手动释放,因为read内部没有释放上一次读取的分类器数据!

关于新旧格式的分类器参考《OpenCV存储解读之Adaboost分类器》

使用CascadeClassifier检测目标的过程

1) load分类器并调用empty函数检测是否load成功

// 读取stages

bool CascadeClassifier::Data::read(const FileNode &root)

{

static const float THRESHOLD_EPS = 1e-5f;

// load stage params

string stageTypeStr = (string)root[CC_STAGE_TYPE];

if( stageTypeStr == CC_BOOST )

stageType = BOOST;

else

return false;

printf("stageType: %s\n", stageTypeStr.c_str());

string featureTypeStr = (string)root[CC_FEATURE_TYPE];

if( featureTypeStr == CC_HAAR )

featureType = FeatureEvaluator::HAAR;

else if( featureTypeStr == CC_LBP )

featureType = FeatureEvaluator::LBP;

else if( featureTypeStr == CC_HOG )

featureType = FeatureEvaluator::HOG;

else

return false;

printf("featureType: %s\n", featureTypeStr.c_str());

origWinSize.width = (int)root[CC_WIDTH];

origWinSize.height = (int)root[CC_HEIGHT];

CV_Assert( origWinSize.height > 0 && origWinSize.width > 0 );

isStumpBased = (int)(root[CC_STAGE_PARAMS][CC_MAX_DEPTH]) == 1 ? true : false;

printf("stumpBased: %d\n", isStumpBased);

// load feature params

FileNode fn = root[CC_FEATURE_PARAMS];

if( fn.empty() )

return false;

// LBP的maxCatCount=256,其余特征都等于0

ncategories = fn[CC_MAX_CAT_COUNT]; // ncategories=256/0

int subsetSize = (ncategories + 31)/32,// subsetSize=8/0 // 强制类型转换取整,不是四舍五入

nodeStep = 3 + ( ncategories>0 ? subsetSize : 1 ); //每组数值个数,nodeStep=11/4

printf("subsetSize: %d, nodeStep: %d\n", subsetSize, nodeStep);

// load stages

fn = root[CC_STAGES];

if( fn.empty() )

return false;

stages.reserve(fn.size());

classifiers.clear();

nodes.clear();

FileNodeIterator it = fn.begin(), it_end = fn.end();

for( int si = 0; it != it_end; si++, ++it )

{

FileNode fns = *it;

Stage stage;

stage.threshold = (float)fns[CC_STAGE_THRESHOLD] - THRESHOLD_EPS;

fns = fns[CC_WEAK_CLASSIFIERS];

if(fns.empty())

return false;

stage.ntrees = (int)fns.size();

stage.first = (int)classifiers.size();

printf("stage %d: ntrees: %d, first: %d\n", si, stage.ntrees, stage.first);

stages.push_back(stage);

classifiers.reserve(stages[si].first + stages[si].ntrees);

FileNodeIterator it1 = fns.begin(), it1_end = fns.end();

for( ; it1 != it1_end; ++it1 ) // weak trees

{

FileNode fnw = *it1;

FileNode internalNodes = fnw[CC_INTERNAL_NODES];

FileNode leafValues = fnw[CC_LEAF_VALUES];

if( internalNodes.empty() || leafValues.empty() )

return false;

// 弱分类器中的节点

DTree tree;

tree.nodeCount = (int)internalNodes.size()/nodeStep;

classifiers.push_back(tree);

nodes.reserve(nodes.size() + tree.nodeCount);

leaves.reserve(leaves.size() + leafValues.size());

if( subsetSize > 0 ) // 针对LBP

subsets.reserve(subsets.size() + tree.nodeCount*subsetSize);

FileNodeIterator internalNodesIter = internalNodes.begin(), internalNodesEnd = internalNodes.end();

// 保存每一个node

for( ; internalNodesIter != internalNodesEnd; ) // nodes

{

DTreeNode node;

node.left = (int)*internalNodesIter; ++internalNodesIter;

node.right = (int)*internalNodesIter; ++internalNodesIter;

node.featureIdx = (int)*internalNodesIter; ++internalNodesIter;

// 针对LBP,获取8个数值

if( subsetSize > 0 )

{

for( int j = 0; j < subsetSize; j++, ++internalNodesIter )

subsets.push_back((int)*internalNodesIter);

node.threshold = 0.f;

}

else

{

node.threshold = (float)*internalNodesIter; ++internalNodesIter;

}

nodes.push_back(node);

}

// 保存叶子节点

internalNodesIter = leafValues.begin(), internalNodesEnd = leafValues.end();

for( ; internalNodesIter != internalNodesEnd; ++internalNodesIter ) // leaves

leaves.push_back((float)*internalNodesIter);

}

}

return true;

}

// 读取stages与features

bool CascadeClassifier::read(const FileNode& root)

{

// load stages

if( !data.read(root) )

return false;

// load features,参考《图像特征->XXX特征之OpenCV-估计》

featureEvaluator = FeatureEvaluator::create(data.featureType);

FileNode fn = root[CC_FEATURES];

if( fn.empty() )

return false;

return featureEvaluator->read(fn);

}

// 外部调用的函数

bool CascadeClassifier::load(const string& filename)

{

oldCascade.release();

data = Data();

featureEvaluator.release();

// 读取新格式的分类器

FileStorage fs(filename, FileStorage::READ);

if( !fs.isOpened() )

return false;

if( read(fs.getFirstTopLevelNode()) )

return true;

fs.release();

// 读取新格式失败则读取旧格式的分类器

oldCascade = Ptr<CvHaarClassifierCascade>((CvHaarClassifierCascade*)cvLoad(filename.c_str(), 0, 0, 0));

return !oldCascade.empty();

}

2) 调用detectMultiScale函数进行多尺度检测,该函数可以使用老分类器进行检测也可以使用新分类器进行检测

2.1 如果load的为旧格式的分类器则使用cvHaarDetectObjectsForROC进行检测,flags参数只对旧格式的分类器有效,参考《OpenCV函数解读之cvHaarDetectObjects》

if( isOldFormatCascade() )

{

MemStorage storage(cvCreateMemStorage(0));

CvMat _image = image;

CvSeq* _objects = cvHaarDetectObjectsForROC( &_image, oldCascade, storage, rejectLevels, levelWeights, scaleFactor,

minNeighbors, flags, minObjectSize, maxObjectSize, outputRejectLevels );

vector<CvAvgComp> vecAvgComp;

Seq<CvAvgComp>(_objects).copyTo(vecAvgComp);

objects.resize(vecAvgComp.size());

std::transform(vecAvgComp.begin(), vecAvgComp.end(), objects.begin(), getRect());

return;

}

2.2 新格式分类器多尺度检测

for( double factor = 1; ; factor *= scaleFactor )

{

Size originalWindowSize = getOriginalWindowSize();

Size windowSize( cvRound(originalWindowSize.width*factor), cvRound(originalWindowSize.height*factor) );

Size scaledImageSize( cvRound( grayImage.cols/factor ), cvRound( grayImage.rows/factor ) );

Size processingRectSize( scaledImageSize.width-originalWindowSize.width + 1, scaledImageSize.height-originalWindowSize.height + 1 );

if( processingRectSize.width <= 0 || processingRectSize.height <= 0 )

break;

if( windowSize.width > maxObjectSize.width || windowSize.height > maxObjectSize.height )

break;

if( windowSize.width < minObjectSize.width || windowSize.height < minObjectSize.height )

continue;

// 缩放图像

Mat scaledImage( scaledImageSize, CV_8U, imageBuffer.data );

resize( grayImage, scaledImage, scaledImageSize, 0, 0, CV_INTER_LINEAR );

// 计算步长

int yStep;

if( getFeatureType() == cv::FeatureEvaluator::HOG )

{

yStep = 4;

}

else

{

yStep = factor > 2. ? 1 : 2;

}

// 并行个数以及大小,按照列进行并行处理

int stripCount, stripSize;

// 是否采用TBB进行优化

#ifdef HAVE_TBB

const int PTS_PER_THREAD = 1000;

stripCount = ((processingRectSize.width/yStep)*(processingRectSize.height + yStep-1)/yStep + PTS_PER_THREAD/2)/PTS_PER_THREAD;

stripCount = std::min(std::max(stripCount, 1), 100);

stripSize = (((processingRectSize.height + stripCount - 1)/stripCount + yStep-1)/yStep)*yStep;

#else

stripCount = 1;

stripSize = processingRectSize.height;

#endif

// 调用单尺度检测函数进行检测

if( !detectSingleScale( scaledImage, stripCount, processingRectSize, stripSize, yStep, factor, candidates,

rejectLevels, levelWeights, outputRejectLevels ) )

break;

}

2.3 合并检测结果

objects.resize(candidates.size());

std::copy(candidates.begin(), candidates.end(), objects.begin());

if( outputRejectLevels )

{

groupRectangles( objects, rejectLevels, levelWeights, minNeighbors, GROUP_EPS );

}

else

{

groupRectangles( objects, minNeighbors, GROUP_EPS );

}

单尺度检测函数流程

2.2.1 根据所载入的特征计算积分图、积分直方图等

// 计算当前图像的积分图,参考《图像特征->XXX特征之OpenCV-估计》

if( !featureEvaluator->setImage( image, data.origWinSize ) )

return false;

2.2.2 根据是否输出检测级数并行目标检测

vector<Rect> candidatesVector;

vector<int> rejectLevels;

vector<double> levelWeights;

Mutex mtx;

if( outputRejectLevels )

{

parallel_for_(Range(0, stripCount), CascadeClassifierInvoker( *this, processingRectSize, stripSize, yStep, factor,

candidatesVector, rejectLevels, levelWeights, true, currentMask, &mtx));

levels.insert( levels.end(), rejectLevels.begin(), rejectLevels.end() );

weights.insert( weights.end(), levelWeights.begin(), levelWeights.end() );

}

else

{

parallel_for_(Range(0, stripCount), CascadeClassifierInvoker( *this, processingRectSize, stripSize, yStep, factor,

candidatesVector, rejectLevels, levelWeights, false, currentMask, &mtx));

}

candidates.insert( candidates.end(), candidatesVector.begin(), candidatesVector.end() );

CascadeClassifierInvoker函数的operator()实现具体的检测过程

// 对于没有并行时range.start=0,range.end=1

void operator()(const Range& range) const

{

Ptr<FeatureEvaluator> evaluator = classifier->featureEvaluator->clone();

Size winSize(cvRound(classifier->data.origWinSize.width * scalingFactor),

cvRound(classifier->data.origWinSize.height * scalingFactor));

// strip=processingRectSize.height

int y1 = range.start * stripSize; // 0

int y2 = min(range.end * stripSize, processingRectSize.height); // processSizeRect.height也就是可以处理的高度,已经减去窗口高度

for( int y = y1; y < y2; y += yStep )

{

for( int x = 0; x < processingRectSize.width; x += yStep )

{

if ( (!mask.empty()) && (mask.at<uchar>(Point(x,y))==0)) {

continue;

}

// result=1表示通过了所有的分类器 <=0表示失败的级数

// gypWeight表示返回的阈值

double gypWeight;

int result = classifier->runAt(evaluator, Point(x, y), gypWeight);

// 输出LOG

#if defined (LOG_CASCADE_STATISTIC)

logger.setPoint(Point(x, y), result);

#endif

// 当返回级数的时候可以最后三个分类器不通过

if( rejectLevels )

{

if( result == 1 )

result = -(int)classifier->data.stages.size();

// 可以最后三个分类器不通过

if( classifier->data.stages.size() + result < 4 )

{

mtx->lock();

rectangles->push_back(Rect(cvRound(x*scalingFactor), cvRound(y*scalingFactor), winSize.width, winSize.height));

rejectLevels->push_back(-result);

levelWeights->push_back(gypWeight);

mtx->unlock();

}

}

// 不返回级数的时候通过所有的分类器才保存起来

else if( result > 0 )

{

mtx->lock();

rectangles->push_back(Rect(cvRound(x*scalingFactor), cvRound(y*scalingFactor),

winSize.width, winSize.height));

mtx->unlock();

}

// 如果一级都没有通过那么加大搜索步长

if( result == 0 )

x += yStep;

}

}

}

runAt函数实现某一检测窗口的检测

int CascadeClassifier::runAt( Ptr<FeatureEvaluator>& evaluator, Point pt, double& weight )

{

CV_Assert( oldCascade.empty() );

assert( data.featureType == FeatureEvaluator::HAAR ||

data.featureType == FeatureEvaluator::LBP ||

data.featureType == FeatureEvaluator::HOG );

// 设置某一点处的特征,参考《图像特征->XXX特征之OpenCV-估计》

if( !evaluator->setWindow(pt) )

return -1;

// 如果为树桩,没有树枝

if( data.isStumpBased )

{

if( data.featureType == FeatureEvaluator::HAAR )

return predictOrderedStump<HaarEvaluator>( *this, evaluator, weight );

else if( data.featureType == FeatureEvaluator::LBP )

return predictCategoricalStump<LBPEvaluator>( *this, evaluator, weight );

else if( data.featureType == FeatureEvaluator::HOG )

return predictOrderedStump<HOGEvaluator>( *this, evaluator, weight );

else

return -2;

}

// 每个弱分类器不止一个node

else

{

if( data.featureType == FeatureEvaluator::HAAR )

return predictOrdered<HaarEvaluator>( *this, evaluator, weight );

else if( data.featureType == FeatureEvaluator::LBP )

return predictCategorical<LBPEvaluator>( *this, evaluator, weight );

else if( data.featureType == FeatureEvaluator::HOG )

return predictOrdered<HOGEvaluator>( *this, evaluator, weight );

else

return -2;

}

}

predictOrdered*函数实现判断当前检测窗口的判断

// HAAR与HOG特征的多node检测

template<class FEval>

inline int predictOrdered( CascadeClassifier& cascade, Ptr<FeatureEvaluator> &_featureEvaluator, double& sum )

{

int nstages = (int)cascade.data.stages.size();

int nodeOfs = 0, leafOfs = 0;

FEval& featureEvaluator = (FEval&)*_featureEvaluator;

float* cascadeLeaves = &cascade.data.leaves[0];

CascadeClassifier::Data::DTreeNode* cascadeNodes = &cascade.data.nodes[0];

CascadeClassifier::Data::DTree* cascadeWeaks = &cascade.data.classifiers[0];

CascadeClassifier::Data::Stage* cascadeStages = &cascade.data.stages[0];

// 遍历每个强分类器

for( int si = 0; si < nstages; si++ )

{

CascadeClassifier::Data::Stage& stage = cascadeStages[si];

int wi, ntrees = stage.ntrees;

sum = 0;

// 遍历每个弱分类器

for( wi = 0; wi < ntrees; wi++ )

{

CascadeClassifier::Data::DTree& weak = cascadeWeaks[stage.first + wi];

int idx = 0, root = nodeOfs;

// 遍历每个节点

do

{

// 选择一个node:root和idx初始化为0,即第一个node

CascadeClassifier::Data::DTreeNode& node = cascadeNodes[root + idx];

// 计算当前node特征池编号下的特征值

double val = featureEvaluator(node.featureIdx);

// 如果val小于node阈值则选择左子树,否则选择右子树

idx = val < node.threshold ? node.left : node.right;

} while( idx > 0 );

// 累加最终的叶子节点

sum += cascadeLeaves[leafOfs - idx];

nodeOfs += weak.nodeCount;

leafOfs += weak.nodeCount + 1;

}

// 判断所有叶子节点累加和是否小于强分类器阈值,小于强分类器阈值则失败

if( sum < stage.threshold )

return -si;

}

// 通过了所有的强分类器返回1,否则返回失败的分类器

return 1;

}

// LBP特征的多node检测

template<class FEval>

inline int predictCategorical( CascadeClassifier& cascade, Ptr<FeatureEvaluator> &_featureEvaluator, double& sum )

{

int nstages = (int)cascade.data.stages.size();

int nodeOfs = 0, leafOfs = 0;

FEval& featureEvaluator = (FEval&)*_featureEvaluator;

size_t subsetSize = (cascade.data.ncategories + 31)/32;

int* cascadeSubsets = &cascade.data.subsets[0];

float* cascadeLeaves = &cascade.data.leaves[0];

CascadeClassifier::Data::DTreeNode* cascadeNodes = &cascade.data.nodes[0];

CascadeClassifier::Data::DTree* cascadeWeaks = &cascade.data.classifiers[0];

CascadeClassifier::Data::Stage* cascadeStages = &cascade.data.stages[0];

for(int si = 0; si < nstages; si++ )

{

CascadeClassifier::Data::Stage& stage = cascadeStages[si];

int wi, ntrees = stage.ntrees;

sum = 0;

for( wi = 0; wi < ntrees; wi++ )

{

CascadeClassifier::Data::DTree& weak = cascadeWeaks[stage.first + wi];

int idx = 0, root = nodeOfs;

do

{

CascadeClassifier::Data::DTreeNode& node = cascadeNodes[root + idx];

// c为0-255之间的数

int c = featureEvaluator(node.featureIdx);

// 获取当前node的subset头位置

const int* subset = &cascadeSubsets[(root + idx)*subsetSize]; // LBP:subsetSize=8

// 判断选择左子树还是右子树

idx = (subset[c>>5] & (1 << (c & 31))) ? node.left : node.right;

// c>>5表示将c右移5位,选择高3位,0-7之间

// c&31表示低5位,1<<(c&31)选择低5位后左移1位

// 将上面的数按位与,如果最后结果不为0表示选择左子树,否则选择右子树

}

while( idx > 0 );

sum += cascadeLeaves[leafOfs - idx];

nodeOfs += weak.nodeCount;

leafOfs += weak.nodeCount + 1;

}

if( sum < stage.threshold )

return -si;

}

return 1;

}

// HAAR与HOG特征的单node检测

template<class FEval>

inline int predictOrderedStump( CascadeClassifier& cascade, Ptr<FeatureEvaluator> &_featureEvaluator, double& sum )

{

int nodeOfs = 0, leafOfs = 0;

FEval& featureEvaluator = (FEval&)*_featureEvaluator;

float* cascadeLeaves = &cascade.data.leaves[0];

CascadeClassifier::Data::DTreeNode* cascadeNodes = &cascade.data.nodes[0];

CascadeClassifier::Data::Stage* cascadeStages = &cascade.data.stages[0];

int nstages = (int)cascade.data.stages.size();

// 遍历每个强分类器

for( int stageIdx = 0; stageIdx < nstages; stageIdx++ )

{

CascadeClassifier::Data::Stage& stage = cascadeStages[stageIdx];

sum = 0.0;

int ntrees = stage.ntrees;

// 遍历每个弱分类器

for( int i = 0; i < ntrees; i++, nodeOfs++, leafOfs+= 2 )

{

CascadeClassifier::Data::DTreeNode& node = cascadeNodes[nodeOfs];

double value = featureEvaluator(node.featureIdx);

sum += cascadeLeaves[ value < node.threshold ? leafOfs : leafOfs + 1 ];

}

if( sum < stage.threshold )

return -stageIdx;

}

return 1;

}

// LBP特征的单node检测

template<class FEval>

inline int predictCategoricalStump( CascadeClassifier& cascade, Ptr<FeatureEvaluator> &_featureEvaluator, double& sum )

{

int nstages = (int)cascade.data.stages.size();

int nodeOfs = 0, leafOfs = 0;

FEval& featureEvaluator = (FEval&)*_featureEvaluator;

size_t subsetSize = (cascade.data.ncategories + 31)/32;

int* cascadeSubsets = &cascade.data.subsets[0];

float* cascadeLeaves = &cascade.data.leaves[0];

CascadeClassifier::Data::DTreeNode* cascadeNodes = &cascade.data.nodes[0];

CascadeClassifier::Data::Stage* cascadeStages = &cascade.data.stages[0];

#ifdef HAVE_TEGRA_OPTIMIZATION

float tmp = 0; // float accumulator -- float operations are quicker

#endif

for( int si = 0; si < nstages; si++ )

{

CascadeClassifier::Data::Stage& stage = cascadeStages[si];

int wi, ntrees = stage.ntrees;

#ifdef HAVE_TEGRA_OPTIMIZATION

tmp = 0;

#else

sum = 0;

#endif

for( wi = 0; wi < ntrees; wi++ )

{

CascadeClassifier::Data::DTreeNode& node = cascadeNodes[nodeOfs];

int c = featureEvaluator(node.featureIdx);

const int* subset = &cascadeSubsets[nodeOfs*subsetSize];

#ifdef HAVE_TEGRA_OPTIMIZATION

tmp += cascadeLeaves[ subset[c>>5] & (1 << (c & 31)) ? leafOfs : leafOfs+1];

#else

sum += cascadeLeaves[ subset[c>>5] & (1 << (c & 31)) ? leafOfs : leafOfs+1];

#endif

nodeOfs++;

leafOfs += 2;

}

#ifdef HAVE_TEGRA_OPTIMIZATION

if( tmp < stage.threshold ) {

sum = (double)tmp;

return -si;

}

#else

if( sum < stage.threshold )

return -si;

#endif

}

#ifdef HAVE_TEGRA_OPTIMIZATION

sum = (double)tmp;

#endif

return 1;

}

}

时间: 2024-12-07 02:29:34

OpenCV中CascadeClassifier类实现多尺度检测源码解析的相关文章

死磕 Java 系列(一)&mdash;&mdash; 常用类(1) String 源码解析

写在前面 这是博主新开的一个 java 学习系列,听名字就可以看出来,在这一些系列中,我们学习的知识点不再是蜻蜓点水,而是深入底层,深入源码.由此,学习过程中我们要带着一股钻劲儿,对我们不懂的知识充满质疑,力求把我们学过的知识点都搞清楚,想明白. 一.引言 在 java 的世界里,存在一种特殊的类,它们的创建方式极为特别,不需要用到 new XXX(当然也可以用这种方式创建), 但是却大量出现在我们的代码中,那就是 String 类.作为日常中使用频率最高的类,它是那么普通,普通到我们从来都不会

String类的compareTo()方法的源码解析

private final char value[]; 字符串会自动转换为一个字符数组. public int compareTo(String anotherString) { //this -- s1 -- "hello" //anotherString -- s2 -- "hel" int len1 = value.length; //this.value.length--s1.toCharArray().length--5 int len2 = anothe

利用opencv中的级联分类器进行人脸检测-opencv学习(1)

OpenCV支持的目标检测的方法是利用样本的Haar特征进行的分类器训练,得到的级联boosted分类器(Cascade Classification).注意,新版本的C++接口除了Haar特征以外也可以使用LBP特征. 先介绍一下相关的结构,级联分类器的计算特征值的基础类FeatureEvaluator,功能包括读操作read.复制clone.获得特征类型getFeatureType,分配图片分配窗口的操作setImage.setWindow,计算有序特征calcOrd,计算绝对特征calcC

OpenCV中feature2D学习——FAST特征点检测与SIFT/SURF/BRIEF特征提取与匹配

在前面的文章<OpenCV中feature2D学习--FAST特征点检测>中讲了利用FAST算子进行特征点检测,这里尝试使用FAST算子来进行特征点检测,并结合SIFT/SURF/BRIEF算子进行特征点提取和匹配. I.结合SIFT算子进行特征点提取和匹配 由于数据类型的不同,SIFT和SURF算子只能采用FlannBasedMatcher或者BruteForceMatcher来进行匹配(参考OpenCV中feature2D学习--BFMatcher和FlannBasedMatcher).

OpenCv学习笔记(一)----OpenCv中Mat类源码的详细解读(2)

(一)像素存储的方法 1--本节我们讲解如何存储像素,存储像素值,需要指定: 1--颜色空间 2--数据类型 2--其中,颜色空间是指针对一个给定的颜色,如何组合颜色以其编码. 3--最简单的颜色空间是----灰度级空间----只需要处理:黑色和白色,对它们进行组合便可以产生不同程度的灰 色(256灰度级) 4--对于彩色方式---则有更多种类的颜色空间,但不论那种方式,都是把颜色分成:三个或者四个---基元素,通过 组合基元素,就可以产生所有的颜色 1--RGB颜色空间是最常用的一种颜色空间,

机器学习:weka中Evaluation类源码解析及输出AUC及交叉验证介绍

在机器学习分类结果的评估中,ROC曲线下的面积AOC是一个非常重要的指标.下面是调用weka类,输出AOC的源码: try { // 1.读入数据集 Instances data = new Instances( new BufferedReader( new FileReader("E:\\Develop/Weka-3-6/data/contact-lenses.arff"))); data.setClassIndex(data.numAttributes() - 1); // 2.

第三十六节,目标检测之yolo源码解析

在一个月前,我就已经介绍了yolo目标检测的原理,后来也把tensorflow实现代码仔细看了一遍.但是由于这个暑假事情比较大,就一直搁浅了下来,趁今天有时间,就把源码解析一下.关于yolo目标检测的原理请参考前面一篇文章:第三十五节,目标检测之YOLO算法详解 在讲解源码之前,我们需要做一些准备工作: 下载源码,本文所使用的yolo源码来源于网址:https://github.com/hizhangp/yolo_tensorflow 下载训练所使用的数据集,我们仍然使用以VOC 2012数据集

神经网络caffe框架源码解析--softmax_layer.cpp类代码研究

// Copyright 2013 Yangqing Jia // #include <algorithm> #include <vector> #include "caffe/layer.hpp" #include "caffe/vision_layers.hpp" #include "caffe/util/math_functions.hpp" using std::max; namespace caffe { /**

神经网络caffe框架源码解析--data_layer.cpp类代码研究

dataLayer作为整个网络的输入层, 数据从leveldb中取.leveldb的数据是通过图片转换过来的. 网络建立的时候, datalayer主要是负责设置一些参数,比如batchsize,channels,height,width等. 这次会通过读leveldb一个数据块来获取这些信息. 然后启动一个线程来预先从leveldb拉取一批数据,这些数据是图像数据和图像标签. 正向传播的时候, datalayer就把预先拉取好数据拷贝到指定的cpu或者gpu的内存. 然后启动新线程再预先拉取数