diff --git a/README.md b/README.md index a7f9f6a..a17b79e 100644 --- a/README.md +++ b/README.md @@ -7,4 +7,80 @@ [![CodeQL](https://github.com/vladiant/CascadeClassifier/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/vladiant/CascadeClassifier/actions/workflows/codeql-analysis.yml) ## About -Haar Cascade Classifier implementation, tools and docs +Haar Cascade Classifier implementation, tools and docs. + +This repository revives the classic Viola–Jones cascade trainer that used +to ship with OpenCV (the legacy `opencv_traincascade` program plus its +companion utilities) so it can keep building against modern OpenCV +releases. It contains: + +- a stand-alone trainer library and CLI (`traincascade/`) that can train + a multi-stage cascade with Haar, LBP or HOG features; +- the original sample-preparation, annotation, detection and + visualisation utilities (`tools/`); +- documentation on the relevant command-line flags (`docs/`). + +## Repository layout + +| Path | Contents | +| ---- | -------- | +| `traincascade/` | Cascade trainer library (`lib/`), `traincascade` executable and unit tests (`test/`). | +| `traincascade/lib/include/` | Public headers, fully Doxygen-annotated. | +| `traincascade/lib/src/` | Implementation; the `o_*.cpp` files are extracted from OpenCV's legacy ML module and retain their original copyright headers. | +| `tools/createsamples/` | `opencv_createsamples` — generates `.vec` files of positives. | +| `tools/annotation/` | `opencv_annotation` — interactive bounding-box tool. | +| `tools/detection/Cpp/` | C++ sample that runs a trained cascade on an image. | +| `tools/detection/Python/`| Equivalent Python detection sample. | +| `tools/visualisation/` | `opencv_visualisation` — visualises the stages of a trained cascade. | +| `docs/` | Markdown documentation of CLI parameters. | +| `external/` | CMake helpers (e.g. `FindOpenCV.cmake`). | + +## Building + +The project uses CMake (>= 3.10) and depends on OpenCV (core, imgproc, +imgcodecs, ml, objdetect, highgui). + +```sh +cmake -S . -B build -G Ninja +cmake --build build +``` + +A coverage-enabled build is also available under `build-coverage/`. + +## Architecture overview + +The trainer is structured as a small hierarchy of legacy OpenCV ML +classes plus cascade-specific subclasses: + +``` +CvStatModel + └── CvDTree (CART decision tree, o_cvdtree.h) + └── CvBoostTree (boosting weak learner, o_cvboostree.h) + └── CvCascadeBoostTree (cascade-aware weak tree) + +CvStatModel + └── CvBoost (AdaBoost ensemble, o_cvboost.h) + └── CvCascadeBoost (single cascade stage, boost.h) + +CvCascadeClassifier (multi-stage trainer, cascadeclassifier.h) +``` + +Feature evaluation is decoupled through `CvFeatureEvaluator` +(`traincascade_features.h`) with concrete subclasses +`CvHaarEvaluator`, `CvLBPEvaluator` and `CvHOGEvaluator`. Sample +streaming is handled by `CvCascadeImageReader` (`imagestorage.h`). +Refer to the Doxygen comments in `traincascade/lib/include/` for class- +and method-level documentation. + +## Documentation + +* [Cascade trainer parameters](docs/traincascade_params.md) +* [createsamples parameters](docs/createsamples_params.md) +* [Useful links and references](docs/links.md) +* [Test-suite README](traincascade/test/README.md) + +## License + +See [LICENSE](LICENSE). Files derived from OpenCV's legacy ML module +(`traincascade/lib/src/o_*.cpp` and the tools under `tools/`) keep their +original Intel / OpenCV Foundation copyright headers. diff --git a/traincascade/lib/include/HOGfeatures.h b/traincascade/lib/include/HOGfeatures.h index 8a24817..987cad6 100644 --- a/traincascade/lib/include/HOGfeatures.h +++ b/traincascade/lib/include/HOGfeatures.h @@ -1,3 +1,14 @@ +/** + * @file HOGfeatures.h + * @brief Block-cell Histogram-of-Oriented-Gradients features. + * + * Each HOG feature here is a 2x2 block of cells; per cell the evaluator + * accumulates @c N_BINS oriented-gradient histograms, then L1-normalizes + * the four per-cell histograms by the block sum. The descriptor length + * therefore is @c N_BINS*N_CELLS = 36 floats, exposed by mapping the + * boosting variable index back to (featureIdx, componentIdx). + */ + #ifndef _OPENCV_HOGFEATURES_H_ #define _OPENCV_HOGFEATURES_H_ @@ -6,15 +17,29 @@ //#define TEST_INTHIST_BUILD //#define TEST_FEAT_CALC +/// Number of orientation bins per cell histogram. #define N_BINS 9 +/// Number of cells per block (2x2 grid). #define N_CELLS 4 #define HOGF_NAME "HOGFeatureParams" + +/// HOG-specific parameter struct; mostly delegates to @ref CvFeatureParams +/// after setting @c featSize = N_BINS*N_CELLS = 36. struct CvHOGFeatureParams : public CvFeatureParams { CvHOGFeatureParams(); }; +/** + * @brief HOG feature evaluator for cascade training. + * + * Stores @c N_BINS integral-histogram channels (@c hist) and an integral + * image of cell magnitudes (@c normSum) per sample. Each abstract + * "variable" the boosting trainer asks for is decoded as + * @c (varIdx / 36, varIdx % 36) into (feature, component) so a single + * feature contributes 36 boosting variables. + */ class CvHOGEvaluator : public CvFeatureEvaluator { public: @@ -25,25 +50,32 @@ class CvHOGEvaluator : public CvFeatureEvaluator float operator()(int varIdx, int sampleIdx) const override; void writeFeatures( cv::FileStorage &fs, const cv::Mat& featureMap ) const override; protected: + /// Enumerate every valid 2x2-block HOG feature for the current window. void generateFeatures() override; + /// Build the @p nbins integral histograms plus the L1-normalization + /// integral image @p norm from the input gray image @p img. virtual void integralHistogram(const cv::Mat &img, std::vector &histogram, cv::Mat &norm, int nbins) const; + + /// Geometry of a single HOG feature: a 2x2 grid of cell rectangles. class Feature { public: Feature(); Feature( int offset, int x, int y, int cellW, int cellH ); + /// Read one descriptor component (@p featComponent in [0, N_BINS*N_CELLS)). float calc( const std::vector &_hists, const cv::Mat &_normSum, size_t y, int featComponent ) const; void write( cv::FileStorage &fs ) const; void write( cv::FileStorage &fs, int varIdx ) const; cv::Rect rect[N_CELLS]; //cells + /// Precomputed corner offsets per cell into the integral histograms. struct { int p0, p1, p2, p3; } fastRect[N_CELLS]{}; }; - std::vector features; + std::vector features; ///< Generated HOG-feature catalog. cv::Mat normSum; //for normalization calculation (L1 or L2) std::vector hist; diff --git a/traincascade/lib/include/boost.h b/traincascade/lib/include/boost.h index aca30e9..a8a86e2 100644 --- a/traincascade/lib/include/boost.h +++ b/traincascade/lib/include/boost.h @@ -1,3 +1,16 @@ +/** + * @file boost.h + * @brief Cascade-stage boosting classifier and its parameter struct. + * + * Hosts the two cascade-specific subclasses derived from the legacy + * @c CvBoost / @c CvBoostParams machinery (see the @c o_cvboost*.h family): + * - @ref CvCascadeBoostParams adds the cascade-only knobs @c minHitRate + * and @c maxFalseAlarm to the boosting parameter set. + * - @ref CvCascadeBoost overrides the boosting training loop so it stops + * when those rates are met and exposes the per-stage decision threshold + * used at runtime. + */ + #ifndef _OPENCV_BOOST_H_ #define _OPENCV_BOOST_H_ @@ -10,44 +23,86 @@ #include "traincascade_features.h" -// CvCascadeBoostParams <- CvBoostParams <- CvCascadeBoostParams +/** + * @brief Parameters for a single cascade stage trained as a boosted ensemble. + * + * Extends @ref CvBoostParams (boost type, weak-count cap, weight-trim + * threshold, weak-tree max depth) with two cascade-specific targets: + * - @c minHitRate — minimum fraction of positives the stage must keep. + * - @c maxFalseAlarm — maximum fraction of negatives allowed through. + * + * Training stops as soon as both rates are satisfied, even if @c weak_count + * weak learners have not been added yet. + */ struct CvCascadeBoostParams : CvBoostParams { - float minHitRate; - float maxFalseAlarm; + float minHitRate; ///< Lower bound on the per-stage true-positive rate. + float maxFalseAlarm; ///< Upper bound on the per-stage false-positive rate. CvCascadeBoostParams(); CvCascadeBoostParams( int _boostType, float _minHitRate, float _maxFalseAlarm, double _weightTrimRate, int _maxDepth, int _maxWeakCount ); virtual ~CvCascadeBoostParams() {} + /// Persist parameters to an XML/YAML node (used by @c params.xml). void write( cv::FileStorage &fs ) const; + /// Restore parameters from a node; returns @c false on malformed input. bool read( const cv::FileNode &node ); virtual void printDefaults() const; virtual void printAttrs() const; + /// Parse one @c -name value command-line attribute. virtual bool scanAttr( const std::string prmName, const std::string val); }; -// CvCascadeBoost <- CvBoost <- CvStatModel +/** + * @brief Boosted ensemble representing a single cascade stage. + * + * Built on top of OpenCV's legacy @c CvBoost; the cascade trainer adds + * weak learners until either @c minHitRate / @c maxFalseAlarm are reached + * or the configured weak-count cap is hit. After training the stage stores + * a real-valued decision @c threshold tuned so the desired hit rate is met + * on the working positive set. + * + * At runtime the stage accepts a sample iff the sum of weak responses + * exceeds @c threshold. + */ class CvCascadeBoost : public CvBoost { public: + /** + * @brief Train one cascade stage on the current working sample set. + * @param _featureEvaluator Feature evaluator already populated with images. + * @param _numSamples Total number of samples (positives + negatives). + * @param _precalcValBufSize Buffer size (MB) for cached feature values. + * @param _precalcIdxBufSize Buffer size (MB) for cached sorted indices. + * @param _params Boosting + cascade rate targets. + * @return @c true once both rate targets are satisfied. + */ bool train( const CvFeatureEvaluator* _featureEvaluator, int _numSamples, int _precalcValBufSize, int _precalcIdxBufSize, const CvCascadeBoostParams& _params=CvCascadeBoostParams() ); + /// Evaluate the stage on sample @p sampleIdx. When @p returnSum is true + /// returns the raw weak-response sum, otherwise the binary 0/1 decision. float predict( int sampleIdx, bool returnSum = false ) const; + /// Decision threshold tuned to satisfy @c minHitRate. float getThreshold() const { return threshold; } + /// Serialize the stage; @p featureMap remaps used feature indices to compact ids. void write( cv::FileStorage &fs, const cv::Mat& featureMap ) const; + /// Restore the stage from a previously written XML node. bool read( const cv::FileNode &node, const CvFeatureEvaluator* _featureEvaluator, const CvCascadeBoostParams& _params ); + /// Mark every feature this stage references as used in @p featureMap. void markUsedFeaturesInMap( cv::Mat& featureMap ); private: + /// Re-evaluate the stage on the working set and return @c true once + /// the cascade rate targets are met. bool isErrDesired(); bool set_params( const CvBoostParams& _params ) override; + /// Update boosting sample weights after appending a new weak tree. void update_weights( CvBoostTree* tree );// override; - float threshold; - float minHitRate, maxFalseAlarm; + float threshold; ///< Decision threshold tuned per stage. + float minHitRate, maxFalseAlarm; ///< Targets copied from CvCascadeBoostParams. }; #endif diff --git a/traincascade/lib/include/cascadeclassifier.h b/traincascade/lib/include/cascadeclassifier.h index 62bed6a..453e3d6 100644 --- a/traincascade/lib/include/cascadeclassifier.h +++ b/traincascade/lib/include/cascadeclassifier.h @@ -1,3 +1,18 @@ +/** + * @file cascadeclassifier.h + * @brief Top-level cascade classifier training driver. + * + * Defines @ref CvCascadeParams (per-cascade settings: feature type, window + * size, stage type) and @ref CvCascadeClassifier, the orchestrator that + * drives the multi-stage training loop. The class iterates over stages, + * trains a @ref CvCascadeBoost per stage, samples positives/negatives + * through @ref CvCascadeImageReader and writes the resulting cascade + * (along with @c params.xml and @c cascade.xml) to disk. + * + * String constants prefixed @c CC_ are XML node names used by the cascade + * file format and shared with OpenCV's runtime detector loader. + */ + #ifndef _OPENCV_CASCADECLASSIFIER_H_ #define _OPENCV_CASCADECLASSIFIER_H_ @@ -8,7 +23,9 @@ #include "HOGfeatures.h" //new #include "boost.h" +/// Default file name of the trained cascade written into the output directory. #define CC_CASCADE_FILENAME "cascade.xml" +/// File name used to persist the training parameter set, allowing a session to be resumed. #define CC_PARAMS_FILENAME "params.xml" #define CC_CASCADE_PARAMS "cascadeParams" @@ -55,36 +72,88 @@ #define CC_HOG "HOG" +/// Cross-platform wall-clock timer macro used by the training driver to +/// stamp elapsed-time messages. Resolves to @c clock() on Windows and +/// @c time() elsewhere so the trainer compiles without POSIX-only headers. #ifdef _WIN32 #define TIME( arg ) (((double) clock()) / CLOCKS_PER_SEC) #else #define TIME( arg ) (time( arg )) #endif +/** + * @brief Top-level parameters describing the cascade as a whole. + * + * Captures the high-level choices that apply to every stage: the boosting + * variant used at each stage (@c stageType), the feature family + * (@c featureType, one of HAAR/LBP/HOG via @ref CvFeatureParams) and the + * detection window size (@c winSize). Values are persisted to and loaded + * from the @c params.xml / @c cascade.xml file alongside per-stage data. + */ class CvCascadeParams : public CvParams { public: - enum { BOOST = 0 }; + enum { BOOST = 0 }; ///< Supported stage types (currently only boosting). static const int defaultStageType = BOOST; static const int defaultFeatureType = CvFeatureParams::HAAR; CvCascadeParams(); CvCascadeParams( int _stageType, int _featureType ); + /// Serialize the cascade-level parameters to an open @c FileStorage node. void write( cv::FileStorage &fs ) const override; + /// Restore parameters from an XML/YAML node; returns @c false on malformed input. bool read( const cv::FileNode &node ) override; void printDefaults() const override; void printAttrs() const override; + /// Parse a single command-line attribute (e.g. @c -featureType, @c -w, @c -h). bool scanAttr( const std::string prmName, const std::string val ) override; - int stageType; - int featureType; - cv::Size winSize; + int stageType; ///< Which stage classifier flavor to train (currently always BOOST). + int featureType; ///< Feature family: HAAR, LBP or HOG (see @ref CvFeatureParams). + cv::Size winSize; ///< Sub-window size used to crop positives and scan negatives. }; +/** + * @brief Driver class that trains a multi-stage Haar/LBP/HOG cascade. + * + * The cascade-training loop is implemented in @ref train: for each stage + * the trainer (1) refreshes the working sample set with positives still + * accepted by the previous stages and freshly mined negatives, (2) trains + * a single @ref CvCascadeBoost so its true-positive rate meets + * @c minHitRate while staying below @c maxFalseAlarm, and (3) appends the + * resulting weak ensemble to @c stageClassifiers. Intermediate state is + * checkpointed under @c cascadeDirName so interrupted training can resume. + * + * After all @c numStages have been completed the cascade is exported via + * @ref save in either the modern @c cascade.xml format or the legacy + * "baseFormatSave" layout produced by older OpenCV versions. + */ class CvCascadeClassifier { public: + /** + * @brief Train a complete cascade and write it to disk. + * + * @param _cascadeDirName Output directory; receives intermediate XML + * files plus the final @c cascade.xml. + * @param _posFilename Path to the @c .vec file produced by + * opencv_createsamples that holds positive samples. + * @param _negFilename Path to a text file listing background images. + * @param _numPos Number of positives to consume per stage. + * @param _numNeg Number of false positives to mine per stage. + * @param _precalcValBufSize Buffer size (MB) for precomputed feature values. + * @param _precalcIdxBufSize Buffer size (MB) for precomputed sorted indices. + * @param _numStages Maximum number of cascade stages to train. + * @param _cascadeParams Cascade-wide parameters (window size, feature type). + * @param _featureParams Feature-family-specific parameters. + * @param _stageParams Boosting parameters used by every stage. + * @param baseFormatSave When @c true also writes the legacy XML layout. + * @param acceptanceRatioBreakValue Stop training early if the running + * acceptance ratio falls below this threshold; @c -1 disables it. + * @return @c true on successful training, @c false if the trainer could + * not collect enough positive/negative samples for some stage. + */ bool train( const std::string& _cascadeDirName, const std::string& _posFilename, const std::string& _negFilename, @@ -97,10 +166,18 @@ class CvCascadeClassifier bool baseFormatSave = false, double acceptanceRatioBreakValue = -1.0 ); private: + /// Run @p sampleIdx through every already-trained stage and report + /// whether all stages accepted it (1) or some rejected it (0). int predict( int sampleIdx ); + /// Persist the cascade to @p cascadeDirName; @p baseFormat selects the legacy XML layout. void save( const std::string& cascadeDirName, bool baseFormat = false ); + /// Resume an interrupted training session from @c params.xml / stage XMLs. bool load( const std::string& cascadeDirName ); + /// Refill the working sample set for the next stage; rejects negatives + /// the cascade already discards. @p acceptanceRatio reports how rare + /// usable negatives have become (used by the early-stop check). bool updateTrainingSet( double minimumAcceptanceRatio, double& acceptanceRatio ); + /// Mine a contiguous block of samples that pass the current cascade. int fillPassedSamples( int first, int count, bool isPositive, double requiredAcceptanceRatio, int64& consumed ); void writeParams( cv::FileStorage &fs ) const; @@ -109,15 +186,17 @@ class CvCascadeClassifier bool readParams( const cv::FileNode &node ); bool readStages( const cv::FileNode &node ); + /// Build a dense remapping from global feature indices to the subset + /// that any stage actually selected; used to compact the saved cascade. void getUsedFeaturesIdxMap( cv::Mat& featureMap ); - CvCascadeParams cascadeParams; - cv::Ptr featureParams; - cv::Ptr stageParams; + CvCascadeParams cascadeParams; ///< Cascade-wide parameters. + cv::Ptr featureParams; ///< Feature-family parameters. + cv::Ptr stageParams; ///< Boosting parameters shared by all stages. - cv::Ptr featureEvaluator; - std::vector< cv::Ptr > stageClassifiers; - CvCascadeImageReader imgReader; + cv::Ptr featureEvaluator; ///< Computes feature responses on cached images. + std::vector< cv::Ptr > stageClassifiers; ///< Trained stages in order of evaluation. + CvCascadeImageReader imgReader; ///< Streaming source of positives and negatives. int numStages, curNumSamples; int numPos, numNeg; }; diff --git a/traincascade/lib/include/haarfeatures.h b/traincascade/lib/include/haarfeatures.h index c25a945..32b3efa 100644 --- a/traincascade/lib/include/haarfeatures.h +++ b/traincascade/lib/include/haarfeatures.h @@ -1,11 +1,33 @@ +/** + * @file haarfeatures.h + * @brief Haar-like rectangle features used by Viola–Jones-style cascades. + * + * Implements @ref CvHaarFeatureParams (with @c BASIC / @c CORE / @c ALL + * feature-set modes) and @ref CvHaarEvaluator. A Haar feature is a sum of + * up to @ref CV_HAAR_FEATURE_MAX weighted rectangles whose responses are + * read from an integral image (and a 45°-tilted integral image when + * @c tilted is set), then divided by a per-window normalization factor + * to gain illumination invariance. + */ + #ifndef _OPENCV_HAARFEATURES_H_ #define _OPENCV_HAARFEATURES_H_ #include "traincascade_features.h" +/// Maximum number of weighted rectangles composing a single Haar feature. #define CV_HAAR_FEATURE_MAX 3 #define HFP_NAME "haarFeatureParams" + +/** + * @brief Parameters specific to Haar feature generation. + * + * @c mode picks the feature catalog density: + * - @c BASIC: only upright Viola–Jones features; + * - @c CORE: full upright catalog; + * - @c ALL: upright + 45°-tilted features (largest catalog). + */ class CvHaarFeatureParams : public CvFeatureParams { public: @@ -25,9 +47,17 @@ class CvHaarFeatureParams : public CvFeatureParams void printAttrs() const override; bool scanAttr( const std::string prm, const std::string val) override; - int mode; + int mode; ///< Feature-catalog density: BASIC, CORE or ALL. }; +/** + * @brief Evaluator that computes Haar feature responses from integral images. + * + * On @ref setImage the evaluator stores the sample's integral image in + * @c sum (and tilted integral image in @c tilted when needed) plus the + * normalization factor in @c normfactor. @ref operator() then evaluates + * the geometry stored in @c features[featureIdx] in O(rects) time. + */ class CvHaarEvaluator : public CvFeatureEvaluator { public: @@ -36,10 +66,13 @@ class CvHaarEvaluator : public CvFeatureEvaluator void setImage(const cv::Mat& img, uchar clsLabel, int idx) override; float operator()(int featureIdx, int sampleIdx) const override; void writeFeatures( cv::FileStorage &fs, const cv::Mat& featureMap ) const override; + /// Write a single feature using the legacy XML schema (for compatibility). void writeFeature( cv::FileStorage &fs, int fi ) const; // for old file fornat protected: + /// Enumerate every valid Haar feature for the configured @c winSize / mode. void generateFeatures() override; + /// Geometry of one Haar feature: up to three weighted rectangles, optionally tilted. class Feature { public: @@ -48,23 +81,25 @@ class CvHaarEvaluator : public CvFeatureEvaluator int x0, int y0, int w0, int h0, float wt0, int x1, int y1, int w1, int h1, float wt1, int x2 = 0, int y2 = 0, int w2 = 0, int h2 = 0, float wt2 = 0.0F ); + /// Compute the unnormalized feature response for sample @p y. float calc( const cv::Mat &sum, const cv::Mat &tilted, size_t y) const; void write( cv::FileStorage &fs ) const; - bool tilted; + bool tilted; ///< @c true when reading from the tilted integral image. struct { cv::Rect r; float weight; } rect[CV_HAAR_FEATURE_MAX]; + /// Precomputed corner offsets for each rectangle into the row-stride flattening. struct { int p0, p1, p2, p3; } fastRect[CV_HAAR_FEATURE_MAX]{}; }; - std::vector features; + std::vector features; ///< Generated feature catalog. cv::Mat sum; /* sum images (each row represents image) */ cv::Mat tilted; /* tilted sum images (each row represents image) */ cv::Mat normfactor; /* normalization factor */ diff --git a/traincascade/lib/include/imagestorage.h b/traincascade/lib/include/imagestorage.h index b4b81c8..4c3dafe 100644 --- a/traincascade/lib/include/imagestorage.h +++ b/traincascade/lib/include/imagestorage.h @@ -1,3 +1,21 @@ +/** + * @file imagestorage.h + * @brief Streaming readers for positive and negative training samples. + * + * @ref CvCascadeImageReader bundles two specialized streams used during + * cascade training: + * - @c PosReader iterates through the binary @c .vec file produced by + * @c opencv_createsamples; each record is a fixed-size sub-window + * image of dimension @c winSize. + * - @c NegReader walks a list of background images, scanning each at + * multiple scales and offsets to mine sub-windows for use as negative + * samples. + * + * Both readers expose a uniform `bool get(cv::Mat&)` interface so the + * trainer can request the next positive/negative without caring about + * the underlying storage format. + */ + #ifndef _OPENCV_IMAGESTORAGE_H_ #define _OPENCV_IMAGESTORAGE_H_ @@ -5,15 +23,28 @@ #include +/** + * @brief Pair of streaming sample readers used by the cascade trainer. + * + * After @ref create both @c posReader and @c negReader can be queried + * via @ref getPos / @ref getNeg until they exhaust their respective + * sources. @ref restart resets the positive stream so a new stage can + * iterate it again from the beginning. + */ class CvCascadeImageReader { public: + /// Open both streams. @p _winSize is the cascade detection window. bool create( const std::string& _posFilename, const std::string& _negFilename, cv::Size _winSize ); + /// Rewind the positive stream to its first record. void restart() { posReader.restart(); } + /// Fetch the next negative sub-window; returns @c false when exhausted. bool getNeg(cv::Mat &_img) { return negReader.get( _img ); } + /// Fetch the next positive sub-window; returns @c false when exhausted. bool getPos(cv::Mat &_img) { return posReader.get( _img ); } private: + /// Reader over the binary @c .vec file produced by @c opencv_createsamples. class PosReader { public: @@ -23,14 +54,16 @@ class CvCascadeImageReader bool get( cv::Mat &_img ); void restart(); - short* vec; - FILE* file; - int count; - int vecSize; - int last; - int base; + short* vec; ///< Buffer holding one decoded sample. + FILE* file; ///< Underlying @c .vec file handle. + int count; ///< Number of records in the file. + int vecSize; ///< Pixels per record (= winSize.area()). + int last; ///< Index of the last delivered record. + int base; ///< Offset of the first record after the header. } posReader; + /// Reader over a list of background images, sliding a sub-window + /// over each at multiple scales. class NegReader { public: @@ -40,13 +73,13 @@ class CvCascadeImageReader bool nextImg(); cv::Mat src, img; - std::vector imgFilenames; + std::vector imgFilenames; ///< Lines from the bg list file. cv::Point offset, point; float scale; - float scaleFactor; - float stepFactor; + float scaleFactor; ///< Per-step scale multiplier (>1 for shrinking). + float stepFactor; ///< Sliding-window step relative to @c winSize. size_t last, round; - cv::Size winSize; + cv::Size winSize; ///< Detection window size copied from the cascade. } negReader; }; diff --git a/traincascade/lib/include/lbpfeatures.h b/traincascade/lib/include/lbpfeatures.h index 10cefc3..4392e99 100644 --- a/traincascade/lib/include/lbpfeatures.h +++ b/traincascade/lib/include/lbpfeatures.h @@ -1,15 +1,38 @@ +/** + * @file lbpfeatures.h + * @brief Local Binary Pattern (LBP) features for cascade training. + * + * Defines @ref CvLBPFeatureParams (a thin wrapper that fixes + * @c maxCatCount = 256 — LBP is categorical) and @ref CvLBPEvaluator, + * which computes 8-bit LBP codes by comparing the integral-image sum of + * the central cell with each of its eight neighbours in a 3×3 grid of + * equal-sized rectangles. Compared to Haar features LBP is faster to + * evaluate but produces categorical responses (0..255). + */ + #ifndef _OPENCV_LBPFEATURES_H_ #define _OPENCV_LBPFEATURES_H_ #include "traincascade_features.h" #define LBPF_NAME "lbpFeatureParams" + +/// LBP-specific parameter struct; LBP responses are categorical so the +/// constructor sets @c maxCatCount appropriately. struct CvLBPFeatureParams : CvFeatureParams { CvLBPFeatureParams(); }; +/** + * @brief Evaluator for block-based LBP features. + * + * Each feature compares the central cell's pixel sum with its eight + * neighbours in a 3x3 block grid; the outcome is encoded as an 8-bit + * pattern. The class caches per-sample integral images in @c sum and + * decodes feature geometry through @c features[featureIdx]. + */ class CvLBPEvaluator : public CvFeatureEvaluator { public: @@ -17,26 +40,31 @@ class CvLBPEvaluator : public CvFeatureEvaluator void init(const CvFeatureParams *_featureParams, int _maxSampleCount, cv::Size _winSize ) override; void setImage(const cv::Mat& img, uchar clsLabel, int idx) override; + /// Return the 8-bit LBP code (0..255) cast to float. float operator()(int featureIdx, int sampleIdx) const override { return (float)features[featureIdx].calc( sum, sampleIdx); } void writeFeatures( cv::FileStorage &fs, const cv::Mat& featureMap ) const override; protected: + /// Enumerate every valid 3×3-block LBP feature for the configured window. void generateFeatures() override; + /// Geometry of one LBP feature: rectangle covering the 3×3 grid plus + /// 16 cached corner offsets (one per cell corner) into the integral image. class Feature { public: Feature(); Feature( int offset, int x, int y, int _block_w, int _block_h ); + /// Compute the 8-bit LBP code on sample row @p y. uchar calc( const cv::Mat& _sum, size_t y ) const; void write( cv::FileStorage &fs ) const; cv::Rect rect; int p[16]{}; }; - std::vector features; + std::vector features; ///< Generated feature catalog. - cv::Mat sum; + cv::Mat sum; ///< Cached integral images (one per sample, one per row). }; inline uchar CvLBPEvaluator::Feature::calc(const cv::Mat &_sum, size_t y) const diff --git a/traincascade/lib/include/o_blockedrange.h b/traincascade/lib/include/o_blockedrange.h index 75089a3..0e2f606 100644 --- a/traincascade/lib/include/o_blockedrange.h +++ b/traincascade/lib/include/o_blockedrange.h @@ -1,25 +1,46 @@ +/** + * @file o_blockedrange.h + * @brief Lightweight serial replacement for OpenCV / TBB parallel primitives. + * + * The cascade trainer was originally written against TBB's + * @c blocked_range / @c parallel_for / @c parallel_reduce. This header + * provides a serial drop-in so the library can build without a parallel + * runtime; the @c BlockedRange object simply describes a half-open + * integer interval [begin, end) and the @c parallel_* helpers run the + * supplied @c Body in the calling thread. + */ #pragma once -// DTreeBestSplitFinder +/// Half-open integer range used as the iteration domain for @ref parallel_for / @ref parallel_reduce. class BlockedRange { public: BlockedRange() : _begin(0), _end(0), _grainsize(0) {} BlockedRange(int b, int e, int g = 1) : _begin(b), _end(e), _grainsize(g) {} int begin() const { return _begin; } int end() const { return _end; } + /// Approximate chunk size used by parallel runtimes; ignored by this serial impl. int grainsize() const { return _grainsize; } protected: int _begin, _end, _grainsize; }; +/** + * @brief Serial replacement for @c tbb::parallel_for: invokes @p body once + * with the full @p range. + */ template static inline void parallel_for(const BlockedRange& range, const Body& body) { body(range); } +/// Placeholder type kept for API compatibility with TBB's @c split tag. class Split {}; +/** + * @brief Serial replacement for @c tbb::parallel_reduce: invokes @p body + * once with the full @p range and never calls @c join. + */ template static inline void parallel_reduce(const BlockedRange& range, Body& body) { body(range); diff --git a/traincascade/lib/include/o_cvboost.h b/traincascade/lib/include/o_cvboost.h index 09fa405..af11f07 100644 --- a/traincascade/lib/include/o_cvboost.h +++ b/traincascade/lib/include/o_cvboost.h @@ -1,3 +1,13 @@ +/** + * @file o_cvboost.h + * @brief Legacy OpenCV @c CvBoost: AdaBoost ensemble of decision trees. + * + * This is the canonical boosting machinery the cascade trainer extends. + * It maintains the per-sample weight vector, holds the ordered list of + * weak learners (decision trees implemented by + * @ref CvBoostTree), and offers the standard predict/clear/getters + * exposed by every legacy @c CvStatModel. + */ #pragma once #include @@ -7,27 +17,42 @@ struct CvDTreeTrainData; -// CvCascadeBoost +/** + * @brief AdaBoost ensemble of small decision trees. + * + * Used unchanged inside the cascade as @ref CvCascadeBoost's base class. + * Configuration is passed via @ref CvBoostParams (boost variant, weak + * count, weight trim rate, ...). Concrete weak learners are + * @ref CvBoostTree instances stored in the @c weak sequence. + */ class CvBoost : public CvStatModel { public: - // Boosting type + /// Boosting variant used during training. enum { DISCRETE = 0, REAL = 1, LOGIT = 2, GENTLE = 3 }; - // Splitting criteria + /// Per-split scoring criterion used by individual weak trees. enum { DEFAULT = 0, GINI = 1, MISCLASS = 3, SQERR = 4 }; CvBoost(); virtual ~CvBoost(); + /** + * @brief One-shot constructor: build a model and immediately train it + * on the supplied data set. + * @see CvDTree::train for the meaning of the data-related parameters. + */ CvBoost(const CvMat* trainData, int tflag, const CvMat* responses, const CvMat* varIdx = 0, const CvMat* sampleIdx = 0, const CvMat* varType = 0, const CvMat* missingDataMask = 0, CvBoostParams params = CvBoostParams()); + /// Drop weak learners whose indices fall in @p slice. virtual void prune(CvSlice slice); + /// Release every internal buffer (weights, weak learners, working data). void clear() override; + /// Return the (typed) sequence of weak learners. CvSeq* get_weak_predictors(); CvMat* get_weights(); @@ -36,14 +61,16 @@ class CvBoost : public CvStatModel { const CvBoostParams& get_params() const; protected: + /// Validate and copy parameters; called by every public train overload. virtual bool set_params(const CvBoostParams& params); + /// Apply weight trimming so very-low-weight samples are ignored next round. virtual void trim_weights(); CvDTreeTrainData* data; CvMat train_data_hdr{}, responses_hdr{}; cv::Mat train_data_mat, responses_mat; CvBoostParams params; - CvSeq* weak; + CvSeq* weak; ///< Trained weak learners in order of evaluation. CvMat* active_vars; CvMat* active_vars_abs; @@ -53,7 +80,7 @@ class CvBoost : public CvStatModel { CvMat* sum_response; CvMat* weak_eval; CvMat* subsample_mask; - CvMat* weights; + CvMat* weights; ///< Current per-sample boosting weights. CvMat* subtree_weights; bool have_subsample; }; diff --git a/traincascade/lib/include/o_cvboostparams.h b/traincascade/lib/include/o_cvboostparams.h index 4761f26..afe34d1 100644 --- a/traincascade/lib/include/o_cvboostparams.h +++ b/traincascade/lib/include/o_cvboostparams.h @@ -1,13 +1,27 @@ +/** + * @file o_cvboostparams.h + * @brief Parameters of OpenCV's legacy boosted-tree ensemble (@c CvBoost). + */ #pragma once #include "o_cvdtreeparams.h" -// CvCascadeBoostParams +/** + * @brief Hyperparameters for boosted decision-tree training. + * + * Inherits the decision-tree settings from @ref CvDTreeParams (max + * depth, min sample count per leaf, etc.) and adds: + * - @c boost_type: variant — DISCRETE, REAL, LOGIT or GENTLE AdaBoost. + * - @c weak_count: maximum number of weak learners in the ensemble. + * - @c split_criteria: per-split scoring (Gini, misclassification, SSE...). + * - @c weight_trim_rate: discard low-weight samples whose cumulative + * weight is below this rate to speed up training. + */ struct CvBoostParams : public CvDTreeParams { - int boost_type; - int weak_count; - int split_criteria; - double weight_trim_rate; + int boost_type; ///< AdaBoost variant (see @ref CvBoost). + int weak_count; ///< Maximum number of weak learners. + int split_criteria; ///< Split scoring criterion (Gini, misclass, SSE, ...). + double weight_trim_rate; ///< Cumulative-weight trimming threshold. CvBoostParams(); CvBoostParams(int boost_type, int weak_count, double weight_trim_rate, diff --git a/traincascade/lib/include/o_cvboostree.h b/traincascade/lib/include/o_cvboostree.h index cb3638d..dfec8cd 100644 --- a/traincascade/lib/include/o_cvboostree.h +++ b/traincascade/lib/include/o_cvboostree.h @@ -1,19 +1,36 @@ +/** + * @file o_cvboostree.h + * @brief Decision-tree weak learner used by @ref CvBoost / @ref CvCascadeBoost. + */ #pragma once #include "o_cvdtree.h" class CvBoost; -// CvBoost, CvCascadeBoostTree +/** + * @brief Single decision tree trained as a weak learner inside a boosted + * ensemble. + * + * Inherits @ref CvDTree's split-finding machinery and overrides the + * value-computation hooks (@c calc_node_value, @c calc_node_dir, + * @c find_split_*) so the tree minimizes the weighted objective + * implied by the parent ensemble's boosting variant. The standalone + * @ref CvDTree::train overloads are kept around as no-ops to avoid + * compiler warnings; only the @c train(trainData, idx, ensemble) + * overload should be called from outside. + */ class CvBoostTree : public CvDTree { public: CvBoostTree(); virtual ~CvBoostTree(); using CvDTree::train; + /// Train this weak learner against the boosting state held by @p ensemble. bool train(CvDTreeTrainData* trainData, const CvMat* subsample_idx, CvBoost* ensemble); + /// Multiply every leaf value by @p s (used by REAL/LOGIT AdaBoost). virtual void scale(double s); void clear() override; @@ -47,8 +64,10 @@ class CvBoostTree : public CvDTree { float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0) override; + /// Compute the leaf prediction using the boosting weights. void calc_node_value(CvDTreeNode* n) override; + /// Compute the routing direction (left/right majority) for node @p n. double calc_node_dir(CvDTreeNode* n) override; - CvBoost* ensemble; + CvBoost* ensemble; ///< Non-owning back-pointer to the parent ensemble. }; diff --git a/traincascade/lib/include/o_cvcascadeboosttraindata.h b/traincascade/lib/include/o_cvcascadeboosttraindata.h index a84a035..5417894 100644 --- a/traincascade/lib/include/o_cvcascadeboosttraindata.h +++ b/traincascade/lib/include/o_cvcascadeboosttraindata.h @@ -1,3 +1,15 @@ +/** + * @file o_cvcascadeboosttraindata.h + * @brief Specialization of @ref CvDTreeTrainData for cascade boosting. + * + * The cascade trainer computes feature responses lazily through a + * @ref CvFeatureEvaluator. Building a full numeric @c trainData matrix + * up front would be prohibitive (millions of features per sample), so + * @ref CvCascadeBoostTrainData precomputes only a small number of the + * highest-ranked feature columns (@c numPrecalcVal feature values plus + * @c numPrecalcIdx sorted-index columns) and falls back to @ref getVarValue + * for everything else. + */ #pragma once #include @@ -8,7 +20,14 @@ class CvFeatureEvaluator; struct CvDTreeNode; -// CvCascadeBoostTree +/** + * @brief Train-data store used by @ref CvCascadeBoost. + * + * Overrides the @c get_*_data accessors so the boosting trainer can + * iterate features that are not in the precalculated cache transparently. + * @c valCache holds the precalculated feature values for the + * @c numPrecalcVal best features (selected by variance). + */ struct CvCascadeBoostTrainData : CvDTreeTrainData { CvCascadeBoostTrainData(const CvFeatureEvaluator* _featureEvaluator, const CvDTreeParams& _params); @@ -16,10 +35,12 @@ struct CvCascadeBoostTrainData : CvDTreeTrainData { int _numSamples, int _precalcValBufSize, int _precalcIdxBufSize, const CvDTreeParams& _params = CvDTreeParams()); + /// Reconfigure the data store; reallocates @c valCache. virtual void setData(const CvFeatureEvaluator* _featureEvaluator, int _numSamples, int _precalcValBufSize, int _precalcIdxBufSize, const CvDTreeParams& _params = CvDTreeParams()); + /// Populate @c valCache with the @c numPrecalcVal highest-variance feature columns. void precalculate(); CvDTreeNode* subsample_data(const CvMat* _subsample_idx) override; @@ -34,10 +55,11 @@ struct CvCascadeBoostTrainData : CvDTreeTrainData { int* sampleIndicesBuf) override; const int* get_cat_var_data(CvDTreeNode* n, int vi, int* catValuesBuf) override; + /// Compute (or look up in @c valCache) the value of feature @p vi on sample @p si. virtual float getVarValue(int vi, int si); void free_train_data() override; - const CvFeatureEvaluator* featureEvaluator{}; + const CvFeatureEvaluator* featureEvaluator{}; ///< Non-owning evaluator used to compute feature values on demand. cv::Mat valCache; // precalculated feature values (CV_32FC1) CvMat _resp{}; // for casting int numPrecalcVal{}, numPrecalcIdx{}; diff --git a/traincascade/lib/include/o_cvcascadeboosttree.h b/traincascade/lib/include/o_cvcascadeboosttree.h index 283155d..b806b2b 100644 --- a/traincascade/lib/include/o_cvcascadeboosttree.h +++ b/traincascade/lib/include/o_cvcascadeboosttree.h @@ -1,3 +1,7 @@ +/** + * @file o_cvcascadeboosttree.h + * @brief Weak tree subclass tailored for cascade stages. + */ #pragma once #include "o_cvboostree.h" @@ -12,16 +16,32 @@ class FileNode; class Mat; } // namespace cv -// CvCascadeClassifier, CvCascadeBoost +/** + * @brief Decision tree weak learner specialized for cascade boosting. + * + * Adds an integer-indexed @ref predict overload that consults the + * cascade-specific @ref CvCascadeBoostTrainData (instead of going + * through CvMat sample objects) and a @ref write / @ref read pair that + * uses the cascade XML schema. @ref split_node_data is overridden so + * the child sample assignments stay consistent with the cached feature + * value matrix maintained by @ref CvCascadeBoostTrainData. + */ class CvCascadeBoostTree : public CvBoostTree { public: using CvBoostTree::predict; + /// Predict the leaf reached by sample @p sampleIdx using cached data. CvDTreeNode* predict(int sampleIdx) const; + /// Serialize the tree using the cascade XML schema; @p featureMap + /// remaps used feature indices to compact ones. void write(cv::FileStorage& fs, const cv::Mat& featureMap); + /// Restore a previously-saved tree. void read(const cv::FileNode& node, CvBoost* _ensemble, CvDTreeTrainData* _data); + /// Mark every feature this tree references as used in @p featureMap. void markFeaturesInMap(cv::Mat& featureMap); protected: + /// Override that updates the cached feature value matrix when routing + /// samples to children. void split_node_data(CvDTreeNode* n) override; }; diff --git a/traincascade/lib/include/o_cvdtree.h b/traincascade/lib/include/o_cvdtree.h index 5edb406..d810e40 100644 --- a/traincascade/lib/include/o_cvdtree.h +++ b/traincascade/lib/include/o_cvdtree.h @@ -1,3 +1,14 @@ +/** + * @file o_cvdtree.h + * @brief Legacy OpenCV decision tree (@c CvDTree). + * + * Implements the standard CART training pipeline: best-split search, + * recursive node splitting, optional surrogate splits for missing values, + * leaf value computation, and post-training cost-complexity pruning with + * cross-validation. Used directly inside the cascade as the base class + * for @ref CvBoostTree (boosting weak learner) and indirectly via + * @ref CvCascadeBoostTree. + */ #pragma once #include @@ -14,23 +25,59 @@ struct CvDTreeTrainData; struct CvDTreeNode; struct CvDTreeSplit; -// CvBoostTree +/** + * @brief CART-style decision tree. + * + * The class supports both classification (categorical response) and + * regression (ordered response) modes; the choice is made automatically + * from the @p varType matrix passed to @ref train. Two training entry + * points exist: + * - the high-level @ref train(const cv::Mat&, ...) overload, which + * builds a private @ref CvDTreeTrainData from raw matrices, and + * - the low-level @ref train(CvDTreeTrainData*, const CvMat*) overload, + * used by the boosting trainer to share a precomputed data store + * across all weak learners. + */ class CvDTree : public CvStatModel { public: CvDTree(); virtual ~CvDTree(); + /** + * @brief CvMat-based training entry point (legacy API). + * + * @param trainData @c (sample_count x var_count) feature matrix. + * @param tflag @c CV_ROW_SAMPLE or @c CV_COL_SAMPLE. + * @param responses Response vector (categorical or ordered). + * @param varIdx Optional subset of variables to use. + * @param sampleIdx Optional subset of samples to use. + * @param varType Per-variable type vector (last entry is the response type). + * @param missingDataMask Optional 8U mask of missing values. + * @param params Tree-training hyperparameters. + * @return @c true on success. + */ virtual bool train(const CvMat* trainData, int tflag, const CvMat* responses, const CvMat* varIdx = 0, const CvMat* sampleIdx = 0, const CvMat* varType = 0, const CvMat* missingDataMask = 0, CvDTreeParams params = CvDTreeParams()); + /// Train on an externally-built and possibly shared @ref CvDTreeTrainData. + /// Used by the boosting trainer to amortize data preparation across weak learners. virtual bool train(CvDTreeTrainData* trainData, const CvMat* subsampleIdx); + /** + * @brief Predict the leaf reached by @p sample. + * @param sample 1xvar_count or var_countx1 feature vector. + * @param missingDataMask Optional mask (1 where a value is missing). + * @param preprocessedInput If @c true the categorical fields of @p sample + * are already category indices (skip the lookup). + * @return The leaf node reached or @c nullptr on failure. + */ virtual CvDTreeNode* predict(const CvMat* sample, const CvMat* missingDataMask = 0, bool preprocessedInput = false) const; + /// @copydoc CvDTree::train(const CvMat*,int,const CvMat*,const CvMat*,const CvMat*,const CvMat*,const CvMat*,CvDTreeParams) virtual bool train(const cv::Mat& trainData, int tflag, const cv::Mat& responses, const cv::Mat& varIdx = cv::Mat(), @@ -39,16 +86,24 @@ class CvDTree : public CvStatModel { const cv::Mat& missingDataMask = cv::Mat(), CvDTreeParams params = CvDTreeParams()); + /// @copydoc CvDTree::predict(const CvMat*,const CvMat*,bool) const virtual CvDTreeNode* predict(const cv::Mat& sample, const cv::Mat& missingDataMask = cv::Mat(), bool preprocessedInput = false) const; + /// Release every internal buffer so the tree can be retrained. void clear() override; const CvDTreeNode* get_root() const; CvDTreeTrainData* get_data(); protected: + /** + * @brief Functor that searches the best split among a range of variables. + * + * Used as the body of @ref parallel_reduce so the per-variable split + * search can be parallelized when a real parallel runtime is plugged in. + */ struct DTreeBestSplitFinder { DTreeBestSplitFinder(CvDTree* _tree, CvDTreeNode* _node); void operator()(const BlockedRange& range); @@ -61,43 +116,62 @@ class CvDTree : public CvStatModel { friend struct DTreeBestSplitFinder; + /// Internal training driver shared by both public train overloads. virtual bool do_train(const CvMat* _subsample_idx); + /// Recursive splitter: try to split @p n; if it qualifies, recurse on children. virtual void try_split_node(CvDTreeNode* n); + /// Partition @p n's samples between its left/right children using @c n->split. virtual void split_node_data(CvDTreeNode* n); + /// Search every variable for the best split of node @p n. virtual CvDTreeSplit* find_best_split(CvDTreeNode* n); + /// Search the best split for an ordered variable in classification mode. virtual CvDTreeSplit* find_split_ord_class(CvDTreeNode* n, int vi, float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0); + /// Search the best split for a categorical variable in classification mode. virtual CvDTreeSplit* find_split_cat_class(CvDTreeNode* n, int vi, float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0); + /// Search the best split for an ordered variable in regression mode. virtual CvDTreeSplit* find_split_ord_reg(CvDTreeNode* n, int vi, float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0); + /// Search the best split for a categorical variable in regression mode. virtual CvDTreeSplit* find_split_cat_reg(CvDTreeNode* n, int vi, float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0); + /// Find an ordered-variable surrogate that approximates the primary split. virtual CvDTreeSplit* find_surrogate_split_ord(CvDTreeNode* n, int vi, uchar* ext_buf = 0); + /// Find a categorical-variable surrogate that approximates the primary split. virtual CvDTreeSplit* find_surrogate_split_cat(CvDTreeNode* n, int vi, uchar* ext_buf = 0); + /// Compute the routing direction (left/right majority) for samples reaching @p node. virtual double calc_node_dir(CvDTreeNode* node); + /// Resolve the final left/right routing including surrogate-driven decisions. virtual void complete_node_dir(CvDTreeNode* node); + /// k-means clustering of categories used for high-cardinality categorical splits. virtual void cluster_categories(const int* vectors, int vector_count, int var_count, int* sums, int k, int* cluster_labels); + /// Compute the leaf prediction value (class label or regression mean) for @p node. virtual void calc_node_value(CvDTreeNode* node); + /// Run cost-complexity pruning with cross-validation; populated by @c cv_folds. virtual void prune_cv(); + /// Update per-fold risk/node-count tables for pruning level @p T at fold @p fold. virtual double update_tree_rnc(int T, int fold); + /// Cut the tree at every node whose alpha falls below @p min_alpha. virtual int cut_tree(int T, int fold, double min_alpha); + /// Release pruning bookkeeping; if @p cut_tree is true also discard pruned subtrees. virtual void free_prune_data(bool cut_tree); + /// Release the in-memory tree (root, nodes, splits). virtual void free_tree(); CvDTreeNode* root{}; @@ -107,5 +181,5 @@ class CvDTree : public CvStatModel { cv::Mat train_data_mat, responses_mat; public: - int pruned_tree_idx{}; + int pruned_tree_idx{}; ///< Pruning truncation index in effect (0 = full tree). }; diff --git a/traincascade/lib/include/o_cvdtreenode.h b/traincascade/lib/include/o_cvdtreenode.h index 6cc889b..43755f4 100644 --- a/traincascade/lib/include/o_cvdtreenode.h +++ b/traincascade/lib/include/o_cvdtreenode.h @@ -1,38 +1,51 @@ +/** + * @file o_cvdtreenode.h + * @brief In-memory node of a decision tree. + */ #pragma once struct CvDTreeNode; struct CvDTreeSplit; -// CvBoostTree, CvCascadeBoostTrainData, CvCascadeBoostTree, CvDTree, -// CvDTreeTrainData +/** + * @brief A single decision-tree node (internal or leaf). + * + * Holds: the tree structure (parent / left / right pointers), the split + * predicate that routes samples (@c split, null for leaves), the + * prediction value or class index emitted at this node, sample + * bookkeeping and per-node statistics used by cost-complexity and + * cross-validation pruning. + */ struct CvDTreeNode { - int class_idx; - int Tn; - double value; + int class_idx; ///< Class index emitted at a leaf (classification mode). + int Tn; ///< Pruning truncation index used by cost-complexity pruning. + double value; ///< Leaf prediction value (regression) or class label (classification). CvDTreeNode* parent; CvDTreeNode* left; CvDTreeNode* right; - CvDTreeSplit* split; + CvDTreeSplit* split; ///< Primary split predicate; null on leaves. int sample_count; int depth; - int* num_valid; + int* num_valid; ///< Per-variable count of non-missing samples reaching this node. int offset; int buf_idx; double maxlr; // global pruning data - int complexity; - double alpha; + int complexity; ///< Subtree size used for cost-complexity scoring. + double alpha; ///< Cost-complexity threshold at which this subtree is pruned. double node_risk, tree_risk, tree_error; // cross-validation pruning data - int* cv_Tn; - double* cv_node_risk; - double* cv_node_error; + int* cv_Tn; ///< Per-fold pruning level. + double* cv_node_risk; ///< Per-fold node risk estimate. + double* cv_node_error;///< Per-fold prediction error. + /// Number of samples reaching this node with a valid value for variable @p vi; + /// returns @c sample_count if no missing-data tracking is allocated. int get_num_valid(int vi) const { return num_valid ? num_valid[vi] : sample_count; } void set_num_valid(int vi, int n) { if (num_valid) num_valid[vi] = n; diff --git a/traincascade/lib/include/o_cvdtreeparams.h b/traincascade/lib/include/o_cvdtreeparams.h index ab60655..773c122 100644 --- a/traincascade/lib/include/o_cvdtreeparams.h +++ b/traincascade/lib/include/o_cvdtreeparams.h @@ -1,6 +1,31 @@ +/** + * @file o_cvdtreeparams.h + * @brief Parameters governing decision-tree training. + */ #pragma once -// CvCascadeBoostParams +/** + * @brief Hyperparameters consumed by @ref CvDTree::train. + * + * Field summary: + * - @c max_categories: maximum cardinality of categorical splits before + * the trainer falls back to clustering. + * - @c max_depth: hard cap on tree depth (1 produces decision stumps, + * which is what cascade boosting normally uses). + * - @c min_sample_count: leaves with fewer samples than this are not split. + * - @c cv_folds: number of cross-validation folds used during cost- + * complexity pruning; @c 0 disables pruning. + * - @c use_surrogates: if @c true, learn surrogate splits to handle + * missing values at predict time. + * - @c use_1se_rule: pick the simplest tree within one standard error of + * the cv-optimal one (Breiman's 1-SE rule). + * - @c truncate_pruned_tree: physically discard pruned subtrees instead + * of keeping them as inactive nodes. + * - @c regression_accuracy: leaf-variance threshold below which a + * regression node is no longer split. + * - @c priors: per-class loss weights (length = number of classes); a + * null pointer means uniform priors. + */ struct CvDTreeParams { int max_categories; int max_depth; @@ -10,7 +35,7 @@ struct CvDTreeParams { bool use_1se_rule; bool truncate_pruned_tree; float regression_accuracy; - const float* priors; + const float* priors; ///< Optional per-class loss weights (not owned). CvDTreeParams(); CvDTreeParams(int max_depth, int min_sample_count, float regression_accuracy, diff --git a/traincascade/lib/include/o_cvdtreesplit.h b/traincascade/lib/include/o_cvdtreesplit.h index 509ab2c..c3c0bb3 100644 --- a/traincascade/lib/include/o_cvdtreesplit.h +++ b/traincascade/lib/include/o_cvdtreesplit.h @@ -1,19 +1,31 @@ +/** + * @file o_cvdtreesplit.h + * @brief Split predicate stored on each internal decision-tree node. + */ #pragma once -// CvBoostTree, CvCascadeBoostTrainData, CvCascadeBoostTree, CvDTree, -// CvDTreeTrainData +/** + * @brief One split predicate attached to a @ref CvDTreeNode. + * + * @c CvDTreeSplit overlays an ordered split (@c ord — feature value @c c + * compared against the @c split_point quantile) and a categorical split + * (@c subset — bitmask listing which categories go to the left child). + * Multiple splits may form a linked list via @c next, e.g. surrogate + * splits used for handling missing values. + */ struct CvDTreeSplit { - int var_idx; - int condensed_idx; - int inversed; - float quality; - CvDTreeSplit* next; + int var_idx; ///< Index of the variable this split tests. + int condensed_idx; ///< Index after var_idx remapping (set internally). + int inversed; ///< When non-zero the left/right semantic is inverted. + float quality; ///< Split-quality score (Gini drop or variance drop). + CvDTreeSplit* next; ///< Next surrogate split for the same node, or null. + /// Threshold-based split parameters for ordered features. struct Ord { - float c; - int split_point; + float c; ///< Threshold value: samples with @c x[var_idx] <= c go left. + int split_point; ///< Sorted-sample index at which the threshold sits. }; union { - int subset[2]; + int subset[2]; ///< 64-bit bitmask listing left-going categories. Ord ord; }; }; \ No newline at end of file diff --git a/traincascade/lib/include/o_cvdtreetraindata.h b/traincascade/lib/include/o_cvdtreetraindata.h index 6188309..9ecf786 100644 --- a/traincascade/lib/include/o_cvdtreetraindata.h +++ b/traincascade/lib/include/o_cvdtreetraindata.h @@ -1,3 +1,15 @@ +/** + * @file o_cvdtreetraindata.h + * @brief Shared training-data store backing @ref CvDTree and @ref CvBoost. + * + * @ref CvDTreeTrainData converts the public CvMat inputs (training data, + * responses, optional sample / variable masks) into the internal + * representation used by the split-finding algorithms: per-variable + * sorted indices, categorical maps, sample-index buffers, and the + * memory-pooled node / split / cv-data heaps. A single instance can be + * shared across many @c CvDTree / @c CvBoostTree weak learners by + * passing @c shared = true at construction. + */ #pragma once #include @@ -14,9 +26,22 @@ struct CvDTreeSplit; struct CvMemStorage; struct CvSet; -// CvBoost +/** + * @brief Internal data store for one or more decision trees. + * + * The class holds three categories of state: + * - immutable copies of the original training data (@c train_data, + * @c responses) and the metadata needed to interpret them + * (@c var_type, @c cat_count / @c cat_ofs / @c cat_map, @c priors); + * - working buffers reused across nodes (@c buf, @c counts, + * @c direction, @c split_buf); + * - memory pools for nodes, splits and pruning bookkeeping + * (@c tree_storage, @c temp_storage, @c node_heap, @c split_heap, + * @c cv_heap, @c nv_heap). + */ struct CvDTreeTrainData { CvDTreeTrainData(); + /// Build the data store and immediately pre-process @p trainData. CvDTreeTrainData(const CvMat* trainData, int tflag, const CvMat* responses, const CvMat* varIdx = 0, const CvMat* sampleIdx = 0, const CvMat* varType = 0, const CvMat* missingDataMask = 0, @@ -24,6 +49,17 @@ struct CvDTreeTrainData { bool _shared = false, bool _add_labels = false); virtual ~CvDTreeTrainData(); + /** + * @brief Replace or initialize the underlying training data set. + * + * @param _shared When @c true the same instance can back multiple trees + * (used by boosting). Implies the data must outlive every tree + * that references it. + * @param _add_labels Append a categorical label column synthesized from + * the responses; needed for some boosting variants. + * @param _update_data When @c true reuse already-allocated buffers if + * the new data is layout-compatible; otherwise reallocate. + */ virtual void set_data(const CvMat* trainData, int tflag, const CvMat* responses, const CvMat* varIdx = 0, const CvMat* sampleIdx = 0, const CvMat* varType = 0, @@ -31,18 +67,23 @@ struct CvDTreeTrainData { const CvDTreeParams& params = CvDTreeParams(), bool _shared = false, bool _add_labels = false, bool _update_data = false); + /// Make a writable copy of the responses (boosting needs to mutate them). virtual void do_responses_copy(); + /// Materialize the dense (values, missing mask, responses) tuple for @p _subsample_idx. virtual void get_vectors(const CvMat* _subsample_idx, float* values, unsigned char* missing, float* responses, bool get_class_idx = false); + /// Build the root node of a new tree from the supplied subsample. virtual CvDTreeNode* subsample_data(const CvMat* _subsample_idx); - // release all the data + /// Release every internal buffer; safe to call on a default-constructed instance. virtual void clear(); + /// Number of distinct response classes (1 in regression mode). int get_num_classes() const; + /// Type of variable @p vi: <0 = ordered, >=0 = categorical (index into cat_*). int get_var_type(int vi) const; int get_work_var_count() const { return work_var_count; } @@ -62,21 +103,28 @@ struct CvDTreeTrainData { //////////////////////////////////// + /// Validate parameters and propagate them to internal flags. Returns @c false on bad input. virtual bool set_params(const CvDTreeParams& params); + /// Allocate and link a new tree node from the @c node_heap. virtual CvDTreeNode* new_node(CvDTreeNode* parent, int count, int storage_idx, int offset); + /// Allocate an ordered-variable split from the @c split_heap. virtual CvDTreeSplit* new_split_ord(int vi, float cmp_val, int split_point, int inversed, float quality); + /// Allocate a categorical-variable split from the @c split_heap. virtual CvDTreeSplit* new_split_cat(int vi, float quality); + /// Release per-node bookkeeping (pruning data, num_valid, ...). virtual void free_node_data(CvDTreeNode* node); + /// Release the working buffers shared across nodes. virtual void free_train_data(); + /// Return one node to the @c node_heap (with its split). virtual void free_node(CvDTreeNode* node); int sample_count{}, var_all{}, var_count{}, max_c_count{}; int ord_var_count{}, cat_var_count{}, work_var_count{}; bool have_labels{}, have_priors{}; - bool is_classifier{}; + bool is_classifier{}; ///< @c true when the response is categorical. int tflag{}; const CvMat* train_data{}; @@ -95,6 +143,7 @@ struct CvDTreeTrainData { CvMat* counts; CvMat* buf; + /// Length (in elements) of one sub-buffer inside @c buf. inline size_t get_length_subbuf() const { size_t res = (size_t)(work_var_count + 1) * (size_t)sample_count; return res; @@ -112,8 +161,8 @@ struct CvDTreeTrainData { CvDTreeParams params; - CvMemStorage* tree_storage; - CvMemStorage* temp_storage; + CvMemStorage* tree_storage; ///< Pool for permanent tree nodes / splits. + CvMemStorage* temp_storage; ///< Pool for scratch data freed at the end of training. CvDTreeNode* data_root{}; diff --git a/traincascade/lib/include/o_cvstatmodel.h b/traincascade/lib/include/o_cvstatmodel.h index ebd679e..c35fee4 100644 --- a/traincascade/lib/include/o_cvstatmodel.h +++ b/traincascade/lib/include/o_cvstatmodel.h @@ -1,13 +1,29 @@ +/** + * @file o_cvstatmodel.h + * @brief Minimal base class extracted from OpenCV's legacy @c CvStatModel. + * + * @ref CvStatModel is the root of the legacy ML class hierarchy reused by + * the cascade trainer (CvDTree -> CvBoost -> CvCascadeBoost). It only + * provides a virtual destructor and a @ref clear hook so subclasses can + * release their internal buffers polymorphically. + */ #pragma once -// CvCascadeBoost +/** + * @brief Polymorphic base for the legacy ML model hierarchy. + * + * Direct subclasses in this project: @c CvDTree (decision tree) and + * @c CvBoost (boosted ensemble). The @c default_model_name field is + * preserved for ABI compatibility with OpenCV's stored XML format. + */ class CvStatModel { public: CvStatModel(); virtual ~CvStatModel(); + /// Release internal state so the instance can be retrained or destroyed. virtual void clear(); protected: - const char* default_model_name; + const char* default_model_name; ///< XML tag used by OpenCV's legacy persistence layer. }; diff --git a/traincascade/lib/include/o_utils.h b/traincascade/lib/include/o_utils.h index 7347131..4a2a916 100644 --- a/traincascade/lib/include/o_utils.h +++ b/traincascade/lib/include/o_utils.h @@ -1,7 +1,19 @@ +/** + * @file o_utils.h + * @brief Small utility predicates and helpers reused by the legacy ML code. + */ #pragma once struct CvMat; +/** + * @brief Indirect comparator: compares @c arr[a] with @c arr[b]. + * + * Useful with @c std::sort to sort an array of indices by the values + * they reference, which is the standard pattern used by the decision- + * tree splitter to order ordered-variable candidates without permuting + * the underlying value array. + */ template class LessThanIdx { public: @@ -10,17 +22,28 @@ class LessThanIdx { const T* arr; }; +/// Indirect comparator: compares the values pointed to by @p a and @p b. template class LessThanPtr { public: bool operator()(T* a, T* b) const { return *a < *b; } }; +/// `qsort` callback that compares two integers stored at @p a and @p b. int icvCmpIntegers(const void* a, const void* b); +/// Round @p size up to the next multiple of @p align (power of two). int cvAlign(int size, int align); +/// Probe a categorical-split bitmask: returns 0 (left) or 1 (right) for category @p idx. int CV_DTREE_CAT_DIR(int idx, const int* subset); +/** + * @brief Validate and normalize an external index array. + * + * Used by the trainer to pre-process the optional @c sampleIdx / + * @c varIdx arguments before feeding them to the data structures. + * Optionally raises an exception on duplicate entries. + */ CvMat* cvPreprocessIndexArray(const CvMat* idx_arr, int data_arr_size, bool check_for_duplicates = false); diff --git a/traincascade/lib/include/traincascade_features.h b/traincascade/lib/include/traincascade_features.h index 372f094..10a5bf5 100644 --- a/traincascade/lib/include/traincascade_features.h +++ b/traincascade/lib/include/traincascade_features.h @@ -1,3 +1,24 @@ +/** + * @file traincascade_features.h + * @brief Common feature-evaluation infrastructure shared by Haar, LBP and HOG. + * + * Declares: + * - @ref CvParams, the polymorphic base for every parameter struct that + * can be loaded from / saved to a @c FileStorage and parsed from + * command-line attributes. + * - @ref CvFeatureParams, the family-agnostic feature parameter struct + * plus a factory that instantiates HAAR/LBP/HOG variants on demand. + * - @ref CvFeatureEvaluator, the abstract evaluator used by the boosting + * trainer to read the response of feature @p featureIdx on sample + * @p sampleIdx without exposing the underlying integral images. + * + * The macros @c CV_SUM_OFFSETS / @c CV_TILTED_OFFSETS and the helper + * @ref calcNormFactor are used by the concrete evaluators to convert + * (x, y, w, h) rectangles into linear offsets into a precomputed integral + * image, which is the bottleneck operation of every weak-classifier + * evaluation. + */ + #ifndef _OPENCV_FEATURES_H_ #define _OPENCV_FEATURES_H_ @@ -5,8 +26,17 @@ #include +/// XML tag under which the list of features is serialized in @c cascade.xml. #define FEATURES "features" +/** + * @brief Compute the four corner offsets of an axis-aligned rectangle in an + * integral image laid out with row stride @p step. + * + * Given an integral image @c S the sum of pixels inside @p rect can be + * recovered as @c S[p3] - S[p1] - S[p2] + S[p0], so feature evaluators + * cache the four offsets up front and reuse them per sample. + */ template void CV_SUM_OFFSETS(T& p0, T& p1, T& p2, T& p3, const Rect& rect, T step ) { /* (x, y) */ @@ -19,6 +49,8 @@ void CV_SUM_OFFSETS(T& p0, T& p1, T& p2, T& p3, const Rect& rect, T step ) { p3 = rect.x + rect.width + step * (rect.y + rect.height); } +/// Same as @ref CV_SUM_OFFSETS but for a 45°-rotated (tilted) rectangle. +/// Tilted Haar features rely on a separately-computed tilted integral image. #define CV_TILTED_OFFSETS( p0, p1, p2, p3, rect, step ) \ /* (x, y) */ \ (p0) = (rect).x + (step) * (rect).y; \ @@ -30,8 +62,23 @@ void CV_SUM_OFFSETS(T& p0, T& p1, T& p2, T& p3, const Rect& rect, T step ) { (p3) = (rect).x + (rect).width - (rect).height \ + (step) * ((rect).y + (rect).width + (rect).height); +/** + * @brief Compute the per-window normalization factor used by Haar features. + * + * Returns @f$\sqrt{N \cdot \text{sqSum} - \text{sum}^2}@f$ for the + * detection window covered by the integral images @p sum and @p sqSum. + * Haar feature responses are divided by this factor to be invariant to + * window-level brightness and contrast. + */ float calcNormFactor( const cv::Mat& sum, const cv::Mat& sqSum ); +/** + * @brief Serialize the subset of @p features marked as used in @p featureMap. + * + * @p featureMap is a 1xN row mapping each global feature index to its + * compact index (or -1 if the cascade never selected it). Skipping unused + * features keeps the output @c cascade.xml small. + */ template void _writeFeatures( const std::vector features, cv::FileStorage &fs, const cv::Mat& featureMap ) { @@ -47,58 +94,97 @@ void _writeFeatures( const std::vector features, cv::FileStorage &fs, c fs << "]"; } +/** + * @brief Polymorphic base for parameter structs that round-trip to disk. + * + * Every concrete parameter struct in the trainer (cascade-level, feature, + * boosting, etc.) inherits @ref CvParams so the driver can iterate them + * generically when reading/writing @c params.xml or scanning command-line + * attributes. + */ class CvParams { public: CvParams(); virtual ~CvParams() {} - // from|to file + /// Persist to @p fs (called inside an open mapping node). virtual void write( cv::FileStorage &fs ) const = 0; + /// Restore from @p node; @c false on malformed input. virtual bool read( const cv::FileNode &node ) = 0; - // from|to screen + /// Print the default value of every parameter to stdout. virtual void printDefaults() const; + /// Print the currently configured value of every parameter to stdout. virtual void printAttrs() const; + /// Apply a single command-line attribute (e.g. @c -w 24); returns + /// @c false when the attribute name is not recognized. virtual bool scanAttr( const std::string prmName, const std::string val ); - std::string name; + std::string name; ///< Human-readable name used as the FileStorage tag. }; +/** + * @brief Family-agnostic parameter struct for feature evaluators. + * + * Holds the descriptor sizes shared across feature families and provides + * a static @ref create factory that instantiates the matching subclass + * (@c CvHaarFeatureParams, @c CvLBPFeatureParams or @c CvHOGFeatureParams). + */ class CvFeatureParams : public CvParams { public: - enum { HAAR = 0, LBP = 1, HOG = 2 }; + enum { HAAR = 0, LBP = 1, HOG = 2 }; ///< Supported feature families. CvFeatureParams(); + /// Copy field-by-field from another instance (used to clone defaults). virtual void init( const CvFeatureParams& fp ); void write( cv::FileStorage &fs ) const override; bool read( const cv::FileNode &node ) override; + /// Factory: instantiate the parameters subclass matching @p featureType. static cv::Ptr create( int featureType ); - int maxCatCount; // 0 in case of numerical features - int featSize; // 1 in case of simple features (HAAR, LBP) and N_BINS(9)*N_CELLS(4) in case of Dalal's HOG features + int maxCatCount; ///< Number of categorical bins (0 for purely numerical features such as Haar). + int featSize; ///< Feature descriptor length: 1 for HAAR/LBP, @c N_BINS*N_CELLS for HOG. }; +/** + * @brief Abstract evaluator that returns feature responses on demand. + * + * The boosting trainer never sees the underlying integral images directly + * — it only calls @ref operator() with an @em index pair and gets back a + * float. Concrete subclasses (@c CvHaarEvaluator, @c CvLBPEvaluator, + * @c CvHOGEvaluator) cache per-sample integral images inside @c setImage + * and decode @p featureIdx via their own feature catalog. + */ class CvFeatureEvaluator { public: virtual ~CvFeatureEvaluator() {} + /// Allocate per-sample buffers for at most @p _maxSampleCount samples + /// of size @p _winSize and generate the feature catalog. virtual void init(const CvFeatureParams *_featureParams, int _maxSampleCount, cv::Size _winSize ); + /// Cache integral images for sample @p idx; @p clsLabel is the binary + /// class label (1 for positives, 0 for negatives). virtual void setImage(const cv::Mat& img, uchar clsLabel, int idx); + /// Serialize every selected feature's geometry (used at save time). virtual void writeFeatures( cv::FileStorage &fs, const cv::Mat& featureMap ) const = 0; + /// Return the response of feature @p featureIdx on sample @p sampleIdx. virtual float operator()(int featureIdx, int sampleIdx) const = 0; + /// Factory: instantiate the evaluator matching @p type (HAAR/LBP/HOG). static cv::Ptr create(int type); int getNumFeatures() const { return numFeatures; } int getMaxCatCount() const { return featureParams->maxCatCount; } int getFeatureSize() const { return featureParams->featSize; } + /// Cached class-label vector (1 = positive, 0 = negative) per sample. const cv::Mat& getCls() const { return cls; } float getCls(int si) const { return cls.at(si, 0); } protected: + /// Build the feature catalog for the current window size. virtual void generateFeatures() = 0; - int npos, nneg; - int numFeatures; - cv::Size winSize; - CvFeatureParams *featureParams; - cv::Mat cls; + int npos, nneg; ///< Number of positives / negatives currently cached. + int numFeatures; ///< Size of the feature catalog. + cv::Size winSize; ///< Sub-window size for which features were generated. + CvFeatureParams *featureParams; ///< Non-owning pointer to feature parameters. + cv::Mat cls; ///< Per-sample class labels (Nx1 CV_32F). }; #endif diff --git a/traincascade/test/README.md b/traincascade/test/README.md index d7f1c71..09f2797 100644 --- a/traincascade/test/README.md +++ b/traincascade/test/README.md @@ -1,6 +1,41 @@ # Unit tests for the TrainCascadeLib +These tests exercise the `TrainCascadeLib` static library directly, +without going through the `traincascade` command-line driver. Each file +focuses on one component of the library and follows an +Arrange / Act / Assert structure with comments per step. + ## Test framework: doctest * https://github.com/onqtam/doctest * commit b7c21ec5ceeadb4951b00396fc1e4642dd347e5f -* 2.4.9 \ No newline at end of file +* 2.4.9 + +The `doctest/doctest.h` header is vendored under `doctest/`. A single +translation unit (`main.cpp`) defines `DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN` +so each test executable links against one copy of the framework. + +## Test files + +| File | Component under test | +| ---- | -------------------- | +| [test_dtree.cpp](test_dtree.cpp) | `CvDTree` and `CvDTreeTrainData` — direct decision-tree training, prediction, regression mode, pruning and sample-mask preprocessing. | +| [test_features.cpp](test_features.cpp) | Haar / LBP / HOG feature evaluators (`CvHaarEvaluator`, `CvLBPEvaluator`, `CvHOGEvaluator`). | +| [test_imagestorage.cpp](test_imagestorage.cpp) | Positive/negative sample streaming via `CvCascadeImageReader`. | +| [test_o_utils.cpp](test_o_utils.cpp) | Small utilities from `o_utils.h` (alignment, comparators, index preprocessing). | +| [test_params.cpp](test_params.cpp) | Parameter serialisation and CLI scanning for `CvCascadeParams`, `CvCascadeBoostParams`, feature-family params. | +| [test_serialization.cpp](test_serialization.cpp) | Round-trip persistence of trees and stages through `cv::FileStorage`. | +| [test_integration.cpp](test_integration.cpp) | End-to-end smoke test: a small cascade is trained and reloaded. | +| [main.cpp](main.cpp) | doctest entry point. | + +## Running the tests + +The tests are picked up automatically by CTest: + +```sh +cmake -S . -B build -G Ninja +cmake --build build +ctest --test-dir build/traincascade --output-on-failure +``` + +A coverage-enabled configuration lives under `build-coverage/`; HTML +reports land in `build-coverage/coverage-html/`. diff --git a/traincascade/traincascade.cpp b/traincascade/traincascade.cpp index 257d08b..0210726 100644 --- a/traincascade/traincascade.cpp +++ b/traincascade/traincascade.cpp @@ -1,3 +1,23 @@ +/** + * @file traincascade.cpp + * @brief Command-line entry point for the cascade classifier trainer. + * + * This is the @c traincascade executable. It instantiates a + * @ref CvCascadeClassifier, parses command-line attributes into the + * three nested parameter structs (@ref CvCascadeParams, + * @ref CvCascadeBoostParams, @ref CvFeatureParams) and dispatches to + * @ref CvCascadeClassifier::train. Unknown attributes are forwarded to + * each parameter struct in turn via @c scanAttr; the first one that + * recognizes the flag consumes the value. + * + * Required arguments: + * - @c -data : output directory for stage XMLs and @c cascade.xml. + * - @c -vec : positives, produced by @c opencv_createsamples. + * - @c -bg : text file listing background images for negatives. + * + * Run the binary without arguments for the full list of optional flags + * and their default values. + */ #include #include @@ -7,24 +27,33 @@ using namespace std; using namespace cv; -/* -traincascade.cpp is the source file of the program used for cascade training. -User has to provide training input in form of positive and negative training images, -and other data related to training in form of command line argument. -*/ +/** + * @brief Program entry point: parse CLI flags and run cascade training. + * + * The argument-parsing loop tries known top-level flags first, then + * delegates unknown @c -name value pairs to the parameter structs + * (@c cascadeParams, @c stageParams, then each entry of @c featureParams) + * so feature-family-specific flags such as @c -mode (Haar) or @c -mode + * (LBP) are picked up by the matching evaluator's parameters. + */ int main( int argc, char* argv[] ) { + /// Cascade trainer; populates output directory with stage XMLs. CvCascadeClassifier classifier; string cascadeDirName, vecName, bgName; - int numPos = 2000; - int numNeg = 1000; - int numStages = 20; + int numPos = 2000; ///< Default positives consumed per stage. + int numNeg = 1000; ///< Default negatives mined per stage. + int numStages = 20; ///< Maximum number of cascade stages. int numThreads = getNumThreads(); - int precalcValBufSize = 1024, - precalcIdxBufSize = 1024; - bool baseFormatSave = false; - double acceptanceRatioBreakValue = -1.0; + int precalcValBufSize = 1024, ///< Feature-value cache size in MB. + precalcIdxBufSize = 1024; ///< Sorted-index cache size in MB. + bool baseFormatSave = false; ///< When true also write the legacy XML layout. + double acceptanceRatioBreakValue = -1.0; ///< -1 disables early stopping. + // The three parameter structs collectively configure every aspect of + // the cascade: window size & feature type (cascadeParams), boosting + // and stage rate targets (stageParams), and feature-specific knobs + // (featureParams[HAAR / LBP / HOG]). CvCascadeParams cascadeParams; CvCascadeBoostParams stageParams; Ptr featureParams[] = { makePtr(), @@ -55,6 +84,9 @@ int main( int argc, char* argv[] ) for( int i = 1; i < argc; i++ ) { + // Try every known top-level flag in order; if none match, fall + // through to the parameter structs which expose their own + // attributes via scanAttr(). bool set = false; if( !strcmp( argv[i], "-data" ) ) { @@ -117,6 +149,8 @@ int main( int argc, char* argv[] ) } setNumThreads( numThreads ); + // Hand control over to the cascade trainer; it owns the multi-stage + // training loop, sample mining, and persistence of intermediate state. classifier.train( cascadeDirName, vecName, bgName,