Path: blob/master/modules/objdetect/test/test_cascadeandhog.cpp
16337 views
/*M///////////////////////////////////////////////////////////////////////////////////////1//2// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.3//4// By downloading, copying, installing or using the software you agree to this license.5// If you do not agree to this license, do not download, install,6// copy or use the software.7//8//9// Intel License Agreement10// For Open Source Computer Vision Library11//12// Copyright (C) 2000, Intel Corporation, all rights reserved.13// Third party copyrights are property of their respective owners.14//15// Redistribution and use in source and binary forms, with or without modification,16// are permitted provided that the following conditions are met:17//18// * Redistribution's of source code must retain the above copyright notice,19// this list of conditions and the following disclaimer.20//21// * Redistribution's in binary form must reproduce the above copyright notice,22// this list of conditions and the following disclaimer in the documentation23// and/or other materials provided with the distribution.24//25// * The name of Intel Corporation may not be used to endorse or promote products26// derived from this software without specific prior written permission.27//28// This software is provided by the copyright holders and contributors "as is" and29// any express or implied warranties, including, but not limited to, the implied30// warranties of merchantability and fitness for a particular purpose are disclaimed.31// In no event shall the Intel Corporation or contributors be liable for any direct,32// indirect, incidental, special, exemplary, or consequential damages33// (including, but not limited to, procurement of substitute goods or services;34// loss of use, data, or profits; or business interruption) however caused35// and on any theory of liability, whether in contract, strict liability,36// or tort (including negligence or otherwise) arising in any way out of37// the use of this software, even if advised of the possibility of such damage.38//39//M*/4041#include "test_precomp.hpp"4243namespace opencv_test { namespace {4445//#define GET_STAT4647#define DIST_E "distE"48#define S_E "sE"49#define NO_PAIR_E "noPairE"50//#define TOTAL_NO_PAIR_E "totalNoPairE"5152#define DETECTOR_NAMES "detector_names"53#define DETECTORS "detectors"54#define IMAGE_FILENAMES "image_filenames"55#define VALIDATION "validation"56#define FILENAME "fn"5758#define C_SCALE_CASCADE "scale_cascade"5960class CV_DetectorTest : public cvtest::BaseTest61{62public:63CV_DetectorTest();64protected:65virtual int prepareData( FileStorage& fs );66virtual void run( int startFrom );67virtual string& getValidationFilename();6869virtual void readDetector( const FileNode& fn ) = 0;70virtual void writeDetector( FileStorage& fs, int di ) = 0;71int runTestCase( int detectorIdx, vector<vector<Rect> >& objects );72virtual int detectMultiScale( int di, const Mat& img, vector<Rect>& objects ) = 0;73int validate( int detectorIdx, vector<vector<Rect> >& objects );7475struct76{77float dist;78float s;79float noPair;80//float totalNoPair;81} eps;82vector<string> detectorNames;83vector<string> detectorFilenames;84vector<string> imageFilenames;85vector<Mat> images;86string validationFilename;87string configFilename;88FileStorage validationFS;89bool write_results;90};9192CV_DetectorTest::CV_DetectorTest()93{94configFilename = "dummy";95write_results = false;96}9798string& CV_DetectorTest::getValidationFilename()99{100return validationFilename;101}102103int CV_DetectorTest::prepareData( FileStorage& _fs )104{105if( !_fs.isOpened() )106test_case_count = -1;107else108{109FileNode fn = _fs.getFirstTopLevelNode();110111fn[DIST_E] >> eps.dist;112fn[S_E] >> eps.s;113fn[NO_PAIR_E] >> eps.noPair;114// fn[TOTAL_NO_PAIR_E] >> eps.totalNoPair;115116// read detectors117if( fn[DETECTOR_NAMES].size() != 0 )118{119FileNodeIterator it = fn[DETECTOR_NAMES].begin();120for( ; it != fn[DETECTOR_NAMES].end(); )121{122String _name;123it >> _name;124detectorNames.push_back(_name);125readDetector(fn[DETECTORS][_name]);126}127}128test_case_count = (int)detectorNames.size();129130// read images filenames and images131string dataPath = ts->get_data_path();132if( fn[IMAGE_FILENAMES].size() != 0 )133{134for( FileNodeIterator it = fn[IMAGE_FILENAMES].begin(); it != fn[IMAGE_FILENAMES].end(); )135{136String filename;137it >> filename;138imageFilenames.push_back(filename);139Mat img = imread( dataPath+filename, 1 );140images.push_back( img );141}142}143}144return cvtest::TS::OK;145}146147void CV_DetectorTest::run( int )148{149string dataPath = ts->get_data_path();150string vs_filename = dataPath + getValidationFilename();151152write_results = !validationFS.open( vs_filename, FileStorage::READ );153154int code;155if( !write_results )156{157code = prepareData( validationFS );158}159else160{161FileStorage fs0(dataPath + configFilename, FileStorage::READ );162code = prepareData(fs0);163}164165if( code < 0 )166{167ts->set_failed_test_info( code );168return;169}170171if( write_results )172{173validationFS.release();174validationFS.open( vs_filename, FileStorage::WRITE );175validationFS << FileStorage::getDefaultObjectName(validationFilename) << "{";176177validationFS << DIST_E << eps.dist;178validationFS << S_E << eps.s;179validationFS << NO_PAIR_E << eps.noPair;180// validationFS << TOTAL_NO_PAIR_E << eps.totalNoPair;181182// write detector names183validationFS << DETECTOR_NAMES << "[";184vector<string>::const_iterator nit = detectorNames.begin();185for( ; nit != detectorNames.end(); ++nit )186{187validationFS << *nit;188}189validationFS << "]"; // DETECTOR_NAMES190191// write detectors192validationFS << DETECTORS << "{";193assert( detectorNames.size() == detectorFilenames.size() );194nit = detectorNames.begin();195for( int di = 0; nit != detectorNames.end(); ++nit, di++ )196{197validationFS << *nit << "{";198writeDetector( validationFS, di );199validationFS << "}";200}201validationFS << "}";202203// write image filenames204validationFS << IMAGE_FILENAMES << "[";205vector<string>::const_iterator it = imageFilenames.begin();206for( int ii = 0; it != imageFilenames.end(); ++it, ii++ )207{208//String buf = cv::format("img_%d", ii);209//cvWriteComment( validationFS.fs, buf, 0 );210validationFS << *it;211}212validationFS << "]"; // IMAGE_FILENAMES213214validationFS << VALIDATION << "{";215}216217int progress = 0;218for( int di = 0; di < test_case_count; di++ )219{220progress = update_progress( progress, di, test_case_count, 0 );221if( write_results )222validationFS << detectorNames[di] << "{";223vector<vector<Rect> > objects;224int temp_code = runTestCase( di, objects );225226if (!write_results && temp_code == cvtest::TS::OK)227temp_code = validate( di, objects );228229if (temp_code != cvtest::TS::OK)230code = temp_code;231232if( write_results )233validationFS << "}"; // detectorNames[di]234}235236if( write_results )237{238validationFS << "}"; // VALIDATION239validationFS << "}"; // getDefaultObjectName240}241242if ( test_case_count <= 0 || imageFilenames.size() <= 0 )243{244ts->printf( cvtest::TS::LOG, "validation file is not determined or not correct" );245code = cvtest::TS::FAIL_INVALID_TEST_DATA;246}247ts->set_failed_test_info( code );248}249250int CV_DetectorTest::runTestCase( int detectorIdx, vector<vector<Rect> >& objects )251{252string dataPath = ts->get_data_path(), detectorFilename;253if( !detectorFilenames[detectorIdx].empty() )254detectorFilename = dataPath + detectorFilenames[detectorIdx];255printf("detector %s\n", detectorFilename.c_str());256257for( int ii = 0; ii < (int)imageFilenames.size(); ++ii )258{259vector<Rect> imgObjects;260Mat image = images[ii];261if( image.empty() )262{263String msg = cv::format("image %d is empty", ii);264ts->printf( cvtest::TS::LOG, msg.c_str() );265return cvtest::TS::FAIL_INVALID_TEST_DATA;266}267int code = detectMultiScale( detectorIdx, image, imgObjects );268if( code != cvtest::TS::OK )269return code;270271objects.push_back( imgObjects );272273if( write_results )274{275String imageIdxStr = cv::format("img_%d", ii);276validationFS << imageIdxStr << "[:";277for( vector<Rect>::const_iterator it = imgObjects.begin();278it != imgObjects.end(); ++it )279{280validationFS << it->x << it->y << it->width << it->height;281}282validationFS << "]"; // imageIdxStr283}284}285return cvtest::TS::OK;286}287288289static bool isZero( uchar i ) {return i == 0;}290291int CV_DetectorTest::validate( int detectorIdx, vector<vector<Rect> >& objects )292{293assert( imageFilenames.size() == objects.size() );294int imageIdx = 0;295int totalNoPair = 0, totalValRectCount = 0;296297for( vector<vector<Rect> >::const_iterator it = objects.begin();298it != objects.end(); ++it, imageIdx++ ) // for image299{300Size imgSize = images[imageIdx].size();301float dist = min(imgSize.height, imgSize.width) * eps.dist;302float wDiff = imgSize.width * eps.s;303float hDiff = imgSize.height * eps.s;304305int noPair = 0;306307// read validation rectangles308String imageIdxStr = cv::format("img_%d", imageIdx);309FileNode node = validationFS.getFirstTopLevelNode()[VALIDATION][detectorNames[detectorIdx]][imageIdxStr];310vector<Rect> valRects;311if( node.size() != 0 )312{313for( FileNodeIterator it2 = node.begin(); it2 != node.end(); )314{315Rect r;316it2 >> r.x >> r.y >> r.width >> r.height;317valRects.push_back(r);318}319}320totalValRectCount += (int)valRects.size();321322// compare rectangles323vector<uchar> map(valRects.size(), 0);324for( vector<Rect>::const_iterator cr = it->begin();325cr != it->end(); ++cr )326{327// find nearest rectangle328Point2f cp1 = Point2f( cr->x + (float)cr->width/2.0f, cr->y + (float)cr->height/2.0f );329int minIdx = -1, vi = 0;330float minDist = (float)cv::norm( Point(imgSize.width, imgSize.height) );331for( vector<Rect>::const_iterator vr = valRects.begin();332vr != valRects.end(); ++vr, vi++ )333{334Point2f cp2 = Point2f( vr->x + (float)vr->width/2.0f, vr->y + (float)vr->height/2.0f );335float curDist = (float)cv::norm(cp1-cp2);336if( curDist < minDist )337{338minIdx = vi;339minDist = curDist;340}341}342if( minIdx == -1 )343{344noPair++;345}346else347{348Rect vr = valRects[minIdx];349if( map[minIdx] != 0 || (minDist > dist) || (abs(cr->width - vr.width) > wDiff) ||350(abs(cr->height - vr.height) > hDiff) )351noPair++;352else353map[minIdx] = 1;354}355}356noPair += (int)count_if( map.begin(), map.end(), isZero );357totalNoPair += noPair;358359EXPECT_LE(noPair, cvRound(valRects.size()*eps.noPair)+1)360<< "detector " << detectorNames[detectorIdx] << " has overrated count of rectangles without pair on "361<< imageFilenames[imageIdx] << " image";362363if (::testing::Test::HasFailure())364break;365}366367EXPECT_LE(totalNoPair, cvRound(totalValRectCount*eps./*total*/noPair)+1)368<< "detector " << detectorNames[detectorIdx] << " has overrated count of rectangles without pair on all images set";369370if (::testing::Test::HasFailure())371return cvtest::TS::FAIL_BAD_ACCURACY;372373return cvtest::TS::OK;374}375376//----------------------------------------------- CascadeDetectorTest -----------------------------------377class CV_CascadeDetectorTest : public CV_DetectorTest378{379public:380CV_CascadeDetectorTest();381protected:382virtual void readDetector( const FileNode& fn );383virtual void writeDetector( FileStorage& fs, int di );384virtual int detectMultiScale( int di, const Mat& img, vector<Rect>& objects );385virtual int detectMultiScale_C( const string& filename, int di, const Mat& img, vector<Rect>& objects );386vector<int> flags;387};388389CV_CascadeDetectorTest::CV_CascadeDetectorTest()390{391validationFilename = "cascadeandhog/cascade.xml";392configFilename = "cascadeandhog/_cascade.xml";393}394395void CV_CascadeDetectorTest::readDetector( const FileNode& fn )396{397String filename;398int flag;399fn[FILENAME] >> filename;400detectorFilenames.push_back(filename);401fn[C_SCALE_CASCADE] >> flag;402if( flag )403flags.push_back( 0 );404else405flags.push_back( CASCADE_SCALE_IMAGE );406}407408void CV_CascadeDetectorTest::writeDetector( FileStorage& fs, int di )409{410int sc = flags[di] & CASCADE_SCALE_IMAGE ? 0 : 1;411fs << FILENAME << detectorFilenames[di];412fs << C_SCALE_CASCADE << sc;413}414415416int CV_CascadeDetectorTest::detectMultiScale_C( const string& filename,417int di, const Mat& img,418vector<Rect>& objects )419{420Ptr<CvHaarClassifierCascade> c_cascade(cvLoadHaarClassifierCascade(filename.c_str(), cvSize(0,0)));421Ptr<CvMemStorage> storage(cvCreateMemStorage());422423if( !c_cascade )424{425ts->printf( cvtest::TS::LOG, "cascade %s can not be opened");426return cvtest::TS::FAIL_INVALID_TEST_DATA;427}428Mat grayImg;429cvtColor( img, grayImg, COLOR_BGR2GRAY );430equalizeHist( grayImg, grayImg );431432CvMat c_gray = cvMat(grayImg);433CvSeq* rs = cvHaarDetectObjects(&c_gray, c_cascade, storage, 1.1, 3, flags[di] );434435objects.clear();436for( int i = 0; i < rs->total; i++ )437{438Rect r = *(Rect*)cvGetSeqElem(rs, i);439objects.push_back(r);440}441442return cvtest::TS::OK;443}444445int CV_CascadeDetectorTest::detectMultiScale( int di, const Mat& img,446vector<Rect>& objects)447{448string dataPath = ts->get_data_path(), filename;449filename = dataPath + detectorFilenames[di];450const string pattern = "haarcascade_frontalface_default.xml";451452if( filename.size() >= pattern.size() &&453strcmp(filename.c_str() + (filename.size() - pattern.size()),454pattern.c_str()) == 0 )455return detectMultiScale_C(filename, di, img, objects);456457CascadeClassifier cascade( filename );458if( cascade.empty() )459{460ts->printf( cvtest::TS::LOG, "cascade %s can not be opened");461return cvtest::TS::FAIL_INVALID_TEST_DATA;462}463Mat grayImg;464cvtColor( img, grayImg, COLOR_BGR2GRAY );465equalizeHist( grayImg, grayImg );466cascade.detectMultiScale( grayImg, objects, 1.1, 3, flags[di] );467return cvtest::TS::OK;468}469470//----------------------------------------------- HOGDetectorTest -----------------------------------471class CV_HOGDetectorTest : public CV_DetectorTest472{473public:474CV_HOGDetectorTest();475protected:476virtual void readDetector( const FileNode& fn );477virtual void writeDetector( FileStorage& fs, int di );478virtual int detectMultiScale( int di, const Mat& img, vector<Rect>& objects );479};480481CV_HOGDetectorTest::CV_HOGDetectorTest()482{483validationFilename = "cascadeandhog/hog.xml";484}485486void CV_HOGDetectorTest::readDetector( const FileNode& fn )487{488String filename;489if( fn[FILENAME].size() != 0 )490fn[FILENAME] >> filename;491detectorFilenames.push_back( filename);492}493494void CV_HOGDetectorTest::writeDetector( FileStorage& fs, int di )495{496fs << FILENAME << detectorFilenames[di];497}498499int CV_HOGDetectorTest::detectMultiScale( int di, const Mat& img,500vector<Rect>& objects)501{502HOGDescriptor hog;503if( detectorFilenames[di].empty() )504hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector());505else506assert(0);507hog.detectMultiScale(img, objects);508return cvtest::TS::OK;509}510511//----------------------------------------------- HOGDetectorReadWriteTest -----------------------------------512TEST(Objdetect_HOGDetectorReadWrite, regression)513{514// Inspired by bug #2607515Mat img;516img = imread(cvtest::TS::ptr()->get_data_path() + "/cascadeandhog/images/karen-and-rob.png");517ASSERT_FALSE(img.empty());518519HOGDescriptor hog;520hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector());521522string tempfilename = cv::tempfile(".xml");523FileStorage fs(tempfilename, FileStorage::WRITE);524hog.write(fs, "myHOG");525526fs.open(tempfilename, FileStorage::READ);527remove(tempfilename.c_str());528529FileNode n = fs["opencv_storage"]["myHOG"];530531ASSERT_NO_THROW(hog.read(n));532}533534535536TEST(Objdetect_CascadeDetector, regression) { CV_CascadeDetectorTest test; test.safe_run(); }537TEST(Objdetect_HOGDetector, regression) { CV_HOGDetectorTest test; test.safe_run(); }538539540//----------------------------------------------- HOG SSE2 compatible test -----------------------------------541542class HOGDescriptorTester :543public cv::HOGDescriptor544{545HOGDescriptor* actual_hog;546cvtest::TS* ts;547mutable bool failed;548549public:550HOGDescriptorTester(HOGDescriptor& instance) :551cv::HOGDescriptor(instance), actual_hog(&instance),552ts(cvtest::TS::ptr()), failed(false)553{ }554555virtual void computeGradient(InputArray img, InputOutputArray grad, InputOutputArray qangle,556Size paddingTL, Size paddingBR) const;557558virtual void detect(InputArray img,559vector<Point>& hits, vector<double>& weights, double hitThreshold = 0.0,560Size winStride = Size(), Size padding = Size(),561const vector<Point>& locations = vector<Point>()) const;562563virtual void detect(InputArray img, vector<Point>& hits, double hitThreshold = 0.0,564Size winStride = Size(), Size padding = Size(),565const vector<Point>& locations = vector<Point>()) const;566567virtual void compute(InputArray img, vector<float>& descriptors,568Size winStride = Size(), Size padding = Size(),569const vector<Point>& locations = vector<Point>()) const;570571bool is_failed() const;572};573574struct HOGCacheTester575{576struct BlockData577{578BlockData() : histOfs(0), imgOffset() {}579int histOfs;580Point imgOffset;581};582583struct PixData584{585size_t gradOfs, qangleOfs;586int histOfs[4];587float histWeights[4];588float gradWeight;589};590591HOGCacheTester(const HOGDescriptorTester* descriptor,592const Mat& img, Size paddingTL, Size paddingBR,593bool useCache, Size cacheStride);594virtual ~HOGCacheTester() { }595virtual void init(const HOGDescriptorTester* descriptor,596const Mat& img, Size paddingTL, Size paddingBR,597bool useCache, Size cacheStride);598599Size windowsInImage(Size imageSize, Size winStride) const;600Rect getWindow(Size imageSize, Size winStride, int idx) const;601602const float* getBlock(Point pt, float* buf);603virtual void normalizeBlockHistogram(float* histogram) const;604605vector<PixData> pixData;606vector<BlockData> blockData;607608bool useCache;609vector<int> ymaxCached;610Size winSize, cacheStride;611Size nblocks, ncells;612int blockHistogramSize;613int count1, count2, count4;614Point imgoffset;615Mat_<float> blockCache;616Mat_<uchar> blockCacheFlags;617618Mat grad, qangle;619const HOGDescriptorTester* descriptor;620621private:622HOGCacheTester(); //= delete623};624625HOGCacheTester::HOGCacheTester(const HOGDescriptorTester* _descriptor,626const Mat& _img, Size _paddingTL, Size _paddingBR,627bool _useCache, Size _cacheStride)628{629init(_descriptor, _img, _paddingTL, _paddingBR, _useCache, _cacheStride);630}631632void HOGCacheTester::init(const HOGDescriptorTester* _descriptor,633const Mat& _img, Size _paddingTL, Size _paddingBR,634bool _useCache, Size _cacheStride)635{636descriptor = _descriptor;637cacheStride = _cacheStride;638useCache = _useCache;639640descriptor->computeGradient(_img, grad, qangle, _paddingTL, _paddingBR);641imgoffset = _paddingTL;642643winSize = descriptor->winSize;644Size blockSize = descriptor->blockSize;645Size blockStride = descriptor->blockStride;646Size cellSize = descriptor->cellSize;647int i, j, nbins = descriptor->nbins;648int rawBlockSize = blockSize.width*blockSize.height;649650nblocks = Size((winSize.width - blockSize.width)/blockStride.width + 1,651(winSize.height - blockSize.height)/blockStride.height + 1);652ncells = Size(blockSize.width/cellSize.width, blockSize.height/cellSize.height);653blockHistogramSize = ncells.width*ncells.height*nbins;654655if( useCache )656{657Size cacheSize((grad.cols - blockSize.width)/cacheStride.width+1,658(winSize.height/cacheStride.height)+1);659blockCache.create(cacheSize.height, cacheSize.width*blockHistogramSize);660blockCacheFlags.create(cacheSize);661size_t cacheRows = blockCache.rows;662ymaxCached.resize(cacheRows);663for(size_t ii = 0; ii < cacheRows; ii++ )664ymaxCached[ii] = -1;665}666667Mat_<float> weights(blockSize);668float sigma = (float)descriptor->getWinSigma();669float scale = 1.f/(sigma*sigma*2);670671for(i = 0; i < blockSize.height; i++)672for(j = 0; j < blockSize.width; j++)673{674float di = i - blockSize.height*0.5f;675float dj = j - blockSize.width*0.5f;676weights(i,j) = std::exp(-(di*di + dj*dj)*scale);677}678679blockData.resize(nblocks.width*nblocks.height);680pixData.resize(rawBlockSize*3);681682// Initialize 2 lookup tables, pixData & blockData.683// Here is why:684//685// The detection algorithm runs in 4 nested loops (at each pyramid layer):686// loop over the windows within the input image687// loop over the blocks within each window688// loop over the cells within each block689// loop over the pixels in each cell690//691// As each of the loops runs over a 2-dimensional array,692// we could get 8(!) nested loops in total, which is very-very slow.693//694// To speed the things up, we do the following:695// 1. loop over windows is unrolled in the HOGDescriptor::{compute|detect} methods;696// inside we compute the current search window using getWindow() method.697// Yes, it involves some overhead (function call + couple of divisions),698// but it's tiny in fact.699// 2. loop over the blocks is also unrolled. Inside we use pre-computed blockData[j]700// to set up gradient and histogram pointers.701// 3. loops over cells and pixels in each cell are merged702// (since there is no overlap between cells, each pixel in the block is processed once)703// and also unrolled. Inside we use PixData[k] to access the gradient values and704// update the histogram705//706count1 = count2 = count4 = 0;707for( j = 0; j < blockSize.width; j++ )708for( i = 0; i < blockSize.height; i++ )709{710PixData* data = 0;711float cellX = (j+0.5f)/cellSize.width - 0.5f;712float cellY = (i+0.5f)/cellSize.height - 0.5f;713int icellX0 = cvFloor(cellX);714int icellY0 = cvFloor(cellY);715int icellX1 = icellX0 + 1, icellY1 = icellY0 + 1;716cellX -= icellX0;717cellY -= icellY0;718719if( (unsigned)icellX0 < (unsigned)ncells.width &&720(unsigned)icellX1 < (unsigned)ncells.width )721{722if( (unsigned)icellY0 < (unsigned)ncells.height &&723(unsigned)icellY1 < (unsigned)ncells.height )724{725data = &pixData[rawBlockSize*2 + (count4++)];726data->histOfs[0] = (icellX0*ncells.height + icellY0)*nbins;727data->histWeights[0] = (1.f - cellX)*(1.f - cellY);728data->histOfs[1] = (icellX1*ncells.height + icellY0)*nbins;729data->histWeights[1] = cellX*(1.f - cellY);730data->histOfs[2] = (icellX0*ncells.height + icellY1)*nbins;731data->histWeights[2] = (1.f - cellX)*cellY;732data->histOfs[3] = (icellX1*ncells.height + icellY1)*nbins;733data->histWeights[3] = cellX*cellY;734}735else736{737data = &pixData[rawBlockSize + (count2++)];738if( (unsigned)icellY0 < (unsigned)ncells.height )739{740icellY1 = icellY0;741cellY = 1.f - cellY;742}743data->histOfs[0] = (icellX0*ncells.height + icellY1)*nbins;744data->histWeights[0] = (1.f - cellX)*cellY;745data->histOfs[1] = (icellX1*ncells.height + icellY1)*nbins;746data->histWeights[1] = cellX*cellY;747data->histOfs[2] = data->histOfs[3] = 0;748data->histWeights[2] = data->histWeights[3] = 0;749}750}751else752{753if( (unsigned)icellX0 < (unsigned)ncells.width )754{755icellX1 = icellX0;756cellX = 1.f - cellX;757}758759if( (unsigned)icellY0 < (unsigned)ncells.height &&760(unsigned)icellY1 < (unsigned)ncells.height )761{762data = &pixData[rawBlockSize + (count2++)];763data->histOfs[0] = (icellX1*ncells.height + icellY0)*nbins;764data->histWeights[0] = cellX*(1.f - cellY);765data->histOfs[1] = (icellX1*ncells.height + icellY1)*nbins;766data->histWeights[1] = cellX*cellY;767data->histOfs[2] = data->histOfs[3] = 0;768data->histWeights[2] = data->histWeights[3] = 0;769}770else771{772data = &pixData[count1++];773if( (unsigned)icellY0 < (unsigned)ncells.height )774{775icellY1 = icellY0;776cellY = 1.f - cellY;777}778data->histOfs[0] = (icellX1*ncells.height + icellY1)*nbins;779data->histWeights[0] = cellX*cellY;780data->histOfs[1] = data->histOfs[2] = data->histOfs[3] = 0;781data->histWeights[1] = data->histWeights[2] = data->histWeights[3] = 0;782}783}784data->gradOfs = (grad.cols*i + j)*2;785data->qangleOfs = (qangle.cols*i + j)*2;786data->gradWeight = weights(i,j);787}788789assert( count1 + count2 + count4 == rawBlockSize );790// defragment pixData791for( j = 0; j < count2; j++ )792pixData[j + count1] = pixData[j + rawBlockSize];793for( j = 0; j < count4; j++ )794pixData[j + count1 + count2] = pixData[j + rawBlockSize*2];795count2 += count1;796count4 += count2;797798// initialize blockData799for( j = 0; j < nblocks.width; j++ )800for( i = 0; i < nblocks.height; i++ )801{802BlockData& data = blockData[j*nblocks.height + i];803data.histOfs = (j*nblocks.height + i)*blockHistogramSize;804data.imgOffset = Point(j*blockStride.width,i*blockStride.height);805}806}807808const float* HOGCacheTester::getBlock(Point pt, float* buf)809{810float* blockHist = buf;811assert(descriptor != 0);812813Size blockSize = descriptor->blockSize;814pt += imgoffset;815816CV_Assert( (unsigned)pt.x <= (unsigned)(grad.cols - blockSize.width) &&817(unsigned)pt.y <= (unsigned)(grad.rows - blockSize.height) );818819if( useCache )820{821CV_Assert( pt.x % cacheStride.width == 0 &&822pt.y % cacheStride.height == 0 );823Point cacheIdx(pt.x/cacheStride.width,824(pt.y/cacheStride.height) % blockCache.rows);825if( pt.y != ymaxCached[cacheIdx.y] )826{827Mat_<uchar> cacheRow = blockCacheFlags.row(cacheIdx.y);828cacheRow = (uchar)0;829ymaxCached[cacheIdx.y] = pt.y;830}831832blockHist = &blockCache[cacheIdx.y][cacheIdx.x*blockHistogramSize];833uchar& computedFlag = blockCacheFlags(cacheIdx.y, cacheIdx.x);834if( computedFlag != 0 )835return blockHist;836computedFlag = (uchar)1; // set it at once, before actual computing837}838839int k, C1 = count1, C2 = count2, C4 = count4;840const float* gradPtr = grad.ptr<float>(pt.y) + pt.x*2;841const uchar* qanglePtr = qangle.ptr(pt.y) + pt.x*2;842843CV_Assert( blockHist != 0 );844for( k = 0; k < blockHistogramSize; k++ )845blockHist[k] = 0.f;846847const PixData* _pixData = &pixData[0];848849for( k = 0; k < C1; k++ )850{851const PixData& pk = _pixData[k];852const float* a = gradPtr + pk.gradOfs;853float w = pk.gradWeight*pk.histWeights[0];854const uchar* h = qanglePtr + pk.qangleOfs;855int h0 = h[0], h1 = h[1];856float* hist = blockHist + pk.histOfs[0];857float t0 = hist[h0] + a[0]*w;858float t1 = hist[h1] + a[1]*w;859hist[h0] = t0; hist[h1] = t1;860}861862for( ; k < C2; k++ )863{864const PixData& pk = _pixData[k];865const float* a = gradPtr + pk.gradOfs;866float w, t0, t1, a0 = a[0], a1 = a[1];867const uchar* h = qanglePtr + pk.qangleOfs;868int h0 = h[0], h1 = h[1];869870float* hist = blockHist + pk.histOfs[0];871w = pk.gradWeight*pk.histWeights[0];872t0 = hist[h0] + a0*w;873t1 = hist[h1] + a1*w;874hist[h0] = t0; hist[h1] = t1;875876hist = blockHist + pk.histOfs[1];877w = pk.gradWeight*pk.histWeights[1];878t0 = hist[h0] + a0*w;879t1 = hist[h1] + a1*w;880hist[h0] = t0; hist[h1] = t1;881}882883for( ; k < C4; k++ )884{885const PixData& pk = _pixData[k];886const float* a = gradPtr + pk.gradOfs;887float w, t0, t1, a0 = a[0], a1 = a[1];888const uchar* h = qanglePtr + pk.qangleOfs;889int h0 = h[0], h1 = h[1];890891float* hist = blockHist + pk.histOfs[0];892w = pk.gradWeight*pk.histWeights[0];893t0 = hist[h0] + a0*w;894t1 = hist[h1] + a1*w;895hist[h0] = t0; hist[h1] = t1;896897hist = blockHist + pk.histOfs[1];898w = pk.gradWeight*pk.histWeights[1];899t0 = hist[h0] + a0*w;900t1 = hist[h1] + a1*w;901hist[h0] = t0; hist[h1] = t1;902903hist = blockHist + pk.histOfs[2];904w = pk.gradWeight*pk.histWeights[2];905t0 = hist[h0] + a0*w;906t1 = hist[h1] + a1*w;907hist[h0] = t0; hist[h1] = t1;908909hist = blockHist + pk.histOfs[3];910w = pk.gradWeight*pk.histWeights[3];911t0 = hist[h0] + a0*w;912t1 = hist[h1] + a1*w;913hist[h0] = t0; hist[h1] = t1;914}915916normalizeBlockHistogram(blockHist);917918return blockHist;919}920921void HOGCacheTester::normalizeBlockHistogram(float* _hist) const922{923float* hist = &_hist[0], partSum[4] = { 0.0f, 0.0f, 0.0f, 0.0f };924size_t i, sz = blockHistogramSize;925926for (i = 0; i <= sz - 4; i += 4)927{928partSum[0] += hist[i] * hist[i];929partSum[1] += hist[i+1] * hist[i+1];930partSum[2] += hist[i+2] * hist[i+2];931partSum[3] += hist[i+3] * hist[i+3];932}933float t0 = partSum[0] + partSum[1];934float t1 = partSum[2] + partSum[3];935float sum = t0 + t1;936for( ; i < sz; i++ )937sum += hist[i]*hist[i];938939float scale = 1.f/(std::sqrt(sum)+sz*0.1f), thresh = (float)descriptor->L2HysThreshold;940partSum[0] = partSum[1] = partSum[2] = partSum[3] = 0.0f;941for(i = 0; i <= sz - 4; i += 4)942{943hist[i] = std::min(hist[i]*scale, thresh);944hist[i+1] = std::min(hist[i+1]*scale, thresh);945hist[i+2] = std::min(hist[i+2]*scale, thresh);946hist[i+3] = std::min(hist[i+3]*scale, thresh);947partSum[0] += hist[i]*hist[i];948partSum[1] += hist[i+1]*hist[i+1];949partSum[2] += hist[i+2]*hist[i+2];950partSum[3] += hist[i+3]*hist[i+3];951}952t0 = partSum[0] + partSum[1];953t1 = partSum[2] + partSum[3];954sum = t0 + t1;955for( ; i < sz; i++ )956{957hist[i] = std::min(hist[i]*scale, thresh);958sum += hist[i]*hist[i];959}960961scale = 1.f/(std::sqrt(sum)+1e-3f);962for( i = 0; i < sz; i++ )963hist[i] *= scale;964}965966Size HOGCacheTester::windowsInImage(Size imageSize, Size winStride) const967{968return Size((imageSize.width - winSize.width)/winStride.width + 1,969(imageSize.height - winSize.height)/winStride.height + 1);970}971972Rect HOGCacheTester::getWindow(Size imageSize, Size winStride, int idx) const973{974int nwindowsX = (imageSize.width - winSize.width)/winStride.width + 1;975int y = idx / nwindowsX;976int x = idx - nwindowsX*y;977return Rect( x*winStride.width, y*winStride.height, winSize.width, winSize.height );978}979980inline bool HOGDescriptorTester::is_failed() const981{982return failed;983}984985static inline int gcd(int a, int b) { return (a % b == 0) ? b : gcd (b, a % b); }986987void HOGDescriptorTester::detect(InputArray _img,988vector<Point>& hits, vector<double>& weights, double hitThreshold,989Size winStride, Size padding, const vector<Point>& locations) const990{991if (failed)992return;993994hits.clear();995if( svmDetector.empty() )996return;997998Mat img = _img.getMat();999if( winStride == Size() )1000winStride = cellSize;1001Size cacheStride(gcd(winStride.width, blockStride.width),1002gcd(winStride.height, blockStride.height));1003size_t nwindows = locations.size();1004padding.width = (int)alignSize(std::max(padding.width, 0), cacheStride.width);1005padding.height = (int)alignSize(std::max(padding.height, 0), cacheStride.height);1006Size paddedImgSize(img.cols + padding.width*2, img.rows + padding.height*2);10071008HOGCacheTester cache(this, img, padding, padding, nwindows == 0, cacheStride);10091010if( !nwindows )1011nwindows = cache.windowsInImage(paddedImgSize, winStride).area();10121013const HOGCacheTester::BlockData* blockData = &cache.blockData[0];10141015int nblocks = cache.nblocks.area();1016int blockHistogramSize = cache.blockHistogramSize;1017size_t dsize = getDescriptorSize();10181019double rho = svmDetector.size() > dsize ? svmDetector[dsize] : 0;1020vector<float> blockHist(blockHistogramSize);10211022for( size_t i = 0; i < nwindows; i++ )1023{1024Point pt0;1025if( !locations.empty() )1026{1027pt0 = locations[i];1028if( pt0.x < -padding.width || pt0.x > img.cols + padding.width - winSize.width ||1029pt0.y < -padding.height || pt0.y > img.rows + padding.height - winSize.height )1030continue;1031}1032else1033{1034pt0 = cache.getWindow(paddedImgSize, winStride, (int)i).tl() - Point(padding);1035CV_Assert(pt0.x % cacheStride.width == 0 && pt0.y % cacheStride.height == 0);1036}1037double s = rho;1038const float* svmVec = &svmDetector[0];1039int j, k;1040for( j = 0; j < nblocks; j++, svmVec += blockHistogramSize )1041{1042const HOGCacheTester::BlockData& bj = blockData[j];1043Point pt = pt0 + bj.imgOffset;10441045const float* vec = cache.getBlock(pt, &blockHist[0]);1046for( k = 0; k <= blockHistogramSize - 4; k += 4 )1047s += vec[k]*svmVec[k] + vec[k+1]*svmVec[k+1] +1048vec[k+2]*svmVec[k+2] + vec[k+3]*svmVec[k+3];1049for( ; k < blockHistogramSize; k++ )1050s += vec[k]*svmVec[k];1051}1052if( s >= hitThreshold )1053{1054hits.push_back(pt0);1055weights.push_back(s);1056}1057}10581059// validation1060std::vector<Point> actual_find_locations;1061std::vector<double> actual_weights;1062actual_hog->detect(img, actual_find_locations, actual_weights,1063hitThreshold, winStride, padding, locations);10641065if (!std::equal(hits.begin(), hits.end(),1066actual_find_locations.begin()))1067{1068ts->printf(cvtest::TS::SUMMARY, "Found locations are not equal (see detect function)\n");1069ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);1070ts->set_gtest_status();1071failed = true;1072return;1073}10741075const double eps = FLT_EPSILON * 100;1076double diff_norm = cvtest::norm(actual_weights, weights, NORM_L2 + NORM_RELATIVE);1077if (diff_norm > eps)1078{1079ts->printf(cvtest::TS::SUMMARY, "Weights for found locations aren't equal.\n"1080"Norm of the difference is %lf\n", diff_norm);1081ts->printf(cvtest::TS::LOG, "Channels: %d\n", img.channels());1082failed = true;1083ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);1084ts->set_gtest_status();1085}1086}10871088void HOGDescriptorTester::detect(InputArray img, vector<Point>& hits, double hitThreshold,1089Size winStride, Size padding, const vector<Point>& locations) const1090{1091vector<double> weightsV;1092detect(img, hits, weightsV, hitThreshold, winStride, padding, locations);1093}10941095void HOGDescriptorTester::compute(InputArray _img, vector<float>& descriptors,1096Size winStride, Size padding, const vector<Point>& locations) const1097{1098Mat img = _img.getMat();10991100if( winStride == Size() )1101winStride = cellSize;1102Size cacheStride(gcd(winStride.width, blockStride.width),1103gcd(winStride.height, blockStride.height));1104size_t nwindows = locations.size();1105padding.width = (int)alignSize(std::max(padding.width, 0), cacheStride.width);1106padding.height = (int)alignSize(std::max(padding.height, 0), cacheStride.height);1107Size paddedImgSize(img.cols + padding.width*2, img.rows + padding.height*2);11081109HOGCacheTester cache(this, img, padding, padding, nwindows == 0, cacheStride);11101111if( !nwindows )1112nwindows = cache.windowsInImage(paddedImgSize, winStride).area();11131114const HOGCacheTester::BlockData* blockData = &cache.blockData[0];11151116int nblocks = cache.nblocks.area();1117int blockHistogramSize = cache.blockHistogramSize;1118size_t dsize = getDescriptorSize();1119descriptors.resize(dsize*nwindows);11201121for( size_t i = 0; i < nwindows; i++ )1122{1123float* descriptor = &descriptors[i*dsize];11241125Point pt0;1126if( !locations.empty() )1127{1128pt0 = locations[i];1129if( pt0.x < -padding.width || pt0.x > img.cols + padding.width - winSize.width ||1130pt0.y < -padding.height || pt0.y > img.rows + padding.height - winSize.height )1131continue;1132}1133else1134{1135pt0 = cache.getWindow(paddedImgSize, winStride, (int)i).tl() - Point(padding);1136CV_Assert(pt0.x % cacheStride.width == 0 && pt0.y % cacheStride.height == 0);1137}11381139for( int j = 0; j < nblocks; j++ )1140{1141const HOGCacheTester::BlockData& bj = blockData[j];1142Point pt = pt0 + bj.imgOffset;11431144float* dst = descriptor + bj.histOfs;1145const float* src = cache.getBlock(pt, dst);1146if( src != dst )1147for( int k = 0; k < blockHistogramSize; k++ )1148dst[k] = src[k];1149}1150}11511152// validation1153std::vector<float> actual_descriptors;1154actual_hog->compute(img, actual_descriptors, winStride, padding, locations);11551156double diff_norm = cvtest::norm(actual_descriptors, descriptors, NORM_L2 + NORM_RELATIVE);1157const double eps = FLT_EPSILON * 100;1158if (diff_norm > eps)1159{1160ts->printf(cvtest::TS::SUMMARY, "Norm of the difference: %lf\n", diff_norm);1161ts->printf(cvtest::TS::SUMMARY, "Found descriptors are not equal (see compute function)\n");1162ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);1163ts->printf(cvtest::TS::LOG, "Channels: %d\n", img.channels());1164ts->set_gtest_status();1165failed = true;1166}1167}11681169void HOGDescriptorTester::computeGradient(InputArray _img, InputOutputArray _grad, InputOutputArray _qangle,1170Size paddingTL, Size paddingBR) const1171{1172Mat img = _img.getMat();1173CV_Assert( img.type() == CV_8U || img.type() == CV_8UC3 );11741175Size gradsize(img.cols + paddingTL.width + paddingBR.width,1176img.rows + paddingTL.height + paddingBR.height);1177_grad.create(gradsize, CV_32FC2); // <magnitude*(1-alpha), magnitude*alpha>1178_qangle.create(gradsize, CV_8UC2); // [0..nbins-1] - quantized gradient orientation1179Mat grad = _grad.getMat();1180Mat qangle = _qangle.getMat();11811182Size wholeSize;1183Point roiofs;1184img.locateROI(wholeSize, roiofs);11851186int i, x, y;1187int cn = img.channels();11881189Mat_<float> _lut(1, 256);1190const float* lut = &_lut(0,0);11911192if( gammaCorrection )1193for( i = 0; i < 256; i++ )1194_lut(0,i) = std::sqrt((float)i);1195else1196for( i = 0; i < 256; i++ )1197_lut(0,i) = (float)i;11981199AutoBuffer<int> mapbuf(gradsize.width + gradsize.height + 4);1200int* xmap = mapbuf.data() + 1;1201int* ymap = xmap + gradsize.width + 2;12021203const int borderType = (int)BORDER_REFLECT_101;12041205for( x = -1; x < gradsize.width + 1; x++ )1206xmap[x] = borderInterpolate(x - paddingTL.width + roiofs.x,1207wholeSize.width, borderType) - roiofs.x;1208for( y = -1; y < gradsize.height + 1; y++ )1209ymap[y] = borderInterpolate(y - paddingTL.height + roiofs.y,1210wholeSize.height, borderType) - roiofs.y;12111212// x- & y- derivatives for the whole row1213int width = gradsize.width;1214AutoBuffer<float> _dbuf(width*4);1215float* dbuf = _dbuf.data();1216Mat Dx(1, width, CV_32F, dbuf);1217Mat Dy(1, width, CV_32F, dbuf + width);1218Mat Mag(1, width, CV_32F, dbuf + width*2);1219Mat Angle(1, width, CV_32F, dbuf + width*3);12201221int _nbins = nbins;1222float angleScale = (float)(_nbins/CV_PI);1223for( y = 0; y < gradsize.height; y++ )1224{1225const uchar* imgPtr = img.ptr(ymap[y]);1226const uchar* prevPtr = img.ptr(ymap[y-1]);1227const uchar* nextPtr = img.ptr(ymap[y+1]);1228float* gradPtr = (float*)grad.ptr(y);1229uchar* qanglePtr = (uchar*)qangle.ptr(y);12301231if( cn == 1 )1232{1233for( x = 0; x < width; x++ )1234{1235int x1 = xmap[x];1236dbuf[x] = (float)(lut[imgPtr[xmap[x+1]]] - lut[imgPtr[xmap[x-1]]]);1237dbuf[width + x] = (float)(lut[nextPtr[x1]] - lut[prevPtr[x1]]);1238}1239}1240else1241{1242for( x = 0; x < width; x++ )1243{1244int x1 = xmap[x]*3;1245float dx0, dy0, dx, dy, mag0, mag;1246const uchar* p2 = imgPtr + xmap[x+1]*3;1247const uchar* p0 = imgPtr + xmap[x-1]*3;12481249dx0 = lut[p2[2]] - lut[p0[2]];1250dy0 = lut[nextPtr[x1+2]] - lut[prevPtr[x1+2]];1251mag0 = dx0*dx0 + dy0*dy0;12521253dx = lut[p2[1]] - lut[p0[1]];1254dy = lut[nextPtr[x1+1]] - lut[prevPtr[x1+1]];1255mag = dx*dx + dy*dy;12561257if( mag0 < mag )1258{1259dx0 = dx;1260dy0 = dy;1261mag0 = mag;1262}12631264dx = lut[p2[0]] - lut[p0[0]];1265dy = lut[nextPtr[x1]] - lut[prevPtr[x1]];1266mag = dx*dx + dy*dy;12671268if( mag0 < mag )1269{1270dx0 = dx;1271dy0 = dy;1272mag0 = mag;1273}12741275dbuf[x] = dx0;1276dbuf[x+width] = dy0;1277}1278}12791280cartToPolar( Dx, Dy, Mag, Angle, false );1281for( x = 0; x < width; x++ )1282{1283float mag = dbuf[x+width*2], angle = dbuf[x+width*3]*angleScale - 0.5f;1284int hidx = cvFloor(angle);1285angle -= hidx;1286gradPtr[x*2] = mag*(1.f - angle);1287gradPtr[x*2+1] = mag*angle;1288if( hidx < 0 )1289hidx += _nbins;1290else if( hidx >= _nbins )1291hidx -= _nbins;1292assert( (unsigned)hidx < (unsigned)_nbins );12931294qanglePtr[x*2] = (uchar)hidx;1295hidx++;1296hidx &= hidx < _nbins ? -1 : 0;1297qanglePtr[x*2+1] = (uchar)hidx;1298}1299}13001301// validation1302Mat actual_mats[2], reference_mats[2] = { grad, qangle };1303const char* args[] = { "Gradient's", "Qangles's" };1304actual_hog->computeGradient(img, actual_mats[0], actual_mats[1], paddingTL, paddingBR);13051306const double eps = FLT_EPSILON * 100;1307for (i = 0; i < 2; ++i)1308{1309double diff_norm = cvtest::norm(actual_mats[i], reference_mats[i], NORM_L2 + NORM_RELATIVE);1310if (diff_norm > eps)1311{1312ts->printf(cvtest::TS::LOG, "%s matrices are not equal\n"1313"Norm of the difference is %lf\n", args[i], diff_norm);1314ts->printf(cvtest::TS::LOG, "Channels: %d\n", img.channels());1315ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);1316ts->set_gtest_status();1317failed = true;1318}1319}1320}13211322TEST(Objdetect_HOGDetector_Strict, accuracy)1323{1324cvtest::TS* ts = cvtest::TS::ptr();1325RNG& rng = ts->get_rng();13261327HOGDescriptor actual_hog;1328actual_hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector());1329HOGDescriptorTester reference_hog(actual_hog);13301331const unsigned int test_case_count = 5;1332for (unsigned int i = 0; i < test_case_count && !reference_hog.is_failed(); ++i)1333{1334// creating a matrix1335Size ssize(rng.uniform(1, 10) * actual_hog.winSize.width,1336rng.uniform(1, 10) * actual_hog.winSize.height);1337int type = rng.uniform(0, 1) > 0 ? CV_8UC1 : CV_8UC3;1338Mat image(ssize, type);1339rng.fill(image, RNG::UNIFORM, 0, 256, true);13401341// checking detect1342std::vector<Point> hits;1343std::vector<double> weights;1344reference_hog.detect(image, hits, weights);13451346// checking compute1347std::vector<float> descriptors;1348reference_hog.compute(image, descriptors);1349}1350}13511352TEST(Objdetect_CascadeDetector, small_img)1353{1354String root = cvtest::TS::ptr()->get_data_path() + "cascadeandhog/cascades/";1355String cascades[] =1356{1357root + "haarcascade_frontalface_alt.xml",1358root + "lbpcascade_frontalface.xml",1359String()1360};13611362vector<Rect> objects;1363RNG rng((uint64)-1);13641365for( int i = 0; !cascades[i].empty(); i++ )1366{1367printf("%d. %s\n", i, cascades[i].c_str());1368CascadeClassifier cascade(cascades[i]);1369for( int j = 0; j < 100; j++ )1370{1371int width = rng.uniform(1, 100);1372int height = rng.uniform(1, 100);1373Mat img(height, width, CV_8U);1374randu(img, 0, 256);1375cascade.detectMultiScale(img, objects);1376}1377}1378}13791380}} // namespace138113821383