/*M/////////////////////////////////////////////////////////////////////////////////////// // // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. // // By downloading, copying, installing or using the software you agree to this license. // If you do not agree to this license, do not download, install, // copy or use the software. // // // Intel License Agreement // For Open Source Computer Vision Library // // Copyright (C) 2000, Intel Corporation, all rights reserved. // Third party copyrights are property of their respective owners. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // * Redistribution's of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // * Redistribution's in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // * The name of Intel Corporation may not be used to endorse or promote products // derived from this software without specific prior written permission. // // This software is provided by the copyright holders and contributors "as is" and // any express or implied warranties, including, but not limited to, the implied // warranties of merchantability and fitness for a particular purpose are disclaimed. // In no event shall the Intel Corporation or contributors be liable for any direct, // indirect, incidental, special, exemplary, or consequential damages // (including, but not limited to, procurement of substitute goods or services; // loss of use, data, or profits; or business interruption) however caused // and on any theory of liability, whether in contract, strict liability, // or tort (including negligence or otherwise) arising in any way out of // the use of this software, even if advised of the possibility of such damage. // //M*/ #include "test_precomp.hpp" #include "opencv2/imgproc.hpp" #include "opencv2/objdetect/objdetect_c.h" using namespace cv; using namespace std; //#define GET_STAT #define DIST_E "distE" #define S_E "sE" #define NO_PAIR_E "noPairE" //#define TOTAL_NO_PAIR_E "totalNoPairE" #define DETECTOR_NAMES "detector_names" #define DETECTORS "detectors" #define IMAGE_FILENAMES "image_filenames" #define VALIDATION "validation" #define FILENAME "fn" #define C_SCALE_CASCADE "scale_cascade" class CV_DetectorTest : public cvtest::BaseTest { public: CV_DetectorTest(); protected: virtual int prepareData( FileStorage& fs ); virtual void run( int startFrom ); virtual string& getValidationFilename(); virtual void readDetector( const FileNode& fn ) = 0; virtual void writeDetector( FileStorage& fs, int di ) = 0; int runTestCase( int detectorIdx, vector >& objects ); virtual int detectMultiScale( int di, const Mat& img, vector& objects ) = 0; int validate( int detectorIdx, vector >& objects ); struct { float dist; float s; float noPair; //float totalNoPair; } eps; vector detectorNames; vector detectorFilenames; vector imageFilenames; vector images; string validationFilename; string configFilename; FileStorage validationFS; bool write_results; }; CV_DetectorTest::CV_DetectorTest() { configFilename = "dummy"; write_results = false; } string& CV_DetectorTest::getValidationFilename() { return validationFilename; } int CV_DetectorTest::prepareData( FileStorage& _fs ) { if( !_fs.isOpened() ) test_case_count = -1; else { FileNode fn = _fs.getFirstTopLevelNode(); fn[DIST_E] >> eps.dist; fn[S_E] >> eps.s; fn[NO_PAIR_E] >> eps.noPair; // fn[TOTAL_NO_PAIR_E] >> eps.totalNoPair; // read detectors if( fn[DETECTOR_NAMES].size() != 0 ) { FileNodeIterator it = fn[DETECTOR_NAMES].begin(); for( ; it != fn[DETECTOR_NAMES].end(); ) { String _name; it >> _name; detectorNames.push_back(_name); readDetector(fn[DETECTORS][_name]); } } test_case_count = (int)detectorNames.size(); // read images filenames and images string dataPath = ts->get_data_path(); if( fn[IMAGE_FILENAMES].size() != 0 ) { for( FileNodeIterator it = fn[IMAGE_FILENAMES].begin(); it != fn[IMAGE_FILENAMES].end(); ) { String filename; it >> filename; imageFilenames.push_back(filename); Mat img = imread( dataPath+filename, 1 ); images.push_back( img ); } } } return cvtest::TS::OK; } void CV_DetectorTest::run( int ) { string dataPath = ts->get_data_path(); string vs_filename = dataPath + getValidationFilename(); write_results = !validationFS.open( vs_filename, FileStorage::READ ); int code; if( !write_results ) { code = prepareData( validationFS ); } else { FileStorage fs0(dataPath + configFilename, FileStorage::READ ); code = prepareData(fs0); } if( code < 0 ) { ts->set_failed_test_info( code ); return; } if( write_results ) { validationFS.release(); validationFS.open( vs_filename, FileStorage::WRITE ); validationFS << FileStorage::getDefaultObjectName(validationFilename) << "{"; validationFS << DIST_E << eps.dist; validationFS << S_E << eps.s; validationFS << NO_PAIR_E << eps.noPair; // validationFS << TOTAL_NO_PAIR_E << eps.totalNoPair; // write detector names validationFS << DETECTOR_NAMES << "["; vector::const_iterator nit = detectorNames.begin(); for( ; nit != detectorNames.end(); ++nit ) { validationFS << *nit; } validationFS << "]"; // DETECTOR_NAMES // write detectors validationFS << DETECTORS << "{"; assert( detectorNames.size() == detectorFilenames.size() ); nit = detectorNames.begin(); for( int di = 0; nit != detectorNames.end(); ++nit, di++ ) { validationFS << *nit << "{"; writeDetector( validationFS, di ); validationFS << "}"; } validationFS << "}"; // write image filenames validationFS << IMAGE_FILENAMES << "["; vector::const_iterator it = imageFilenames.begin(); for( int ii = 0; it != imageFilenames.end(); ++it, ii++ ) { char buf[10]; sprintf( buf, "%s%d", "img_", ii ); //cvWriteComment( validationFS.fs, buf, 0 ); validationFS << *it; } validationFS << "]"; // IMAGE_FILENAMES validationFS << VALIDATION << "{"; } int progress = 0; for( int di = 0; di < test_case_count; di++ ) { progress = update_progress( progress, di, test_case_count, 0 ); if( write_results ) validationFS << detectorNames[di] << "{"; vector > objects; int temp_code = runTestCase( di, objects ); if (!write_results && temp_code == cvtest::TS::OK) temp_code = validate( di, objects ); if (temp_code != cvtest::TS::OK) code = temp_code; if( write_results ) validationFS << "}"; // detectorNames[di] } if( write_results ) { validationFS << "}"; // VALIDATION validationFS << "}"; // getDefaultObjectName } if ( test_case_count <= 0 || imageFilenames.size() <= 0 ) { ts->printf( cvtest::TS::LOG, "validation file is not determined or not correct" ); code = cvtest::TS::FAIL_INVALID_TEST_DATA; } ts->set_failed_test_info( code ); } int CV_DetectorTest::runTestCase( int detectorIdx, vector >& objects ) { string dataPath = ts->get_data_path(), detectorFilename; if( !detectorFilenames[detectorIdx].empty() ) detectorFilename = dataPath + detectorFilenames[detectorIdx]; printf("detector %s\n", detectorFilename.c_str()); for( int ii = 0; ii < (int)imageFilenames.size(); ++ii ) { vector imgObjects; Mat image = images[ii]; if( image.empty() ) { char msg[30]; sprintf( msg, "%s %d %s", "image ", ii, " can not be read" ); ts->printf( cvtest::TS::LOG, msg ); return cvtest::TS::FAIL_INVALID_TEST_DATA; } int code = detectMultiScale( detectorIdx, image, imgObjects ); if( code != cvtest::TS::OK ) return code; objects.push_back( imgObjects ); if( write_results ) { char buf[10]; sprintf( buf, "%s%d", "img_", ii ); string imageIdxStr = buf; validationFS << imageIdxStr << "[:"; for( vector::const_iterator it = imgObjects.begin(); it != imgObjects.end(); ++it ) { validationFS << it->x << it->y << it->width << it->height; } validationFS << "]"; // imageIdxStr } } return cvtest::TS::OK; } bool isZero( uchar i ) {return i == 0;} int CV_DetectorTest::validate( int detectorIdx, vector >& objects ) { assert( imageFilenames.size() == objects.size() ); int imageIdx = 0; int totalNoPair = 0, totalValRectCount = 0; for( vector >::const_iterator it = objects.begin(); it != objects.end(); ++it, imageIdx++ ) // for image { Size imgSize = images[imageIdx].size(); float dist = min(imgSize.height, imgSize.width) * eps.dist; float wDiff = imgSize.width * eps.s; float hDiff = imgSize.height * eps.s; int noPair = 0; // read validation rectangles char buf[10]; sprintf( buf, "%s%d", "img_", imageIdx ); string imageIdxStr = buf; FileNode node = validationFS.getFirstTopLevelNode()[VALIDATION][detectorNames[detectorIdx]][imageIdxStr]; vector valRects; if( node.size() != 0 ) { for( FileNodeIterator it2 = node.begin(); it2 != node.end(); ) { Rect r; it2 >> r.x >> r.y >> r.width >> r.height; valRects.push_back(r); } } totalValRectCount += (int)valRects.size(); // compare rectangles vector map(valRects.size(), 0); for( vector::const_iterator cr = it->begin(); cr != it->end(); ++cr ) { // find nearest rectangle Point2f cp1 = Point2f( cr->x + (float)cr->width/2.0f, cr->y + (float)cr->height/2.0f ); int minIdx = -1, vi = 0; float minDist = (float)norm( Point(imgSize.width, imgSize.height) ); for( vector::const_iterator vr = valRects.begin(); vr != valRects.end(); ++vr, vi++ ) { Point2f cp2 = Point2f( vr->x + (float)vr->width/2.0f, vr->y + (float)vr->height/2.0f ); float curDist = (float)norm(cp1-cp2); if( curDist < minDist ) { minIdx = vi; minDist = curDist; } } if( minIdx == -1 ) { noPair++; } else { Rect vr = valRects[minIdx]; if( map[minIdx] != 0 || (minDist > dist) || (abs(cr->width - vr.width) > wDiff) || (abs(cr->height - vr.height) > hDiff) ) noPair++; else map[minIdx] = 1; } } noPair += (int)count_if( map.begin(), map.end(), isZero ); totalNoPair += noPair; EXPECT_LE(noPair, cvRound(valRects.size()*eps.noPair)+1) << "detector " << detectorNames[detectorIdx] << " has overrated count of rectangles without pair on " << imageFilenames[imageIdx] << " image"; if (::testing::Test::HasFailure()) break; } EXPECT_LE(totalNoPair, cvRound(totalValRectCount*eps./*total*/noPair)+1) << "detector " << detectorNames[detectorIdx] << " has overrated count of rectangles without pair on all images set"; if (::testing::Test::HasFailure()) return cvtest::TS::FAIL_BAD_ACCURACY; return cvtest::TS::OK; } //----------------------------------------------- CascadeDetectorTest ----------------------------------- class CV_CascadeDetectorTest : public CV_DetectorTest { public: CV_CascadeDetectorTest(); protected: virtual void readDetector( const FileNode& fn ); virtual void writeDetector( FileStorage& fs, int di ); virtual int detectMultiScale( int di, const Mat& img, vector& objects ); virtual int detectMultiScale_C( const string& filename, int di, const Mat& img, vector& objects ); vector flags; }; CV_CascadeDetectorTest::CV_CascadeDetectorTest() { validationFilename = "cascadeandhog/cascade.xml"; configFilename = "cascadeandhog/_cascade.xml"; } void CV_CascadeDetectorTest::readDetector( const FileNode& fn ) { String filename; int flag; fn[FILENAME] >> filename; detectorFilenames.push_back(filename); fn[C_SCALE_CASCADE] >> flag; if( flag ) flags.push_back( 0 ); else flags.push_back( CASCADE_SCALE_IMAGE ); } void CV_CascadeDetectorTest::writeDetector( FileStorage& fs, int di ) { int sc = flags[di] & CASCADE_SCALE_IMAGE ? 0 : 1; fs << FILENAME << detectorFilenames[di]; fs << C_SCALE_CASCADE << sc; } int CV_CascadeDetectorTest::detectMultiScale_C( const string& filename, int di, const Mat& img, vector& objects ) { Ptr c_cascade(cvLoadHaarClassifierCascade(filename.c_str(), cvSize(0,0))); Ptr storage(cvCreateMemStorage()); if( !c_cascade ) { ts->printf( cvtest::TS::LOG, "cascade %s can not be opened"); return cvtest::TS::FAIL_INVALID_TEST_DATA; } Mat grayImg; cvtColor( img, grayImg, COLOR_BGR2GRAY ); equalizeHist( grayImg, grayImg ); CvMat c_gray = grayImg; CvSeq* rs = cvHaarDetectObjects(&c_gray, c_cascade, storage, 1.1, 3, flags[di] ); objects.clear(); for( int i = 0; i < rs->total; i++ ) { Rect r = *(Rect*)cvGetSeqElem(rs, i); objects.push_back(r); } return cvtest::TS::OK; } int CV_CascadeDetectorTest::detectMultiScale( int di, const Mat& img, vector& objects) { string dataPath = ts->get_data_path(), filename; filename = dataPath + detectorFilenames[di]; const string pattern = "haarcascade_frontalface_default.xml"; if( filename.size() >= pattern.size() && strcmp(filename.c_str() + (filename.size() - pattern.size()), pattern.c_str()) == 0 ) return detectMultiScale_C(filename, di, img, objects); CascadeClassifier cascade( filename ); if( cascade.empty() ) { ts->printf( cvtest::TS::LOG, "cascade %s can not be opened"); return cvtest::TS::FAIL_INVALID_TEST_DATA; } Mat grayImg; cvtColor( img, grayImg, COLOR_BGR2GRAY ); equalizeHist( grayImg, grayImg ); cascade.detectMultiScale( grayImg, objects, 1.1, 3, flags[di] ); return cvtest::TS::OK; } //----------------------------------------------- HOGDetectorTest ----------------------------------- class CV_HOGDetectorTest : public CV_DetectorTest { public: CV_HOGDetectorTest(); protected: virtual void readDetector( const FileNode& fn ); virtual void writeDetector( FileStorage& fs, int di ); virtual int detectMultiScale( int di, const Mat& img, vector& objects ); }; CV_HOGDetectorTest::CV_HOGDetectorTest() { validationFilename = "cascadeandhog/hog.xml"; } void CV_HOGDetectorTest::readDetector( const FileNode& fn ) { String filename; if( fn[FILENAME].size() != 0 ) fn[FILENAME] >> filename; detectorFilenames.push_back( filename); } void CV_HOGDetectorTest::writeDetector( FileStorage& fs, int di ) { fs << FILENAME << detectorFilenames[di]; } int CV_HOGDetectorTest::detectMultiScale( int di, const Mat& img, vector& objects) { HOGDescriptor hog; if( detectorFilenames[di].empty() ) hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector()); else assert(0); hog.detectMultiScale(img, objects); return cvtest::TS::OK; } //----------------------------------------------- HOGDetectorReadWriteTest ----------------------------------- TEST(Objdetect_HOGDetectorReadWrite, regression) { // Inspired by bug #2607 Mat img; img = imread(cvtest::TS::ptr()->get_data_path() + "/cascadeandhog/images/karen-and-rob.png"); ASSERT_FALSE(img.empty()); HOGDescriptor hog; hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector()); string tempfilename = cv::tempfile(".xml"); FileStorage fs(tempfilename, FileStorage::WRITE); hog.write(fs, "myHOG"); fs.open(tempfilename, FileStorage::READ); remove(tempfilename.c_str()); FileNode n = fs["opencv_storage"]["myHOG"]; ASSERT_NO_THROW(hog.read(n)); } TEST(Objdetect_CascadeDetector, regression) { CV_CascadeDetectorTest test; test.safe_run(); } TEST(Objdetect_HOGDetector, regression) { CV_HOGDetectorTest test; test.safe_run(); } //----------------------------------------------- HOG SSE2 compatible test ----------------------------------- class HOGDescriptorTester : public cv::HOGDescriptor { HOGDescriptor* actual_hog; cvtest::TS* ts; mutable bool failed; public: HOGDescriptorTester(HOGDescriptor& instance) : cv::HOGDescriptor(instance), actual_hog(&instance), ts(cvtest::TS::ptr()), failed(false) { } virtual void computeGradient(const Mat& img, Mat& grad, Mat& qangle, Size paddingTL, Size paddingBR) const; virtual void detect(const Mat& img, vector& hits, vector& weights, double hitThreshold = 0.0, Size winStride = Size(), Size padding = Size(), const vector& locations = vector()) const; virtual void detect(const Mat& img, vector& hits, double hitThreshold = 0.0, Size winStride = Size(), Size padding = Size(), const vector& locations = vector()) const; virtual void compute(InputArray img, vector& descriptors, Size winStride = Size(), Size padding = Size(), const vector& locations = vector()) const; bool is_failed() const; }; struct HOGCacheTester { struct BlockData { BlockData() : histOfs(0), imgOffset() {} int histOfs; Point imgOffset; }; struct PixData { size_t gradOfs, qangleOfs; int histOfs[4]; float histWeights[4]; float gradWeight; }; HOGCacheTester(); HOGCacheTester(const HOGDescriptorTester* descriptor, const Mat& img, Size paddingTL, Size paddingBR, bool useCache, Size cacheStride); virtual ~HOGCacheTester() { } virtual void init(const HOGDescriptorTester* descriptor, const Mat& img, Size paddingTL, Size paddingBR, bool useCache, Size cacheStride); Size windowsInImage(Size imageSize, Size winStride) const; Rect getWindow(Size imageSize, Size winStride, int idx) const; const float* getBlock(Point pt, float* buf); virtual void normalizeBlockHistogram(float* histogram) const; vector pixData; vector blockData; bool useCache; vector ymaxCached; Size winSize, cacheStride; Size nblocks, ncells; int blockHistogramSize; int count1, count2, count4; Point imgoffset; Mat_ blockCache; Mat_ blockCacheFlags; Mat grad, qangle; const HOGDescriptorTester* descriptor; }; HOGCacheTester::HOGCacheTester() { useCache = false; blockHistogramSize = count1 = count2 = count4 = 0; descriptor = 0; } HOGCacheTester::HOGCacheTester(const HOGDescriptorTester* _descriptor, const Mat& _img, Size _paddingTL, Size _paddingBR, bool _useCache, Size _cacheStride) { init(_descriptor, _img, _paddingTL, _paddingBR, _useCache, _cacheStride); } void HOGCacheTester::init(const HOGDescriptorTester* _descriptor, const Mat& _img, Size _paddingTL, Size _paddingBR, bool _useCache, Size _cacheStride) { descriptor = _descriptor; cacheStride = _cacheStride; useCache = _useCache; descriptor->computeGradient(_img, grad, qangle, _paddingTL, _paddingBR); imgoffset = _paddingTL; winSize = descriptor->winSize; Size blockSize = descriptor->blockSize; Size blockStride = descriptor->blockStride; Size cellSize = descriptor->cellSize; int i, j, nbins = descriptor->nbins; int rawBlockSize = blockSize.width*blockSize.height; nblocks = Size((winSize.width - blockSize.width)/blockStride.width + 1, (winSize.height - blockSize.height)/blockStride.height + 1); ncells = Size(blockSize.width/cellSize.width, blockSize.height/cellSize.height); blockHistogramSize = ncells.width*ncells.height*nbins; if( useCache ) { Size cacheSize((grad.cols - blockSize.width)/cacheStride.width+1, (winSize.height/cacheStride.height)+1); blockCache.create(cacheSize.height, cacheSize.width*blockHistogramSize); blockCacheFlags.create(cacheSize); size_t cacheRows = blockCache.rows; ymaxCached.resize(cacheRows); for(size_t ii = 0; ii < cacheRows; ii++ ) ymaxCached[ii] = -1; } Mat_ weights(blockSize); float sigma = (float)descriptor->getWinSigma(); float scale = 1.f/(sigma*sigma*2); for(i = 0; i < blockSize.height; i++) for(j = 0; j < blockSize.width; j++) { float di = i - blockSize.height*0.5f; float dj = j - blockSize.width*0.5f; weights(i,j) = std::exp(-(di*di + dj*dj)*scale); } blockData.resize(nblocks.width*nblocks.height); pixData.resize(rawBlockSize*3); // Initialize 2 lookup tables, pixData & blockData. // Here is why: // // The detection algorithm runs in 4 nested loops (at each pyramid layer): // loop over the windows within the input image // loop over the blocks within each window // loop over the cells within each block // loop over the pixels in each cell // // As each of the loops runs over a 2-dimensional array, // we could get 8(!) nested loops in total, which is very-very slow. // // To speed the things up, we do the following: // 1. loop over windows is unrolled in the HOGDescriptor::{compute|detect} methods; // inside we compute the current search window using getWindow() method. // Yes, it involves some overhead (function call + couple of divisions), // but it's tiny in fact. // 2. loop over the blocks is also unrolled. Inside we use pre-computed blockData[j] // to set up gradient and histogram pointers. // 3. loops over cells and pixels in each cell are merged // (since there is no overlap between cells, each pixel in the block is processed once) // and also unrolled. Inside we use PixData[k] to access the gradient values and // update the histogram // count1 = count2 = count4 = 0; for( j = 0; j < blockSize.width; j++ ) for( i = 0; i < blockSize.height; i++ ) { PixData* data = 0; float cellX = (j+0.5f)/cellSize.width - 0.5f; float cellY = (i+0.5f)/cellSize.height - 0.5f; int icellX0 = cvFloor(cellX); int icellY0 = cvFloor(cellY); int icellX1 = icellX0 + 1, icellY1 = icellY0 + 1; cellX -= icellX0; cellY -= icellY0; if( (unsigned)icellX0 < (unsigned)ncells.width && (unsigned)icellX1 < (unsigned)ncells.width ) { if( (unsigned)icellY0 < (unsigned)ncells.height && (unsigned)icellY1 < (unsigned)ncells.height ) { data = &pixData[rawBlockSize*2 + (count4++)]; data->histOfs[0] = (icellX0*ncells.height + icellY0)*nbins; data->histWeights[0] = (1.f - cellX)*(1.f - cellY); data->histOfs[1] = (icellX1*ncells.height + icellY0)*nbins; data->histWeights[1] = cellX*(1.f - cellY); data->histOfs[2] = (icellX0*ncells.height + icellY1)*nbins; data->histWeights[2] = (1.f - cellX)*cellY; data->histOfs[3] = (icellX1*ncells.height + icellY1)*nbins; data->histWeights[3] = cellX*cellY; } else { data = &pixData[rawBlockSize + (count2++)]; if( (unsigned)icellY0 < (unsigned)ncells.height ) { icellY1 = icellY0; cellY = 1.f - cellY; } data->histOfs[0] = (icellX0*ncells.height + icellY1)*nbins; data->histWeights[0] = (1.f - cellX)*cellY; data->histOfs[1] = (icellX1*ncells.height + icellY1)*nbins; data->histWeights[1] = cellX*cellY; data->histOfs[2] = data->histOfs[3] = 0; data->histWeights[2] = data->histWeights[3] = 0; } } else { if( (unsigned)icellX0 < (unsigned)ncells.width ) { icellX1 = icellX0; cellX = 1.f - cellX; } if( (unsigned)icellY0 < (unsigned)ncells.height && (unsigned)icellY1 < (unsigned)ncells.height ) { data = &pixData[rawBlockSize + (count2++)]; data->histOfs[0] = (icellX1*ncells.height + icellY0)*nbins; data->histWeights[0] = cellX*(1.f - cellY); data->histOfs[1] = (icellX1*ncells.height + icellY1)*nbins; data->histWeights[1] = cellX*cellY; data->histOfs[2] = data->histOfs[3] = 0; data->histWeights[2] = data->histWeights[3] = 0; } else { data = &pixData[count1++]; if( (unsigned)icellY0 < (unsigned)ncells.height ) { icellY1 = icellY0; cellY = 1.f - cellY; } data->histOfs[0] = (icellX1*ncells.height + icellY1)*nbins; data->histWeights[0] = cellX*cellY; data->histOfs[1] = data->histOfs[2] = data->histOfs[3] = 0; data->histWeights[1] = data->histWeights[2] = data->histWeights[3] = 0; } } data->gradOfs = (grad.cols*i + j)*2; data->qangleOfs = (qangle.cols*i + j)*2; data->gradWeight = weights(i,j); } assert( count1 + count2 + count4 == rawBlockSize ); // defragment pixData for( j = 0; j < count2; j++ ) pixData[j + count1] = pixData[j + rawBlockSize]; for( j = 0; j < count4; j++ ) pixData[j + count1 + count2] = pixData[j + rawBlockSize*2]; count2 += count1; count4 += count2; // initialize blockData for( j = 0; j < nblocks.width; j++ ) for( i = 0; i < nblocks.height; i++ ) { BlockData& data = blockData[j*nblocks.height + i]; data.histOfs = (j*nblocks.height + i)*blockHistogramSize; data.imgOffset = Point(j*blockStride.width,i*blockStride.height); } } const float* HOGCacheTester::getBlock(Point pt, float* buf) { float* blockHist = buf; assert(descriptor != 0); Size blockSize = descriptor->blockSize; pt += imgoffset; CV_Assert( (unsigned)pt.x <= (unsigned)(grad.cols - blockSize.width) && (unsigned)pt.y <= (unsigned)(grad.rows - blockSize.height) ); if( useCache ) { CV_Assert( pt.x % cacheStride.width == 0 && pt.y % cacheStride.height == 0 ); Point cacheIdx(pt.x/cacheStride.width, (pt.y/cacheStride.height) % blockCache.rows); if( pt.y != ymaxCached[cacheIdx.y] ) { Mat_ cacheRow = blockCacheFlags.row(cacheIdx.y); cacheRow = (uchar)0; ymaxCached[cacheIdx.y] = pt.y; } blockHist = &blockCache[cacheIdx.y][cacheIdx.x*blockHistogramSize]; uchar& computedFlag = blockCacheFlags(cacheIdx.y, cacheIdx.x); if( computedFlag != 0 ) return blockHist; computedFlag = (uchar)1; // set it at once, before actual computing } int k, C1 = count1, C2 = count2, C4 = count4; const float* gradPtr = grad.ptr(pt.y) + pt.x*2; const uchar* qanglePtr = qangle.ptr(pt.y) + pt.x*2; CV_Assert( blockHist != 0 ); for( k = 0; k < blockHistogramSize; k++ ) blockHist[k] = 0.f; const PixData* _pixData = &pixData[0]; for( k = 0; k < C1; k++ ) { const PixData& pk = _pixData[k]; const float* a = gradPtr + pk.gradOfs; float w = pk.gradWeight*pk.histWeights[0]; const uchar* h = qanglePtr + pk.qangleOfs; int h0 = h[0], h1 = h[1]; float* hist = blockHist + pk.histOfs[0]; float t0 = hist[h0] + a[0]*w; float t1 = hist[h1] + a[1]*w; hist[h0] = t0; hist[h1] = t1; } for( ; k < C2; k++ ) { const PixData& pk = _pixData[k]; const float* a = gradPtr + pk.gradOfs; float w, t0, t1, a0 = a[0], a1 = a[1]; const uchar* h = qanglePtr + pk.qangleOfs; int h0 = h[0], h1 = h[1]; float* hist = blockHist + pk.histOfs[0]; w = pk.gradWeight*pk.histWeights[0]; t0 = hist[h0] + a0*w; t1 = hist[h1] + a1*w; hist[h0] = t0; hist[h1] = t1; hist = blockHist + pk.histOfs[1]; w = pk.gradWeight*pk.histWeights[1]; t0 = hist[h0] + a0*w; t1 = hist[h1] + a1*w; hist[h0] = t0; hist[h1] = t1; } for( ; k < C4; k++ ) { const PixData& pk = _pixData[k]; const float* a = gradPtr + pk.gradOfs; float w, t0, t1, a0 = a[0], a1 = a[1]; const uchar* h = qanglePtr + pk.qangleOfs; int h0 = h[0], h1 = h[1]; float* hist = blockHist + pk.histOfs[0]; w = pk.gradWeight*pk.histWeights[0]; t0 = hist[h0] + a0*w; t1 = hist[h1] + a1*w; hist[h0] = t0; hist[h1] = t1; hist = blockHist + pk.histOfs[1]; w = pk.gradWeight*pk.histWeights[1]; t0 = hist[h0] + a0*w; t1 = hist[h1] + a1*w; hist[h0] = t0; hist[h1] = t1; hist = blockHist + pk.histOfs[2]; w = pk.gradWeight*pk.histWeights[2]; t0 = hist[h0] + a0*w; t1 = hist[h1] + a1*w; hist[h0] = t0; hist[h1] = t1; hist = blockHist + pk.histOfs[3]; w = pk.gradWeight*pk.histWeights[3]; t0 = hist[h0] + a0*w; t1 = hist[h1] + a1*w; hist[h0] = t0; hist[h1] = t1; } normalizeBlockHistogram(blockHist); return blockHist; } void HOGCacheTester::normalizeBlockHistogram(float* _hist) const { float* hist = &_hist[0], partSum[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; size_t i, sz = blockHistogramSize; for (i = 0; i <= sz - 4; i += 4) { partSum[0] += hist[i] * hist[i]; partSum[1] += hist[i+1] * hist[i+1]; partSum[2] += hist[i+2] * hist[i+2]; partSum[3] += hist[i+3] * hist[i+3]; } float t0 = partSum[0] + partSum[1]; float t1 = partSum[2] + partSum[3]; float sum = t0 + t1; for( ; i < sz; i++ ) sum += hist[i]*hist[i]; float scale = 1.f/(std::sqrt(sum)+sz*0.1f), thresh = (float)descriptor->L2HysThreshold; partSum[0] = partSum[1] = partSum[2] = partSum[3] = 0.0f; for(i = 0; i <= sz - 4; i += 4) { hist[i] = std::min(hist[i]*scale, thresh); hist[i+1] = std::min(hist[i+1]*scale, thresh); hist[i+2] = std::min(hist[i+2]*scale, thresh); hist[i+3] = std::min(hist[i+3]*scale, thresh); partSum[0] += hist[i]*hist[i]; partSum[1] += hist[i+1]*hist[i+1]; partSum[2] += hist[i+2]*hist[i+2]; partSum[3] += hist[i+3]*hist[i+3]; } t0 = partSum[0] + partSum[1]; t1 = partSum[2] + partSum[3]; sum = t0 + t1; for( ; i < sz; i++ ) { hist[i] = std::min(hist[i]*scale, thresh); sum += hist[i]*hist[i]; } scale = 1.f/(std::sqrt(sum)+1e-3f); for( i = 0; i < sz; i++ ) hist[i] *= scale; } Size HOGCacheTester::windowsInImage(Size imageSize, Size winStride) const { return Size((imageSize.width - winSize.width)/winStride.width + 1, (imageSize.height - winSize.height)/winStride.height + 1); } Rect HOGCacheTester::getWindow(Size imageSize, Size winStride, int idx) const { int nwindowsX = (imageSize.width - winSize.width)/winStride.width + 1; int y = idx / nwindowsX; int x = idx - nwindowsX*y; return Rect( x*winStride.width, y*winStride.height, winSize.width, winSize.height ); } inline bool HOGDescriptorTester::is_failed() const { return failed; } static inline int gcd(int a, int b) { return (a % b == 0) ? b : gcd (b, a % b); } void HOGDescriptorTester::detect(const Mat& img, vector& hits, vector& weights, double hitThreshold, Size winStride, Size padding, const vector& locations) const { if (failed) return; hits.clear(); if( svmDetector.empty() ) return; if( winStride == Size() ) winStride = cellSize; Size cacheStride(gcd(winStride.width, blockStride.width), gcd(winStride.height, blockStride.height)); size_t nwindows = locations.size(); padding.width = (int)alignSize(std::max(padding.width, 0), cacheStride.width); padding.height = (int)alignSize(std::max(padding.height, 0), cacheStride.height); Size paddedImgSize(img.cols + padding.width*2, img.rows + padding.height*2); HOGCacheTester cache(this, img, padding, padding, nwindows == 0, cacheStride); if( !nwindows ) nwindows = cache.windowsInImage(paddedImgSize, winStride).area(); const HOGCacheTester::BlockData* blockData = &cache.blockData[0]; int nblocks = cache.nblocks.area(); int blockHistogramSize = cache.blockHistogramSize; size_t dsize = getDescriptorSize(); double rho = svmDetector.size() > dsize ? svmDetector[dsize] : 0; vector blockHist(blockHistogramSize); for( size_t i = 0; i < nwindows; i++ ) { Point pt0; if( !locations.empty() ) { pt0 = locations[i]; if( pt0.x < -padding.width || pt0.x > img.cols + padding.width - winSize.width || pt0.y < -padding.height || pt0.y > img.rows + padding.height - winSize.height ) continue; } else { pt0 = cache.getWindow(paddedImgSize, winStride, (int)i).tl() - Point(padding); CV_Assert(pt0.x % cacheStride.width == 0 && pt0.y % cacheStride.height == 0); } double s = rho; const float* svmVec = &svmDetector[0]; int j, k; for( j = 0; j < nblocks; j++, svmVec += blockHistogramSize ) { const HOGCacheTester::BlockData& bj = blockData[j]; Point pt = pt0 + bj.imgOffset; const float* vec = cache.getBlock(pt, &blockHist[0]); for( k = 0; k <= blockHistogramSize - 4; k += 4 ) s += vec[k]*svmVec[k] + vec[k+1]*svmVec[k+1] + vec[k+2]*svmVec[k+2] + vec[k+3]*svmVec[k+3]; for( ; k < blockHistogramSize; k++ ) s += vec[k]*svmVec[k]; } if( s >= hitThreshold ) { hits.push_back(pt0); weights.push_back(s); } } // validation std::vector actual_find_locations; std::vector actual_weights; actual_hog->detect(img, actual_find_locations, actual_weights, hitThreshold, winStride, padding, locations); if (!std::equal(hits.begin(), hits.end(), actual_find_locations.begin())) { ts->printf(cvtest::TS::SUMMARY, "Found locations are not equal (see detect function)\n"); ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); ts->set_gtest_status(); failed = true; return; } const double eps = 0.0; double diff_norm = cvtest::norm(actual_weights, weights, NORM_L2); if (diff_norm > eps) { ts->printf(cvtest::TS::SUMMARY, "Weights for found locations aren't equal.\n" "Norm of the difference is %lf\n", diff_norm); ts->printf(cvtest::TS::LOG, "Channels: %d\n", img.channels()); failed = true; ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); ts->set_gtest_status(); return; } } void HOGDescriptorTester::detect(const Mat& img, vector& hits, double hitThreshold, Size winStride, Size padding, const vector& locations) const { vector weightsV; detect(img, hits, weightsV, hitThreshold, winStride, padding, locations); } void HOGDescriptorTester::compute(InputArray _img, vector& descriptors, Size winStride, Size padding, const vector& locations) const { Mat img = _img.getMat(); if( winStride == Size() ) winStride = cellSize; Size cacheStride(gcd(winStride.width, blockStride.width), gcd(winStride.height, blockStride.height)); size_t nwindows = locations.size(); padding.width = (int)alignSize(std::max(padding.width, 0), cacheStride.width); padding.height = (int)alignSize(std::max(padding.height, 0), cacheStride.height); Size paddedImgSize(img.cols + padding.width*2, img.rows + padding.height*2); HOGCacheTester cache(this, img, padding, padding, nwindows == 0, cacheStride); if( !nwindows ) nwindows = cache.windowsInImage(paddedImgSize, winStride).area(); const HOGCacheTester::BlockData* blockData = &cache.blockData[0]; int nblocks = cache.nblocks.area(); int blockHistogramSize = cache.blockHistogramSize; size_t dsize = getDescriptorSize(); descriptors.resize(dsize*nwindows); for( size_t i = 0; i < nwindows; i++ ) { float* descriptor = &descriptors[i*dsize]; Point pt0; if( !locations.empty() ) { pt0 = locations[i]; if( pt0.x < -padding.width || pt0.x > img.cols + padding.width - winSize.width || pt0.y < -padding.height || pt0.y > img.rows + padding.height - winSize.height ) continue; } else { pt0 = cache.getWindow(paddedImgSize, winStride, (int)i).tl() - Point(padding); CV_Assert(pt0.x % cacheStride.width == 0 && pt0.y % cacheStride.height == 0); } for( int j = 0; j < nblocks; j++ ) { const HOGCacheTester::BlockData& bj = blockData[j]; Point pt = pt0 + bj.imgOffset; float* dst = descriptor + bj.histOfs; const float* src = cache.getBlock(pt, dst); if( src != dst ) for( int k = 0; k < blockHistogramSize; k++ ) dst[k] = src[k]; } } // validation std::vector actual_descriptors; actual_hog->compute(img, actual_descriptors, winStride, padding, locations); double diff_norm = cvtest::norm(actual_descriptors, descriptors, NORM_L2); const double eps = 0.0; if (diff_norm > eps) { ts->printf(cvtest::TS::SUMMARY, "Norm of the difference: %lf\n", diff_norm); ts->printf(cvtest::TS::SUMMARY, "Found descriptors are not equal (see compute function)\n"); ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); ts->printf(cvtest::TS::LOG, "Channels: %d\n", img.channels()); ts->set_gtest_status(); failed = true; return; } } void HOGDescriptorTester::computeGradient(const Mat& img, Mat& grad, Mat& qangle, Size paddingTL, Size paddingBR) const { CV_Assert( img.type() == CV_8U || img.type() == CV_8UC3 ); Size gradsize(img.cols + paddingTL.width + paddingBR.width, img.rows + paddingTL.height + paddingBR.height); grad.create(gradsize, CV_32FC2); // qangle.create(gradsize, CV_8UC2); // [0..nbins-1] - quantized gradient orientation Size wholeSize; Point roiofs; img.locateROI(wholeSize, roiofs); int i, x, y; int cn = img.channels(); Mat_ _lut(1, 256); const float* lut = &_lut(0,0); if( gammaCorrection ) for( i = 0; i < 256; i++ ) _lut(0,i) = std::sqrt((float)i); else for( i = 0; i < 256; i++ ) _lut(0,i) = (float)i; AutoBuffer mapbuf(gradsize.width + gradsize.height + 4); int* xmap = (int*)mapbuf + 1; int* ymap = xmap + gradsize.width + 2; const int borderType = (int)BORDER_REFLECT_101; for( x = -1; x < gradsize.width + 1; x++ ) xmap[x] = borderInterpolate(x - paddingTL.width + roiofs.x, wholeSize.width, borderType) - roiofs.x; for( y = -1; y < gradsize.height + 1; y++ ) ymap[y] = borderInterpolate(y - paddingTL.height + roiofs.y, wholeSize.height, borderType) - roiofs.y; // x- & y- derivatives for the whole row int width = gradsize.width; AutoBuffer _dbuf(width*4); float* dbuf = _dbuf; Mat Dx(1, width, CV_32F, dbuf); Mat Dy(1, width, CV_32F, dbuf + width); Mat Mag(1, width, CV_32F, dbuf + width*2); Mat Angle(1, width, CV_32F, dbuf + width*3); int _nbins = nbins; float angleScale = (float)(_nbins/CV_PI); for( y = 0; y < gradsize.height; y++ ) { const uchar* imgPtr = img.ptr(ymap[y]); const uchar* prevPtr = img.ptr(ymap[y-1]); const uchar* nextPtr = img.ptr(ymap[y+1]); float* gradPtr = (float*)grad.ptr(y); uchar* qanglePtr = (uchar*)qangle.ptr(y); if( cn == 1 ) { for( x = 0; x < width; x++ ) { int x1 = xmap[x]; dbuf[x] = (float)(lut[imgPtr[xmap[x+1]]] - lut[imgPtr[xmap[x-1]]]); dbuf[width + x] = (float)(lut[nextPtr[x1]] - lut[prevPtr[x1]]); } } else { for( x = 0; x < width; x++ ) { int x1 = xmap[x]*3; float dx0, dy0, dx, dy, mag0, mag; const uchar* p2 = imgPtr + xmap[x+1]*3; const uchar* p0 = imgPtr + xmap[x-1]*3; dx0 = lut[p2[2]] - lut[p0[2]]; dy0 = lut[nextPtr[x1+2]] - lut[prevPtr[x1+2]]; mag0 = dx0*dx0 + dy0*dy0; dx = lut[p2[1]] - lut[p0[1]]; dy = lut[nextPtr[x1+1]] - lut[prevPtr[x1+1]]; mag = dx*dx + dy*dy; if( mag0 < mag ) { dx0 = dx; dy0 = dy; mag0 = mag; } dx = lut[p2[0]] - lut[p0[0]]; dy = lut[nextPtr[x1]] - lut[prevPtr[x1]]; mag = dx*dx + dy*dy; if( mag0 < mag ) { dx0 = dx; dy0 = dy; mag0 = mag; } dbuf[x] = dx0; dbuf[x+width] = dy0; } } cartToPolar( Dx, Dy, Mag, Angle, false ); for( x = 0; x < width; x++ ) { float mag = dbuf[x+width*2], angle = dbuf[x+width*3]*angleScale - 0.5f; int hidx = cvFloor(angle); angle -= hidx; gradPtr[x*2] = mag*(1.f - angle); gradPtr[x*2+1] = mag*angle; if( hidx < 0 ) hidx += _nbins; else if( hidx >= _nbins ) hidx -= _nbins; assert( (unsigned)hidx < (unsigned)_nbins ); qanglePtr[x*2] = (uchar)hidx; hidx++; hidx &= hidx < _nbins ? -1 : 0; qanglePtr[x*2+1] = (uchar)hidx; } } // validation Mat actual_mats[2], reference_mats[2] = { grad, qangle }; const char* args[] = { "Gradient's", "Qangles's" }; actual_hog->computeGradient(img, actual_mats[0], actual_mats[1], paddingTL, paddingBR); const double eps = 0.0; for (i = 0; i < 2; ++i) { double diff_norm = cvtest::norm(reference_mats[i], actual_mats[i], NORM_L2); if (diff_norm > eps) { ts->printf(cvtest::TS::LOG, "%s matrices are not equal\n" "Norm of the difference is %lf\n", args[i], diff_norm); ts->printf(cvtest::TS::LOG, "Channels: %d\n", img.channels()); ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); ts->set_gtest_status(); failed = true; return; } } } TEST(Objdetect_HOGDetector_Strict, accuracy) { cvtest::TS* ts = cvtest::TS::ptr(); RNG& rng = ts->get_rng(); HOGDescriptor actual_hog; actual_hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector()); HOGDescriptorTester reference_hog(actual_hog); const unsigned int test_case_count = 5; for (unsigned int i = 0; i < test_case_count && !reference_hog.is_failed(); ++i) { // creating a matrix Size ssize(rng.uniform(1, 10) * actual_hog.winSize.width, rng.uniform(1, 10) * actual_hog.winSize.height); int type = rng.uniform(0, 1) > 0 ? CV_8UC1 : CV_8UC3; Mat image(ssize, type); rng.fill(image, RNG::UNIFORM, 0, 256, true); // checking detect std::vector hits; std::vector weights; reference_hog.detect(image, hits, weights); // checking compute std::vector descriptors; reference_hog.compute(image, descriptors); } }