博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
opencv-特征描述符评估
阅读量:2050 次
发布时间:2019-04-28

本文共 44595 字,大约阅读时间需要 148 分钟。

adetectordescriptor_evaluation.cpp

    opencv/tests/cv/src/adetectordescriptor_evaluation.cpp

具体出自:

#include "cxts.h"#include "opencv2/legacy/legacy.hpp"#include "opencv2/legacy/compat.hpp"#include "opencv2/contrib/contrib.hpp"#include "opencv2/core/core_c.h"#include "opencv2/core/internal.hpp"#include "opencv2/imgproc/imgproc.hpp"#include "opencv2/imgproc/imgproc_c.h"#include "opencv2/calib3d/calib3d.hpp"#include "opencv2/highgui/highgui.hpp"#include "opencv2/video/tracking.hpp"#include "opencv2/features2d/features2d.hpp"#include "opencv2/objdetect/objdetect.hpp"
/*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 "cvtest.h"#include 
#include
#include
#include
using namespace std;using namespace cv;/****************************************************************************************\* Functions to evaluate affine covariant detectors and descriptors. *\****************************************************************************************/static inline Point2f applyHomography( const Mat_
& H, const Point2f& pt ){ double z = H(2,0)*pt.x + H(2,1)*pt.y + H(2,2); if( z ) { double w = 1./z; return Point2f( (float)((H(0,0)*pt.x + H(0,1)*pt.y + H(0,2))*w), (float)((H(1,0)*pt.x + H(1,1)*pt.y + H(1,2))*w) ); } return Point2f( numeric_limits
::max(), numeric_limits
::max() );}static inline void linearizeHomographyAt( const Mat_
& H, const Point2f& pt, Mat_
& A ){ A.create(2,2); double p1 = H(0,0)*pt.x + H(0,1)*pt.y + H(0,2), p2 = H(1,0)*pt.x + H(1,1)*pt.y + H(1,2), p3 = H(2,0)*pt.x + H(2,1)*pt.y + H(2,2), p3_2 = p3*p3; if( p3 ) { A(0,0) = H(0,0)/p3 - p1*H(2,0)/p3_2; // fxdx A(0,1) = H(0,1)/p3 - p1*H(2,1)/p3_2; // fxdy A(1,0) = H(1,0)/p3 - p2*H(2,0)/p3_2; // fydx A(1,1) = H(1,1)/p3 - p2*H(2,1)/p3_2; // fydx } else A.setTo(Scalar::all(numeric_limits
::max()));}static void calcKeyPointProjections( const vector
& src, const Mat_
& H, vector
& dst ){ if( !src.empty() ) { assert( !H.empty() && H.cols == 3 && H.rows == 3); dst.resize(src.size()); vector
::const_iterator srcIt = src.begin(); vector
::iterator dstIt = dst.begin(); for( ; srcIt != src.end(); ++srcIt, ++dstIt ) { Point2f dstPt = applyHomography(H, srcIt->pt); float srcSize2 = srcIt->size * srcIt->size; Mat_
M(2, 2); M(0,0) = M(1,1) = 1./srcSize2; M(1,0) = M(0,1) = 0; Mat_
invM; invert(M, invM); Mat_
Aff; linearizeHomographyAt(H, srcIt->pt, Aff); Mat_
dstM; invert(Aff*invM*Aff.t(), dstM); Mat_
eval; eigen( dstM, eval ); assert( eval(0,0) && eval(1,0) ); float dstSize = (float)pow(1./(eval(0,0)*eval(1,0)), 0.25); // TODO: check angle projection float srcAngleRad = (float)(srcIt->angle*CV_PI/180); Point2f vec1(cos(srcAngleRad), sin(srcAngleRad)), vec2; vec2.x = (float)(Aff(0,0)*vec1.x + Aff(0,1)*vec1.y); vec2.y = (float)(Aff(1,0)*vec1.x + Aff(0,1)*vec1.y); float dstAngleGrad = fastAtan2(vec2.y, vec2.x); *dstIt = KeyPoint( dstPt, dstSize, dstAngleGrad, srcIt->response, srcIt->octave, srcIt->class_id ); } }}static void filterKeyPointsByImageSize( vector
& keypoints, const Size& imgSize ){ if( !keypoints.empty() ) { vector
filtered; filtered.reserve(keypoints.size()); Rect r(0, 0, imgSize.width, imgSize.height); vector
::const_iterator it = keypoints.begin(); for( int i = 0; it != keypoints.end(); ++it, i++ ) if( r.contains(it->pt) ) filtered.push_back(*it); keypoints.assign(filtered.begin(), filtered.end()); }}/****************************************************************************************\* Detectors evaluation *\****************************************************************************************/const int DATASETS_COUNT = 8;const int TEST_CASE_COUNT = 5;const string IMAGE_DATASETS_DIR = "detectors_descriptors_evaluation/images_datasets/";const string DETECTORS_DIR = "detectors_descriptors_evaluation/detectors/";const string DESCRIPTORS_DIR = "detectors_descriptors_evaluation/descriptors/";const string KEYPOINTS_DIR = "detectors_descriptors_evaluation/keypoints_datasets/";const string PARAMS_POSTFIX = "_params.xml";const string RES_POSTFIX = "_res.xml";const string REPEAT = "repeatability";const string CORRESP_COUNT = "correspondence_count";string DATASET_NAMES[DATASETS_COUNT] = { "bark", "bikes", "boat", "graf", "leuven", "trees", "ubc", "wall"};string DEFAULT_PARAMS = "default";string IS_ACTIVE_PARAMS = "isActiveParams";string IS_SAVE_KEYPOINTS = "isSaveKeypoints";class BaseQualityTest : public CvTest{public: BaseQualityTest( const char* _algName, const char* _testName, const char* _testFuncs ) : CvTest( _testName, _testFuncs ), algName(_algName) { //TODO: change this isWriteGraphicsData = true; }protected: virtual string getRunParamsFilename() const = 0; virtual string getResultsFilename() const = 0; virtual string getPlotPath() const = 0; virtual void validQualityClear( int datasetIdx ) = 0; virtual void calcQualityClear( int datasetIdx ) = 0; virtual void validQualityCreate( int datasetIdx ) = 0; virtual bool isValidQualityEmpty( int datasetIdx ) const = 0; virtual bool isCalcQualityEmpty( int datasetIdx ) const = 0; void readAllDatasetsRunParams(); virtual void readDatasetRunParams( FileNode& fn, int datasetIdx ) = 0; void writeAllDatasetsRunParams() const; virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const = 0; void setDefaultAllDatasetsRunParams(); virtual void setDefaultDatasetRunParams( int datasetIdx ) = 0; virtual void readDefaultRunParams( FileNode& /*fn*/ ) {} virtual void writeDefaultRunParams( FileStorage& /*fs*/ ) const {} virtual void readResults(); virtual void readResults( FileNode& fn, int datasetIdx, int caseIdx ) = 0; void writeResults() const; virtual void writeResults( FileStorage& fs, int datasetIdx, int caseIdx ) const = 0; bool readDataset( const string& datasetName, vector
& Hs, vector
& imgs ); virtual void readAlgorithm( ) {}; virtual void processRunParamsFile () {}; virtual void runDatasetTest( const vector
& /*imgs*/, const vector
& /*Hs*/, int /*di*/, int& /*progress*/ ) {} void run( int ); virtual void processResults( int datasetIdx ); virtual int processResults( int datasetIdx, int caseIdx ) = 0; virtual void processResults(); virtual void writePlotData( int /*datasetIdx*/ ) const {} virtual void writeAveragePlotData() const {}; string algName; bool isWriteParams, isWriteResults, isWriteGraphicsData;};void BaseQualityTest::readAllDatasetsRunParams(){ string filename = getRunParamsFilename(); FileStorage fs( filename, FileStorage::READ ); if( !fs.isOpened() ) { isWriteParams = true; setDefaultAllDatasetsRunParams(); ts->printf(CvTS::LOG, "all runParams are default\n"); } else { isWriteParams = false; FileNode topfn = fs.getFirstTopLevelNode(); FileNode fn = topfn[DEFAULT_PARAMS]; readDefaultRunParams(fn); for( int i = 0; i < DATASETS_COUNT; i++ ) { FileNode fn = topfn[DATASET_NAMES[i]]; if( fn.empty() ) { ts->printf( CvTS::LOG, "%d-runParams is default\n", i); setDefaultDatasetRunParams(i); } else readDatasetRunParams(fn, i); } }}void BaseQualityTest::writeAllDatasetsRunParams() const{ string filename = getRunParamsFilename(); FileStorage fs( filename, FileStorage::WRITE ); if( fs.isOpened() ) { fs << "run_params" << "{"; // top file node fs << DEFAULT_PARAMS << "{"; writeDefaultRunParams(fs); fs << "}"; for( int i = 0; i < DATASETS_COUNT; i++ ) { fs << DATASET_NAMES[i] << "{"; writeDatasetRunParams(fs, i); fs << "}"; } fs << "}"; } else ts->printf(CvTS::LOG, "file %s for writing run params can not be opened\n", filename.c_str() );}void BaseQualityTest::setDefaultAllDatasetsRunParams(){ for( int i = 0; i < DATASETS_COUNT; i++ ) setDefaultDatasetRunParams(i);}bool BaseQualityTest::readDataset( const string& datasetName, vector
& Hs, vector
& imgs ){ Hs.resize( TEST_CASE_COUNT ); imgs.resize( TEST_CASE_COUNT+1 ); string dirname = string(ts->get_data_path()) + IMAGE_DATASETS_DIR + datasetName + "/"; for( int i = 0; i < (int)Hs.size(); i++ ) { stringstream filename; filename << "H1to" << i+2 << "p.xml"; FileStorage fs( dirname + filename.str(), FileStorage::READ ); if( !fs.isOpened() ) return false; fs.getFirstTopLevelNode() >> Hs[i]; } for( int i = 0; i < (int)imgs.size(); i++ ) { stringstream filename; filename << "img" << i+1 << ".png"; imgs[i] = imread( dirname + filename.str(), 0 ); if( imgs[i].empty() ) return false; } return true;}void BaseQualityTest::readResults(){ string filename = getResultsFilename(); FileStorage fs( filename, FileStorage::READ ); if( fs.isOpened() ) { isWriteResults = false; FileNode topfn = fs.getFirstTopLevelNode(); for( int di = 0; di < DATASETS_COUNT; di++ ) { FileNode datafn = topfn[DATASET_NAMES[di]]; if( datafn.empty() ) { validQualityClear(di); ts->printf( CvTS::LOG, "results for %s dataset were not read\n", DATASET_NAMES[di].c_str() ); } else { validQualityCreate(di); for( int ci = 0; ci < TEST_CASE_COUNT; ci++ ) { stringstream ss; ss << "case" << ci; FileNode casefn = datafn[ss.str()]; CV_Assert( !casefn.empty() ); readResults( casefn , di, ci ); } } } } else isWriteResults = true;}void BaseQualityTest::writeResults() const{ string filename = getResultsFilename();; FileStorage fs( filename, FileStorage::WRITE ); if( fs.isOpened() ) { fs << "results" << "{"; for( int di = 0; di < DATASETS_COUNT; di++ ) { if( isCalcQualityEmpty(di) ) { ts->printf(CvTS::LOG, "results on %s dataset were not write because of empty\n", DATASET_NAMES[di].c_str()); } else { fs << DATASET_NAMES[di] << "{"; for( int ci = 0; ci < TEST_CASE_COUNT; ci++ ) { stringstream ss; ss << "case" << ci; fs << ss.str() << "{"; writeResults( fs, di, ci ); fs << "}"; //ss.str() } fs << "}"; //DATASET_NAMES[di] } } fs << "}"; //results } else ts->printf(CvTS::LOG, "results were not written because file %s can not be opened\n", filename.c_str() );}void BaseQualityTest::processResults( int datasetIdx ){ if( isWriteGraphicsData ) writePlotData( datasetIdx );}void BaseQualityTest::processResults(){ if( isWriteParams ) writeAllDatasetsRunParams(); if( isWriteGraphicsData ) writeAveragePlotData(); int res = CvTS::OK; if( isWriteResults ) writeResults(); else { for( int di = 0; di < DATASETS_COUNT; di++ ) { if( isValidQualityEmpty(di) || isCalcQualityEmpty(di) ) continue; ts->printf(CvTS::LOG, "\nDataset: %s\n", DATASET_NAMES[di].c_str() ); for( int ci = 0; ci < TEST_CASE_COUNT; ci++ ) { ts->printf(CvTS::LOG, "case%d\n", ci); int currRes = processResults( di, ci ); res = currRes == CvTS::OK ? res : currRes; } } } if( res != CvTS::OK ) ts->printf(CvTS::LOG, "BAD ACCURACY\n"); ts->set_failed_test_info( res );}void BaseQualityTest::run ( int ){ readAlgorithm (); processRunParamsFile (); readResults(); int notReadDatasets = 0; int progress = 0; FileStorage runParamsFS( getRunParamsFilename(), FileStorage::READ ); isWriteParams = (! runParamsFS.isOpened()); FileNode topfn = runParamsFS.getFirstTopLevelNode(); FileNode defaultParams = topfn[DEFAULT_PARAMS]; readDefaultRunParams (defaultParams); for(int di = 0; di < DATASETS_COUNT; di++ ) { vector
imgs, Hs; if( !readDataset( DATASET_NAMES[di], Hs, imgs ) ) { calcQualityClear (di); ts->printf( CvTS::LOG, "images or homography matrices of dataset named %s can not be read\n", DATASET_NAMES[di].c_str()); notReadDatasets++; continue; } FileNode fn = topfn[DATASET_NAMES[di]]; readDatasetRunParams(fn, di); runDatasetTest (imgs, Hs, di, progress); processResults( di ); } if( notReadDatasets == DATASETS_COUNT ) { ts->printf(CvTS::LOG, "All datasets were not be read\n"); ts->set_failed_test_info( CvTS::FAIL_INVALID_TEST_DATA ); } else processResults(); runParamsFS.release();}class DetectorQualityTest : public BaseQualityTest{public: DetectorQualityTest( const char* _detectorName, const char* _testName ) : BaseQualityTest( _detectorName, _testName, "quality-of-detector" ) { validQuality.resize(DATASETS_COUNT); calcQuality.resize(DATASETS_COUNT); isSaveKeypoints.resize(DATASETS_COUNT); isActiveParams.resize(DATASETS_COUNT); isSaveKeypointsDefault = false; isActiveParamsDefault = false; }protected: using BaseQualityTest::readResults; using BaseQualityTest::writeResults; using BaseQualityTest::processResults; virtual string getRunParamsFilename() const; virtual string getResultsFilename() const; virtual string getPlotPath() const; virtual void validQualityClear( int datasetIdx ); virtual void calcQualityClear( int datasetIdx ); virtual void validQualityCreate( int datasetIdx ); virtual bool isValidQualityEmpty( int datasetIdx ) const; virtual bool isCalcQualityEmpty( int datasetIdx ) const; virtual void readResults( FileNode& fn, int datasetIdx, int caseIdx ); virtual void writeResults( FileStorage& fs, int datasetIdx, int caseIdx ) const; virtual void readDatasetRunParams( FileNode& fn, int datasetIdx ); virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const; virtual void setDefaultDatasetRunParams( int datasetIdx ); virtual void readDefaultRunParams( FileNode &fn ); virtual void writeDefaultRunParams( FileStorage &fs ) const; virtual void writePlotData( int di ) const; virtual void writeAveragePlotData() const; void openToWriteKeypointsFile( FileStorage& fs, int datasetIdx ); virtual void readAlgorithm( ); virtual void processRunParamsFile () {}; virtual void runDatasetTest( const vector
&imgs, const vector
&Hs, int di, int &progress ); virtual int processResults( int datasetIdx, int caseIdx ); Ptr
specificDetector; Ptr
defaultDetector; struct Quality { float repeatability; int correspondenceCount; }; vector
> validQuality; vector
> calcQuality; vector
isSaveKeypoints; vector
isActiveParams; bool isSaveKeypointsDefault; bool isActiveParamsDefault;};string DetectorQualityTest::getRunParamsFilename() const{ return string(ts->get_data_path()) + DETECTORS_DIR + algName + PARAMS_POSTFIX;}string DetectorQualityTest::getResultsFilename() const{ return string(ts->get_data_path()) + DETECTORS_DIR + algName + RES_POSTFIX;}string DetectorQualityTest::getPlotPath() const{ return string(ts->get_data_path()) + DETECTORS_DIR + "plots/";}void DetectorQualityTest::validQualityClear( int datasetIdx ){ validQuality[datasetIdx].clear();}void DetectorQualityTest::calcQualityClear( int datasetIdx ){ calcQuality[datasetIdx].clear();}void DetectorQualityTest::validQualityCreate( int datasetIdx ){ validQuality[datasetIdx].resize(TEST_CASE_COUNT);}bool DetectorQualityTest::isValidQualityEmpty( int datasetIdx ) const{ return validQuality[datasetIdx].empty();}bool DetectorQualityTest::isCalcQualityEmpty( int datasetIdx ) const{ return calcQuality[datasetIdx].empty();}void DetectorQualityTest::readResults( FileNode& fn, int datasetIdx, int caseIdx ){ validQuality[datasetIdx][caseIdx].repeatability = fn[REPEAT]; validQuality[datasetIdx][caseIdx].correspondenceCount = fn[CORRESP_COUNT];}void DetectorQualityTest::writeResults( FileStorage& fs, int datasetIdx, int caseIdx ) const{ fs << REPEAT << calcQuality[datasetIdx][caseIdx].repeatability; fs << CORRESP_COUNT << calcQuality[datasetIdx][caseIdx].correspondenceCount;}void DetectorQualityTest::readDefaultRunParams (FileNode &fn){ if (! fn.empty() ) { isSaveKeypointsDefault = (int)fn[IS_SAVE_KEYPOINTS] != 0; defaultDetector->read (fn); }}void DetectorQualityTest::writeDefaultRunParams (FileStorage &fs) const{ fs << IS_SAVE_KEYPOINTS << isSaveKeypointsDefault; defaultDetector->write (fs);}void DetectorQualityTest::readDatasetRunParams( FileNode& fn, int datasetIdx ){ isActiveParams[datasetIdx] = (int)fn[IS_ACTIVE_PARAMS] != 0; if (isActiveParams[datasetIdx]) { isSaveKeypoints[datasetIdx] = (int)fn[IS_SAVE_KEYPOINTS] != 0; specificDetector->read (fn); } else { setDefaultDatasetRunParams(datasetIdx); }}void DetectorQualityTest::writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const{ fs << IS_ACTIVE_PARAMS << isActiveParams[datasetIdx]; fs << IS_SAVE_KEYPOINTS << isSaveKeypoints[datasetIdx]; defaultDetector->write (fs);}void DetectorQualityTest::setDefaultDatasetRunParams( int datasetIdx ){ isSaveKeypoints[datasetIdx] = isSaveKeypointsDefault; isActiveParams[datasetIdx] = isActiveParamsDefault;}void DetectorQualityTest::writePlotData(int di ) const{ int imgXVals[] = { 2, 3, 4, 5, 6 }; // if scale, blur or light changes int viewpointXVals[] = { 20, 30, 40, 50, 60 }; // if viewpoint changes int jpegXVals[] = { 60, 80, 90, 95, 98 }; // if jpeg compression int* xVals = 0; if( !DATASET_NAMES[di].compare("ubc") ) { xVals = jpegXVals; } else if( !DATASET_NAMES[di].compare("graf") || !DATASET_NAMES[di].compare("wall") ) { xVals = viewpointXVals; } else xVals = imgXVals; stringstream rFilename, cFilename; rFilename << getPlotPath() << algName << "_" << DATASET_NAMES[di] << "_repeatability.csv"; cFilename << getPlotPath() << algName << "_" << DATASET_NAMES[di] << "_correspondenceCount.csv"; ofstream rfile(rFilename.str().c_str()), cfile(cFilename.str().c_str()); for( int ci = 0; ci < TEST_CASE_COUNT; ci++ ) { rfile << xVals[ci] << ", " << calcQuality[di][ci].repeatability << endl; cfile << xVals[ci] << ", " << calcQuality[di][ci].correspondenceCount << endl; }}void DetectorQualityTest::writeAveragePlotData() const{ stringstream rFilename, cFilename; rFilename << getPlotPath() << algName << "_average_repeatability.csv"; cFilename << getPlotPath() << algName << "_average_correspondenceCount.csv"; ofstream rfile(rFilename.str().c_str()), cfile(cFilename.str().c_str()); float avRep = 0, avCorCount = 0; for( int di = 0; di < DATASETS_COUNT; di++ ) { for( int ci = 0; ci < TEST_CASE_COUNT; ci++ ) { avRep += calcQuality[di][ci].repeatability; avCorCount += calcQuality[di][ci].correspondenceCount; } } avRep /= DATASETS_COUNT*TEST_CASE_COUNT; avCorCount /= DATASETS_COUNT*TEST_CASE_COUNT; rfile << algName << ", " << avRep << endl; cfile << algName << ", " << cvRound(avCorCount) << endl;}void DetectorQualityTest::openToWriteKeypointsFile( FileStorage& fs, int datasetIdx ){ string filename = string(ts->get_data_path()) + KEYPOINTS_DIR + algName + "_"+ DATASET_NAMES[datasetIdx] + ".xml.gz" ; fs.open(filename, FileStorage::WRITE); if( !fs.isOpened() ) ts->printf( CvTS::LOG, "keypoints can not be written in file %s because this file can not be opened\n", filename.c_str());}inline void writeKeypoints( FileStorage& fs, const vector
& keypoints, int imgIdx ){ if( fs.isOpened() ) { stringstream imgName; imgName << "img" << imgIdx; write( fs, imgName.str(), keypoints ); }}inline void readKeypoints( FileStorage& fs, vector
& keypoints, int imgIdx ){ assert( fs.isOpened() ); stringstream imgName; imgName << "img" << imgIdx; read( fs[imgName.str()], keypoints);}void DetectorQualityTest::readAlgorithm (){ defaultDetector = FeatureDetector::create( algName ); specificDetector = FeatureDetector::create( algName ); if( defaultDetector == 0 ) { ts->printf(CvTS::LOG, "Algorithm can not be read\n"); ts->set_failed_test_info( CvTS::FAIL_GENERIC); }}void DetectorQualityTest::runDatasetTest (const vector
&imgs, const vector
&Hs, int di, int &progress){ Ptr
detector = isActiveParams[di] ? specificDetector : defaultDetector; FileStorage keypontsFS; if( isSaveKeypoints[di] ) openToWriteKeypointsFile( keypontsFS, di ); calcQuality[di].resize(TEST_CASE_COUNT); vector
keypoints1; detector->detect( imgs[0], keypoints1 ); writeKeypoints( keypontsFS, keypoints1, 0); int progressCount = DATASETS_COUNT*TEST_CASE_COUNT; for( int ci = 0; ci < TEST_CASE_COUNT; ci++ ) { progress = update_progress( progress, di*TEST_CASE_COUNT + ci, progressCount, 0 ); vector
keypoints2; float rep; evaluateFeatureDetector( imgs[0], imgs[ci+1], Hs[ci], &keypoints1, &keypoints2, rep, calcQuality[di][ci].correspondenceCount, detector ); calcQuality[di][ci].repeatability = rep == -1 ? rep : 100.f*rep; writeKeypoints( keypontsFS, keypoints2, ci+1); }}void testLog( CvTS* ts, bool isBadAccuracy ){ if( isBadAccuracy ) ts->printf(CvTS::LOG, " bad accuracy\n"); else ts->printf(CvTS::LOG, "\n");}int DetectorQualityTest::processResults( int datasetIdx, int caseIdx ){ int res = CvTS::OK; bool isBadAccuracy; Quality valid = validQuality[datasetIdx][caseIdx], calc = calcQuality[datasetIdx][caseIdx]; const int countEps = 1 + cvRound( 0.005f*(float)valid.correspondenceCount ); const float rltvEps = 0.5f; ts->printf(CvTS::LOG, "%s: calc=%f, valid=%f", REPEAT.c_str(), calc.repeatability, valid.repeatability ); isBadAccuracy = (valid.repeatability - calc.repeatability) > rltvEps; testLog( ts, isBadAccuracy ); res = isBadAccuracy ? CvTS::FAIL_BAD_ACCURACY : res; ts->printf(CvTS::LOG, "%s: calc=%d, valid=%d", CORRESP_COUNT.c_str(), calc.correspondenceCount, valid.correspondenceCount ); isBadAccuracy = (valid.correspondenceCount - calc.correspondenceCount) > countEps; testLog( ts, isBadAccuracy ); res = isBadAccuracy ? CvTS::FAIL_BAD_ACCURACY : res; return res;}/****************************************************************************************\* Descriptors evaluation *\****************************************************************************************/const string RECALL = "recall";const string PRECISION = "precision";const string KEYPOINTS_FILENAME = "keypointsFilename";const string PROJECT_KEYPOINTS_FROM_1IMAGE = "projectKeypointsFrom1Image";const string MATCH_FILTER = "matchFilter";const string RUN_PARAMS_IS_IDENTICAL = "runParamsIsIdentical";const string ONE_WAY_TRAIN_DIR = "detectors_descriptors_evaluation/one_way_train_images/";const string ONE_WAY_IMAGES_LIST = "one_way_train_images.txt";class DescriptorQualityTest : public BaseQualityTest{public: enum{ NO_MATCH_FILTER = 0 }; DescriptorQualityTest( const char* _descriptorName, const char* _testName, const char* _matcherName = 0 ) : BaseQualityTest( _descriptorName, _testName, "quality-of-descriptor" ) { validQuality.resize(DATASETS_COUNT); calcQuality.resize(DATASETS_COUNT); calcDatasetQuality.resize(DATASETS_COUNT); commRunParams.resize(DATASETS_COUNT); commRunParamsDefault.projectKeypointsFrom1Image = true; commRunParamsDefault.matchFilter = NO_MATCH_FILTER; commRunParamsDefault.isActiveParams = false; if( _matcherName ) matcherName = _matcherName; }protected: using BaseQualityTest::readResults; using BaseQualityTest::writeResults; using BaseQualityTest::processResults; virtual string getRunParamsFilename() const; virtual string getResultsFilename() const; virtual string getPlotPath() const; virtual void validQualityClear( int datasetIdx ); virtual void calcQualityClear( int datasetIdx ); virtual void validQualityCreate( int datasetIdx ); virtual bool isValidQualityEmpty( int datasetIdx ) const; virtual bool isCalcQualityEmpty( int datasetIdx ) const; virtual void readResults( FileNode& fn, int datasetIdx, int caseIdx ); virtual void writeResults( FileStorage& fs, int datasetIdx, int caseIdx ) const; virtual void readDatasetRunParams( FileNode& fn, int datasetIdx ); // virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const; virtual void setDefaultDatasetRunParams( int datasetIdx ); virtual void readDefaultRunParams( FileNode &fn ); virtual void writeDefaultRunParams( FileStorage &fs ) const; virtual void readAlgorithm( ); virtual void processRunParamsFile () {}; virtual void runDatasetTest( const vector
&imgs, const vector
&Hs, int di, int &progress ); virtual int processResults( int datasetIdx, int caseIdx ); virtual void writePlotData( int di ) const; void calculatePlotData( vector
> &allMatches, vector
> &allCorrectMatchesMask, int di ); struct Quality { float recall; float precision; }; vector
> validQuality; vector
> calcQuality; vector
> calcDatasetQuality; struct CommonRunParams { string keypontsFilename; bool projectKeypointsFrom1Image; int matchFilter; // not used now bool isActiveParams; }; vector
commRunParams; Ptr
specificDescMatcher; Ptr
defaultDescMatcher; CommonRunParams commRunParamsDefault; string matcherName;};string DescriptorQualityTest::getRunParamsFilename() const{ return string(ts->get_data_path()) + DESCRIPTORS_DIR + algName + PARAMS_POSTFIX;}string DescriptorQualityTest::getResultsFilename() const{ return string(ts->get_data_path()) + DESCRIPTORS_DIR + algName + RES_POSTFIX;}string DescriptorQualityTest::getPlotPath() const{ return string(ts->get_data_path()) + DESCRIPTORS_DIR + "plots/";}void DescriptorQualityTest::validQualityClear( int datasetIdx ){ validQuality[datasetIdx].clear();}void DescriptorQualityTest::calcQualityClear( int datasetIdx ){ calcQuality[datasetIdx].clear();}void DescriptorQualityTest::validQualityCreate( int datasetIdx ){ validQuality[datasetIdx].resize(TEST_CASE_COUNT);}bool DescriptorQualityTest::isValidQualityEmpty( int datasetIdx ) const{ return validQuality[datasetIdx].empty();}bool DescriptorQualityTest::isCalcQualityEmpty( int datasetIdx ) const{ return calcQuality[datasetIdx].empty();}void DescriptorQualityTest::readResults( FileNode& fn, int datasetIdx, int caseIdx ){ validQuality[datasetIdx][caseIdx].recall = fn[RECALL]; validQuality[datasetIdx][caseIdx].precision = fn[PRECISION];}void DescriptorQualityTest::writeResults( FileStorage& fs, int datasetIdx, int caseIdx ) const{ fs << RECALL << calcQuality[datasetIdx][caseIdx].recall; fs << PRECISION << calcQuality[datasetIdx][caseIdx].precision;}void DescriptorQualityTest::readDefaultRunParams (FileNode &fn){ if (! fn.empty() ) { commRunParamsDefault.projectKeypointsFrom1Image = (int)fn[PROJECT_KEYPOINTS_FROM_1IMAGE] != 0; commRunParamsDefault.matchFilter = (int)fn[MATCH_FILTER]; defaultDescMatcher->read (fn); }}void DescriptorQualityTest::writeDefaultRunParams (FileStorage &fs) const{ fs << PROJECT_KEYPOINTS_FROM_1IMAGE << commRunParamsDefault.projectKeypointsFrom1Image; fs << MATCH_FILTER << commRunParamsDefault.matchFilter; defaultDescMatcher->write (fs);}void DescriptorQualityTest::readDatasetRunParams( FileNode& fn, int datasetIdx ){ commRunParams[datasetIdx].isActiveParams = (int)fn[IS_ACTIVE_PARAMS] != 0; if (commRunParams[datasetIdx].isActiveParams) { commRunParams[datasetIdx].keypontsFilename = (string)fn[KEYPOINTS_FILENAME]; commRunParams[datasetIdx].projectKeypointsFrom1Image = (int)fn[PROJECT_KEYPOINTS_FROM_1IMAGE] != 0; commRunParams[datasetIdx].matchFilter = (int)fn[MATCH_FILTER]; specificDescMatcher->read (fn); } else { setDefaultDatasetRunParams(datasetIdx); }}void DescriptorQualityTest::writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const{ fs << IS_ACTIVE_PARAMS << commRunParams[datasetIdx].isActiveParams; fs << KEYPOINTS_FILENAME << commRunParams[datasetIdx].keypontsFilename; fs << PROJECT_KEYPOINTS_FROM_1IMAGE << commRunParams[datasetIdx].projectKeypointsFrom1Image; fs << MATCH_FILTER << commRunParams[datasetIdx].matchFilter; defaultDescMatcher->write (fs);}void DescriptorQualityTest::setDefaultDatasetRunParams( int datasetIdx ){ commRunParams[datasetIdx] = commRunParamsDefault; commRunParams[datasetIdx].keypontsFilename = "SURF_" + DATASET_NAMES[datasetIdx] + ".xml.gz";}void DescriptorQualityTest::writePlotData( int di ) const{ stringstream filename; filename << getPlotPath() << algName << "_" << DATASET_NAMES[di] << ".csv"; FILE *file = fopen (filename.str().c_str(), "w"); size_t size = calcDatasetQuality[di].size(); for (size_t i=0;i
extractor = DescriptorExtractor::create( algName ); Ptr
matcher = DescriptorMatcher::create( matcherName ); defaultDescMatcher = new VectorDescriptorMatch( extractor, matcher ); specificDescMatcher = new VectorDescriptorMatch( extractor, matcher ); if( extractor == 0 || matcher == 0 ) { ts->printf(CvTS::LOG, "Algorithm can not be read\n"); ts->set_failed_test_info( CvTS::FAIL_GENERIC); } }}void DescriptorQualityTest::calculatePlotData( vector
> &allMatches, vector
> &allCorrectMatchesMask, int di ){ vector
recallPrecisionCurve; computeRecallPrecisionCurve( allMatches, allCorrectMatchesMask, recallPrecisionCurve ); calcDatasetQuality[di].clear(); const float resultPrecision = 0.5; bool isResultCalculated = false; const double eps = 1e-2; Quality initQuality; initQuality.recall = 0; initQuality.precision = 0; calcDatasetQuality[di].push_back( initQuality ); for( size_t i=0;i
&imgs, const vector
&Hs, int di, int &progress){ FileStorage keypontsFS( string(ts->get_data_path()) + KEYPOINTS_DIR + commRunParams[di].keypontsFilename, FileStorage::READ ); if( !keypontsFS.isOpened()) { calcQuality[di].clear(); ts->printf( CvTS::LOG, "keypoints from file %s can not be read\n", commRunParams[di].keypontsFilename.c_str() ); return; } Ptr
descMatch = commRunParams[di].isActiveParams ? specificDescMatcher : defaultDescMatcher; calcQuality[di].resize(TEST_CASE_COUNT); vector
keypoints1; readKeypoints( keypontsFS, keypoints1, 0); int progressCount = DATASETS_COUNT*TEST_CASE_COUNT; vector
> allMatches1to2; vector
> allCorrectMatchesMask; for( int ci = 0; ci < TEST_CASE_COUNT; ci++ ) { progress = update_progress( progress, di*TEST_CASE_COUNT + ci, progressCount, 0 ); vector
keypoints2; if( commRunParams[di].projectKeypointsFrom1Image ) { // TODO need to test function calcKeyPointProjections calcKeyPointProjections( keypoints1, Hs[ci], keypoints2 ); filterKeyPointsByImageSize( keypoints2, imgs[ci+1].size() ); } else readKeypoints( keypontsFS, keypoints2, ci+1 ); // TODO if( commRunParams[di].matchFilter ) vector
> matches1to2; vector
> correctMatchesMask; vector
recallPrecisionCurve; // not used because we need recallPrecisionCurve for // all images in dataset evaluateGenericDescriptorMatcher( imgs[0], imgs[ci+1], Hs[ci], keypoints1, keypoints2, &matches1to2, &correctMatchesMask, recallPrecisionCurve, descMatch ); allMatches1to2.insert( allMatches1to2.end(), matches1to2.begin(), matches1to2.end() ); allCorrectMatchesMask.insert( allCorrectMatchesMask.end(), correctMatchesMask.begin(), correctMatchesMask.end() ); } calculatePlotData( allMatches1to2, allCorrectMatchesMask, di );}int DescriptorQualityTest::processResults( int datasetIdx, int caseIdx ){ const float rltvEps = 0.001f; int res = CvTS::OK; bool isBadAccuracy; Quality valid = validQuality[datasetIdx][caseIdx], calc = calcQuality[datasetIdx][caseIdx]; ts->printf(CvTS::LOG, "%s: calc=%f, valid=%f", RECALL.c_str(), calc.recall, valid.recall ); isBadAccuracy = (valid.recall - calc.recall) > rltvEps; testLog( ts, isBadAccuracy ); res = isBadAccuracy ? CvTS::FAIL_BAD_ACCURACY : res; ts->printf(CvTS::LOG, "%s: calc=%f, valid=%f", PRECISION.c_str(), calc.precision, valid.precision ); isBadAccuracy = (valid.precision - calc.precision) > rltvEps; testLog( ts, isBadAccuracy ); res = isBadAccuracy ? CvTS::FAIL_BAD_ACCURACY : res; return res;}//--------------------------------- Calonder descriptor test --------------------------------------------class CalonderDescriptorQualityTest : public DescriptorQualityTest{public: CalonderDescriptorQualityTest() : DescriptorQualityTest( "Calonder", "quality-descriptor-calonder") {} virtual void readAlgorithm( ) { string classifierFile = string(ts->get_data_path()) + "/features2d/calonder_classifier.rtc"; defaultDescMatcher = new VectorDescriptorMatch( new CalonderDescriptorExtractor
( classifierFile ), new BruteForceMatcher
> ); specificDescMatcher = defaultDescMatcher; }};//--------------------------------- One Way descriptor test --------------------------------------------class OneWayDescriptorQualityTest : public DescriptorQualityTest{public: OneWayDescriptorQualityTest() : DescriptorQualityTest("ONEWAY", "quality-descriptor-one-way") { }protected: virtual void processRunParamsFile (); virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const;};void OneWayDescriptorQualityTest::processRunParamsFile (){ string filename = getRunParamsFilename(); FileStorage fs = FileStorage (filename, FileStorage::READ); FileNode fn = fs.getFirstTopLevelNode(); fn = fn[DEFAULT_PARAMS]; string pcaFilename = string(ts->get_data_path()) + (string)fn["pcaFilename"]; string trainPath = string(ts->get_data_path()) + (string)fn["trainPath"]; string trainImagesList = (string)fn["trainImagesList"]; int patch_width = fn["patchWidth"]; int patch_height = fn["patchHeight"]; Size patchSize = cvSize (patch_width, patch_height); int poseCount = fn["poseCount"]; if (trainImagesList.length () == 0 ) return; fs.release (); readAllDatasetsRunParams(); OneWayDescriptorBase *base = new OneWayDescriptorBase(patchSize, poseCount, pcaFilename, trainPath, trainImagesList); OneWayDescriptorMatch *match = new OneWayDescriptorMatch (); match->initialize( OneWayDescriptorMatch::Params (), base ); defaultDescMatcher = match; writeAllDatasetsRunParams();}void OneWayDescriptorQualityTest::writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const{ fs << IS_ACTIVE_PARAMS << commRunParams[datasetIdx].isActiveParams; fs << KEYPOINTS_FILENAME << commRunParams[datasetIdx].keypontsFilename; fs << PROJECT_KEYPOINTS_FROM_1IMAGE << commRunParams[datasetIdx].projectKeypointsFrom1Image; fs << MATCH_FILTER << commRunParams[datasetIdx].matchFilter;}// Detectors//DetectorQualityTest fastDetectorQuality = DetectorQualityTest( "FAST", "quality-detector-fast" );//DetectorQualityTest gfttDetectorQuality = DetectorQualityTest( "GFTT", "quality-detector-gftt" );//DetectorQualityTest harrisDetectorQuality = DetectorQualityTest( "HARRIS", "quality-detector-harris" );//DetectorQualityTest mserDetectorQuality = DetectorQualityTest( "MSER", "quality-detector-mser" );//DetectorQualityTest starDetectorQuality = DetectorQualityTest( "STAR", "quality-detector-star" );//DetectorQualityTest siftDetectorQuality = DetectorQualityTest( "SIFT", "quality-detector-sift" );//DetectorQualityTest surfDetectorQuality = DetectorQualityTest( "SURF", "quality-detector-surf" );// Descriptors//DescriptorQualityTest siftDescriptorQuality = DescriptorQualityTest( "SIFT", "quality-descriptor-sift", "BruteForce" );//DescriptorQualityTest surfDescriptorQuality = DescriptorQualityTest( "SURF", "quality-descriptor-surf", "BruteForce" );//DescriptorQualityTest fernDescriptorQualityTest( "FERN", "quality-descriptor-fern");//CalonderDescriptorQualityTest calonderDescriptorQualityTest;// Don't run it because of bug in OneWayDescriptorBase many to many matching. TODO: fix this bug.//OneWayDescriptorQualityTest oneWayDescriptorQuality;// Don't run them (will validate and save results as "quality-descriptor-sift" and "quality-descriptor-surf" test data).// TODO: differ result filenames.//DescriptorQualityTest siftL1DescriptorQuality = DescriptorQualityTest( "SIFT", "quality-descriptor-sift-L1", "BruteForce-L1" );//DescriptorQualityTest surfL1DescriptorQuality = DescriptorQualityTest( "SURF", "quality-descriptor-surf-L1", "BruteForce-L1" );//DescriptorQualityTest oppSiftL1DescriptorQuality = DescriptorQualityTest( "SIFT", "quality-descriptor-opponent-sift-L1", "BruteForce-L1" );//DescriptorQualityTest oppSurfL1DescriptorQuality = DescriptorQualityTest( "SURF", "quality-descriptor-opponent-surf-L1", "BruteForce-L1" );
------------------------------------------------------------------------          0. Directory contents                             ------------------------------------------------------------------------In this directory there is a data of tests evaluating detectors and descriptors.[detectors]    contains files with run parameters and results of detectors,     (detectorName)_params.xml and (detectorName)_res.xml respectively.[descriptors]    contains files with run parameters and results of descriptors,     (detectorName)_params.xml and (detectorName)_res.xml respectively.[images_datasets]    contains 8 folders with images datasets taken from http://www.robots.ox.ac.uk/~vgg/data/data-aff.html.    Each dataset consists from 6 images and 5 homography matrices.[keypoints_datasets]         contains files with keypoints calculated by detectors for each images dataset from [images_datasets],    e.g. surf_bark.xml contains 6 keypoints sets corresponding to 6 images of dataset "bark". These files    are written by the detector evaluation tests, but used in the description evaluation tests.    ------------------------------------------------------------------------          1. Detector evaluation tests------------------------------------------------------------------------These tests are regression tests of opencv_test system. The criterions of the detector repeatability implemented in these tests are described in "Scale & Affine Invariant Interest Point Detectors" and "A Comparison of Affine Region Detectors", Mikolajczyk et al.Repeatability criterions:1. repeating key points locations,2. repeating regions. There are two cases:    1.) scale invariant detector (the region is a circle),    2.) affine covariant detector (the region is an ellipse).Current test implementation supports case of the elliptic regions and close to MatLab code from http://www.robots.ox.ac.uk/~vgg/research/affine/evaluation.html#eval_soft. But KeyPoint class used for storing a key point detected by some inheritor of FeatureDetector (features2d.hpp) is a circular region. We assume that the circular region (KeyPoint) is a local case of elliptic region.TODO: affine adaptation.Detector quality:    1.) repeatability,    2.) correspondence count.Common run parameter of detectors:    
if 1 (true), then keypoints will be saved in directory [keypoints_datasets], if 0 (false), then keypoints will not be saved.Source: opencv/tests/cv/src/adetectordescriptor_evaluation.cpp DetectorQualityTest is a base class for the detector repeatability test. (DetectorName)DetectorQualityTest is a class for testing the corresponding detector.Testdata: The tests run the detectors on the images from [images_datasets] (as Mikolajczyk et al.). Run parameters and results are in [detectors]. Keypoints sets are saved in [keypoints_datasets].To run these tests:1. specify the test names in configure file of opencv_test system or using -tn parameter (see opencv_test --help). The test names: "quality-detector-fast" "quality-detector-gftt" (GFTT with useHarrisDetector == false) "quality-detector-harris" (GFTT with useHarrisDetector == false) "quality-detector-mser" "quality-detector-star" "quality-detector-sift" "quality-detector-surf"2. run opencv_test with spesified test names. There are two ways for this: using -f or -tn parameter of test system. The command line examples: 1st way) ./opencv_test -d /home/opencv_extra/testdata/cv -f /home/cvcfg.xml; 2nd way) ./opencv_test -d /home/opencv_extra/testdata/cv -tn "quality-detector-fast, quality-detector-gftt"If there is not the file with run parameters for some detector in the directory [detectors], the default values of these parameters will be used and written in such file.If there is not the file with quality results for some detector in the directory [detectors], the corresponding test will not be validate the results, but simply save them in such file.TODO: regression keipoints sets comparison (?).To add test for new detector:1. if new detector class is not an inheritor of FeatureDetector, wrap this class as inheritor of FeatureDetector,2. add object creation in factory function createDetector,3. create instance of DetectorQualityTest passing detector name and test name as parameters See existing detector test classes.To plot graphics of detector quality:1. Go to folder detector/plots. This folder contains data (csv files) written by the test and script createPlots.pneed to plot graphics of detectors quality. Both metrics are supported: repeatability and correspondences count. Detectors are evaluated on each image dataset (8) independently. Each dataset is used to assess detectors quality under some transformation: rotation+zoom, viewpoint changes, blur, light changes or JPEG compression.2. Run gnuplot on createPlots.p script, i.e. type "gnuplot createPlots.p".To add new detector evaluation in graphics modify createPlots.p.------------------------------------------------------------------------ 2. Description evaluation tests------------------------------------------------------------------------These tests are regression tests of opencv_test system. The criterion of the descriptor quality implemented in these tests are described in "A Performance Evaluation of LocalDedcriptors", Mikolajczyk et al.Descriptor quality: 1.) recall, 2.) precision.Common run parameters of descriptors:
is a keypoints filename located in [keypoints_datasets], e.g. surf_bark.xml.gz. The descriptors will be calculated for these keypoints. Such file may be written by detector evaluation test.
if 0 (false) then first image keypoints (from 6) will be projected on other images using homography matrix to evaluate the descriptor. If 1 (true) then all 6 keypoints sets calculated by some detector will be used to evaluate the descriptor.
is not supported now. This parameter specify a filter to matches, e.g. confidence of Lowe. Now one correspondence is found for each keypoint of first image and not filtered.TODO: support
.Source: opencv/tests/cv/src/adetectordescriptor_evaluation.cpp DescriptorQualityTest is a base class for the descriptor quality test.Testdata: The tests run the descriptors on the images from [images_datasets] (as Mikolajczyk et al.). Run parameters and results are in [descriptors]. Keypoints file must be in [keypoints_datasets]. To run these tests:1. specify the test names in configure file of opencv_test system or using -tn parameter (see opencv_test --help). The test names: "quality-descriptor-sift" "quality-descriptor-surf" "quality-descriptor-calonder" "quality-descriptor-fern"2. run opencv_test with spesified test names. There are two ways for this: using -f or -tn parameter of test system. The command line examples: 1st way) ./opencv_test -d /home/opencv_extra/testdata/cv -f /home/cvcfg.xml; 2nd way) ./opencv_test -d /home/opencv_extra/testdata/cv -tn "quality-descriptor-surf, quality-descriptor-calonder"If there is not the file with run parameters for some descriptor in the directory [descriptors], the default values of these parameters will be used and written in such file.If there is not the file with quality results for some descriptor in the directory [descriptors], the corresponding test will not be validate the results, but simply save them in such file.To add test for new descriptor:1. if new descriptor class is not an inheritor of DescriptorExtractor or GenericDescriptorMatch, wrap this class as inheritor of DescriptorExtractor or GenericDescriptorMatch,2. add object creation in factory function createDescriptorExtractor or createDescriptorMatcher,3. create instance of DescriptorQualityTest passing descriptor name and test name as parameters. See existing descriptor test classes.To plot graphics of descriptor quality:1. Run createPlots.sh. All evaluated descriptors are being added automatically.

 

转载地址:http://krgof.baihongyu.com/

你可能感兴趣的文章
C结构体、C++结构体、C++类的区别
查看>>
进程和线程的概念、区别和联系
查看>>
CMake 入门实战
查看>>
绑定CPU逻辑核心的利器——taskset
查看>>
Linux下perf性能测试火焰图只显示函数地址不显示函数名的问题
查看>>
c结构体、c++结构体和c++类的区别以及错误纠正
查看>>
Linux下查看根目录各文件内存占用情况
查看>>
A星算法详解(个人认为最详细,最通俗易懂的一个版本)
查看>>
利用栈实现DFS
查看>>
逆序对的数量(递归+归并思想)
查看>>
数的范围(二分查找上下界)
查看>>
算法导论阅读顺序
查看>>
Windows程序设计:直线绘制
查看>>
linux之CentOS下文件解压方式
查看>>
Django字段的创建并连接MYSQL
查看>>
div标签布局的使用
查看>>
HTML中表格的使用
查看>>
(模板 重要)Tarjan算法解决LCA问题(PAT 1151 LCA in a Binary Tree)
查看>>
(PAT 1154) Vertex Coloring (图的广度优先遍历)
查看>>
(PAT 1115) Counting Nodes in a BST (二叉查找树-统计指定层元素个数)
查看>>