From d922ad61d35e9a6996730bec24b16f8bf7bc426c Mon Sep 17 00:00:00 2001 From: Francois Fleuret Date: Thu, 9 Oct 2008 10:00:45 +0200 Subject: [PATCH] automatic commit --- Makefile | 89 +++++ boosted_classifier.cc | 130 +++++++ boosted_classifier.h | 58 +++ chrono.cc | 62 ++++ chrono.h | 39 ++ classifier.cc | 42 +++ classifier.h | 61 ++++ classifier_reader.cc | 43 +++ classifier_reader.h | 28 ++ decision_tree.cc | 303 ++++++++++++++++ decision_tree.h | 67 ++++ detector.cc | 474 ++++++++++++++++++++++++ detector.h | 76 ++++ error_rates.cc | 137 +++++++ error_rates.h | 30 ++ folding.cc | 339 ++++++++++++++++++ fusion_sort.cc | 85 +++++ fusion_sort.h | 28 ++ gaussian.cc | 45 +++ gaussian.h | 35 ++ global.cc | 172 +++++++++ global.h | 101 ++++++ graph.sh | 62 ++++ image.cc | 74 ++++ image.h | 60 ++++ interval.cc | 48 +++ interval.h | 58 +++ jpeg_misc.cc | 25 ++ jpeg_misc.h | 36 ++ labelled_image.cc | 101 ++++++ labelled_image.h | 53 +++ labelled_image_pool.cc | 21 ++ labelled_image_pool.h | 33 ++ labelled_image_pool_file.cc | 101 ++++++ labelled_image_pool_file.h | 46 +++ labelled_image_pool_subset.cc | 47 +++ labelled_image_pool_subset.h | 36 ++ list_to_pool.cc | 280 +++++++++++++++ loss_machine.cc | 421 ++++++++++++++++++++++ loss_machine.h | 55 +++ materials.cc | 250 +++++++++++++ materials.h | 47 +++ misc.cc | 125 +++++++ misc.h | 116 ++++++ param_parser.cc | 146 ++++++++ param_parser.h | 44 +++ parsing.cc | 186 ++++++++++ parsing.h | 87 +++++ parsing_pool.cc | 259 ++++++++++++++ parsing_pool.h | 74 ++++ pi_feature.cc | 471 ++++++++++++++++++++++++ pi_feature.h | 86 +++++ pi_feature_family.cc | 70 ++++ pi_feature_family.h | 50 +++ pi_referential.cc | 655 ++++++++++++++++++++++++++++++++++ pi_referential.h | 133 +++++++ pose.cc | 190 ++++++++++ pose.h | 49 +++ pose_cell.cc | 63 ++++ pose_cell.h | 47 +++ pose_cell_hierarchy.cc | 392 ++++++++++++++++++++ pose_cell_hierarchy.h | 66 ++++ pose_cell_hierarchy_reader.cc | 25 ++ pose_cell_hierarchy_reader.h | 26 ++ pose_cell_scored_set.cc | 122 +++++++ pose_cell_scored_set.h | 46 +++ pose_cell_set.cc | 51 +++ pose_cell_set.h | 54 +++ progress_bar.cc | 93 +++++ progress_bar.h | 48 +++ rectangle.h | 28 ++ rgb_image.cc | 566 +++++++++++++++++++++++++++++ rgb_image.h | 78 ++++ rgb_image_subpixel.cc | 86 +++++ rgb_image_subpixel.h | 52 +++ rich_image.cc | 453 +++++++++++++++++++++++ rich_image.h | 105 ++++++ run.sh | 124 +++++++ sample_set.cc | 65 ++++ sample_set.h | 62 ++++ shared.cc | 34 ++ shared.h | 39 ++ shared_responses.cc | 27 ++ shared_responses.h | 31 ++ storable.cc | 42 +++ storable.h | 37 ++ tools.cc | 108 ++++++ tools.h | 32 ++ 88 files changed, 10041 insertions(+) create mode 100644 Makefile create mode 100644 boosted_classifier.cc create mode 100644 boosted_classifier.h create mode 100644 chrono.cc create mode 100644 chrono.h create mode 100644 classifier.cc create mode 100644 classifier.h create mode 100644 classifier_reader.cc create mode 100644 classifier_reader.h create mode 100644 decision_tree.cc create mode 100644 decision_tree.h create mode 100644 detector.cc create mode 100644 detector.h create mode 100644 error_rates.cc create mode 100644 error_rates.h create mode 100644 folding.cc create mode 100644 fusion_sort.cc create mode 100644 fusion_sort.h create mode 100644 gaussian.cc create mode 100644 gaussian.h create mode 100644 global.cc create mode 100644 global.h create mode 100755 graph.sh create mode 100644 image.cc create mode 100644 image.h create mode 100644 interval.cc create mode 100644 interval.h create mode 100644 jpeg_misc.cc create mode 100644 jpeg_misc.h create mode 100644 labelled_image.cc create mode 100644 labelled_image.h create mode 100644 labelled_image_pool.cc create mode 100644 labelled_image_pool.h create mode 100644 labelled_image_pool_file.cc create mode 100644 labelled_image_pool_file.h create mode 100644 labelled_image_pool_subset.cc create mode 100644 labelled_image_pool_subset.h create mode 100644 list_to_pool.cc create mode 100644 loss_machine.cc create mode 100644 loss_machine.h create mode 100644 materials.cc create mode 100644 materials.h create mode 100644 misc.cc create mode 100644 misc.h create mode 100644 param_parser.cc create mode 100644 param_parser.h create mode 100644 parsing.cc create mode 100644 parsing.h create mode 100644 parsing_pool.cc create mode 100644 parsing_pool.h create mode 100644 pi_feature.cc create mode 100644 pi_feature.h create mode 100644 pi_feature_family.cc create mode 100644 pi_feature_family.h create mode 100644 pi_referential.cc create mode 100644 pi_referential.h create mode 100644 pose.cc create mode 100644 pose.h create mode 100644 pose_cell.cc create mode 100644 pose_cell.h create mode 100644 pose_cell_hierarchy.cc create mode 100644 pose_cell_hierarchy.h create mode 100644 pose_cell_hierarchy_reader.cc create mode 100644 pose_cell_hierarchy_reader.h create mode 100644 pose_cell_scored_set.cc create mode 100644 pose_cell_scored_set.h create mode 100644 pose_cell_set.cc create mode 100644 pose_cell_set.h create mode 100644 progress_bar.cc create mode 100644 progress_bar.h create mode 100644 rectangle.h create mode 100644 rgb_image.cc create mode 100644 rgb_image.h create mode 100644 rgb_image_subpixel.cc create mode 100644 rgb_image_subpixel.h create mode 100644 rich_image.cc create mode 100644 rich_image.h create mode 100755 run.sh create mode 100644 sample_set.cc create mode 100644 sample_set.h create mode 100644 shared.cc create mode 100644 shared.h create mode 100644 shared_responses.cc create mode 100644 shared_responses.h create mode 100644 storable.cc create mode 100644 storable.h create mode 100644 tools.cc create mode 100644 tools.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7fc3339 --- /dev/null +++ b/Makefile @@ -0,0 +1,89 @@ + +######################################################################### +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the version 3 of the GNU General Public License # +# as published by the Free Software Foundation. # +# # +# This program is distributed in the hope that it will be useful, but # +# WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # +# General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +# Written by Francois Fleuret, (C) IDIAP # +# Contact for comments & bug reports # +######################################################################### + +CXX=g++-4.1 + +ifeq ($(STATIC),yes) + LDFLAGS=-static -lm -ljpeg -lpng -lz +else + LDFLAGS=-lm -ljpeg -lpng +endif + +ifeq ($(DEBUG),yes) + OPTIMIZE_FLAG = -ggdb3 -DDEBUG +else + OPTIMIZE_FLAG = -ggdb3 -O3 +endif + +ifeq ($(PROFILE),yes) + PROFILE_FLAG = -pg +endif + +CXXFLAGS = -Wall $(OPTIMIZE_FLAG) $(PROFILE_FLAG) + +# ifeq ($(CURSES),yes) +# LDFLAGS=$(LDFLAGS) -lncurses +# CXXFLAGS = $(CXXFLAGS) -DWITH_CURSES +# endif + +all: folding list_to_pool TAGS + +TAGS: *.cc *.h + etags --members -l c++ *.cc *.h + +folding: misc.o interval.o gaussian.o fusion_sort.o global.o tools.o \ + progress_bar.o chrono.o \ + jpeg_misc.o rgb_image.o rgb_image_subpixel.o param_parser.o \ + shared.o \ + storable.o \ + image.o rich_image.o \ + labelled_image.o \ + labelled_image_pool.o labelled_image_pool_file.o labelled_image_pool_subset.o \ + pose.o pose_cell.o pose_cell_set.o pose_cell_scored_set.o \ + parsing.o parsing_pool.o \ + pose_cell_hierarchy.o pose_cell_hierarchy_reader.o \ + pi_referential.o pi_feature.o pi_feature_family.o \ + sample_set.o \ + shared_responses.o \ + classifier.o classifier_reader.o \ + detector.o \ + loss_machine.o decision_tree.o boosted_classifier.o \ + error_rates.o \ + materials.o \ + folding.o + $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) + +list_to_pool: misc.o interval.o fusion_sort.o shared.o global.o progress_bar.o \ + storable.o \ + jpeg_misc.o rgb_image.o param_parser.o \ + image.o rich_image.o \ + pose.o pose_cell.o \ + labelled_image.o labelled_image_pool.o \ + list_to_pool.o + $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) + +Makefile.depend: *.h *.cc Makefile + $(CC) -M *.cc > Makefile.depend + +archive: + cd .. && tar zcvf folding-gpl.tgz folding/*.{cc,h,sh,txt} folding/Makefile folding/gpl-3.0.txt + +clean: + \rm -f folding list_to_pool *.o Makefile.depend TAGS + +-include Makefile.depend diff --git a/boosted_classifier.cc b/boosted_classifier.cc new file mode 100644 index 0000000..8080ad2 --- /dev/null +++ b/boosted_classifier.cc @@ -0,0 +1,130 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "classifier_reader.h" +#include "fusion_sort.h" + +#include "boosted_classifier.h" +#include "tools.h" + +BoostedClassifier::BoostedClassifier(int nb_weak_learners) { + _loss_type = global.loss_type; + _nb_weak_learners = nb_weak_learners; + _weak_learners = 0; +} + +BoostedClassifier::BoostedClassifier() { + _loss_type = global.loss_type; + _nb_weak_learners = 0; + _weak_learners = 0; +} + +BoostedClassifier::~BoostedClassifier() { + if(_weak_learners) { + for(int w = 0; w < _nb_weak_learners; w++) + delete _weak_learners[w]; + delete[] _weak_learners; + } +} + +scalar_t BoostedClassifier::response(SampleSet *sample_set, int n_sample) { + scalar_t r = 0; + for(int w = 0; w < _nb_weak_learners; w++) { + r += _weak_learners[w]->response(sample_set, n_sample); + ASSERT(!isnan(r)); + } + return r; +} + +void BoostedClassifier::train(LossMachine *loss_machine, + SampleSet *sample_set, scalar_t *train_responses) { + + if(_weak_learners) { + cerr << "Can not re-train a BoostedClassifier" << endl; + exit(1); + } + + int nb_pos = 0, nb_neg = 0; + + for(int s = 0; s < sample_set->nb_samples(); s++) { + if(sample_set->label(s) > 0) nb_pos++; + else if(sample_set->label(s) < 0) nb_neg++; + } + + _weak_learners = new DecisionTree *[_nb_weak_learners]; + + (*global.log_stream) << "With " << nb_pos << " positive and " << nb_neg << " negative samples." << endl; + + for(int w = 0; w < _nb_weak_learners; w++) { + + _weak_learners[w] = new DecisionTree(); + _weak_learners[w]->train(loss_machine, sample_set, train_responses); + + for(int n = 0; n < sample_set->nb_samples(); n++) + train_responses[n] += _weak_learners[w]->response(sample_set, n); + + (*global.log_stream) << "Weak learner " << w + << " depth " << _weak_learners[w]->depth() + << " nb_leaves " << _weak_learners[w]->nb_leaves() + << " train loss " << loss_machine->loss(sample_set, train_responses) + << endl; + + } + + (*global.log_stream) << "Built a classifier with " << _nb_weak_learners << " weak_learners." << endl; +} + +void BoostedClassifier::tag_used_features(bool *used) { + for(int w = 0; w < _nb_weak_learners; w++) + _weak_learners[w]->tag_used_features(used); +} + +void BoostedClassifier::re_index_features(int *new_indexes) { + for(int w = 0; w < _nb_weak_learners; w++) + _weak_learners[w]->re_index_features(new_indexes); +} + +void BoostedClassifier::read(istream *is) { + if(_weak_learners) { + cerr << "Can not read over an existing BoostedClassifier" << endl; + exit(1); + } + + read_var(is, &_nb_weak_learners); + _weak_learners = new DecisionTree *[_nb_weak_learners]; + for(int w = 0; w < _nb_weak_learners; w++) { + _weak_learners[w] = new DecisionTree(); + _weak_learners[w]->read(is); + (*global.log_stream) << "Read tree " << w << " of depth " + << _weak_learners[w]->depth() << " with " + << _weak_learners[w]->nb_leaves() << " leaves." << endl; + } + + (*global.log_stream) + << "Read BoostedClassifier containing " << _nb_weak_learners << " weak learners." << endl; +} + +void BoostedClassifier::write(ostream *os) { + unsigned int id; + id = CLASSIFIER_BOOSTED; + write_var(os, &id); + + write_var(os, &_nb_weak_learners); + for(int w = 0; w < _nb_weak_learners; w++) + _weak_learners[w]->write(os); +} diff --git a/boosted_classifier.h b/boosted_classifier.h new file mode 100644 index 0000000..8f52e5a --- /dev/null +++ b/boosted_classifier.h @@ -0,0 +1,58 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +/* + + This class is an implementation of the Classifier with a boosting of + tree. It works with samples from R^n and has no concept of the + pi-features. + +*/ + +#ifndef BOOSTED_CLASSIFIER_H +#define BOOSTED_CLASSIFIER_H + +#include "classifier.h" +#include "sample_set.h" +#include "decision_tree.h" +#include "loss_machine.h" + +class BoostedClassifier : public Classifier { +public: + + int _loss_type; + int _nb_weak_learners; + DecisionTree **_weak_learners; + +public: + + BoostedClassifier(int nb_weak_learners); + BoostedClassifier(); + virtual ~BoostedClassifier(); + + virtual scalar_t response(SampleSet *sample_set, int n_sample); + virtual void train(LossMachine *loss_machine, SampleSet *train, scalar_t *response); + + virtual void tag_used_features(bool *used); + virtual void re_index_features(int *new_indexes); + + virtual void read(istream *is); + virtual void write(ostream *os); +}; + +#endif diff --git a/chrono.cc b/chrono.cc new file mode 100644 index 0000000..c4b7267 --- /dev/null +++ b/chrono.cc @@ -0,0 +1,62 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include + +#include "chrono.h" + +Chrono::Chrono() : _nb_ticks_per_second(scalar_t(sysconf(_SC_CLK_TCK))), + _nb(0), _current(-1) { } + +void Chrono::print(ostream *os) { + scalar_t total_durations = 0; + for(int n = 0; n < _nb; n++) total_durations += _durations[n]; + for(int n = 0; n < _nb; n++) + (*os) << "INFO CHRONO_" << _labels[n] + << " " << scalar_t(_durations[n] * 100) / total_durations << "%" + << " " << _durations[n] << " seconds" + << endl; +} + +void Chrono::start(const char *label) { + if(_current >= 0) { + struct tms tmp; + times(&tmp); + _durations[_current] += scalar_t(tmp.tms_stime - _last.tms_stime)/_nb_ticks_per_second; + } + + if(label) { + _current = 0; + while(_current < _nb && strcmp(_labels[_current], label) != 0) + _current++; + if(_current == _nb) { + if(_nb < _nb_max) { + strncpy(_labels[_nb], label, buffer_size); + _durations[_nb] = 0; + _nb++; + } else { + cerr << "Too many timers." << endl; + exit(1); + } + } + } else _current = -1; + + times(&_last); +} + +void Chrono::stop() { start(0); } diff --git a/chrono.h b/chrono.h new file mode 100644 index 0000000..8b26bcf --- /dev/null +++ b/chrono.h @@ -0,0 +1,39 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef CHRONO_H +#define CHRONO_H + +#include +#include "misc.h" + +class Chrono { + struct tms _last; + scalar_t _nb_ticks_per_second; + static const int _nb_max = 32; + int _nb, _current; + char _labels[_nb_max][buffer_size]; + scalar_t _durations[_nb_max]; +public: + Chrono(); + void print(ostream *os); + void start(const char *label); + void stop(); +}; + +#endif diff --git a/classifier.cc b/classifier.cc new file mode 100644 index 0000000..6cf61e8 --- /dev/null +++ b/classifier.cc @@ -0,0 +1,42 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "classifier.h" + +Classifier::~Classifier() { } + +void Classifier::extract_pi_feature_family(PiFeatureFamily *full_pi_feature_family, + PiFeatureFamily *extracted_pi_feature_family) { + + bool *used_features = new bool[full_pi_feature_family->nb_features()]; + int *new_feature_indexes = new int[full_pi_feature_family->nb_features()]; + + for(int f = 0; f < full_pi_feature_family->nb_features(); f++) + used_features[f] = false; + + tag_used_features(used_features); + + extracted_pi_feature_family->extract(full_pi_feature_family, + used_features, + new_feature_indexes); + + re_index_features(new_feature_indexes); + + delete[] new_feature_indexes; + delete[] used_features; +} diff --git a/classifier.h b/classifier.h new file mode 100644 index 0000000..b5dc2a7 --- /dev/null +++ b/classifier.h @@ -0,0 +1,61 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +/* + + This class is mostly able to learn a classifier from a SampleSet and + to provide a scalar response on any test sample. Additional methods + are required for persistence and select the possibly very few used + features. + +*/ + +#ifndef CLASSIFIER_H +#define CLASSIFIER_H + +#include "storable.h" +#include "pi_feature_family.h" +#include "sample_set.h" +#include "pose_cell_hierarchy.h" +#include "labelled_image_pool.h" +#include "loss_machine.h" + +class Classifier : public Storable { +public: + virtual ~Classifier(); + + // Evaluate on a sample + virtual scalar_t response(SampleSet *sample_set, int n_sample) = 0; + + // Train the classifier + virtual void train(LossMachine *loss_machine, SampleSet *train, scalar_t *response) = 0; + + // Tag in the boolean array which features are actually used + virtual void tag_used_features(bool *used) = 0; + // Change the indexes of the used features + virtual void re_index_features(int *new_indexes) = 0; + + // Storage + virtual void read(istream *is) = 0; + virtual void write(ostream *os) = 0; + + void extract_pi_feature_family(PiFeatureFamily *full_pi_feature_family, + PiFeatureFamily *extracted_pi_feature_family); +}; + +#endif diff --git a/classifier_reader.cc b/classifier_reader.cc new file mode 100644 index 0000000..dcd1e5b --- /dev/null +++ b/classifier_reader.cc @@ -0,0 +1,43 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "classifier_reader.h" +#include "misc.h" +#include "boosted_classifier.h" + +Classifier *read_classifier(istream *is) { + Classifier *result; + unsigned int id; + + read_var(is, &id); + + switch(id) { + case CLASSIFIER_BOOSTED: + result = new BoostedClassifier(); + break; +// case CLASSIFIER_BAYESIAN: +// result = new BayesianClassifier(); +// break; + default: + cerr << "Unknown classifier ID " << id << endl; + exit(1); + } + + result->read(is); + return result; +} diff --git a/classifier_reader.h b/classifier_reader.h new file mode 100644 index 0000000..5d68cee --- /dev/null +++ b/classifier_reader.h @@ -0,0 +1,28 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef CLASSIFIER_READER_H +#define CLASSIFIER_READER_H + +#include "classifier.h" + +enum { CLASSIFIER_BOOSTED, CLASSIFIER_BAYESIAN }; + +Classifier *read_classifier(istream *is); + +#endif diff --git a/decision_tree.cc b/decision_tree.cc new file mode 100644 index 0000000..e2b3daa --- /dev/null +++ b/decision_tree.cc @@ -0,0 +1,303 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "decision_tree.h" +#include "fusion_sort.h" + +DecisionTree::DecisionTree() { + _feature_index = -1; + _threshold = 0; + _weight = 0; + _subtree_greater = 0; + _subtree_lesser = 0; +} + +DecisionTree::~DecisionTree() { + if(_subtree_lesser) + delete _subtree_lesser; + if(_subtree_greater) + delete _subtree_greater; +} + +int DecisionTree::nb_leaves() { + if(_subtree_lesser ||_subtree_greater) + return _subtree_lesser->nb_leaves() + _subtree_greater->nb_leaves(); + else + return 1; +} + +int DecisionTree::depth() { + if(_subtree_lesser ||_subtree_greater) + return 1 + max(_subtree_lesser->depth(), _subtree_greater->depth()); + else + return 1; +} + +scalar_t DecisionTree::response(SampleSet *sample_set, int n_sample) { + if(_subtree_lesser && _subtree_greater) { + if(sample_set->feature_value(n_sample, _feature_index) < _threshold) + return _subtree_lesser->response(sample_set, n_sample); + else + return _subtree_greater->response(sample_set, n_sample); + } else { + return _weight; + } +} + +void DecisionTree::pick_best_split(SampleSet *sample_set, scalar_t *loss_derivatives) { + + int nb_samples = sample_set->nb_samples(); + + scalar_t *responses = new scalar_t[nb_samples]; + int *indexes = new int[nb_samples]; + int *sorted_indexes = new int[nb_samples]; + + scalar_t max_abs_sum = 0; + _feature_index = -1; + + for(int f = 0; f < sample_set->nb_features(); f++) { + scalar_t sum = 0; + + for(int s = 0; s < nb_samples; s++) { + indexes[s] = s; + responses[s] = sample_set->feature_value(s, f); + sum += loss_derivatives[s]; + } + + indexed_fusion_sort(nb_samples, indexes, sorted_indexes, responses); + + int t, u = sorted_indexes[0]; + for(int s = 0; s < nb_samples - 1; s++) { + t = u; + u = sorted_indexes[s + 1]; + sum -= 2 * loss_derivatives[t]; + + if(responses[t] < responses[u] && abs(sum) > max_abs_sum) { + max_abs_sum = abs(sum); + _feature_index = f; + _threshold = (responses[t] + responses[u])/2; + } + } + } + + delete[] indexes; + delete[] sorted_indexes; + delete[] responses; +} + +void DecisionTree::train(LossMachine *loss_machine, + SampleSet *sample_set, + scalar_t *current_responses, + scalar_t *loss_derivatives, + int depth) { + + if(_subtree_lesser || _subtree_greater || _feature_index >= 0) { + cerr << "You can not re-train a tree." << endl; + abort(); + } + + int nb_samples = sample_set->nb_samples(); + + int nb_pos = 0, nb_neg = 0; + for(int s = 0; s < sample_set->nb_samples(); s++) { + if(sample_set->label(s) > 0) nb_pos++; + else if(sample_set->label(s) < 0) nb_neg++; + } + + (*global.log_stream) << "Training tree" << endl; + (*global.log_stream) << " nb_samples " << nb_samples << endl; + (*global.log_stream) << " depth " << depth << endl; + (*global.log_stream) << " nb_pos = " << nb_pos << endl; + (*global.log_stream) << " nb_neg = " << nb_neg << endl; + + if(depth >= global.tree_depth_max) + (*global.log_stream) << " Maximum depth reached." << endl; + if(nb_pos < min_nb_samples_for_split) + (*global.log_stream) << " Not enough positive samples." << endl; + if(nb_neg < min_nb_samples_for_split) + (*global.log_stream) << " Not enough negative samples." << endl; + + if(depth < global.tree_depth_max && + nb_pos >= min_nb_samples_for_split && + nb_neg >= min_nb_samples_for_split) { + + pick_best_split(sample_set, loss_derivatives); + + if(_feature_index >= 0) { + int indexes[nb_samples]; + scalar_t *parted_current_responses = new scalar_t[nb_samples]; + scalar_t *parted_loss_derivatives = new scalar_t[nb_samples]; + + int nb_lesser = 0, nb_greater = 0; + int nb_lesser_pos = 0, nb_lesser_neg = 0, nb_greater_pos = 0, nb_greater_neg = 0; + + for(int s = 0; s < nb_samples; s++) { + if(sample_set->feature_value(s, _feature_index) < _threshold) { + indexes[nb_lesser] = s; + parted_current_responses[nb_lesser] = current_responses[s]; + parted_loss_derivatives[nb_lesser] = loss_derivatives[s]; + + if(sample_set->label(s) > 0) + nb_lesser_pos++; + else if(sample_set->label(s) < 0) + nb_lesser_neg++; + + nb_lesser++; + } else { + nb_greater++; + + indexes[nb_samples - nb_greater] = s; + parted_current_responses[nb_samples - nb_greater] = current_responses[s]; + parted_loss_derivatives[nb_samples - nb_greater] = loss_derivatives[s]; + + if(sample_set->label(s) > 0) + nb_greater_pos++; + else if(sample_set->label(s) < 0) + nb_greater_neg++; + } + } + + if((nb_lesser_pos >= min_nb_samples_for_split || + nb_lesser_neg >= min_nb_samples_for_split) && + (nb_greater_pos >= min_nb_samples_for_split || + nb_greater_neg >= min_nb_samples_for_split)) { + + _subtree_lesser = new DecisionTree(); + + { + SampleSet sub_sample_set(sample_set, nb_lesser, indexes); + + _subtree_lesser->train(loss_machine, + &sub_sample_set, + parted_current_responses, + parted_loss_derivatives, + depth + 1); + } + + _subtree_greater = new DecisionTree(); + + { + SampleSet sub_sample_set(sample_set, nb_greater, indexes + nb_lesser); + + _subtree_greater->train(loss_machine, + &sub_sample_set, + parted_current_responses + nb_lesser, + parted_loss_derivatives + nb_lesser, + depth + 1); + } + } + + delete[] parted_current_responses; + delete[] parted_loss_derivatives; + } else { + (*global.log_stream) << "Could not find a feature for split." << endl; + } + } + + if(!(_subtree_greater && _subtree_lesser)) { + scalar_t *tmp_responses = new scalar_t[nb_samples]; + for(int s = 0; s < nb_samples; s++) + tmp_responses[s] = 1; + + _weight = loss_machine->optimal_weight(sample_set, tmp_responses, current_responses); + + const scalar_t max_weight = 10.0; + + if(_weight > max_weight) { + _weight = max_weight; + } else if(_weight < - max_weight) { + _weight = - max_weight; + } + + (*global.log_stream) << " _weight " << _weight << endl; + + delete[] tmp_responses; + } +} + +void DecisionTree::train(LossMachine *loss_machine, + SampleSet *sample_set, + scalar_t *current_responses) { + + scalar_t *loss_derivatives = new scalar_t[sample_set->nb_samples()]; + + loss_machine->get_loss_derivatives(sample_set, current_responses, loss_derivatives); + + train(loss_machine, sample_set, current_responses, loss_derivatives, 0); + + delete[] loss_derivatives; +} + +////////////////////////////////////////////////////////////////////// + +void DecisionTree::tag_used_features(bool *used) { + if(_subtree_lesser && _subtree_greater) { + used[_feature_index] = true; + _subtree_lesser->tag_used_features(used); + _subtree_greater->tag_used_features(used); + } +} + +void DecisionTree::re_index_features(int *new_indexes) { + if(_subtree_lesser && _subtree_greater) { + _feature_index = new_indexes[_feature_index]; + _subtree_lesser->re_index_features(new_indexes); + _subtree_greater->re_index_features(new_indexes); + } +} + +////////////////////////////////////////////////////////////////////// + +void DecisionTree::read(istream *is) { + if(_subtree_lesser || _subtree_greater) { + cerr << "You can not read in an existing tree." << endl; + abort(); + } + + read_var(is, &_feature_index); + read_var(is, &_threshold); + read_var(is, &_weight); + + int split; + read_var(is, &split); + + if(split) { + _subtree_lesser = new DecisionTree(); + _subtree_lesser->read(is); + _subtree_greater = new DecisionTree(); + _subtree_greater->read(is); + } +} + +void DecisionTree::write(ostream *os) { + + write_var(os, &_feature_index); + write_var(os, &_threshold); + write_var(os, &_weight); + + int split; + if(_subtree_lesser && _subtree_greater) { + split = 1; + write_var(os, &split); + _subtree_lesser->write(os); + _subtree_greater->write(os); + } else { + split = 0; + write_var(os, &split); + } +} diff --git a/decision_tree.h b/decision_tree.h new file mode 100644 index 0000000..59dba57 --- /dev/null +++ b/decision_tree.h @@ -0,0 +1,67 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef DECISION_TREE_H +#define DECISION_TREE_H + +#include "misc.h" +#include "classifier.h" +#include "sample_set.h" +#include "loss_machine.h" + +class DecisionTree : public Classifier { + + int _feature_index; + scalar_t _threshold; + scalar_t _weight; + + DecisionTree *_subtree_lesser, *_subtree_greater; + + static const int min_nb_samples_for_split = 5; + + void pick_best_split(SampleSet *sample_set, + scalar_t *loss_derivatives); + + void train(LossMachine *loss_machine, + SampleSet *sample_set, + scalar_t *current_responses, + scalar_t *loss_derivatives, + int depth); + +public: + + DecisionTree(); + ~DecisionTree(); + + int nb_leaves(); + int depth(); + + scalar_t response(SampleSet *sample_set, int n_sample); + + void train(LossMachine *loss_machine, + SampleSet *sample_set, + scalar_t *current_responses); + + void tag_used_features(bool *used); + void re_index_features(int *new_indexes); + + void read(istream *is); + void write(ostream *os); +}; + +#endif diff --git a/detector.cc b/detector.cc new file mode 100644 index 0000000..1c3ef23 --- /dev/null +++ b/detector.cc @@ -0,0 +1,474 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "tools.h" +#include "detector.h" +#include "global.h" +#include "classifier_reader.h" +#include "pose_cell_hierarchy_reader.h" + +Detector::Detector() { + _hierarchy = 0; + _nb_levels = 0; + _nb_classifiers_per_level = 0; + _thresholds = 0; + _nb_classifiers = 0; + _classifiers = 0; + _pi_feature_families = 0; +} + + +Detector::~Detector() { + if(_hierarchy) { + delete[] _thresholds; + for(int q = 0; q < _nb_classifiers; q++) { + delete _classifiers[q]; + delete _pi_feature_families[q]; + } + delete[] _classifiers; + delete[] _pi_feature_families; + delete _hierarchy; + } +} + +////////////////////////////////////////////////////////////////////// +// Training + +void Detector::train_classifier(int level, + LossMachine *loss_machine, + ParsingPool *parsing_pool, + PiFeatureFamily *pi_feature_family, + Classifier *classifier) { + + // Randomize the pi-feature family + + PiFeatureFamily full_pi_feature_family; + + full_pi_feature_family.resize(global.nb_features_for_boosting_optimization); + full_pi_feature_family.randomize(level); + + int nb_positives = parsing_pool->nb_positive_cells(); + + int nb_negatives_to_sample = + parsing_pool->nb_positive_cells() * global.nb_negative_samples_per_positive; + + SampleSet *sample_set = new SampleSet(full_pi_feature_family.nb_features(), + nb_positives + nb_negatives_to_sample); + + scalar_t *responses = new scalar_t[nb_positives + nb_negatives_to_sample]; + + (*global.log_stream) << "Collecting the sampled training set." << endl; + + parsing_pool->weighted_sampling(loss_machine, + &full_pi_feature_family, + sample_set, + responses); + + (*global.log_stream) << "Training the classifier." << endl; + + (*global.log_stream) << "Initial train_loss " + << loss_machine->loss(sample_set, responses) + << endl; + + classifier->train(loss_machine, sample_set, responses); + classifier->extract_pi_feature_family(&full_pi_feature_family, pi_feature_family); + + delete[] responses; + delete sample_set; +} + +void Detector::train(LabelledImagePool *train_pool, + LabelledImagePool *validation_pool, + LabelledImagePool *hierarchy_pool) { + + if(_hierarchy) { + cerr << "Can not re-train a Detector" << endl; + exit(1); + } + + _hierarchy = new PoseCellHierarchy(hierarchy_pool); + + int nb_violations; + + nb_violations = _hierarchy->nb_incompatible_poses(train_pool); + + if(nb_violations > 0) { + cout << "The hierarchy is incompatible with the training set (" + << nb_violations + << " violations)." << endl; + exit(1); + } + + nb_violations = _hierarchy->nb_incompatible_poses(validation_pool); + + if(nb_violations > 0) { + cout << "The hierarchy is incompatible with the validation set (" + << nb_violations << " violations)." + << endl; + exit(1); + } + + _nb_levels = _hierarchy->nb_levels(); + _nb_classifiers_per_level = global.nb_classifiers_per_level; + _nb_classifiers = _nb_levels * _nb_classifiers_per_level; + _thresholds = new scalar_t[_nb_classifiers]; + _classifiers = new Classifier *[_nb_classifiers]; + _pi_feature_families = new PiFeatureFamily *[_nb_classifiers]; + + for(int q = 0; q < _nb_classifiers; q++) { + _classifiers[q] = new BoostedClassifier(global.nb_weak_learners_per_classifier); + _pi_feature_families[q] = new PiFeatureFamily(); + } + + ParsingPool *train_parsing, *validation_parsing; + + train_parsing = new ParsingPool(train_pool, + _hierarchy, + global.proportion_negative_cells_for_training); + + if(global.write_validation_rocs) { + validation_parsing = new ParsingPool(validation_pool, + _hierarchy, + global.proportion_negative_cells_for_training); + } else { + validation_parsing = 0; + } + + LossMachine *loss_machine = new LossMachine(global.loss_type); + + cout << "Building a detector." << endl; + + global.bar.init(&cout, _nb_classifiers); + + for(int l = 0; l < _nb_levels; l++) { + + if(l > 0) { + train_parsing->down_one_level(loss_machine, _hierarchy, l); + if(validation_parsing) { + validation_parsing->down_one_level(loss_machine, _hierarchy, l); + } + } + + for(int c = 0; c < _nb_classifiers_per_level; c++) { + int q = l * _nb_classifiers_per_level + c; + + (*global.log_stream) << "Building classifier " << q << " (level " << l << ")" << endl; + + // Train the classifier + + train_classifier(l, + loss_machine, + train_parsing, + _pi_feature_families[q], _classifiers[q]); + + // Update the cell responses on the training set + + (*global.log_stream) << "Updating training cell responses." << endl; + + train_parsing->update_cell_responses(_pi_feature_families[q], + _classifiers[q]); + + // Save the ROC curves on the training set + + char buffer[buffer_size]; + + sprintf(buffer, "%s/train_%05d.roc", + global.result_path, + (q + 1) * global.nb_weak_learners_per_classifier); + ofstream out(buffer); + train_parsing->write_roc(&out); + + if(validation_parsing) { + + // Update the cell responses on the validation set + + (*global.log_stream) << "Updating validation cell responses." << endl; + + validation_parsing->update_cell_responses(_pi_feature_families[q], + _classifiers[q]); + + // Save the ROC curves on the validation set + + sprintf(buffer, "%s/validation_%05d.roc", + global.result_path, + (q + 1) * global.nb_weak_learners_per_classifier); + ofstream out(buffer); + validation_parsing->write_roc(&out); + } + + _thresholds[q] = 0.0; + + global.bar.refresh(&cout, q); + } + } + + global.bar.finish(&cout); + + delete loss_machine; + delete train_parsing; + delete validation_parsing; +} + +void Detector::compute_thresholds(LabelledImagePool *validation_pool, scalar_t wanted_tp) { + LabelledImage *image; + int nb_targets_total = 0; + + for(int i = 0; i < validation_pool->nb_images(); i++) { + image = validation_pool->grab_image(i); + nb_targets_total += image->nb_targets(); + validation_pool->release_image(i); + } + + scalar_t *responses = new scalar_t[_nb_classifiers * nb_targets_total]; + + int tt = 0; + + for(int i = 0; i < validation_pool->nb_images(); i++) { + image = validation_pool->grab_image(i); + image->compute_rich_structure(); + + PoseCell current_cell; + + for(int t = 0; t < image->nb_targets(); t++) { + + scalar_t response = 0; + + for(int l = 0; l < _nb_levels; l++) { + + // We get the next-level cell for that target + + PoseCellSet cell_set; + + cell_set.erase_content(); + if(l == 0) { + _hierarchy->add_root_cells(image, &cell_set); + } else { + _hierarchy->add_subcells(l, ¤t_cell, &cell_set); + } + + int nb_compliant = 0; + + for(int c = 0; c < cell_set.nb_cells(); c++) { + if(cell_set.get_cell(c)->contains(image->get_target_pose(t))) { + current_cell = *(cell_set.get_cell(c)); + nb_compliant++; + } + } + + if(nb_compliant != 1) { + cerr << "INCONSISTENCY (" << nb_compliant << " should be one)" << endl; + abort(); + } + + for(int c = 0; c < _nb_classifiers_per_level; c++) { + int q = l * _nb_classifiers_per_level + c; + SampleSet *sample_set = new SampleSet(_pi_feature_families[q]->nb_features(), 1); + sample_set->set_sample(0, _pi_feature_families[q], image, ¤t_cell, 0); + response +=_classifiers[q]->response(sample_set, 0); + delete sample_set; + responses[tt + nb_targets_total * q] = response; + } + + } + + tt++; + } + + validation_pool->release_image(i); + } + + ASSERT(tt == nb_targets_total); + + // Here we have in responses[] all the target responses after every + // classifier + + int *still_detected = new int[nb_targets_total]; + int *indexes = new int[nb_targets_total]; + int *sorted_indexes = new int[nb_targets_total]; + + for(int t = 0; t < nb_targets_total; t++) { + still_detected[t] = 1; + indexes[t] = t; + } + + int current_nb_fn = 0; + + for(int q = 0; q < _nb_classifiers; q++) { + + scalar_t wanted_tp_at_this_classifier + = exp(log(wanted_tp) * scalar_t(q + 1) / scalar_t(_nb_classifiers)); + + int wanted_nb_fn_at_this_classifier + = int(nb_targets_total * (1 - wanted_tp_at_this_classifier)); + + (*global.log_stream) << "q = " << q + << " wanted_tp_at_this_classifier = " << wanted_tp_at_this_classifier + << " wanted_nb_fn_at_this_classifier = " << wanted_nb_fn_at_this_classifier + << endl; + + indexed_fusion_sort(nb_targets_total, indexes, sorted_indexes, + responses + q * nb_targets_total); + + for(int t = 0; (current_nb_fn < wanted_nb_fn_at_this_classifier) && (t < nb_targets_total - 1); t++) { + int u = sorted_indexes[t]; + int v = sorted_indexes[t+1]; + _thresholds[q] = responses[v + nb_targets_total * q]; + if(still_detected[u]) { + still_detected[u] = 0; + current_nb_fn++; + } + } + } + + delete[] still_detected; + delete[] indexes; + delete[] sorted_indexes; + + { //////////////////////////////////////////////////////////////////// + // Sanity check + + int nb_positives = 0; + + for(int t = 0; t < nb_targets_total; t++) { + int positive = 1; + for(int q = 0; q < _nb_classifiers; q++) { + if(responses[t + nb_targets_total * q] < _thresholds[q]) positive = 0; + } + if(positive) nb_positives++; + } + + scalar_t actual_tp = scalar_t(nb_positives) / scalar_t(nb_targets_total); + + (*global.log_stream) << "Overall detection rate " << nb_positives << "/" << nb_targets_total + << " " + << "actual_tp = " << actual_tp + << " " + << "wanted_tp = " << wanted_tp + << endl; + + if(actual_tp < wanted_tp) { + cerr << "INCONSISTENCY" << endl; + abort(); + } + } //////////////////////////////////////////////////////////////////// + + delete[] responses; +} + +////////////////////////////////////////////////////////////////////// +// Parsing + +void Detector::parse_rec(RichImage *image, int level, + PoseCell *cell, scalar_t current_response, + PoseCellScoredSet *result) { + + if(level == _nb_levels) { + result->add_cell_with_score(cell, current_response); + return; + } + + PoseCellSet cell_set; + cell_set.erase_content(); + + if(level == 0) { + _hierarchy->add_root_cells(image, &cell_set); + } else { + _hierarchy->add_subcells(level, cell, &cell_set); + } + + scalar_t *responses = new scalar_t[cell_set.nb_cells()]; + int *keep = new int[cell_set.nb_cells()]; + + for(int c = 0; c < cell_set.nb_cells(); c++) { + responses[c] = current_response; + keep[c] = 1; + } + + for(int a = 0; a < _nb_classifiers_per_level; a++) { + int q = level * _nb_classifiers_per_level + a; + SampleSet *samples = new SampleSet(_pi_feature_families[q]->nb_features(), 1); + for(int c = 0; c < cell_set.nb_cells(); c++) { + if(keep[c]) { + samples->set_sample(0, _pi_feature_families[q], image, cell_set.get_cell(c), 0); + responses[c] += _classifiers[q]->response(samples, 0); + keep[c] = responses[c] >= _thresholds[q]; + } + } + delete samples; + } + + for(int c = 0; c < cell_set.nb_cells(); c++) { + if(keep[c]) { + parse_rec(image, level + 1, cell_set.get_cell(c), responses[c], result); + } + } + + delete[] keep; + delete[] responses; +} + +void Detector::parse(RichImage *image, PoseCellScoredSet *result_cell_set) { + result_cell_set->erase_content(); + parse_rec(image, 0, 0, 0, result_cell_set); +} + +////////////////////////////////////////////////////////////////////// +// Storage + +void Detector::read(istream *is) { + if(_hierarchy) { + cerr << "Can not read over an existing Detector" << endl; + exit(1); + } + + read_var(is, &_nb_levels); + read_var(is, &_nb_classifiers_per_level); + + _nb_classifiers = _nb_levels * _nb_classifiers_per_level; + + _classifiers = new Classifier *[_nb_classifiers]; + _pi_feature_families = new PiFeatureFamily *[_nb_classifiers]; + _thresholds = new scalar_t[_nb_classifiers]; + + for(int q = 0; q < _nb_classifiers; q++) { + cout << "Read classifier " << q << endl; + _pi_feature_families[q] = new PiFeatureFamily(); + _pi_feature_families[q]->read(is); + _classifiers[q] = read_classifier(is); + read_var(is, &_thresholds[q]); + } + + _hierarchy = read_hierarchy(is); + + (*global.log_stream) << "Read Detector" << endl + << " _nb_levels " << _nb_levels << endl + << " _nb_classifiers_per_level " << _nb_classifiers_per_level << endl; +} + +void Detector::write(ostream *os) { + write_var(os, &_nb_levels); + write_var(os, &_nb_classifiers_per_level); + + for(int q = 0; q < _nb_classifiers; q++) { + _pi_feature_families[q]->write(os); + _classifiers[q]->write(os); + write_var(os, &_thresholds[q]); + } + + _hierarchy->write(os); +} diff --git a/detector.h b/detector.h new file mode 100644 index 0000000..72248af --- /dev/null +++ b/detector.h @@ -0,0 +1,76 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +/* + + This is the main class, a folded hierarchy of classifiers. + +*/ + +#ifndef DETECTOR_H +#define DETECTOR_H + +#include "storable.h" +#include "boosted_classifier.h" +#include "labelled_image_pool.h" +#include "parsing_pool.h" +#include "pose_cell_scored_set.h" + +class Detector : public Storable { +public: + PoseCellHierarchy *_hierarchy; + int _nb_levels, _nb_classifiers_per_level, _nb_classifiers; + scalar_t *_thresholds; + Classifier **_classifiers; + PiFeatureFamily **_pi_feature_families; + + void free(); + + virtual void train_classifier(int level, + LossMachine *loss_machine, + ParsingPool *parsing, + PiFeatureFamily *pi_feature_family, + Classifier *classifier); + + virtual void parse_rec(RichImage *image, int level, + PoseCell *cell, scalar_t response, + PoseCellScoredSet *result); + +public: + + Detector(); + virtual ~Detector(); + + inline int nb_levels() { return _nb_levels; } + + // The validation set is only used to save ROCs curves during + // training for monitoring the learning + + virtual void train(LabelledImagePool *train_pool, + LabelledImagePool *validation_pool, + LabelledImagePool *hierarchy_pool); + + virtual void compute_thresholds(LabelledImagePool *validation_pool, scalar_t wanted_tp); + + virtual void parse(RichImage *image, PoseCellScoredSet *result_cell_set); + + virtual void read(istream *is); + virtual void write(ostream *os); +}; + +#endif diff --git a/error_rates.cc b/error_rates.cc new file mode 100644 index 0000000..14d4d8c --- /dev/null +++ b/error_rates.cc @@ -0,0 +1,137 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "error_rates.h" +#include "fusion_sort.h" +#include "pose_cell_hierarchy.h" +#include "boosted_classifier.h" +#include "parsing_pool.h" +#include "chrono.h" +#include "materials.h" + +void compute_errors_on_one_image(int level, + LabelledImage *image, + PoseCellSet *cell_set, + int *nb_fns, int *nb_fas) { + + int hit[image->nb_targets()]; + + for(int t = 0; t < image->nb_targets(); t++) { + hit[t] = 0; + } + + Pose pose; + + for(int c = 0; c < cell_set->nb_cells(); c++) { + cell_set->get_cell(c)->get_centroid(&pose); + + int false_positive = 1; + + for(int t = 0; t < image->nb_targets(); t++) { + if(pose.hit(level, image->get_target_pose(t))) { + hit[t] = 1; + false_positive = 0; + } + } + + if(false_positive) (*nb_fas)++; + } + + for(int t = 0; t < image->nb_targets(); t++) { + if(!hit[t]) (*nb_fns)++; + } +} + +void print_decimated_error_rate(int level, LabelledImagePool *pool, Detector *detector) { + LabelledImage *image; + PoseCellScoredSet result_cell_set; + + int nb_fns = 0, nb_fas = 0, nb_targets = 0; + long int total_surface = 0; + + cout << "Testing the detector." << endl; + + global.bar.init(&cout, pool->nb_images()); + for(int i = 0; i < pool->nb_images(); i++) { + image = pool->grab_image(i); + total_surface += image->width() * image->height(); + image->compute_rich_structure(); + + detector->parse(image, &result_cell_set); + result_cell_set.decimate_collide(level); + result_cell_set.decimate_hit(level); + + compute_errors_on_one_image(level, image, &result_cell_set, &nb_fns, &nb_fas); + + if(global.write_parse_images) { + char buffer[buffer_size]; + sprintf(buffer, "%s/parse-%04d.png", global.result_path, i); + cout << "LEVEL = " << level << endl; + write_image_with_detections(buffer, + image, + &result_cell_set, level); + } + + nb_targets += image->nb_targets(); + pool->release_image(i); + global.bar.refresh(&cout, i); + } + global.bar.finish(&cout); + + scalar_t fn_rate = scalar_t(nb_fns)/scalar_t(nb_targets); + scalar_t nb_fas_per_vga = (scalar_t(nb_fas) / scalar_t(total_surface)) * scalar_t(640 * 480); + + (*global.log_stream) + << "INFO DECIMATED_NB_FALSE_NEGATIVES " << nb_fns << endl + << "INFO DECIMATED_NB_TARGETS " << nb_targets << endl + << "INFO DECIMATED_FALSE_NEGATIVE_RATE " << fn_rate << endl + << "INFO DECIMATED_NB_FALSE_POSITIVES " << nb_fas << endl + << "INFO DECIMATED_NB_FALSE_POSITIVES_PER_VGA " << nb_fas_per_vga << endl + << "INFO NB_SCENES " << pool->nb_images() << endl + << "INFO TOTAL_SURFACE " << total_surface << endl; + ; +} + +void parse_scene(Detector *detector, const char *image_name) { + RGBImage tmp; + tmp.read_jpg(image_name); + RichImage image(tmp.width(), tmp.height()); + + for(int y = 0; y < tmp.height(); y++) { + for(int x = 0; x < tmp.width(); x++) { + image.set_value(x, y, int(scalar_t(tmp.pixel(x, y, 0)) * 0.2989 + + scalar_t(tmp.pixel(x, y, 1)) * 0.5870 + + scalar_t(tmp.pixel(x, y, 2)) * 0.1140)); + } + } + + image.compute_rich_structure(); + + PoseCellScoredSet cell_set; + detector->parse(&image, &cell_set); + cell_set.decimate_hit(detector->nb_levels() - 1); + + cout << "RESULT " << image_name << endl; + for(int c = 0; c < cell_set.nb_cells(); c++) { + cout << "ALARM " << c << endl; + Pose alarm; + cell_set.get_cell(c)->get_centroid(&alarm); + alarm.print(&cout); + } + cout << "END_RESULT" << endl; +} diff --git a/error_rates.h b/error_rates.h new file mode 100644 index 0000000..71906d6 --- /dev/null +++ b/error_rates.h @@ -0,0 +1,30 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef ERROR_RATES_H +#define ERROR_RATES_H + +#include "misc.h" +#include "labelled_image_pool.h" +#include "detector.h" + +void print_decimated_error_rate(int level, LabelledImagePool *pool, Detector *detector); + +void parse_scene(Detector *detector, const char *image_name); + +#endif diff --git a/folding.cc b/folding.cc new file mode 100644 index 0000000..34c785c --- /dev/null +++ b/folding.cc @@ -0,0 +1,339 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +using namespace std; + +#include "misc.h" +#include "param_parser.h" +#include "global.h" +#include "labelled_image_pool_file.h" +#include "labelled_image_pool_subset.h" +#include "tools.h" +#include "detector.h" +#include "pose_cell_hierarchy.h" +#include "error_rates.h" +#include "materials.h" + +////////////////////////////////////////////////////////////////////// + +void check(bool condition, const char *message) { + if(!condition) { + cerr << message << endl; + exit(1); + } +} + +////////////////////////////////////////////////////////////////////// + +int main(int argc, char **argv) { + char *new_argv[argc]; + int new_argc = 0; + +#ifdef DEBUG + cout << endl; + cout << "**********************************************************************" << endl; + cout << "** COMPILED IN DEBUG MODE **" << endl; + cout << "**********************************************************************" << endl; + cout << endl; +#endif + + cout << "-- ARGUMENTS ---------------------------------------------------------" << endl; + for(int i = 0; i < argc; i++) + cout << (i > 0 ? " " : "") << argv[i] << (i < argc - 1 ? " \\" : "") + << endl; + + { + ParamParser parser; + global.init_parser(&parser); + parser.parse_options(argc, argv, false, &new_argc, new_argv); + global.read_parser(&parser); + (*global.log_stream) + << "-- PARAMETERS --------------------------------------------------------" + << endl; + parser.print_all(global.log_stream); + } + + nice(global.niceness); + + (*global.log_stream) << "INFO RANDOM_SEED " << global.random_seed << endl; + srand48(global.random_seed); + + LabelledImagePool *main_pool = 0; + LabelledImagePool *train_pool = 0, *validation_pool = 0, *hierarchy_pool = 0; + LabelledImagePool *test_pool = 0; + Detector *detector = 0; + + { + char buffer[buffer_size]; + gethostname(buffer, buffer_size); + (*global.log_stream) << "INFO HOSTNAME " << buffer << endl; + } + + for(int c = 1; c < new_argc; c++) { + + if(strcmp(new_argv[c], "open-pool") == 0) { + cout + << "-- OPENING POOL ------------------------------------------------------" + << endl; + + check(!main_pool, "Pool already opened."); + check(global.pool_name[0], "No pool file."); + + main_pool = new LabelledImagePoolFile(global.pool_name); + + bool for_test[main_pool->nb_images()]; + bool for_train[main_pool->nb_images()]; + bool for_validation[main_pool->nb_images()]; + bool for_hierarchy[main_pool->nb_images()]; + + for(int n = 0; n < main_pool->nb_images(); n++) { + for_test[n] = false; + for_train[n] = false; + for_validation[n] = false; + scalar_t r = drand48(); + if(r < global.proportion_for_train) + for_train[n] = true; + else if(r < global.proportion_for_train + global.proportion_for_validation) + for_validation[n] = true; + else if(global.proportion_for_test < 0 || + r < global.proportion_for_train + + global.proportion_for_validation + + global.proportion_for_test) + for_test[n] = true; + for_hierarchy[n] = for_train[n] || for_validation[n]; + } + + train_pool = new LabelledImagePoolSubset(main_pool, for_train); + validation_pool = new LabelledImagePoolSubset(main_pool, for_validation); + hierarchy_pool = new LabelledImagePoolSubset(main_pool, for_hierarchy); + + if(global.test_pool_name[0]) { + test_pool = new LabelledImagePoolFile(global.test_pool_name); + } else { + test_pool = new LabelledImagePoolSubset(main_pool, for_test); + } + + cout << "Using " + << train_pool->nb_images() << " images for train, " + << validation_pool->nb_images() << " images for validation, " + << hierarchy_pool->nb_images() << " images for the hierarchy and " + << test_pool->nb_images() << " images for test." + << endl; + + } + + else if(strcmp(new_argv[c], "write-target-poses") == 0) { + check(main_pool, "No pool available."); + LabelledImage *image; + for(int p = 0; p < main_pool->nb_images(); p++) { + image = main_pool->grab_image(p); + for(int t = 0; t < image->nb_targets(); t++) { + cout << "IMAGE " << p << " TARGET " << t << endl; + image->get_target_pose(t)->print(&cout); + } + main_pool->release_image(p); + } + } + + ////////////////////////////////////////////////////////////////////// + + else if(strcmp(new_argv[c], "train-detector") == 0) { + cout << "-- TRAIN DETECTOR ----------------------------------------------------" << endl; + check(train_pool, "No train pool available."); + check(validation_pool, "No validation pool available."); + check(hierarchy_pool, "No hierarchy pool available."); + check(!detector, "Existing detector, can not train another one."); + detector = new Detector(); + detector->train(train_pool, validation_pool, hierarchy_pool); + } + + else if(strcmp(new_argv[c], "compute-thresholds") == 0) { + cout << "-- COMPUTE THRESHOLDS ------------------------------------------------" << endl; + check(validation_pool, "No validation pool available."); + check(detector, "No detector."); + detector->compute_thresholds(validation_pool, global.wanted_true_positive_rate); + } + + else if(strcmp(new_argv[c], "check-hierarchy") == 0) { + cout << "-- CHECK HIERARCHY ---------------------------------------------------" << endl; + PoseCellHierarchy *h = new PoseCellHierarchy(hierarchy_pool); + cout << "Train incompatible poses " << h->nb_incompatible_poses(train_pool) << endl; + cout << "Validation incompatible poses " << h->nb_incompatible_poses(validation_pool) << endl; + delete h; + } + + ////////////////////////////////////////////////////////////////////// + + else if(strcmp(new_argv[c], "validate-detector") == 0) { + cout << "-- VALIDATE DETECTOR -------------------------------------------------" << endl; + + check(validation_pool, "No validation pool available."); + check(detector, "No detector."); + + print_decimated_error_rate(global.nb_levels - 1, validation_pool, detector); + } + + ////////////////////////////////////////////////////////////////////// + + else if(strcmp(new_argv[c], "test-detector") == 0) { + cout << "-- TEST DETECTOR -----------------------------------------------------" << endl; + + check(test_pool, "No test pool available."); + check(detector, "No detector."); + + if(test_pool->nb_images() > 0) { + print_decimated_error_rate(global.nb_levels - 1, test_pool, detector); + } else { + cout << "No test image." << endl; + } + } + + else if(strcmp(new_argv[c], "parse-images") == 0) { + cout << "-- PARSING IMAGES -----------------------------------------------------" << endl; + check(detector, "No detector."); + while(!cin.eof()) { + char image_name[buffer_size]; + cin.getline(image_name, buffer_size); + if(strlen(image_name) > 0) { + parse_scene(detector, image_name); + } + } + } + + ////////////////////////////////////////////////////////////////////// + + else if(strcmp(new_argv[c], "sequence-test-detector") == 0) { + cout << "-- SEQUENCE TEST DETECTOR --------------------------------------------" << endl; + + check(test_pool, "No test pool available."); + check(detector, "No detector."); + + if(test_pool->nb_images() > 0) { + + for(int n = 0; n < global.nb_wanted_true_positive_rates; n++) { + scalar_t r = global.wanted_true_positive_rate * + scalar_t(n + 1) / scalar_t(global.nb_wanted_true_positive_rates); + cout << "Testint at tp " << r + << " (" << n + 1 << "/" << global.nb_wanted_true_positive_rates << ")" + << endl; + (*global.log_stream) << "INFO THRESHOLD_FOR_TP " << r << endl; + detector->compute_thresholds(validation_pool, r); + print_decimated_error_rate(global.nb_levels - 1, test_pool, detector); + } + } else { + cout << "No test image." << endl; + } + } + + ////////////////////////////////////////////////////////////////////// + + else if(strcmp(new_argv[c], "write-detector") == 0) { + cout << "-- WRITE DETECTOR ----------------------------------------------------" << endl; + ofstream out(global.detector_name); + if(out.fail()) { + cerr << "Can not write to " << global.detector_name << endl; + exit(1); + } + check(detector, "No detector available."); + detector->write(&out); + } + + ////////////////////////////////////////////////////////////////////// + + else if(strcmp(new_argv[c], "read-detector") == 0) { + cout << "-- READ DETECTOR -----------------------------------------------------" << endl; + + check(!detector, "Existing detector, can not load another one."); + + ifstream in(global.detector_name); + if(in.fail()) { + cerr << "Can not read from " << global.detector_name << endl; + exit(1); + } + + detector = new Detector(); + detector->read(&in); + } + + ////////////////////////////////////////////////////////////////////// + + else if(strcmp(new_argv[c], "write-pool-images") == 0) { + cout << "-- WRITING POOL IMAGES -----------------------------------------------" << endl; + check(global.nb_images > 0, "You must set nb_images to a positive value."); + check(train_pool, "No train pool available."); + write_pool_images_with_poses_and_referentials(train_pool, detector); + } + + else if(strcmp(new_argv[c], "produce-materials") == 0) { + cout << "-- PRODUCING MATERIALS -----------------------------------------------" << endl; + + check(hierarchy_pool, "No hierarchy pool available."); + check(test_pool, "No test pool available."); + + PoseCellHierarchy *hierarchy; + + cout << "Creating hierarchy" << endl; + + hierarchy = new PoseCellHierarchy(hierarchy_pool); + + LabelledImage *image; + for(int p = 0; p < test_pool->nb_images(); p++) { + image = test_pool->grab_image(p); + if(image->width() == 640 && image->height() == 480) { + PoseCellSet pcs; + hierarchy->add_root_cells(image, &pcs); + cout << "WE HAVE " << pcs.nb_cells() << " CELLS" << endl; + exit(0); + test_pool->release_image(p); + } + } + + delete hierarchy; + + } + + ////////////////////////////////////////////////////////////////////// + + else { + cerr << "Unknown action " << new_argv[c] << endl; + exit(1); + } + + ////////////////////////////////////////////////////////////////////// + + } + + delete detector; + + delete train_pool; + delete validation_pool; + delete hierarchy_pool; + delete test_pool; + + delete main_pool; + + cout << "-- FINISHED ----------------------------------------------------------" << endl; + +} diff --git a/fusion_sort.cc b/fusion_sort.cc new file mode 100644 index 0000000..3774588 --- /dev/null +++ b/fusion_sort.cc @@ -0,0 +1,85 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "fusion_sort.h" +#include + +inline void indexed_fusion(int na, int *ia, int nb, int *ib, int *ic, scalar_t *values) { + int *ma = ia + na, *mb = ib + nb; + while(ia < ma && ib < mb) + if(values[*ia] <= values[*ib]) *(ic++) = *(ia++); + else *(ic++) = *(ib++); + while(ia < ma) *(ic++) = *(ia++); + while(ib < mb) *(ic++) = *(ib++); +} + +void indexed_fusion_sort(int n, int *from, int *result, scalar_t *values) { + ASSERT(n > 0); + if(n == 1) result[0] = from[0]; + else { + int k = n/2; + indexed_fusion_sort(k, from, result, values); + indexed_fusion_sort(n - k, from + k, result + k, values); + memcpy((void *) from, (void *) result, n * sizeof(int)); + indexed_fusion(k, from, n - k, from + k, result, values); + } +} + +// Sorting in decreasing order + +inline void indexed_fusion_dec(int na, int *ia, + int nb, int *ib, + int *ic, scalar_t *values) { + int *ma = ia + na, *mb = ib + nb; + while(ia < ma && ib < mb) + if(values[*ia] > values[*ib]) *(ic++) = *(ia++); + else *(ic++) = *(ib++); + while(ia < ma) *(ic++) = *(ia++); + while(ib < mb) *(ic++) = *(ib++); +} + +void indexed_fusion_dec_sort(int n, int *from, int *result, scalar_t *values) { + ASSERT(n > 0); + if(n == 1) result[0] = from[0]; + else { + int k = n/2; + indexed_fusion_dec_sort(k, from, result, values); + indexed_fusion_dec_sort(n - k, from + k, result + k, values); + memcpy((void *) from, (void *) result, n * sizeof(int)); + indexed_fusion_dec(k, from, n - k, from + k, result, values); + } +} + +void fusion_two_cells(int n1, scalar_t *cell1, int n2, scalar_t *cell2, scalar_t *result) { + scalar_t *max1 = cell1 + n1, *max2 = cell2 + n2; + while(cell1 < max1 && cell2 < max2) { + if(*(cell1) <= *(cell2)) *(result++) = *(cell1++); + else *(result++) = *(cell2++); + } + while(cell1 < max1) *(result++) = *(cell1++); + while(cell2 < max2) *(result++) = *(cell2++); +} + +void fusion_sort(int n, scalar_t *from, scalar_t *result) { + if(n > 1) { + fusion_sort(n/2, from, result); + fusion_sort(n - n/2, from + n/2, result + n/2); + memcpy(from, result, sizeof(scalar_t) * n); + fusion_two_cells(n/2, from, n - n/2, from + n/2, result); + } else result[0] = from[0]; +} diff --git a/fusion_sort.h b/fusion_sort.h new file mode 100644 index 0000000..d2c1cd1 --- /dev/null +++ b/fusion_sort.h @@ -0,0 +1,28 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef FUSION_SORT_H +#define FUSION_SORT_H + +#include "misc.h" + +void indexed_fusion_sort(int n, int *from, int *result, scalar_t *values); +void indexed_fusion_dec_sort(int n, int *from, int *result, scalar_t *values); +void fusion_sort(int n, scalar_t *from, scalar_t *result); + +#endif diff --git a/gaussian.cc b/gaussian.cc new file mode 100644 index 0000000..3278377 --- /dev/null +++ b/gaussian.cc @@ -0,0 +1,45 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "gaussian.h" + +Gaussian::Gaussian() { + _nb_samples = 0; + _sum = 0.0; + _sum_sq = 0.0; +} + +void Gaussian::add_sample(scalar_t x) { + _nb_samples++; + _sum += x; + _sum_sq += x * x; +} + +scalar_t Gaussian::expectation() { + return _sum / scalar_t(_nb_samples); +} + +scalar_t Gaussian::variance() { + scalar_t e = _sum / scalar_t(_nb_samples); + return (_sum_sq - _sum * e) / scalar_t(_nb_samples - 1); +} + +scalar_t Gaussian::standard_deviation() { + return sqrt(variance()); +} + diff --git a/gaussian.h b/gaussian.h new file mode 100644 index 0000000..da02d30 --- /dev/null +++ b/gaussian.h @@ -0,0 +1,35 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef GAUSSIAN_H +#define GAUSSIAN_H + +#include "misc.h" + +class Gaussian { + int _nb_samples; + scalar_t _sum, _sum_sq; +public: + Gaussian(); + void add_sample(scalar_t x); + scalar_t expectation(); + scalar_t variance(); + scalar_t standard_deviation(); +}; + +#endif diff --git a/global.cc b/global.cc new file mode 100644 index 0000000..253733c --- /dev/null +++ b/global.cc @@ -0,0 +1,172 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include + +#include "global.h" + +Global global; + +Global::Global() { + log_stream = 0; +} + +Global::~Global() { + delete log_stream; +} + +void Global::init_parser(ParamParser *parser) { + // The nice level of the process + parser->add_association("niceness", "5", false); + + // Seed to initialize the random generator + parser->add_association("random-seed", "0", false); + + // Should the pictures be b&w + parser->add_association("pictures-for-article", "no", false); + + // The name of the image pool to use + parser->add_association("pool-name", "", false); + // The name of the test image pool to use + parser->add_association("test-pool-name", "", false); + // From where to load or where to save the detector + parser->add_association("detector-name", "default.det", false); + // Where to put the generated files + parser->add_association("result-path", "/tmp/", false); + + // What kind of loss for the boosting + parser->add_association("loss-type", "exponential", false); + + // How many images to produce/process + parser->add_association("nb-images", "-1", false); + + // What is the maximum tree depth + parser->add_association("tree-depth-max", "1", false); + // What is the proportion of negative cells we actually use during training + parser->add_association("proportion-negative-cells-for-training", "0.025", false); + // How many negative samples to sub-sample for boosting every classifier + parser->add_association("nb-negative-samples-per-positive", "10", false); + // How many features we will look at for boosting optimization + parser->add_association("nb-features-for-boosting-optimization", "10000", false); + // Do we allow head-belly registration + parser->add_association("force-head-belly-independence", "no", false); + // How many weak-learners in every classifier + parser->add_association("nb-weak-learners-per-classifier", "10", false); + // How many classifiers per level + parser->add_association("nb-classifiers-per-level", "25", false); + // How many levels + parser->add_association("nb-levels", "1", false); + + // Proportion of images from the pool to use for training + parser->add_association("proportion-for-train", "0.5", false); + // Proportion of images from the pool to use for validation + parser->add_association("proportion-for-validation", "0.25", false); + // Proportion of images from the pool to use for test (negative + // means everything else) + parser->add_association("proportion-for-test", "0.25", false); + // During training, should we write the ROC curve estimated on the + // validation set (which cost a bit of computation) + parser->add_association("write-validation-rocs", "no", false); + + // Should we write down the PNGs for the results of the parsing + parser->add_association("write-parse-images", "no", false); + + // Should we write down the PNGs for the tags + parser->add_association("write-tag-images", "no", false); + + // What is the wanted true overall positive rate + parser->add_association("wanted-true-positive-rate", "0.5", false); + // How many rates to try for the sequence of tests + parser->add_association("nb-wanted-true-positive-rates", "10", false); + + // What is the minimum radius of the heads to detect. This is used + // as the reference size. + parser->add_association("min-head-radius", "25", false); + // What is the maximum size of the heads to detect. + parser->add_association("max-head-radius", "200", false); + // How many translation cell for one scale when generating the "top + // level" cells for an image. + parser->add_association("root-cell-nb-xy-per-scale", "5", false); + + // What is the minimum size of the windows + parser->add_association("pi-feature-window-min-size", "0.1", false); + + // How many scales between two powers of two for the multi-scale + // images + parser->add_association("nb-scales-per-power-of-two", "5", false); + + // Should we display a progress bar for lengthy operations + parser->add_association("progress-bar", "yes", false); +} + +void Global::read_parser(ParamParser *parser) { + niceness = parser->get_association_int("niceness"); + random_seed = parser->get_association_int("random-seed"); + pictures_for_article = parser->get_association_bool("pictures-for-article"); + + strncpy(pool_name, parser->get_association("pool-name"), buffer_size); + strncpy(test_pool_name, parser->get_association("test-pool-name"), buffer_size); + strncpy(detector_name, parser->get_association("detector-name"), buffer_size); + strncpy(result_path, parser->get_association("result-path"), buffer_size); + + char buffer[buffer_size]; + sprintf(buffer, "%s/log", result_path); + log_stream = new ofstream(buffer); + + char *l = parser->get_association("loss-type"); + if(strcmp(l, "exponential") == 0) + loss_type = LOSS_EXPONENTIAL; + else if(strcmp(l, "ev-regularized") == 0) + loss_type = LOSS_EV_REGULARIZED; + else if(strcmp(l, "hinge") == 0) + loss_type = LOSS_HINGE; + else if(strcmp(l, "logistic") == 0) + loss_type = LOSS_LOGISTIC; + else { + cerr << "Unknown loss type." << endl; + exit(1); + } + + nb_images = parser->get_association_int("nb-images"); + tree_depth_max = parser->get_association_int("tree-depth-max"); + nb_weak_learners_per_classifier = parser->get_association_int("nb-weak-learners-per-classifier"); + nb_classifiers_per_level = parser->get_association_int("nb-classifiers-per-level"); + nb_levels = parser->get_association_int("nb-levels"); + proportion_negative_cells_for_training = parser->get_association_scalar("proportion-negative-cells-for-training"); + nb_negative_samples_per_positive = parser->get_association_int("nb-negative-samples-per-positive"); + nb_features_for_boosting_optimization = parser->get_association_int("nb-features-for-boosting-optimization"); + force_head_belly_independence = parser->get_association_bool("force-head-belly-independence"); + proportion_for_train = parser->get_association_scalar("proportion-for-train"); + proportion_for_validation = parser->get_association_scalar("proportion-for-validation"); + proportion_for_test = parser->get_association_scalar("proportion-for-test"); + write_validation_rocs = parser->get_association_bool("write-validation-rocs"); + write_parse_images = parser->get_association_bool("write-parse-images"); + write_tag_images = parser->get_association_bool("write-tag-images"); + wanted_true_positive_rate = parser->get_association_scalar("wanted-true-positive-rate"); + nb_wanted_true_positive_rates = parser->get_association_int("nb-wanted-true-positive-rates"); + + min_head_radius = parser->get_association_scalar("min-head-radius"); + max_head_radius = parser->get_association_scalar("max-head-radius"); + root_cell_nb_xy_per_scale = parser->get_association_int("root-cell-nb-xy-per-scale"); + + pi_feature_window_min_size = parser->get_association_scalar("pi-feature-window-min-size"); + + nb_scales_per_power_of_two = parser->get_association_int("nb-scales-per-power-of-two"); + + bar.set_visible(parser->get_association_bool("progress-bar")); +} diff --git a/global.h b/global.h new file mode 100644 index 0000000..a053d3d --- /dev/null +++ b/global.h @@ -0,0 +1,101 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef GLOBAL_H +#define GLOBAL_H + +#include + +using namespace std; + +#include "misc.h" +#include "param_parser.h" +#include "progress_bar.h" + +enum { LOSS_EXPONENTIAL, + LOSS_EV_REGULARIZED, + LOSS_HINGE, + LOSS_LOGISTIC }; + +class Global { +public: + int niceness; + int random_seed; + int pictures_for_article; + + char pool_name[buffer_size]; + char test_pool_name[buffer_size]; + char detector_name[buffer_size]; + char result_path[buffer_size]; + + char materials_image_numbers[buffer_size]; + char materials_pf_numbers[buffer_size]; + + int loss_type; + + int nb_images; + + int tree_depth_max; + + int nb_weak_learners_per_classifier; + int nb_classifiers_per_level; + int nb_levels; + + scalar_t proportion_negative_cells_for_training; + int nb_negative_samples_per_positive; + int nb_features_for_boosting_optimization; + int force_head_belly_independence; + + scalar_t proportion_for_train; + scalar_t proportion_for_validation; + scalar_t proportion_for_test; + bool write_validation_rocs; + bool write_parse_images; + bool write_tag_images; + scalar_t wanted_true_positive_rate; + int nb_wanted_true_positive_rates; + + int nb_scales_per_power_of_two; + scalar_t min_head_radius; + scalar_t max_head_radius; + int root_cell_nb_xy_per_scale; + + scalar_t pi_feature_window_min_size; + + ProgressBar bar; + + Global(); + ~Global(); + + void init_parser(ParamParser *parser); + void read_parser(ParamParser *parser); + + ostream *log_stream; + + inline int scale_to_discrete_log_scale(scalar_t scale_ratio) { + return int(floor(log(scale_ratio) / log(2.0) * nb_scales_per_power_of_two)); + } + + inline scalar_t discrete_log_scale_to_scale(int discrete_scale) { + return exp( - scalar_t(discrete_scale) * log(2.0) / scalar_t(nb_scales_per_power_of_two)); + } +}; + +extern Global global; + +#endif diff --git a/graph.sh b/graph.sh new file mode 100755 index 0000000..b54fcf4 --- /dev/null +++ b/graph.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +######################################################################### +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the version 3 of the GNU General Public License # +# as published by the Free Software Foundation. # +# # +# This program is distributed in the hope that it will be useful, but # +# WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # +# General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +# Written and (C) by Francois Fleuret # +# Contact for comments & bug reports # +######################################################################### + +echo "Parsing the log files" + +for p in hb h+b; do + grep ^INFO results/${p}-*/log | grep "FALSE_NEGATIVE_RATE\|PER_VGA" | \ + sed -e "s/[^0-9A-Z_ .]//g" | \ + awk '{ + if($2 == "DECIMATED_FALSE_NEGATIVE_RATE") { + printf(1-$3) + } else { + printf(" "$3"\n") + } + }' | sort -g > /tmp/${p} + +done + +if [[ ! -s /tmp/hb ]] || [[ ! -s /tmp/h+b ]]; then + echo "Not enough data points." >&2 + exit 1 +fi + +###################################################################### + +echo "Generating the graph per se" + +GRAPH_NAME="/tmp/roc.eps" + +gnuplot<. // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "image.h" + +Image::Image(int width, int height) { + _width = width; + _height = height; + _content = new unsigned char[_width * _height]; +} + +Image::Image() { + _width = 0; + _height = 0; + _content = 0; +} + +Image::~Image() { + delete[] _content; +} + +void Image::crop(int xmin, int ymin, int width, int height) { + ASSERT(xmin >= 0 && xmin + width <= _width && + ymin >= 0 && ymin + height <= _height); + unsigned char *new_content = new unsigned char[width * height]; + for(int y = 0; y < height; y++) { + for(int x = 0; x < width; x++) { + new_content[x + (y * width)] = _content[x + xmin + _width * (y + ymin)]; + } + } + delete[] _content; + _content = new_content; + _width = width; + _height = height; +} + +void Image::to_rgb(RGBImage *image) { + int c; + for(int y = 0; y < _height; y++) { + for(int x = 0; x < _width; x++) { + c = value(x, y); + image->set_pixel(x, y, c, c, c); + } + } +} + +void Image::read(istream *in) { + delete[] _content; + read_var(in, &_width); + read_var(in, &_height); + _content = new unsigned char[_width * _height]; + in->read((char *) _content, sizeof(unsigned char) * _width * _height); +} + +void Image::write(ostream *out) { + write_var(out, &_width); + write_var(out, &_height); + out->write((char *) _content, sizeof(unsigned char) * _width * _height); +} diff --git a/image.h b/image.h new file mode 100644 index 0000000..5ba20a8 --- /dev/null +++ b/image.h @@ -0,0 +1,60 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef IMAGE_H +#define IMAGE_H + +#include "storable.h" +#include "rgb_image.h" +#include "misc.h" + +class Image : public Storable { +protected: + int _width, _height; + unsigned char *_content; +public: + + inline int width() { return _width; } + inline int height() { return _height; } + + inline unsigned char value(int x, int y) { + if(x >= 0 && x < _width && y >= 0 && y < _height) + return _content[x + (y * _width)]; + else + return 0; + } + + inline void set_value(int x, int y, unsigned char v) { + if(x >= 0 && x < _width && y >= 0 && y < _height) + _content[x + (y * _width)] = v; + else abort(); + } + + Image(); + Image(int width, int height); + + virtual ~Image(); + + virtual void crop(int xmin, int ymin, int width, int height); + virtual void to_rgb(RGBImage *image); + + virtual void read(istream *in); + virtual void write(ostream *out); +}; + +#endif diff --git a/interval.cc b/interval.cc new file mode 100644 index 0000000..1d7bde9 --- /dev/null +++ b/interval.cc @@ -0,0 +1,48 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "interval.h" + +void Interval::set(scalar_t x) { + min = x; + max = x; +} + +void Interval::set(scalar_t a, scalar_t b) { + min = a; + max = b; +} + +void Interval::set(Interval *i) { + min = i->min; + max = i->max; +} + +void Interval::set_subinterval(Interval *i, int k, int nb) { + min = i->min + ((i->max - i->min) * scalar_t(k))/scalar_t(nb); + max = i->min + ((i->max - i->min) * scalar_t(k + 1))/scalar_t(nb); +} + +void Interval::swallow(Interval *i) { + min = ::min(min, i->min); + max = ::max(max, i->max); +} + +ostream &operator << (ostream &out, const Interval &i) { + return out << "[" << i.min << ", " << i.max << "]"; +} diff --git a/interval.h b/interval.h new file mode 100644 index 0000000..57810f6 --- /dev/null +++ b/interval.h @@ -0,0 +1,58 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef INTERVAL_H +#define INTERVAL_H + +#include "misc.h" + +class Interval { +public: + scalar_t min, max; + + void set(scalar_t x); + void set(scalar_t a, scalar_t b); + void set(Interval *i); + + // Set this interval to the k-th of the nb regular subintervals of i + void set_subinterval(Interval *i, int k, int nb); + + // Grow to contain i + void swallow(Interval *i); + + inline bool contains(scalar_t x) { + return x >= min && x < max; + } + + inline void include(scalar_t x) { + if(x < min) min = x; + if(x > max) max = x; + } + + inline scalar_t middle() { + return (min + max) / 2; + } + + inline scalar_t length() { + return max - min; + } +}; + +ostream &operator << (ostream &out, const Interval &i); + +#endif diff --git a/jpeg_misc.cc b/jpeg_misc.cc new file mode 100644 index 0000000..3d653aa --- /dev/null +++ b/jpeg_misc.cc @@ -0,0 +1,25 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "jpeg_misc.h" + +void my_error_exit (j_common_ptr cinfo) { + my_error_ptr myerr = (my_error_ptr) cinfo->err; + (*cinfo->err->output_message) (cinfo); + longjmp (myerr->setjmp_buffer, 1); +} diff --git a/jpeg_misc.h b/jpeg_misc.h new file mode 100644 index 0000000..b6d95ea --- /dev/null +++ b/jpeg_misc.h @@ -0,0 +1,36 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef JPEG_MISC_H +#define JPEG_MISC_H + +#include +#include +#include +#include + +struct my_error_mgr { + struct jpeg_error_mgr pub; + jmp_buf setjmp_buffer; +}; + +typedef struct my_error_mgr *my_error_ptr; + +void my_error_exit (j_common_ptr cinfo); + +#endif diff --git a/labelled_image.cc b/labelled_image.cc new file mode 100644 index 0000000..d02313d --- /dev/null +++ b/labelled_image.cc @@ -0,0 +1,101 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "labelled_image.h" + +LabelledImage::LabelledImage() : RichImage() { + _target_poses = 0; +} + +LabelledImage::LabelledImage(int width, int height, int nb_targets) : RichImage(width, height) { + _nb_targets = nb_targets; + _target_poses = new Pose[_nb_targets]; +} + +LabelledImage::~LabelledImage() { + delete[] _target_poses; +} + +int LabelledImage::pose_cell_label(PoseCell *cell) { + int positive = 0; + int negative = 1; + + for(int t = 0; t < _nb_targets; t++) { + if(cell->contains(_target_poses + t)) + positive = 1; + if(!cell->negative_for_train(_target_poses + t)) + negative = 0; + } + + if(positive) return 1; + if(negative) return -1; + return 0; +} + +void LabelledImage::crop(int xmin, int ymin, int width, int height) { + RichImage::crop(xmin, ymin, width, height); + for(int t = 0; t < _nb_targets; t++) { + _target_poses[t].translate(- xmin, - ymin); + } +} + +void LabelledImage::reduce() { + int xmin = _width, xmax = 0, ymin = _height, ymax = 0; + if(_nb_targets > 0) { + for(int t = 0; t < _nb_targets; t++) { + xmin = min(xmin, int(_target_poses[t]._bounding_box_xmin)); + ymin = min(ymin, int(_target_poses[t]._bounding_box_ymin)); + xmax = max(xmax, int(_target_poses[t]._bounding_box_xmax)); + ymax = max(ymax, int(_target_poses[t]._bounding_box_ymax)); + } + } else { + xmin = 0; ymin = 0; + xmax = 640; ymax = 480; + } + xmin = max(0, xmin); + ymin = max(0, ymin); + xmax = min(_width, xmax); + ymax = min(_height, ymax); + crop(xmin, ymin, xmax - xmin, ymax - ymin); +} + +void LabelledImage::write(ostream *out) { + int v = file_format_version; + write_var(out, &v); + RichImage::write(out); + write_var(out, &_nb_targets); + for(int t = 0; t < _nb_targets; t++) + _target_poses[t].write(out); +} + +void LabelledImage::read(istream *in) { + int v; + read_var(in, &v); + if(v != file_format_version) { + cerr << "Pool file format version " << file_format_version << " expected," + << " the file is version " << v + << endl; + exit(1); + } + RichImage::read(in); + delete[] _target_poses; + read_var(in, &_nb_targets); + _target_poses = new Pose[_nb_targets]; + for(int t = 0; t < _nb_targets; t++) + _target_poses[t].read(in); +} diff --git a/labelled_image.h b/labelled_image.h new file mode 100644 index 0000000..2a26d73 --- /dev/null +++ b/labelled_image.h @@ -0,0 +1,53 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef LABELLED_IMAGE_H +#define LABELLED_IMAGE_H + +#include "rich_image.h" +#include "pose.h" +#include "pose_cell.h" + +class LabelledImage : public RichImage { + int _nb_targets; + Pose *_target_poses; + + static const int file_format_version = 2; + +public: + + LabelledImage(); + LabelledImage(int width, int height, int nb_targets); + + virtual ~LabelledImage(); + + inline int nb_targets() { return _nb_targets; } + inline Pose *get_target_pose(int n) { return _target_poses + n; } + + // The label of a cell can be +1 if it contains a target and -1 if + // it is far enough from any target + int pose_cell_label(PoseCell *cell); + + void crop(int xmin, int ymin, int width, int height); + void reduce(); + + virtual void write(ostream *out); + virtual void read(istream *in); +}; + +#endif diff --git a/labelled_image_pool.cc b/labelled_image_pool.cc new file mode 100644 index 0000000..bbfe943 --- /dev/null +++ b/labelled_image_pool.cc @@ -0,0 +1,21 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "labelled_image_pool.h" + +LabelledImagePool::~LabelledImagePool() { } diff --git a/labelled_image_pool.h b/labelled_image_pool.h new file mode 100644 index 0000000..3186dc7 --- /dev/null +++ b/labelled_image_pool.h @@ -0,0 +1,33 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef LABELLED_IMAGE_POOL_H +#define LABELLED_IMAGE_POOL_H + +#include "shared.h" +#include "labelled_image.h" + +class LabelledImagePool { +public: + virtual ~LabelledImagePool(); + virtual int nb_images() = 0; + virtual LabelledImage *grab_image(int n_image) = 0; + virtual void release_image(int n_image) = 0; +}; + +#endif diff --git a/labelled_image_pool_file.cc b/labelled_image_pool_file.cc new file mode 100644 index 0000000..cb099ee --- /dev/null +++ b/labelled_image_pool_file.cc @@ -0,0 +1,101 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "labelled_image_pool_file.h" + +LabelledImagePoolFile::LabelledImagePoolFile(char *file_name) { + _stream = new ifstream(file_name); + + if(_stream->fail()) { + cerr << "Can not open image pool " << file_name << " for reading." << endl; + exit(1); + } + + cout << "Opening image pool " << file_name << " ... "; + cout.flush(); + + LabelledImage dummy; + + int nb_image_max = 1024; + int nb_targets = 0; + + _nb_images = 0; + streampos *tmp_positions = new streampos[nb_image_max]; + + // This looks slightly ugly to me + + _stream->seekg(0, ios::end); + streampos end = _stream->tellg(); + _stream->seekg(0, ios::beg); + + while(_stream->tellg() < end) { + grow(&nb_image_max, _nb_images, &tmp_positions, 2); + tmp_positions[_nb_images] = _stream->tellg(); + dummy.read(_stream); + nb_targets += dummy.nb_targets(); + _nb_images++; + } + + _images = new LabelledImage *[_nb_images]; + _image_stream_positions = new streampos[_nb_images]; + _image_nb_refs = new int[_nb_images]; + + for(int i = 0; i < _nb_images; i++) { + _images[i] = 0; + _image_stream_positions[i] = tmp_positions[i]; + _image_nb_refs[i] = 0; + } + + delete[] tmp_positions; + + cout << "done." << endl; + cout << "It contains " << _nb_images << " images and " << nb_targets << " targets." << endl; +} + +LabelledImagePoolFile::~LabelledImagePoolFile() { +#ifdef DEBUG + for(int i = 0; i < _nb_images; i++) if(_image_nb_refs[i] > 0) { + cerr << "Destroying a pool while images are grabbed." << endl; + abort(); + } +#endif + delete[] _images; + delete[] _image_stream_positions; + delete[] _image_nb_refs; + delete _stream; +} + +int LabelledImagePoolFile::nb_images() { + return _nb_images; +} + +LabelledImage *LabelledImagePoolFile::grab_image(int n_image) { + if(_image_nb_refs[n_image] == 0) { + _stream->seekg(_image_stream_positions[n_image]); + _images[n_image] = new LabelledImage(); + _images[n_image]->read(_stream); + } + _image_nb_refs[n_image]++; + return _images[n_image]; +} + +void LabelledImagePoolFile::release_image(int n_image) { + ASSERT(_image_nb_refs[n_image] > 0); + _image_nb_refs[n_image]--; + if(_image_nb_refs[n_image] <= 0) delete _images[n_image]; +} diff --git a/labelled_image_pool_file.h b/labelled_image_pool_file.h new file mode 100644 index 0000000..a46d781 --- /dev/null +++ b/labelled_image_pool_file.h @@ -0,0 +1,46 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef LABELLED_IMAGE_POOL_FILE_H +#define LABELLED_IMAGE_POOL_FILE_H + +#include "labelled_image_pool.h" +#include "labelled_image.h" + +#include + +class LabelledImagePoolFile : public LabelledImagePool { + int _nb_images; + LabelledImage **_images; + streampos *_image_stream_positions; + int *_image_nb_refs; + ifstream *_stream; + +public: + LabelledImagePoolFile(char *file_name); + virtual ~LabelledImagePoolFile(); + + virtual int nb_images(); + + // grab_image(n) does _NOT_ build the rich structure. One has to call + // compute_rich_structure() for that! + virtual LabelledImage *grab_image(int n_image); + virtual void release_image(int n_image); +}; + +#endif diff --git a/labelled_image_pool_subset.cc b/labelled_image_pool_subset.cc new file mode 100644 index 0000000..3b46d48 --- /dev/null +++ b/labelled_image_pool_subset.cc @@ -0,0 +1,47 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "labelled_image_pool_subset.h" + +LabelledImagePoolSubset::LabelledImagePoolSubset(LabelledImagePool *mother_pool, + bool *used) { + _mother_pool = mother_pool; + _nb_images = 0; + for(int n = 0; n < _mother_pool->nb_images(); n++) + if(used[n]) _nb_images++; + _indexes = new int[_nb_images]; + int k = 0; + for(int n = 0; n < _mother_pool->nb_images(); n++) + if(used[n]) _indexes[k++] = n; +} + +LabelledImagePoolSubset::~LabelledImagePoolSubset() { + delete[] _indexes; +} + +int LabelledImagePoolSubset::nb_images() { + return _nb_images; +} + +LabelledImage *LabelledImagePoolSubset::grab_image(int n_image) { + return _mother_pool->grab_image(_indexes[n_image]); +} + +void LabelledImagePoolSubset::release_image(int n_image) { + _mother_pool->release_image(_indexes[n_image]); +} diff --git a/labelled_image_pool_subset.h b/labelled_image_pool_subset.h new file mode 100644 index 0000000..cc86696 --- /dev/null +++ b/labelled_image_pool_subset.h @@ -0,0 +1,36 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef LABELLED_IMAGE_POOL_SUBSET_H +#define LABELLED_IMAGE_POOL_SUBSET_H + +#include "labelled_image_pool.h" + +class LabelledImagePoolSubset : public LabelledImagePool { + LabelledImagePool *_mother_pool; + int _nb_images; + int *_indexes; +public: + LabelledImagePoolSubset(LabelledImagePool *mother_pool, bool *used); + virtual ~LabelledImagePoolSubset(); + virtual int nb_images(); + virtual LabelledImage *grab_image(int n_image); + virtual void release_image(int n_image); +}; + +#endif diff --git a/list_to_pool.cc b/list_to_pool.cc new file mode 100644 index 0000000..c142534 --- /dev/null +++ b/list_to_pool.cc @@ -0,0 +1,280 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include + +using namespace std; + +#include "misc.h" +#include "global.h" +#include "param_parser.h" +#include "labelled_image.h" + +void parse_warning(const char *message, char *file, int line) { + cerr << message << " " << file << ":" << line << "." << endl; + cerr.flush(); +} + +void parse_error(const char *message, char *file, int line) { + cerr << message << " " << file << ":" << line << "." << endl; + cerr.flush(); + exit(1); +} + +////////////////////////////////////////////////////////////////////// + +void list_to_pool(char *main_path, char *list_name, char *pool_name) { + ifstream list_stream(list_name); + + if(list_stream.fail()) { + cerr << "Can not open " << list_name << " for reading." << endl; + exit(1); + } + + ofstream pool_stream(pool_name); + + if(pool_stream.fail()) { + cerr << "Can not open " << pool_name << " for writing." << endl; + exit(1); + } + + int line_number = 0; + int nb_scenes = 0; + + int nb_cats = -1; + LabelledImage *current_image = 0; + bool open_current_cat = false; + bool head_defined = false; + bool bounding_box_defined = false; + bool body_defined = false; + int nb_ears = 0; + + while(!list_stream.eof() && (global.nb_images < 0 || nb_scenes < global.nb_images)) { + char line[large_buffer_size], token[buffer_size], full_image_name[buffer_size]; + list_stream.getline(line, large_buffer_size); + line_number++; + char *s = line; + s = next_word(token, s, buffer_size); + + ////////////////////////////////////////////////////////////////////// + + if(strcmp(token, "SCENE") == 0) { + + if(current_image) { + parse_error("Non-closed scene ", list_name, line_number); + } + + if(s) { + s = next_word(token, s, buffer_size); + sprintf(full_image_name, "%s/%s", main_path, token); + } else { + parse_error("Image name is missing ", list_name, line_number); + } + + cout << "Processing scene " << full_image_name << "." << endl; + + int nb_cats_in_current_image = -1; + + if(s) { + s = next_word(token, s, buffer_size); + nb_cats_in_current_image = atoi(token); + } else { + parse_error("Number of cats is missing ", list_name, line_number); + } + + if(nb_cats_in_current_image < 0 || nb_cats_in_current_image > 100) { + parse_error("Weird number of cats ", list_name, line_number); + } + + RGBImage tmp; + tmp.read_jpg(full_image_name); + + current_image = new LabelledImage(tmp.width(), + tmp.height(), + nb_cats_in_current_image); + + for(int y = 0; y < tmp.height(); y++) { + for(int x = 0; x < tmp.width(); x++) { + current_image->set_value(x, y, + int(scalar_t(tmp.pixel(x, y, 0)) * 0.2989 + + scalar_t(tmp.pixel(x, y, 1)) * 0.5870 + + scalar_t(tmp.pixel(x, y, 2)) * 0.1140)); + } + } + + nb_cats = 0; + } + + else if(strcmp(token, "END_SCENE") == 0) { + if(current_image == 0) + parse_error("Non-open scene ", list_name, line_number); + + if(nb_cats < current_image->nb_targets()) { + parse_warning("Less cats than advertised (some were ignored?), scene ignored", + list_name, line_number); + } else { + current_image->write(&pool_stream); + nb_scenes++; + } + + delete current_image; + current_image = 0; + } + + ////////////////////////////////////////////////////////////////////// + + else if(strcmp(token, "CAT") == 0) { + if(open_current_cat) + parse_error("Non-closed cat ", list_name, line_number); + if(current_image == 0) + parse_error("Cat without scene ", list_name, line_number); + if(nb_cats >= current_image->nb_targets()) + parse_error("More cats than advertised", list_name, line_number); + open_current_cat = true; + head_defined = false; + bounding_box_defined = false; + body_defined = false; + nb_ears = 0; + } + + else if(strcmp(token, "END_CAT") == 0) { + if(!open_current_cat) + parse_error("Undefined cat ", list_name, line_number); + + if(!bounding_box_defined) { + parse_error("Undefined bounding box ", list_name, line_number); + } + + if(head_defined && body_defined) { + if(current_image->get_target_pose(nb_cats)->_head_radius > global.min_head_radius && + current_image->get_target_pose(nb_cats)->_head_radius < global.max_head_radius) { + nb_cats++; + } else { + cerr << "Cat ignored since the head radius (" + << current_image->get_target_pose(nb_cats)->_head_radius << ") is not in the tolerance (" + << global.min_head_radius << ", " << global.max_head_radius + << ") " + << list_name << ":" << line_number + << endl; + cerr.flush(); + } + } else { + parse_warning("Cat ignored since either the body, head or belly are undefined", + list_name, line_number); + } + + open_current_cat = false; + } + + ////////////////////////////////////////////////////////////////////// + + else if(strcmp(token, "BODYBOX") == 0) { + if(!open_current_cat) parse_error("Undefined cat ", list_name, line_number); + if(bounding_box_defined) parse_error("Two bounding box", list_name, line_number); + int xmin = -1, ymin = -1, xmax = -1, ymax = -1; + if(s) { s = next_word(token, s, buffer_size); xmin = atoi(token); } + else parse_error("BODYBOX parameter xmin missing ", list_name, line_number); + if(s) { s = next_word(token, s, buffer_size); ymin = atoi(token); } + else parse_error("BODYBOX parameter ymin missing ", list_name, line_number); + if(s) { s = next_word(token, s, buffer_size); xmax = atoi(token); } + else parse_error("BODYBOX parameter xmax missing ", list_name, line_number); + if(s) { s = next_word(token, s, buffer_size); ymax = atoi(token); } + else parse_error("BODYBOX parameter ymax missing ", list_name, line_number); + current_image->get_target_pose(nb_cats)->_bounding_box_xmin = xmin; + current_image->get_target_pose(nb_cats)->_bounding_box_ymin = ymin; + current_image->get_target_pose(nb_cats)->_bounding_box_xmax = xmax; + current_image->get_target_pose(nb_cats)->_bounding_box_ymax = ymax; + bounding_box_defined = true; + } + + ////////////////////////////////////////////////////////////////////// + + else if(strcmp(token, "HEADELLIPSE") == 0) { + if(!open_current_cat) parse_error("Undefined cat ", list_name, line_number); + if(head_defined) parse_error("Two head definitions", list_name, line_number); + int xmin = -1, ymin = -1, xmax = -1, ymax = -1; + if(s) { s = next_word(token, s, buffer_size); xmin = atoi(token); } + else parse_error("HEADELLIPSE parameter xmin missing ", list_name, line_number); + if(s) { s = next_word(token, s, buffer_size); ymin = atoi(token); } + else parse_error("HEADELLIPSE parameter ymin missing ", list_name, line_number); + if(s) { s = next_word(token, s, buffer_size); xmax = atoi(token); } + else parse_error("HEADELLIPSE parameter xmax missing ", list_name, line_number); + if(s) { s = next_word(token, s, buffer_size); ymax = atoi(token); } + else parse_error("HEADELLIPSE parameter ymax missing ", list_name, line_number); + current_image->get_target_pose(nb_cats)->_head_xc = (xmin + xmax)/2; + current_image->get_target_pose(nb_cats)->_head_yc = (ymin + ymax)/2; + current_image->get_target_pose(nb_cats)->_head_radius = int(sqrt(scalar_t(xmax - xmin) * scalar_t(ymax - ymin))/2); + head_defined = true; + } + + else if(strcmp(token, "BELLYLOCATION") == 0) { + if(!open_current_cat) parse_error("Undefined cat ", list_name, line_number); + int x1 = -1, y1 = -1; + + if(s) { s = next_word(token, s, buffer_size); x1 = atoi(token); } + else parse_error("BELLYLOCATION parameter x1 missing ", list_name, line_number); + if(s) { s = next_word(token, s, buffer_size); y1 = atoi(token); } + else parse_error("BELLYLOCATION parameter y1 missing ", list_name, line_number); + + if(body_defined) { + parse_error("More than one body location. ", list_name, line_number); + } else { + + Pose *pose = current_image->get_target_pose(nb_cats); + + pose->_body_xc = x1; + pose->_body_yc = y1; + + body_defined = true; + } + } + } +} + +////////////////////////////////////////////////////////////////////// + +int main(int argc, char **argv) { + char *new_argv[argc]; + int new_argc = 0; + + { + ParamParser parser; + global.init_parser(&parser); + parser.parse_options(argc, argv, false, &new_argc, new_argv); + global.read_parser(&parser); + cout << "-- PARAMETERS --------------------------------------------------------" << endl; + parser.print_all(&cout); + } + + if(new_argc != 4) { + cerr << new_argv[0] << " " << endl; + exit(1); + } + + cout << "From list " << new_argv[1] + << " and image dir " << new_argv[2] + << ", generating pool " << new_argv[3] + << "." << endl; + + cout << "-- GENERATING IMAGES -------------------------------------------------" << endl; + + list_to_pool(new_argv[2], new_argv[1], new_argv[3]); + + cout << "-- FINISHED ----------------------------------------------------------" << endl; + +} diff --git a/loss_machine.cc b/loss_machine.cc new file mode 100644 index 0000000..6ff78d5 --- /dev/null +++ b/loss_machine.cc @@ -0,0 +1,421 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "tools.h" +#include "loss_machine.h" + +LossMachine::LossMachine(int loss_type) { + _loss_type = loss_type; +} + +void LossMachine::get_loss_derivatives(SampleSet *samples, + scalar_t *responses, + scalar_t *derivatives) { + + switch(_loss_type) { + + case LOSS_EXPONENTIAL: + { + for(int n = 0; n < samples->nb_samples(); n++) { + derivatives[n] = + - samples->label(n) * exp( - samples->label(n) * responses[n]); + } + } + break; + + case LOSS_EV_REGULARIZED: + { + scalar_t sum_pos = 0, sum_sq_pos = 0, nb_pos = 0, m_pos, v_pos; + scalar_t sum_neg = 0, sum_sq_neg = 0, nb_neg = 0, m_neg, v_neg; + + for(int n = 0; n < samples->nb_samples(); n++) { + if(samples->label(n) > 0) { + sum_pos += responses[n]; + sum_sq_pos += sq(responses[n]); + nb_pos += 1.0; + } + else if(samples->label(n) < 0) { + sum_neg += responses[n]; + sum_sq_neg += sq(responses[n]); + nb_neg += 1.0; + } + } + + m_pos = sum_pos / nb_pos; + v_pos = sum_sq_pos/(nb_pos - 1) - sq(sum_pos)/(nb_pos * (nb_pos - 1)); + + scalar_t loss_pos = nb_pos * exp(v_pos/2 - m_pos); + + m_neg = sum_neg / nb_neg; + v_neg = sum_sq_neg/(nb_neg - 1) - sq(sum_neg)/(nb_neg * (nb_neg - 1)); + + scalar_t loss_neg = nb_neg * exp(v_neg/2 + m_neg); + + for(int n = 0; n < samples->nb_samples(); n++) { + if(samples->label(n) > 0) { + derivatives[n] = + ( - 1/nb_pos + (responses[n] - m_pos)/(nb_pos - 1)) * loss_pos; + } else if(samples->label(n) < 0) { + derivatives[n] = + ( 1/nb_neg + (responses[n] - m_neg)/(nb_neg - 1)) * loss_neg; + } + } + } + + break; + + case LOSS_HINGE: + { + for(int n = 0; n < samples->nb_samples(); n++) { + if(samples->label(n) != 0 && samples->label(n) * responses[n] < 1) + derivatives[n] = 1; + else + derivatives[n] = 0; + } + } + break; + + case LOSS_LOGISTIC: + { + for(int n = 0; n < samples->nb_samples(); n++) { + if(samples->label(n) == 0) + derivatives[n] = 0.0; + else + derivatives[n] = samples->label(n) * 1/(1 + exp(samples->label(n) * responses[n])); + } + } + break; + + default: + cerr << "Unknown loss type in BoostedClassifier::get_loss_derivatives." + << endl; + exit(1); + } + +} + +scalar_t LossMachine::loss(SampleSet *samples, scalar_t *responses) { + scalar_t l = 0; + + switch(_loss_type) { + + case LOSS_EXPONENTIAL: + { + for(int n = 0; n < samples->nb_samples(); n++) { + l += exp( - samples->label(n) * responses[n]); + ASSERT(!isinf(l)); + } + } + break; + + case LOSS_EV_REGULARIZED: + { + scalar_t sum_pos = 0, sum_sq_pos = 0, nb_pos = 0, m_pos, v_pos; + scalar_t sum_neg = 0, sum_sq_neg = 0, nb_neg = 0, m_neg, v_neg; + + for(int n = 0; n < samples->nb_samples(); n++) { + if(samples->label(n) > 0) { + sum_pos += responses[n]; + sum_sq_pos += sq(responses[n]); + nb_pos += 1.0; + } else if(samples->label(n) < 0) { + sum_neg += responses[n]; + sum_sq_neg += sq(responses[n]); + nb_neg += 1.0; + } + } + + l = 0; + + if(nb_pos > 0) { + m_pos = sum_pos / nb_pos; + v_pos = sum_sq_pos/(nb_pos - 1) - sq(sum_pos)/(nb_pos * (nb_pos - 1)); + l += nb_pos * exp(v_pos/2 - m_pos); + } + + if(nb_neg > 0) { + m_neg = sum_neg / nb_neg; + v_neg = sum_sq_neg/(nb_neg - 1) - sq(sum_neg)/(nb_neg * (nb_neg - 1)); + l += nb_neg * exp(v_neg/2 + m_neg); + } + + } + break; + + case LOSS_HINGE: + { + for(int n = 0; n < samples->nb_samples(); n++) { + if(samples->label(n) != 0) { + if(samples->label(n) * responses[n] < 1) + l += (1 - samples->label(n) * responses[n]); + } + } + } + break; + + case LOSS_LOGISTIC: + { + for(int n = 0; n < samples->nb_samples(); n++) { + if(samples->label(n) != 0) { + scalar_t u = - samples->label(n) * responses[n]; + if(u > 20) { + l += u; + } if(u > -20) { + l += log(1 + exp(u)); + } + } + } + } + break; + + default: + cerr << "Unknown loss type in LossMachine::loss." << endl; + exit(1); + } + + return l; +} + +scalar_t LossMachine::optimal_weight(SampleSet *sample_set, + scalar_t *weak_learner_responses, + scalar_t *current_responses) { + + switch(_loss_type) { + + case LOSS_EXPONENTIAL: + { + scalar_t num = 0, den = 0, z; + for(int n = 0; n < sample_set->nb_samples(); n++) { + z = sample_set->label(n) * weak_learner_responses[n]; + if(z > 0) { + num += exp( - sample_set->label(n) * current_responses[n]); + } else if(z < 0) { + den += exp( - sample_set->label(n) * current_responses[n]); + } + } + + return 0.5 * log(num / den); + } + break; + + case LOSS_EV_REGULARIZED: + { + + scalar_t u = 0, du = -0.1; + scalar_t *responses = new scalar_t[sample_set->nb_samples()]; + + scalar_t l, prev_l = -1; + + const scalar_t minimum_delta_for_optimization = 1e-5; + + scalar_t shift = 0; + + { + scalar_t sum_pos = 0, sum_sq_pos = 0, nb_pos = 0, m_pos, v_pos; + scalar_t sum_neg = 0, sum_sq_neg = 0, nb_neg = 0, m_neg, v_neg; + + for(int n = 0; n < sample_set->nb_samples(); n++) { + if(sample_set->label(n) > 0) { + sum_pos += responses[n]; + sum_sq_pos += sq(responses[n]); + nb_pos += 1.0; + } else if(sample_set->label(n) < 0) { + sum_neg += responses[n]; + sum_sq_neg += sq(responses[n]); + nb_neg += 1.0; + } + } + + if(nb_pos > 0) { + m_pos = sum_pos / nb_pos; + v_pos = sum_sq_pos/(nb_pos - 1) - sq(sum_pos)/(nb_pos * (nb_pos - 1)); + shift = max(shift, v_pos/2 - m_pos); + } + + if(nb_neg > 0) { + m_neg = sum_neg / nb_neg; + v_neg = sum_sq_neg/(nb_neg - 1) - sq(sum_neg)/(nb_neg * (nb_neg - 1)); + shift = max(shift, v_neg/2 + m_neg); + } + +// (*global.log_stream) << "nb_pos = " << nb_pos << " nb_neg = " << nb_neg << endl; + + } + + int nb = 0; + + while(nb < 100 && abs(du) > minimum_delta_for_optimization) { + nb++; + +// (*global.log_stream) << "l = " << l << " u = " << u << " du = " << du << endl; + + u += du; + for(int s = 0; s < sample_set->nb_samples(); s++) { + responses[s] = current_responses[s] + u * weak_learner_responses[s] ; + } + + { + scalar_t sum_pos = 0, sum_sq_pos = 0, nb_pos = 0, m_pos, v_pos; + scalar_t sum_neg = 0, sum_sq_neg = 0, nb_neg = 0, m_neg, v_neg; + + for(int n = 0; n < sample_set->nb_samples(); n++) { + if(sample_set->label(n) > 0) { + sum_pos += responses[n]; + sum_sq_pos += sq(responses[n]); + nb_pos += 1.0; + } else if(sample_set->label(n) < 0) { + sum_neg += responses[n]; + sum_sq_neg += sq(responses[n]); + nb_neg += 1.0; + } + } + + l = 0; + + if(nb_pos > 0) { + m_pos = sum_pos / nb_pos; + v_pos = sum_sq_pos/(nb_pos - 1) - sq(sum_pos)/(nb_pos * (nb_pos - 1)); + l += nb_pos * exp(v_pos/2 - m_pos - shift); + } + + if(nb_neg > 0) { + m_neg = sum_neg / nb_neg; + v_neg = sum_sq_neg/(nb_neg - 1) - sq(sum_neg)/(nb_neg * (nb_neg - 1)); + l += nb_neg * exp(v_neg/2 + m_neg - shift); + } + + } + + if(l > prev_l) du = du * -0.25; + prev_l = l; + } + + delete[] responses; + + return u; + } + + case LOSS_HINGE: + case LOSS_LOGISTIC: + { + + scalar_t u = 0, du = -0.1; + scalar_t *responses = new scalar_t[sample_set->nb_samples()]; + + scalar_t l, prev_l = -1; + + const scalar_t minimum_delta_for_optimization = 1e-5; + + int n = 0; + while(n < 100 && abs(du) > minimum_delta_for_optimization) { + n++; + u += du; + for(int s = 0; s < sample_set->nb_samples(); s++) { + responses[s] = current_responses[s] + u * weak_learner_responses[s] ; + } + l = loss(sample_set, responses); + if(l > prev_l) du = du * -0.25; + prev_l = l; + } + + (*global.log_stream) << "END l = " << l << " du = " << du << endl; + + delete[] responses; + + return u; + } + + default: + cerr << "Unknown loss type in LossMachine::optimal_weight." << endl; + exit(1); + } + +} + +void LossMachine::subsample(int nb, scalar_t *labels, scalar_t *responses, + int nb_to_sample, int *sample_nb_occurences, scalar_t *sample_responses, + int allow_duplicates) { + + switch(_loss_type) { + + case LOSS_EXPONENTIAL: + { + scalar_t *weights = new scalar_t[nb]; + + for(int n = 0; n < nb; n++) { + if(labels[n] == 0) { + weights[n] = 0; + } else { + weights[n] = exp( - labels[n] * responses[n]); + } + sample_nb_occurences[n] = 0; + sample_responses[n] = 0.0; + } + + scalar_t total_weight; + int nb_sampled = 0, sum_sample_nb_occurences = 0; + + int *sampled_indexes = new int[nb_to_sample]; + + (*global.log_stream) << "Sampling " << nb_to_sample << " samples." << endl; + + do { + total_weight = robust_sampling(nb, + weights, + nb_to_sample, + sampled_indexes); + + for(int k = 0; nb_sampled < nb_to_sample && k < nb_to_sample; k++) { + int i = sampled_indexes[k]; + if(allow_duplicates || sample_nb_occurences[i] == 0) nb_sampled++; + sample_nb_occurences[i]++; + sum_sample_nb_occurences++; + } + } while(nb_sampled < nb_to_sample); + + (*global.log_stream) << "nb_sampled = " << nb_sampled << " nb_to_sample = " << nb_to_sample << endl; + + (*global.log_stream) << "Done." << endl; + + delete[] sampled_indexes; + + scalar_t unit_weight = log(total_weight / scalar_t(sum_sample_nb_occurences)); + + for(int n = 0; n < nb; n++) { + if(sample_nb_occurences[n] > 0) { + if(allow_duplicates) { + sample_responses[n] = - labels[n] * unit_weight; + } else { + sample_responses[n] = - labels[n] * (unit_weight + log(scalar_t(sample_nb_occurences[n]))); + sample_nb_occurences[n] = 1; + } + } + } + + delete[] weights; + + } + break; + + default: + cerr << "Unknown loss type in LossMachine::resample." << endl; + exit(1); + } + + +} diff --git a/loss_machine.h b/loss_machine.h new file mode 100644 index 0000000..a293e8b --- /dev/null +++ b/loss_machine.h @@ -0,0 +1,55 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef LOSS_MACHINE_H +#define LOSS_MACHINE_H + +#include "misc.h" +#include "sample_set.h" + +class LossMachine { + int _loss_type; + +public: + LossMachine(int loss_type); + + void get_loss_derivatives(SampleSet *samples, + scalar_t *responses, + scalar_t *derivatives); + + scalar_t loss(SampleSet *samples, scalar_t *responses); + + scalar_t optimal_weight(SampleSet *sample_set, + scalar_t *weak_learner_responses, + scalar_t *current_responses); + + // This method returns in sample_nb_occurences[k] the number of time + // the example k was sampled, and in sample_responses[k] the + // consistent response so that the overall loss remains the same. If + // allow_duplicates is set to 1, all samples will have an identical + // response (i.e. weight), but some may have more than one + // occurence. On the contrary, if allow_duplicates is 0, samples + // will all have only one occurence (or zero) but the responses may + // vary to account for the multiple sampling. + + void subsample(int nb, scalar_t *labels, scalar_t *responses, + int nb_to_sample, int *sample_nb_occurences, scalar_t *sample_responses, + int allow_duplicates); +}; + +#endif diff --git a/materials.cc b/materials.cc new file mode 100644 index 0000000..bbb6cbf --- /dev/null +++ b/materials.cc @@ -0,0 +1,250 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "materials.h" +#include "boosted_classifier.h" +#include "parsing_pool.h" +#include "rgb_image_subpixel.h" + +void write_referential_png(char *filename, + int level, + RichImage *image, + PiReferential *referential, + PiFeature *pf) { + + scalar_t s = global.discrete_log_scale_to_scale(referential->common_scale()); + + RGBImage result(int(image->width() * s), int(image->height() * s)); + + for(int y = 0; y < result.height(); y++) { + for(int x = 0; x < result.width(); x++) { + int c = 0; + + // GRAYSCALES + + for(int b = 0; b < RichImage::nb_gray_tags; b++) { + c += (b * 256)/RichImage::nb_gray_tags * + image->nb_tags_in_window(referential->common_scale(), + RichImage::first_gray_tag + b, + x, y, + x + 1, y + 1); + } + + // EDGES + + // for(int b = 0; b < RichImage::nb_edge_tags; b++) { + // c += image->nb_tags_in_window(referential->common_scale(), + // RichImage::first_edge_tag + b, + // x, y, + // x + 1, y + 1); + // } + // c = 255 - (c * 255)/RichImage::nb_edge_tags; + + // THRESHOLDED VARIANCE + + // c = image->nb_tags_in_window(referential->common_scale(), + // RichImage::variance_tag, + // x, y, + // x + 1, y + 1); + // c = (1 - c) * 255; + + result.set_pixel(x, y, c, c, c); + } + } + + RGBImageSubpixel result_sp(&result); + + if(pf) { + pf->draw(&result_sp, 255, 255, 0, referential); + } else { + referential->draw(&result_sp, level); + } + + (*global.log_stream) << "Writing " << filename << endl; + result_sp.write_png(filename); +} + +void write_one_pi_feature_png(char *filename, + LabelledImage *image, + PoseCellHierarchy *hierarchy, + int nb_target, + int level, + PiFeature *pf) { + + PoseCell target_cell; + hierarchy->get_containing_cell(image, level, + image->get_target_pose(nb_target), &target_cell); + PiReferential referential(&target_cell); + RGBImage result(image->width(), image->height()); + image->to_rgb(&result); + RGBImageSubpixel result_sp(&result); + referential.draw(&result_sp, level); + // pf->draw(&result_sp, 255, 255, 0, &referential); + result_sp.write_png(filename); +} + +void write_pool_images_with_poses_and_referentials(LabelledImagePool *pool, + Detector *detector) { + LabelledImage *image; + char buffer[buffer_size]; + + PoseCell target_cell; + Pose p; + PiFeature *pf; + + PoseCellHierarchy *hierarchy = new PoseCellHierarchy(pool); + + for(int i = 0; i < min(global.nb_images, pool->nb_images()); i++) { + image = pool->grab_image(i); + RGBImage result(image->width(), image->height()); + image->to_rgb(&result); + RGBImageSubpixel result_sp(&result); + + if(global.pictures_for_article) { + for(int t = 0; t < image->nb_targets(); t++) { + image->get_target_pose(t)->draw(8, 255, 255, 255, + hierarchy->nb_levels() - 1, &result_sp); + + } + for(int t = 0; t < image->nb_targets(); t++) { + image->get_target_pose(t)->draw(4, 0, 0, 0, + hierarchy->nb_levels() - 1, &result_sp); + } + } else { + for(int t = 0; t < image->nb_targets(); t++) { + image->get_target_pose(t)->draw(4, 255, 128, 0, + hierarchy->nb_levels() - 1, &result_sp); + } + } + + sprintf(buffer, "/tmp/truth-%05d.png", i); + cout << "Writing " << buffer << endl; + result_sp.write_png(buffer); + pool->release_image(i); + } + + for(int i = 0; i < min(global.nb_images, pool->nb_images()); i++) { + image = pool->grab_image(i); + + RGBImage result(image->width(), image->height()); + image->to_rgb(&result); + RGBImageSubpixel result_sp(&result); + + int u = 0; + + // image->compute_rich_structure(); + + for(int t = 0; t < image->nb_targets(); t++) { + + image->get_target_pose(t)->draw(4, 255, 0, 0, + hierarchy->nb_levels() - 1, &result_sp); + + hierarchy->get_containing_cell(image, + hierarchy->nb_levels() - 1, + image->get_target_pose(t), &target_cell); + + target_cell.get_centroid(&p); + + p.draw(4, 0, 255, 0, hierarchy->nb_levels() - 1, &result_sp); + + PiReferential referential(&target_cell); + + sprintf(buffer, "/tmp/referential-%05d-%02d.png", i, u); + image->compute_rich_structure(); + write_referential_png(buffer, hierarchy->nb_levels() - 1, image, &referential, 0); + + if(detector) { + int nb_features = 100; + for(int f = 0; f < nb_features; f++) + if(f == 0 || f ==50 || f == 53) { + int n_family, n_feature; + if(f < nb_features/2) { + n_family = 0; + n_feature = f; + } else { + n_family = detector->_nb_classifiers_per_level; + n_feature = f - nb_features/2; + } + pf = detector->_pi_feature_families[n_family]->get_feature(n_feature); + sprintf(buffer, "/tmp/pf-%05d-%02d-%03d.png", i, u, f); + write_referential_png(buffer, + hierarchy->nb_levels() - 1, + image, + &referential, + pf); + } + } + u++; + } + + // sprintf(buffer, "/tmp/image-%05d.png", i); + // cout << "Writing " << buffer << endl; + // result_sp.write_png(buffer); + + // if(global.write_tag_images) { + // sprintf(buffer, "/tmp/image-%05d_tags.png", i); + // cout << "Writing " << buffer << endl; + // image->compute_rich_structure(); + // image->write_tag_png(buffer); + // } + + pool->release_image(i); + } + + delete hierarchy; +} + +void write_image_with_detections(const char *filename, + LabelledImage *image, + PoseCellSet *detections, + int level) { + + RGBImage result(image->width(), image->height()); + + for(int y = 0; y < result.height(); y++) { + for(int x = 0; x < result.width(); x++) { + int c = image->value(x, y); + result.set_pixel(x, y, c, c, c); + } + } + + RGBImageSubpixel result_sp(&result); + + if(global.pictures_for_article) { + for(int a = 0; a < detections->nb_cells(); a++) { + Pose pose; + detections->get_cell(a)->get_centroid(&pose); + pose.draw(8, 255, 255, 255, level, &result_sp); + } + for(int a = 0; a < detections->nb_cells(); a++) { + Pose pose; + detections->get_cell(a)->get_centroid(&pose); + pose.draw(4, 0, 0, 0, level, &result_sp); + } + } else { + for(int a = 0; a < detections->nb_cells(); a++) { + Pose pose; + detections->get_cell(a)->get_centroid(&pose); + pose.draw(4, 255, 128, 0, level, &result_sp); + } + } + + (*global.log_stream) << "Writing " << filename << endl; + + result_sp.write_png(filename); +} diff --git a/materials.h b/materials.h new file mode 100644 index 0000000..4d6b337 --- /dev/null +++ b/materials.h @@ -0,0 +1,47 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef MATERIALS_H +#define MATERIALS_H + +#include "misc.h" +#include "rich_image.h" +#include "pi_feature.h" +#include "pi_referential.h" +#include "labelled_image_pool.h" +#include "labelled_image.h" +#include "pose_cell_set.h" +#include "pose_cell_hierarchy.h" +#include "detector.h" + +void write_pool_images_with_poses_and_referentials(LabelledImagePool *pool, + Detector *detector); + +void write_one_pi_feature_png(char *filename, + LabelledImage *image, + PoseCellHierarchy *hierarchy, + int nb_target, + int level, + PiFeature *pf); + +void write_image_with_detections(const char *filename, + LabelledImage *image, + PoseCellSet *detections, + int level); + +#endif diff --git a/misc.cc b/misc.cc new file mode 100644 index 0000000..2d2258d --- /dev/null +++ b/misc.cc @@ -0,0 +1,125 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include + +using namespace std; + +#include "misc.h" + +char *basename(char *name) { + char *result = name; + while(*name) { + if(*name == '/') result = name + 1; + name++; + } + return result; +} + +char *next_word(char *buffer, char *r, int buffer_size) { + char *s; + s = buffer; + + if(r != 0) { + while((*r == ' ') || (*r == '\t') || (*r == ',')) r++; + if(*r == '"') { + r++; + while((*r != '"') && (*r != '\0') && + (s 0) { + s += n[k] * log(scalar_t(n[k])); + t += n[k]; + } + return (log(t) - s/scalar_t(t))/log(2.0); +} + +void random_permutation(int *val, int nb) { + for(int k = 0; k < nb; k++) val[k] = k; + int i, t; + for(int k = 0; k < nb - 1; k++) { + i = int(drand48() * (nb - k)) + k; + t = val[i]; + val[i] = val[k]; + val[k] = t; + } +} + +void tag_subset(bool *val, int nb_total, int nb_to_tag) { + ASSERT(nb_to_tag <= nb_total); + int index[nb_total]; + random_permutation(index, nb_total); + for(int n = 0; n < nb_total; n++) val[n] = false; + for(int n = 0; n < nb_to_tag; n++) val[index[n]] = true; +} + +int compare_couple(const void *a, const void *b) { + if(((Couple *) a)->value < ((Couple *) b)->value) return -1; + else if(((Couple *) a)->value > ((Couple *) b)->value) return 1; + else return 0; +} + +void used_memory(size_t &size, size_t &resident, + size_t &share, size_t &text, size_t &lib, size_t &data, + size_t &dt) { + char buffer[buffer_size]; + sprintf(buffer, "/proc/%d/statm", getpid()); + ifstream in(buffer); + if(in.good()) { + in >> size >> resident >> share >> text >> lib >> data >> dt; + size_t ps = getpagesize(); + size *= ps; + resident *= ps; + share *= ps; + text *= ps; + lib *= ps; + data *= ps; + dt *= ps; + } else { + size = 0; + resident = 0; + share = 0; + text = 0; + lib = 0; + data = 0; + dt = 0; + cerr << "Can not open " << buffer << " for reading the memory usage." << endl; + } +} diff --git a/misc.h b/misc.h new file mode 100644 index 0000000..d2d5b22 --- /dev/null +++ b/misc.h @@ -0,0 +1,116 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef MISC_H +#define MISC_H + +#include +#include +#include +#include +#include +#include + +using namespace std; + +//typedef double scalar_t; +typedef float scalar_t; +const scalar_t SCALAR_MAX = FLT_MAX; +const scalar_t SCALAR_MIN = FLT_MIN; + +const int buffer_size = 1024; +const int large_buffer_size = 65536; + +using namespace std; + +#ifdef DEBUG +#define ASSERT(x) if(!(x)) { \ + std::cerr << "ASSERT FAILED IN " << __FILE__ << ":" << __LINE__ << endl; \ + abort(); \ +} +#else +#define ASSERT(x) +#endif + +template +T smooth_min(T x, T y) { + T z = exp(x - y); + return 0.5 * (x + y - (x - y)/(1 + 1/z) - (y - x)/(1 + z)); +} + +template +void write_var(ostream *os, const T *x) { os->write((char *) x, sizeof(T)); } + +template +void read_var(istream *is, T *x) { is->read((char *) x, sizeof(T)); } + +template +void grow(int *nb_max, int nb, T** current, int factor) { + ASSERT(*nb_max > 0); + if(nb == *nb_max) { + T *tmp = new T[*nb_max * factor]; + memcpy(tmp, *current, *nb_max * sizeof(T)); + delete[] *current; + *current = tmp; + *nb_max *= factor; + } +} + +template +inline T sq(T x) { + return x * x; +} + +inline scalar_t log2(scalar_t x) { + return log(x)/log(2.0); +} + +inline scalar_t xi(scalar_t x) { + if(x <= 0.0) return 0.0; + else return - x * log(x)/log(2.0); +} + +scalar_t discrete_entropy(int *n, int nb); + +char *basename(char *name); + +char *next_word(char *buffer, char *r, int buffer_size); + +void random_permutation(int *val, int nb); +void tag_subset(bool *val, int nb_total, int nb_to_tag); + +struct Couple { + int index; + scalar_t value; +}; + +int compare_couple(const void *a, const void *b); + +// size total program size +// resident resident set size +// share shared pages +// text text (code) +// lib library +// data data/stack +// dt dirty pages (unused in Linux 2.6) + +void used_memory(size_t &size, size_t &resident, + size_t &share, size_t &text, size_t &lib, size_t &data, + size_t &dt); + +#endif diff --git a/param_parser.cc b/param_parser.cc new file mode 100644 index 0000000..5ae8a41 --- /dev/null +++ b/param_parser.cc @@ -0,0 +1,146 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +// All this is clearly non-optimal, loaded with news and deletes and +// should be rewritten. + +#include + +#include "param_parser.h" + +ParamParser::ParamParser() : _nb_max(10), + _nb(0), + _names(new char *[_nb_max]), + _values(new char *[_nb_max]), + _changed(new bool[_nb_max]) { } + +ParamParser::~ParamParser() { + for(int k = 0; k < _nb; k++) { + delete[] _names[k]; + delete[] _values[k]; + } + delete[] _names; + delete[] _values; + delete[] _changed; +} + +void ParamParser::add_association(const char *variable_name, const char *variable_value, bool change) { + int n; + + for(n = 0; n < _nb && strcmp(variable_name, _names[n]) != 0; n++); + + if(n < _nb) { + delete[] _values[n]; + _values[n] = new char[strlen(variable_value) + 1]; + strcpy(_values[n], variable_value); + _changed[n] = change; + } else { + int nm; + nm = _nb_max; grow(&nm, _nb, &_names, 2); + nm = _nb_max; grow(&nm, _nb, &_values, 2); + grow(&_nb_max, _nb, &_changed, 2); + + _names[_nb] = new char[strlen(variable_name) + 1]; + strcpy(_names[_nb], variable_name); + _values[_nb] = new char[strlen(variable_value) + 1]; + strcpy(_values[_nb], variable_value); + _changed[_nb] = change; + _nb++; + } +} + +char *ParamParser::get_association(const char *variable_name) { + int n; + for(n = 0; n < _nb && strcmp(variable_name, _names[n]) != 0; n++); + if(n < _nb) return _values[n]; + else { + cerr << "Unknown parameter \"" << variable_name << "\", existing ones are" << endl; + for(int n = 0; n < _nb; n++) + cerr << " \"" << _names[n] << "\"" << endl; + exit(1); + } +} + +int ParamParser::get_association_int(const char *variable_name) { + char *u = get_association(variable_name); + char *s = u; + while(*s) + if((*s < '0' || *s > '9') && *s != '-') { + cerr << "Non-numerical value for " << variable_name << " (" << u << ")" << endl; + exit(1); + } else s++; + return atoi(u); +} + +scalar_t ParamParser::get_association_scalar(const char *variable_name) { + char *u = get_association(variable_name); + char *s = u; + while(*s) + if((*s < '0' || *s > '9') && *s != '.' && *s != 'e' && *s != '-') { + cerr << "Non-numerical value for " << variable_name << " (" << u << ")" << endl; + exit(1); + } else s++; + return atof(u); +} + +bool ParamParser::get_association_bool(const char *variable_name) { + char *value = get_association(variable_name); + if(strcasecmp(value, "") == 0 || strcasecmp(value, "y") == 0 || strcasecmp(value, "yes") == 0) return true; + if(strcasecmp(value, "n") == 0 || strcasecmp(value, "no") == 0) return false; + cerr << "Expects nothing (for yes), or y[es] or n[o] for a boolean argument and got '" << value << "'" << endl; + exit(1); +} + +void ParamParser::parse_options(int argc, char **argv, + bool allow_undefined, + int *new_argc, char **new_argv) { + + int i = 1; + + if(new_argc && new_argv) + new_argv[(*new_argc)++] = argv[0]; + + while(i < argc) { + if(strncmp(argv[i], "--", 2) == 0) { + // This is so 70s! I luuuuv it! + char variable_name[buffer_size] = "", variable_value[buffer_size] = ""; + char *o = argv[i] + 2, *s = variable_name, *u = variable_value; + while(*o && *o != '=') *s++ = *o++; + *s = '\0'; + if(*o) { o++; while(*o) *u++ = *o++; } + *u = '\0'; + if(!allow_undefined) get_association(variable_name); + add_association(variable_name, variable_value, true); + } else { + if(new_argc && new_argv) + new_argv[(*new_argc)++] = argv[i]; + else { + cerr << "Can not parse " << argv[i] << endl; + exit(1); + } + } + i++; + } +} + +void ParamParser::print_all(ostream *os) { + for(int n = 0; n < _nb; n++) { + (*os) << (_changed[n] ? " * " : " ") << "\"" << _names[n] << "\" \"" << _values[n] << "\"" << endl; + } +} + diff --git a/param_parser.h b/param_parser.h new file mode 100644 index 0000000..57d0082 --- /dev/null +++ b/param_parser.h @@ -0,0 +1,44 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef PARAM_PARSER_H +#define PARAM_PARSER_H + +#include +#include "misc.h" + +using namespace std; + +class ParamParser { + int _nb_max, _nb; + char **_names, **_values; + bool *_changed; +public: + ParamParser(); + ~ParamParser(); + void add_association(const char *variable_name, const char *variable_value, bool change); + char *get_association(const char *variable_name); + int get_association_int(const char *variable_name); + scalar_t get_association_scalar(const char *variable_name); + bool get_association_bool(const char *variable_name); + + void parse_options(int argc, char **argv, bool allow_undefined, int *new_argc, char **new_argv); + void print_all(ostream *os); +}; + +#endif diff --git a/parsing.cc b/parsing.cc new file mode 100644 index 0000000..9d0060d --- /dev/null +++ b/parsing.cc @@ -0,0 +1,186 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "parsing.h" +#include "fusion_sort.h" + +Parsing::Parsing(LabelledImagePool *image_pool, + PoseCellHierarchy *hierarchy, + scalar_t proportion_negative_cells, + int image_index) { + + _image_pool = image_pool; + _image_index = image_index; + + PoseCellSet cell_set; + LabelledImage *image; + + image = _image_pool->grab_image(_image_index); + + hierarchy->add_root_cells(image, &cell_set); + + int *kept = new int[cell_set.nb_cells()]; + + _nb_cells = 0; + + for(int c = 0; c < cell_set.nb_cells(); c++) { + int l = image->pose_cell_label(cell_set.get_cell(c)); + kept[c] = (l > 0) || (l < 0 && drand48() < proportion_negative_cells); + if(kept[c]) _nb_cells++; + } + + _cells = new PoseCell[_nb_cells]; + _responses = new scalar_t[_nb_cells]; + _labels = new int[_nb_cells]; + _nb_positives = 0; + _nb_negatives = 0; + + int d = 0; + for(int c = 0; c < cell_set.nb_cells(); c++) { + if(kept[c]) { + _cells[d] = *(cell_set.get_cell(c)); + _labels[d] = image->pose_cell_label(&_cells[d]); + _responses[d] = 0; + if(_labels[d] < 0) { + _nb_negatives++; + } else if(_labels[d] > 0) { + _nb_positives++; + } + d++; + } + } + + delete[] kept; + + _image_pool->release_image(_image_index); +} + +Parsing::~Parsing() { + delete[] _cells; + delete[] _responses; + delete[] _labels; +} + +void Parsing::down_one_level(PoseCellHierarchy *hierarchy, + int level, int *sample_nb_occurences, scalar_t *sample_responses) { + PoseCellSet cell_set; + LabelledImage *image; + + int new_nb_cells = 0; + for(int c = 0; c < _nb_cells; c++) { + new_nb_cells += sample_nb_occurences[c]; + } + + PoseCell *new_cells = new PoseCell[new_nb_cells]; + scalar_t *new_responses = new scalar_t[new_nb_cells]; + int *new_labels = new int[new_nb_cells]; + + image = _image_pool->grab_image(_image_index); + int b = 0; + + for(int c = 0; c < _nb_cells; c++) { + + if(sample_nb_occurences[c] > 0) { + + cell_set.erase_content(); + hierarchy->add_subcells(level, _cells + c, &cell_set); + + if(_labels[c] > 0) { + ASSERT(sample_nb_occurences[c] == 1); + int e = -1; + for(int d = 0; d < cell_set.nb_cells(); d++) { + if(image->pose_cell_label(cell_set.get_cell(d)) > 0) { + ASSERT(e < 0); + e = d; + } + } + ASSERT(e >= 0); + ASSERT(b < new_nb_cells); + new_cells[b] = *(cell_set.get_cell(e)); + new_responses[b] = sample_responses[c]; + new_labels[b] = 1; + b++; + } + + else if(_labels[c] < 0) { + for(int d = 0; d < sample_nb_occurences[c]; d++) { + ASSERT(b < new_nb_cells); + new_cells[b] = *(cell_set.get_cell(int(drand48() * cell_set.nb_cells()))); + new_responses[b] = sample_responses[c]; + new_labels[b] = -1; + b++; + } + } + + else { + cerr << "INCONSISTENCY" << endl; + abort(); + } + } + } + + ASSERT(b == new_nb_cells); + + _image_pool->release_image(_image_index); + + delete[] _cells; + delete[] _labels; + delete[] _responses; + _nb_cells = new_nb_cells; + _cells = new_cells; + _labels = new_labels; + _responses = new_responses; +} + +void Parsing::update_cell_responses(PiFeatureFamily *pi_feature_family, + Classifier *classifier) { + LabelledImage *image; + + image = _image_pool->grab_image(_image_index); + image->compute_rich_structure(); + + SampleSet *samples = new SampleSet(pi_feature_family->nb_features(), 1); + + for(int c = 0; c < _nb_cells; c++) { + samples->set_sample(0, pi_feature_family, image, &_cells[c], 0); + _responses[c] += classifier->response(samples, 0); + ASSERT(!isnan(_responses[c])); + } + + _image_pool->release_image(_image_index); + delete samples; +} + +void Parsing::collect_samples(SampleSet *samples, + PiFeatureFamily *pi_feature_family, + int s, + int *to_collect) { + LabelledImage *image; + + image = _image_pool->grab_image(_image_index); + image->compute_rich_structure(); + + for(int c = 0; c < _nb_cells; c++) { + if(to_collect[c]) { + samples->set_sample(s, pi_feature_family, image, &_cells[c], _labels[c]); + s++; + } + } + + _image_pool->release_image(_image_index); +} diff --git a/parsing.h b/parsing.h new file mode 100644 index 0000000..4f1c9b5 --- /dev/null +++ b/parsing.h @@ -0,0 +1,87 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef PARSING_H +#define PARSING_H + +/* + + A Parsing is associated to a LabelledImage and stores responses over + cells. + +*/ + +#include "fusion_sort.h" +#include "pose_cell_hierarchy.h" +#include "classifier.h" +#include "labelled_image.h" + +class Parsing { + LabelledImagePool *_image_pool; + int _image_index; + int _nb_cells, _nb_positives, _nb_negatives; + + PoseCell *_cells; + scalar_t *_responses; + int *_labels; + +public: + + Parsing(LabelledImagePool *image_pool, + PoseCellHierarchy *hierarchy, + scalar_t proportion_negative_cells, + int image_index); + + ~Parsing(); + + ////////////////////////////////////////////////////////////////////// + + inline int nb_cells() { + return _nb_cells; + } + + inline int nb_positive_cells() { + return _nb_positives; + } + + inline int nb_negative_cells() { + return _nb_negatives; + } + + inline scalar_t response(int c) { + ASSERT(c >= 0 && c < _nb_cells); + return _responses[c]; + } + + inline int label(int c) { + ASSERT(c >= 0 && c < _nb_cells); + return _labels[c]; + } + + void down_one_level(PoseCellHierarchy *hierarchy, int level, int *sample_nb_occurences, scalar_t *sample_responses); + + void update_cell_responses(PiFeatureFamily *pi_feature_family, + Classifier *classifier); + + void collect_samples(SampleSet *samples, + PiFeatureFamily *pi_feature_family, + int s, + int *to_collect); +}; + +#endif diff --git a/parsing_pool.cc b/parsing_pool.cc new file mode 100644 index 0000000..696477a --- /dev/null +++ b/parsing_pool.cc @@ -0,0 +1,259 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "parsing_pool.h" +#include "tools.h" + +ParsingPool::ParsingPool(LabelledImagePool *image_pool, PoseCellHierarchy *hierarchy, scalar_t proportion_negative_cells) { + _nb_images = image_pool->nb_images(); + _parsings = new Parsing *[_nb_images]; + + _nb_cells = 0; + _nb_positive_cells = 0; + _nb_negative_cells = 0; + for(int i = 0; i < _nb_images; i++) { + _parsings[i] = new Parsing(image_pool, hierarchy, proportion_negative_cells, i); + _nb_cells += _parsings[i]->nb_cells(); + _nb_positive_cells += _parsings[i]->nb_positive_cells(); + _nb_negative_cells += _parsings[i]->nb_negative_cells(); + } + (*global.log_stream) << "ParsingPool initialized" << endl; + (*global.log_stream) << " _nb_cells = " << _nb_cells << endl; + (*global.log_stream) << " _nb_positive_cells = " << _nb_positive_cells << endl; + (*global.log_stream) << " _nb_negative_cells = " << _nb_negative_cells << endl; +} + +ParsingPool::~ParsingPool() { + for(int i = 0; i < _nb_images; i++) + delete _parsings[i]; + delete[] _parsings; +} + +void ParsingPool::down_one_level(LossMachine *loss_machine, PoseCellHierarchy *hierarchy, int level) { + scalar_t *labels = new scalar_t[_nb_cells]; + scalar_t *tmp_responses = new scalar_t[_nb_cells]; + + int c; + + { //////////////////////////////////////////////////////////////////// + // Sanity check + scalar_t l = 0; + for(int i = 0; i < _nb_images; i++) { + for(int d = 0; d < _parsings[i]->nb_cells(); d++) { + if(_parsings[i]->label(d) != 0) { + l += exp( - _parsings[i]->label(d) * _parsings[i]->response(d)); + } + } + } + (*global.log_stream) << "* INITIAL LOSS IS " << l << endl; + } //////////////////////////////////////////////////////////////////// + + // Put the negative samples with their current responses, and all + // others to 0 + + c = 0; + for(int i = 0; i < _nb_images; i++) { + for(int d = 0; d < _parsings[i]->nb_cells(); d++) { + if(_parsings[i]->label(d) < 0) { + labels[c] = -1; + tmp_responses[c] = _parsings[i]->response(d); + } else { + labels[c] = 0; + tmp_responses[c] = 0; + } + c++; + } + } + + // Sub-sample among the negative ones + + int *sample_nb_occurences = new int[_nb_cells]; + scalar_t *sample_responses = new scalar_t[_nb_cells]; + + loss_machine->subsample(_nb_cells, labels, tmp_responses, + _nb_negative_cells, sample_nb_occurences, sample_responses, + 1); + c = 0; + for(int i = 0; i < _nb_images; i++) { + for(int d = 0; d < _parsings[i]->nb_cells(); d++) { + if(_parsings[i]->label(d) > 0) { + sample_nb_occurences[c + d] = 1; + sample_responses[c + d] = _parsings[i]->response(d); + } + } + + int d = c + _parsings[i]->nb_cells(); + + _parsings[i]->down_one_level(hierarchy, level, sample_nb_occurences + c, sample_responses + c); + + c = d; + } + + { //////////////////////////////////////////////////////////////////// + // Sanity check + scalar_t l = 0; + for(int i = 0; i < _nb_images; i++) { + for(int d = 0; d < _parsings[i]->nb_cells(); d++) { + if(_parsings[i]->label(d) != 0) { + l += exp( - _parsings[i]->label(d) * _parsings[i]->response(d)); + } + } + } + (*global.log_stream) << "* FINAL LOSS IS " << l << endl; + } //////////////////////////////////////////////////////////////////// + + delete[] sample_responses; + delete[] sample_nb_occurences; +} + +void ParsingPool::update_cell_responses(PiFeatureFamily *pi_feature_family, + Classifier *classifier) { + for(int i = 0; i < _nb_images; i++) { + _parsings[i]->update_cell_responses(pi_feature_family, classifier); + } +} + +void ParsingPool::weighted_sampling(LossMachine *loss_machine, + PiFeatureFamily *pi_feature_family, + SampleSet *sample_set, + scalar_t *responses) { + + int nb_negatives_to_sample = sample_set->nb_samples() - _nb_positive_cells; + + ASSERT(nb_negatives_to_sample > 0); + + scalar_t *labels = new scalar_t[_nb_cells]; + scalar_t *tmp_responses = new scalar_t[_nb_cells]; + + int c, s; + + // Put the negative samples with their current responses, and all + // others to 0 + + c = 0; + for(int i = 0; i < _nb_images; i++) { + for(int d = 0; d < _parsings[i]->nb_cells(); d++) { + if(_parsings[i]->label(d) < 0) { + labels[c] = -1; + tmp_responses[c] = _parsings[i]->response(d); + } else { + labels[c] = 0; + tmp_responses[c] = 0; + } + c++; + } + } + + // Sub-sample among the negative ones + + int *sample_nb_occurences = new int[_nb_cells]; + scalar_t *sample_responses = new scalar_t[_nb_cells]; + + loss_machine->subsample(_nb_cells, labels, tmp_responses, + nb_negatives_to_sample, sample_nb_occurences, sample_responses, + 0); + + for(int k = 0; k < _nb_cells; k++) { + if(sample_nb_occurences[k] > 0) { + ASSERT(sample_nb_occurences[k] == 1); + labels[k] = -1.0; + tmp_responses[k] = sample_responses[k]; + } else { + labels[k] = 0; + } + } + + delete[] sample_responses; + delete[] sample_nb_occurences; + + // Put the positive ones + + c = 0; + for(int i = 0; i < _nb_images; i++) { + for(int d = 0; d < _parsings[i]->nb_cells(); d++) { + if(_parsings[i]->label(d) > 0) { + labels[c] = 1; + tmp_responses[c] = _parsings[i]->response(d); + } + c++; + } + } + + // Here we have the responses for the sub-sampled in tmp_responses, + // and we have labels[n] set to zero for non-sampled samples + + s = 0; + c = 0; + +// global.bar.init(&cout, _nb_images); + + for(int i = 0; i < _nb_images; i++) { + + int *to_collect = new int[_parsings[i]->nb_cells()]; + + for(int d = 0; d < _parsings[i]->nb_cells(); d++) { + to_collect[d] = (labels[c + d] != 0); + } + + _parsings[i]->collect_samples(sample_set, pi_feature_family, s, to_collect); + + for(int d = 0; d < _parsings[i]->nb_cells(); d++) { + if(to_collect[d]) { + responses[s++] = tmp_responses[c + d]; + } + } + + delete[] to_collect; + + c += _parsings[i]->nb_cells(); + +// global.bar.refresh(&cout, i); + } + +// global.bar.finish(&cout); + + delete[] tmp_responses; + delete[] labels; +} + +void ParsingPool::write_roc(ofstream *out) { + int nb_negatives = nb_negative_cells(); + int nb_positives = nb_positive_cells(); + + scalar_t *pos_responses = new scalar_t[nb_positives]; + scalar_t *neg_responses = new scalar_t[nb_negatives]; + int np = 0, nn = 0; + for(int i = 0; i < _nb_images; i++) { + for(int c = 0; c < _parsings[i]->nb_cells(); c++) { + if(_parsings[i]->label(c) > 0) + pos_responses[np++] = _parsings[i]->response(c); + else if(_parsings[i]->label(c) < 0) + neg_responses[nn++] = _parsings[i]->response(c); + } + } + + ASSERT(nn == nb_negatives && np == nb_positives); + + print_roc_small_pos(out, + nb_positives, pos_responses, + nb_negatives, neg_responses, + 1.0); + + delete[] pos_responses; + delete[] neg_responses; +} diff --git a/parsing_pool.h b/parsing_pool.h new file mode 100644 index 0000000..f7b67be --- /dev/null +++ b/parsing_pool.h @@ -0,0 +1,74 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef PARSING_POOL_H +#define PARSING_POOL_H + +/* + + A ParsingPool is a family of Parsing associated to all the images of + a LabelledImagePool. + +*/ + +#include "parsing.h" +#include "pi_feature_family.h" +#include "classifier.h" +#include "labelled_image_pool.h" +#include "pose_cell_hierarchy.h" + +class ParsingPool { + int _nb_images; + long int _nb_cells, _nb_positive_cells, _nb_negative_cells; + Parsing **_parsings; + +public: + + inline int nb_cells() { + return _nb_cells; + } + + inline int nb_positive_cells() { + return _nb_positive_cells; + } + + inline int nb_negative_cells() { + return _nb_negative_cells; + } + + ParsingPool(LabelledImagePool *image_pool, PoseCellHierarchy *hierarchy, scalar_t proportion_negative_cells); + + ~ParsingPool(); + + // The parameter level is the resulting level, not the starting one, + // hence should always be strictly positive + void down_one_level(LossMachine *loss_machine, PoseCellHierarchy *hierarchy, int level); + + void update_cell_responses(PiFeatureFamily *pi_feature_family, + Classifier *classifier); + + // Collect all positive and sample negative examples + void weighted_sampling(LossMachine *loss_machine, + PiFeatureFamily *pi_feature_family, + SampleSet *sample_set, + scalar_t *responses); + + void write_roc(ofstream *out); +}; + +#endif diff --git a/pi_feature.cc b/pi_feature.cc new file mode 100644 index 0000000..300ccee --- /dev/null +++ b/pi_feature.cc @@ -0,0 +1,471 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "pi_feature.h" + +#include "global.h" +#include "rectangle.h" + +int PiFeature::random_registration_mode(int level) { + while(1) { + switch(int(6 * drand48())) { + + case 0: + return PiReferential::RM_HEAD; + break; + + case 1: + if(level >= 1) + return PiReferential::RM_BELLY; + break; + + case 2: + if(level >= 1) + return PiReferential::RM_HEAD_BELLY; + break; + + case 3: + if(level >= 1) + return PiReferential::RM_HEAD_BELLY_EDGES; + break; + + case 4: + if(level >= 2) + return PiReferential::RM_BODY; + break; + + case 5: + if(level >= 2) + return PiReferential::RM_BODY_EDGES; + break; + + default: + abort(); + } + } +} + +void PiFeature::randomize_window(int registration_mode, Rectangle *window) { + scalar_t xc, yc, w, h; + + do { + xc = 2 * drand48() - 1.0; + yc = 2 * drand48() - 1.0; + + w = 2 * drand48(); + + // If we are in a non-rotating frame, allow rectangular window, + // otherwise force it to be squared + + if(registration_mode == PiReferential::RM_HEAD || + registration_mode == PiReferential::RM_BELLY || + registration_mode == PiReferential::RM_HEAD_NO_POLARITY || + registration_mode == PiReferential::RM_BELLY_NO_POLARITY) { + h = 2 * drand48(); + } else { + h = w; + } + } while(w < global.pi_feature_window_min_size || + h < global.pi_feature_window_min_size || + xc - w/2 < -1.0 || xc + w/2 > 1.0 || + yc - h/2 < -1.0 || yc + h/2 > 1.0); + + window->xmin = xc - w/2; + window->ymin = yc - h/2; + window->xmax = xc + w/2; + window->ymax = yc + h/2; +} + +////////////////////////////////////////////////////////////////////// +// PF_EDGE_THRESHOLDING + +scalar_t PiFeature::response_edge_thresholding(RichImage *image, + PiReferential *referential) { + Rectangle registered_window_a; + + referential->register_rectangle(_registration_a, + &_window_a, + ®istered_window_a); + + int tag = referential->register_edge(_registration_a, _tag); + + int xmin_a = int(registered_window_a.xmin) >> _edge_scale; + int ymin_a = int(registered_window_a.ymin) >> _edge_scale; + int xmax_a = int(registered_window_a.xmax) >> _edge_scale; + int ymax_a = int(registered_window_a.ymax) >> _edge_scale; + + int scale = referential->common_scale() + _edge_scale * global.nb_scales_per_power_of_two; + + scalar_t ne, nt; + + ASSERT((tag >= RichImage::first_edge_tag && + tag < RichImage::first_edge_tag + RichImage::nb_edge_tags) || + tag == RichImage::variance_tag); + + if(tag != RichImage::variance_tag) { + ne = scalar_t(image->nb_tags_in_window(scale, tag, + xmin_a, ymin_a, xmax_a, ymax_a)); + nt = scalar_t(image->nb_tags_in_window(scale, RichImage::variance_tag, + xmin_a, ymin_a, xmax_a, ymax_a) + 1); + } else { + ne = scalar_t(image->nb_tags_in_window(scale, RichImage::variance_tag, + xmin_a, ymin_a, xmax_a, ymax_a)); + nt = scalar_t((xmax_a - xmin_a) * (ymax_a - ymin_a)) + 1; + } + + return ne / nt; +} + +void PiFeature::draw_edge_thresholding(RGBImage *image, + int r, int g, int b, + PiReferential *referential) { + + (*global.log_stream) << "draw_edge_thresholding" << endl; + + Rectangle registered_window_a; + + referential->register_rectangle(_registration_a, + &_window_a, + ®istered_window_a); + + referential->draw_window(image, _registration_a, ®istered_window_a, 0); + +// if(!global.pictures_for_article) { +// int tag = referential->register_edge(_registration_a, _tag); + +// referential->draw_edge_and_scale(image, _registration_a, ®istered_window_a, +// tag, _edge_scale); +// } +} + +void PiFeature::print_edge_thresholding(ostream *os) { + (*os) << "_tag " << _tag << endl; + (*os) << "_edge_scale " << _edge_scale << endl; + (*os) << "_window_a.xmin " << _window_a.xmin << endl; + (*os) << "_window_a.ymin " << _window_a.ymin << endl; + (*os) << "_window_a.xmax " << _window_a.xmax << endl; + (*os) << "_window_a.ymax " << _window_a.ymax << endl; +} + +////////////////////////////////////////////////////////////////////// +// PF_EDGE_HISTOGRAM_COMPARISON + +scalar_t PiFeature::response_edge_histogram_comparison(RichImage *image, + PiReferential *referential) { + + Rectangle registered_window_a; + + referential->register_rectangle(_registration_a, + &_window_a, + ®istered_window_a); + + int xmin_a = int(registered_window_a.xmin) >> _edge_scale; + int ymin_a = int(registered_window_a.ymin) >> _edge_scale; + int xmax_a = int(registered_window_a.xmax) >> _edge_scale; + int ymax_a = int(registered_window_a.ymax) >> _edge_scale; + + Rectangle registered_window_b; + + referential->register_rectangle(_registration_b, + &_window_b, + ®istered_window_b); + + int xmin_b = int(registered_window_b.xmin) >> _edge_scale; + int ymin_b = int(registered_window_b.ymin) >> _edge_scale; + int xmax_b = int(registered_window_b.xmax) >> _edge_scale; + int ymax_b = int(registered_window_b.ymax) >> _edge_scale; + + int scale = referential->common_scale() + _edge_scale * global.nb_scales_per_power_of_two; + + scalar_t result = 0.0; + + scalar_t ne_a = scalar_t(image->nb_tags_in_window(scale, RichImage::variance_tag, + xmin_a, ymin_a, + xmax_a, ymax_a) + 1); + + scalar_t ne_b = scalar_t(image->nb_tags_in_window(scale, RichImage::variance_tag, + xmin_b, ymin_b, + xmax_b, ymax_b) + 1); + + for(int t = RichImage::first_edge_tag; t < RichImage::first_edge_tag + RichImage::nb_edge_tags; t++) + result += sq(scalar_t(image->nb_tags_in_window(scale, t, + xmin_a, ymin_a, + xmax_a, ymax_a)) / ne_a + - + scalar_t(image->nb_tags_in_window(scale, t, + xmin_b, ymin_b, + xmax_b, ymax_b)) / ne_b); + + ASSERT(!isnan(result)); + + return result; +} + +void PiFeature::draw_edge_histogram_comparison(RGBImage *image, + int r, int g, int b, + PiReferential *referential) { + + (*global.log_stream) << "draw_edge_histogram_comparison" << endl; + + Rectangle registered_window; + { + + referential->register_rectangle(_registration_a, + &_window_a, + ®istered_window); + referential->draw_window(image, _registration_a, ®istered_window, 0); + } + + { + referential->register_rectangle(_registration_b, + &_window_b, + ®istered_window); + + referential->draw_window(image, _registration_b, ®istered_window, 0); + } +} + +void PiFeature::print_edge_histogram_comparison(ostream *os) { + (*os) << "_edge_scale " << _edge_scale << endl; + (*os) << "_window_a.xmin " << _window_a.xmin << endl; + (*os) << "_window_a.ymin " << _window_a.ymin << endl; + (*os) << "_window_a.xmax " << _window_a.xmax << endl; + (*os) << "_window_a.ymax " << _window_a.ymax << endl; + (*os) << "_window_b.xmin " << _window_a.xmin << endl; + (*os) << "_window_b.ymin " << _window_a.ymin << endl; + (*os) << "_window_b.xmax " << _window_a.xmax << endl; + (*os) << "_window_b.ymax " << _window_a.ymax << endl; +} + +////////////////////////////////////////////////////////////////////// +// PF_GRAYSCALE_HISTOGRAM_COMPARISON + +scalar_t PiFeature::response_grayscale_histogram_comparison(RichImage *image, + PiReferential *referential) { + Rectangle registered_window_a; + + referential->register_rectangle(_registration_a, + &_window_a, + ®istered_window_a); + + int xmin_a = int(registered_window_a.xmin); + int ymin_a = int(registered_window_a.ymin); + int xmax_a = int(registered_window_a.xmax); + int ymax_a = int(registered_window_a.ymax); + + Rectangle registered_window_b; + + referential->register_rectangle(_registration_b, + &_window_b, + ®istered_window_b); + + int xmin_b = int(registered_window_b.xmin); + int ymin_b = int(registered_window_b.ymin); + int xmax_b = int(registered_window_b.xmax); + int ymax_b = int(registered_window_b.ymax); + + int scale = referential->common_scale(); + + scalar_t result = 0.0; + + scalar_t ne_a = scalar_t((xmax_a - xmin_a) * (ymax_a - ymin_a)); + scalar_t ne_b = scalar_t((xmax_b - xmin_b) * (ymax_b - ymin_b)); + + for(int t = RichImage::first_gray_tag; t < RichImage::first_gray_tag + RichImage::nb_gray_tags; t++) + result += sq(scalar_t(image->nb_tags_in_window(scale, t, + xmin_a, ymin_a, + xmax_a, ymax_a))/ne_a + - + scalar_t(image->nb_tags_in_window(scale, t, + xmin_b, ymin_b, + xmax_b, ymax_b))/ne_b); + ASSERT(!isnan(result)); + + return result; +} + +void PiFeature::draw_grayscale_histogram_comparison(RGBImage *image, + int r, int g, int b, + PiReferential *referential) { + + (*global.log_stream) << "draw_grayscale_histogram_comparison" << endl; + + Rectangle registered_window; + { + + referential->register_rectangle(_registration_a, + &_window_a, + ®istered_window); + + referential->draw_window(image, _registration_a, ®istered_window, 0); + } + + { + referential->register_rectangle(_registration_b, + &_window_b, + ®istered_window); + + referential->draw_window(image, _registration_b, ®istered_window, 0); + } +} + +void PiFeature::print_grayscale_histogram_comparison(ostream *os) { + (*os) << "_window_a.xmin " << _window_a.xmin << endl; + (*os) << "_window_a.ymin " << _window_a.ymin << endl; + (*os) << "_window_a.xmax " << _window_a.xmax << endl; + (*os) << "_window_a.ymax " << _window_a.ymax << endl; + (*os) << "_window_b.xmin " << _window_a.xmin << endl; + (*os) << "_window_b.ymin " << _window_a.ymin << endl; + (*os) << "_window_b.xmax " << _window_a.xmax << endl; + (*os) << "_window_b.ymax " << _window_a.ymax << endl; +} + +////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// + +void PiFeature::randomize(int level) { + + // We randomize all parameters, even those which will not be used + // due to the feature type + + _tag = int(drand48() * (RichImage::nb_edge_tags + 1)); + + if(_tag < RichImage::nb_edge_tags) + _tag += RichImage::first_edge_tag; + else + _tag = RichImage::variance_tag; + + _edge_scale = int(drand48() * 3); + + // Windows can not be defined in different frames unless we allow + // head-belly registration + + if(global.force_head_belly_independence) { + if(level == 0) { + _registration_a = PiReferential::RM_HEAD_NO_POLARITY; + _registration_b = PiReferential::RM_HEAD_NO_POLARITY; + } else if(level == 1) { + _registration_a = PiReferential::RM_BELLY_NO_POLARITY; + _registration_b = PiReferential::RM_BELLY_NO_POLARITY; + } else { + abort(); + } + } else { + _registration_a = random_registration_mode(level); + _registration_b = random_registration_mode(level); + } + + randomize_window(_registration_a, &_window_a); + randomize_window(_registration_b, &_window_b); + + switch(int(drand48() * 3)) { + + case 0: + _type = PF_EDGE_THRESHOLDING; + break; + + case 1: + _type = PF_EDGE_HISTOGRAM_COMPARISON; + break; + + case 2: + _type = PF_GRAYSCALE_HISTOGRAM_COMPARISON; + break; + + default: + abort(); + } +} + +scalar_t PiFeature::response(RichImage *image, PiReferential *referential) { + scalar_t r; + + switch(_type) { + + case PF_EDGE_THRESHOLDING: + r = response_edge_thresholding(image, referential); + break; + + case PF_EDGE_HISTOGRAM_COMPARISON: + r = response_edge_histogram_comparison(image, referential); + break; + + case PF_GRAYSCALE_HISTOGRAM_COMPARISON: + r = response_grayscale_histogram_comparison(image, referential); + break; + + default: + abort(); + } + + ASSERT(!isnan(r)); + + return r; +}; + +void PiFeature::draw(RGBImage *image, + int r, int g, int b, PiReferential *referential) { + + switch(_type) { + + case PF_EDGE_THRESHOLDING: + draw_edge_thresholding(image, r, g, b, referential); + break; + + case PF_EDGE_HISTOGRAM_COMPARISON: + draw_edge_histogram_comparison(image, r, g, b, referential); + break; + + case PF_GRAYSCALE_HISTOGRAM_COMPARISON: + draw_grayscale_histogram_comparison(image, r, g, b, referential); + break; + + default: + abort(); + } +} + +void PiFeature::print(ostream *os) { + + (*os) << "registration_a "; + PiReferential::print_registration_mode(os, _registration_a); + (*os) << endl; + + (*os) << "registration_b "; + PiReferential::print_registration_mode(os, _registration_b); + (*os) << endl; + + switch(_type) { + + case PF_EDGE_THRESHOLDING: + print_edge_thresholding(os); + break; + + case PF_EDGE_HISTOGRAM_COMPARISON: + print_edge_histogram_comparison(os); + break; + + case PF_GRAYSCALE_HISTOGRAM_COMPARISON: + print_grayscale_histogram_comparison(os); + break; + + default: + abort(); + } +} diff --git a/pi_feature.h b/pi_feature.h new file mode 100644 index 0000000..b03a922 --- /dev/null +++ b/pi_feature.h @@ -0,0 +1,86 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +/* + + This class implement the notion of pi-feature, that is a feature + which can be evaluated on a pair image / referential, where the + referential is computed from a pose cell. + +*/ + +#ifndef PI_FEATURE_H +#define PI_FEATURE_H + +#include "misc.h" +#include "global.h" +#include "rich_image.h" +#include "pi_referential.h" + +class PiFeature { + + enum { + PF_EDGE_THRESHOLDING, + PF_EDGE_HISTOGRAM_COMPARISON, + PF_GRAYSCALE_HISTOGRAM_COMPARISON, + } _type; + + int _tag, _edge_scale; + Rectangle _window_a, _window_b; + int _registration_a, _registration_b; + + int random_registration_mode(int level); + void randomize_window(int registration_mode, Rectangle *window); + void draw_window(RGBImage *image, int registration_mode, Rectangle *window); + + // EDGE THRESHOLDING + + scalar_t response_edge_thresholding(RichImage *image, PiReferential *referential); + void draw_edge_thresholding(RGBImage *image, int r, int g, int b, + PiReferential *referential); + void print_edge_thresholding(ostream *os); + + // EDGE ORIENTATION HISTOGRAM COMPARISON + + scalar_t response_edge_histogram_comparison(RichImage *image, PiReferential *referential); + void draw_edge_histogram_comparison(RGBImage *image, int r, int g, int b, + PiReferential *referential); + void print_edge_histogram_comparison(ostream *os); + + // GRAYSCALE HISTOGRAM COMPARISON + + scalar_t response_grayscale_histogram_comparison(RichImage *image, PiReferential *referential); + void draw_grayscale_histogram_comparison(RGBImage *image, + int r, int g, int b, + PiReferential *referential); + void print_grayscale_histogram_comparison(ostream *os); + +public: + + void randomize(int level); + + scalar_t response(RichImage *image, PiReferential *referential); + + void draw(RGBImage *image, + int r, int g, int b, + PiReferential *referential); + + void print(ostream *os); +}; + +#endif diff --git a/pi_feature_family.cc b/pi_feature_family.cc new file mode 100644 index 0000000..ba3a35e --- /dev/null +++ b/pi_feature_family.cc @@ -0,0 +1,70 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "pi_feature_family.h" + +PiFeatureFamily::PiFeatureFamily() { + _nb_features = 0; + _pi_features = 0; +} + +PiFeatureFamily::~PiFeatureFamily() { + delete[] _pi_features; +} + +void PiFeatureFamily::read(istream *is) { + delete[] _pi_features; + read_var(is, &_nb_features); + _pi_features = new PiFeature[_nb_features]; + is->read((char *) _pi_features, sizeof(PiFeature) * _nb_features); +} + +void PiFeatureFamily::write(ostream *os) { + write_var(os, &_nb_features); + os->write((char *) _pi_features, sizeof(PiFeature) * _nb_features); +} + +void PiFeatureFamily::resize(int nb_features) { + delete[] _pi_features; + _nb_features = nb_features; + _pi_features = new PiFeature[_nb_features]; +} + +void PiFeatureFamily::randomize(int level) { + for(int f = 0; f < _nb_features; f++) _pi_features[f].randomize(level); +} + +void PiFeatureFamily::extract(PiFeatureFamily *pi_feature_family, + bool *used_features, int *new_feature_indexes) { + delete[] _pi_features; + _nb_features = 0; + + for(int f = 0; f < pi_feature_family->nb_features(); f++) + if(used_features[f]) _nb_features++; + + _pi_features = new PiFeature[_nb_features]; + + int g = 0; + + for(int f = 0; f < pi_feature_family->nb_features(); f++) + if(used_features[f]) { + _pi_features[g] = pi_feature_family->_pi_features[f]; + new_feature_indexes[f] = g; + g++; + } else new_feature_indexes[f] = -1; +} diff --git a/pi_feature_family.h b/pi_feature_family.h new file mode 100644 index 0000000..84ac2f0 --- /dev/null +++ b/pi_feature_family.h @@ -0,0 +1,50 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef PI_FEATURE_FAMILY_H +#define PI_FEATURE_FAMILY_H + +#include "misc.h" +#include "pose_cell_set.h" +#include "pi_feature.h" + +class PiFeatureFamily : public Storable { + int _nb_features; + PiFeature *_pi_features; + +public: + PiFeatureFamily(); + ~PiFeatureFamily(); + + inline int nb_features() { return _nb_features; } + + inline PiFeature *get_feature(int f) { + ASSERT(f >= 0 && f < _nb_features); + return _pi_features + f; + } + + void read(istream *is); + void write(ostream *os); + + void resize(int nb_features); + void randomize(int level); + + void extract(PiFeatureFamily *pi_feature_family, bool *used_features, int *new_feature_indexes); +}; + +#endif diff --git a/pi_referential.cc b/pi_referential.cc new file mode 100644 index 0000000..6e7eb3e --- /dev/null +++ b/pi_referential.cc @@ -0,0 +1,655 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "pi_referential.h" +#include "global.h" +#include "rich_image.h" + +void PiReferential::draw_frame(RGBImage *image, + int registration_mode, + int x1, int y1, + int x2, int y2, + int x3, int y3, + int x4, int y4) { + + int r, g, b; + + switch(registration_mode) { + + case PiReferential::RM_HEAD: + r = 0; g = 255; b = 0; + break; + + case PiReferential::RM_HEAD_NO_POLARITY: + r = 128; g = 255; b = 128; + break; + + case PiReferential::RM_BELLY: + r = 64; g = 0; b = 255; + break; + + case PiReferential::RM_BELLY_NO_POLARITY: + r = 192; g = 128; b = 255; + break; + + case PiReferential::RM_HEAD_BELLY: + case PiReferential::RM_HEAD_BELLY_EDGES: + r = 255; g = 0; b = 0; + break; + + case PiReferential::RM_BODY: + case PiReferential::RM_BODY_EDGES: + r = 0; g = 128; b = 255; + break; + + default: + cerr << "INCONSISTENCY" << endl; + abort(); + } + + if(global.pictures_for_article) { + r = 255; g = 255; b = 255; + image->draw_line(6, r, g, b, x1, y1, x2, y2); + image->draw_line(6, r, g, b, x2, y2, x3, y3); + image->draw_line(6, r, g, b, x3, y3, x4, y4); + image->draw_line(6, r, g, b, x4, y4, x1, y1); + + r = 0; g = 0; b = 0; + image->draw_line(2, r, g, b, x1, y1, x2, y2); + image->draw_line(2, r, g, b, x2, y2, x3, y3); + image->draw_line(2, r, g, b, x3, y3, x4, y4); + image->draw_line(2, r, g, b, x4, y4, x1, y1); + } else { + // int xc = (x1 + x2 + x3 + x4)/4, yc = (y1 + y2 + y3 + y4)/4; + // image->draw_line(1, r, g, b, xc - delta, yc, xc + delta, yc); + // image->draw_line(1, r, g, b, xc, yc - delta, xc, yc + delta); + image->draw_line(2, r, g, b, x1, y1, x2, y2); + image->draw_line(2, r, g, b, x2, y2, x3, y3); + image->draw_line(2, r, g, b, x3, y3, x4, y4); + image->draw_line(2, r, g, b, x4, y4, x1, y1); + // image->draw_line(2, r, g, b, + // (2*xc + 5 * x1 + 5 * x2)/12, (2 * yc + 5 * y1 + 5 * y2)/12, + // (x1 + x2)/2, (y1 + y2)/2); + // image->draw_line(6, r, g, b, + // (2*xc + 3 * x2 + 3 * x3)/8, (2 * yc + 3 * y2 + 3 * y3)/8, + // (x2 + x3)/2, (y2 + y3)/2 + // ); + } +} + +void PiReferential::draw_window(RGBImage *image, + int registration_mode, Rectangle *window, + int filled) { + int r, g, b; + + switch(registration_mode) { + + case PiReferential::RM_HEAD: + r = 0; g = 255; b = 0; + break; + + case PiReferential::RM_HEAD_NO_POLARITY: + r = 128; g = 255; b = 128; + break; + + case PiReferential::RM_BELLY: + r = 64; g = 0; b = 255; + break; + + case PiReferential::RM_BELLY_NO_POLARITY: + r = 192; g = 128; b = 255; + break; + + case PiReferential::RM_HEAD_BELLY: + case PiReferential::RM_HEAD_BELLY_EDGES: + r = 255; g = 0; b = 0; + break; + + case PiReferential::RM_BODY: + case PiReferential::RM_BODY_EDGES: + r = 0; g = 128; b = 255; + break; + + default: + cerr << "INCONSISTENCY" << endl; + abort(); + } + + int xmin = int(window->xmin); + int ymin = int(window->ymin); + int xmax = int(window->xmax); + int ymax = int(window->ymax); + + if(global.pictures_for_article) { + r = 255; g = 255; b = 255; + image->draw_line(6, r, g, b, xmin, ymin, xmax, ymin); + image->draw_line(6, r, g, b, xmax, ymin, xmax, ymax); + image->draw_line(6, r, g, b, xmax, ymax, xmin, ymax); + image->draw_line(6, r, g, b, xmin, ymax, xmin, ymin); + +// if(filled) { +// int delta = 6; +// for(int d = ymin - ymax; d <= xmax - xmin; d += delta) { +// int x1 = xmin + d; +// int y1 = ymin; +// int x2 = xmin + d + ymax - ymin; +// int y2 = ymax; +// if(x1 < xmin) { y1 = y1 + (xmin - x1); x1 = xmin; } +// if(x2 > xmax) { y2 = y2 - (x2 - xmax); x2 = xmax; } +// image->draw_line(3, r, g, b, x1, y1, x2, y2); +// } +// } + + r = 0; g = 0; b = 0; + image->draw_line(2, r, g, b, xmin, ymin, xmax, ymin); + image->draw_line(2, r, g, b, xmax, ymin, xmax, ymax); + image->draw_line(2, r, g, b, xmax, ymax, xmin, ymax); + image->draw_line(2, r, g, b, xmin, ymax, xmin, ymin); + +// if(filled) { +// int delta = 6; +// for(int d = ymin - ymax; d <= xmax - xmin; d += delta) { +// int x1 = xmin + d; +// int y1 = ymin; +// int x2 = xmin + d + ymax - ymin; +// int y2 = ymax; +// if(x1 < xmin) { y1 = y1 + (xmin - x1); x1 = xmin; } +// if(x2 > xmax) { y2 = y2 - (x2 - xmax); x2 = xmax; } +// image->draw_line(1, r, g, b, x1, y1, x2, y2); +// } +// } + } else { + image->draw_line(2, r, g, b, xmin, ymin, xmax, ymin); + image->draw_line(2, r, g, b, xmax, ymin, xmax, ymax); + image->draw_line(2, r, g, b, xmax, ymax, xmin, ymax); + image->draw_line(2, r, g, b, xmin, ymax, xmin, ymin); + if(filled) { + int delta = 4; + for(int d = ymin - ymax; d <= xmax - xmin; d += delta) { + int x1 = xmin + d; + int y1 = ymin; + int x2 = xmin + d + ymax - ymin; + int y2 = ymax; + if(x1 < xmin) { y1 = y1 + (xmin - x1); x1 = xmin; } + if(x2 > xmax) { y2 = y2 - (x2 - xmax); x2 = xmax; } + image->draw_line(1, r, g, b, x1, y1, x2, y2); + } + } + } + +} + +void PiReferential::draw_edge_and_scale(RGBImage *image, + int registration_mode, Rectangle *window, + int _tag, int _edge_scale) { + const int ref_radius = 10; + int r, g, b; + int edges = 0; + + switch(registration_mode) { + + case PiReferential::RM_HEAD: + r = 0; g = 255; b = 0; + break; + + case PiReferential::RM_HEAD_NO_POLARITY: + r = 128; g = 255; b = 128; + break; + + case PiReferential::RM_BELLY: + r = 64; g = 0; b = 255; + break; + + case PiReferential::RM_BELLY_NO_POLARITY: + r = 192; g = 128; b = 255; + break; + + case PiReferential::RM_HEAD_BELLY_EDGES: + edges = 1; + case PiReferential::RM_HEAD_BELLY: + r = 255; g = 0; b = 0; + break; + + case PiReferential::RM_BODY_EDGES: + edges = 1; + case PiReferential::RM_BODY: + r = 0; g = 128; b = 255; + break; + + default: + cerr << "INCONSISTENCY" << endl; + abort(); + } + + scalar_t xc = (window->xmin + window->xmax)/2; + scalar_t yc = (window->ymin + window->ymax)/2; + int radius = ref_radius * (1 << _edge_scale); + + image->draw_ellipse(1, r, g, b, xc, yc, radius, radius, 0); + + if(_tag >= RichImage::first_edge_tag && _tag < RichImage::first_edge_tag + RichImage::nb_edge_tags) { + + scalar_t dx, dy; + + switch(_tag - RichImage::first_edge_tag) { + case 0: + dx = 0; dy = -1; + break; + + case 1: + dx = 1; dy = -1; + break; + + case 2: + dx = 1; dy = 0; + break; + + case 3: + dx = 1; dy = 1; + break; + + case 4: + dx = 0; dy = 1; + break; + + case 5: + dx = -1; dy = 1; + break; + + case 6: + dx = -1; dy = 0; + break; + + case 7: + dx = -1; dy = -1; + break; + + default: + abort(); + } + + scalar_t l = sqrt(dx * dx + dy * dy); + +// dx = dx / l; +// dy = dy / l; + + if(edges) { + int delta = 3; + image->draw_ellipse(1, r, g, b, xc, yc, radius + delta, radius + delta, 0); + } + + for(scalar_t u = 0; u <= radius; u += 0.1) { + scalar_t s = sqrt(radius * radius - (u * u * l * l))/l; + image->draw_line(2, r, g, b, + int(xc + u * dx - s * dy), int(yc + u * dy + s * dx), + int(xc + u * dx + s * dy), int(yc + u * dy - s * dx)); + } + +// for(int y = yc - radius; y <= yc + radius; y++) { +// for(int x = xc - radius; x <= xc + radius; x++) { +// if(x >= 0 && x < image->width() && y >= 0 && y < image->height() && +// (x - xc) * dx + (y - yc) * dy >= 0) { +// image->draw_point(r, g, b, x, y); +// } +// } +// } + + } + + else if(_tag == RichImage::variance_tag) { + image->draw_ellipse(1, r, g, b, xc, yc, 8, 8, 0); + } + + // else if(_tag >= RichImage::first_gray_tag && _tag < RichImage::first_gray_tag + RichImage::nb_gray_tags) { + // } +} + +PiReferential::PiReferential(PoseCell *cell) { + scalar_t head_radius = sqrt(scalar_t(cell->_head_radius.min * cell->_head_radius.max)); + + _common_scale = global.scale_to_discrete_log_scale(head_radius / global.min_head_radius); + + scalar_t discrete_scale_ratio = global.discrete_log_scale_to_scale(_common_scale); + + ////////////////////////////////////////////////////////////////////// + // Locations and scales + + // Head location + + _head_xc = cell->_head_xc.middle() * discrete_scale_ratio; + _head_yc = cell->_head_yc.middle() * discrete_scale_ratio; + _head_radius = cell->_head_radius.middle() * discrete_scale_ratio; + _head_window_scaling = _head_radius * 2.0; + + // Body location + + _body_xc = cell->_body_xc.middle() * discrete_scale_ratio; + _body_yc = cell->_body_yc.middle() * discrete_scale_ratio; + _body_window_scaling = sqrt(_body_radius_1 * _body_radius_2); + + if((_head_xc - _body_xc) * cos(_body_tilt) + (_head_yc - _body_yc) * sin(_body_tilt) > 0) { + _body_tilt += M_PI; + } + + // Belly location + + const scalar_t belly_frame_factor = 2.0; + + _belly_xc = _body_xc; + _belly_yc = _body_yc; + _belly_window_scaling = _head_window_scaling * belly_frame_factor; + + // Head-belly location + + _head_belly_xc = (_head_xc + _body_xc) * 0.5; + _head_belly_yc = (_head_yc + _body_yc) * 0.5; + + ////////////////////////////////////////////////////////////////////// + // Frames + + if(_body_xc >= _head_xc) { + _horizontal_polarity = 1; + } else { + _horizontal_polarity = -1; + } + + // Head frame + + if(_horizontal_polarity < 0) { + _head_ux = _head_radius * 2.0; + _head_uy = 0; + } else { + _head_ux = - _head_radius * 2.0; + _head_uy = 0; + } + + _head_vx = 0; + _head_vy = - _head_radius * 2.0; + + _head_ux_nopolarity = _head_radius * 2.0; + _head_uy_nopolarity = 0; + _head_vx_nopolarity = 0; + _head_vy_nopolarity = - _head_radius * 2.0; + + // Belly frame + + _belly_ux = _head_ux * belly_frame_factor; + _belly_uy = _head_uy * belly_frame_factor; + _belly_vx = _head_vx * belly_frame_factor; + _belly_vy = _head_vy * belly_frame_factor; + + _belly_ux_nopolarity = _head_ux_nopolarity * belly_frame_factor; + _belly_uy_nopolarity = _head_uy_nopolarity * belly_frame_factor; + _belly_vx_nopolarity = _head_vx_nopolarity * belly_frame_factor; + _belly_vy_nopolarity = _head_vy_nopolarity * belly_frame_factor; + + // Head-belly frame + + _head_belly_ux = 2 * (_head_xc - _head_belly_xc); + _head_belly_uy = 2 * (_head_yc - _head_belly_yc); + + if(_horizontal_polarity < 0) { + _head_belly_vx = _head_belly_uy; + _head_belly_vy = - _head_belly_ux; + } else { + _head_belly_vx = - _head_belly_uy; + _head_belly_vy = _head_belly_ux; + } + + scalar_t l = sqrt(_head_belly_vx * _head_belly_vx + _head_belly_vy * _head_belly_vy); + + _head_belly_vx = _head_belly_vx/l * _head_radius * 2; + _head_belly_vy = _head_belly_vy/l * _head_radius * 2; + _head_belly_edge_shift = int(floor(- RichImage::nb_edge_tags * atan2(_head_belly_ux, _head_belly_uy) / (2 * M_PI) + 0.5)); + _head_belly_edge_shift = (RichImage::nb_edge_tags + _head_belly_edge_shift) % RichImage::nb_edge_tags; + + // Body frame + + _body_ux = cos(_body_tilt) * _body_radius_1 * 2.0; + _body_uy = sin(_body_tilt) * _body_radius_1 * 2.0; + _body_vx = - sin(_body_tilt) * _body_radius_2 * 2.0; + _body_vy = cos(_body_tilt) * _body_radius_2 * 2.0; + + _body_edge_shift = int(floor(RichImage::nb_edge_tags * _body_tilt / (2 * M_PI) + 0.5)); + _body_edge_shift = (RichImage::nb_edge_tags + _body_edge_shift) % RichImage::nb_edge_tags; +} + +int PiReferential::common_scale() { + return _common_scale; +} + +void PiReferential::register_rectangle(int registration_mode, + Rectangle *original, + Rectangle *result) { + scalar_t alpha, beta , xc, yc, w, h; + + alpha = (original->xmin + original->xmax) * 0.5; + beta = (original->ymin + original->ymax) * 0.5; + + switch(registration_mode) { + + case RM_HEAD: + { + xc = _head_xc + alpha * _head_ux + beta * _head_vx; + yc = _head_yc + alpha * _head_uy + beta * _head_vy; + w = (original->xmax - original->xmin) * _head_window_scaling; + h = (original->ymax - original->ymin) * _head_window_scaling; + } + break; + + case RM_HEAD_NO_POLARITY: + { + xc = _head_xc + alpha * _head_ux_nopolarity + beta * _head_vx_nopolarity; + yc = _head_yc + alpha * _head_uy_nopolarity + beta * _head_vy_nopolarity; + w = (original->xmax - original->xmin) * _head_window_scaling; + h = (original->ymax - original->ymin) * _head_window_scaling; + } + break; + + case RM_BELLY: + { + xc = _belly_xc + alpha * _belly_ux + beta * _belly_vx; + yc = _belly_yc + alpha * _belly_uy + beta * _belly_vy; + w = (original->xmax - original->xmin) * _belly_window_scaling; + h = (original->ymax - original->ymin) * _belly_window_scaling; + } + break; + + case RM_BELLY_NO_POLARITY: + { + xc = _belly_xc + alpha * _belly_ux_nopolarity + beta * _belly_vx_nopolarity; + yc = _belly_yc + alpha * _belly_uy_nopolarity + beta * _belly_vy_nopolarity; + w = (original->xmax - original->xmin) * _belly_window_scaling; + h = (original->ymax - original->ymin) * _belly_window_scaling; + } + break; + + case RM_HEAD_BELLY: + case RM_HEAD_BELLY_EDGES: + { + xc = _head_belly_xc + alpha * _head_belly_ux + beta * _head_belly_vx; + yc = _head_belly_yc + alpha * _head_belly_uy + beta * _head_belly_vy; + w = (original->xmax - original->xmin) * _head_window_scaling; + h = (original->ymax - original->ymin) * _head_window_scaling; + } + break; + + case RM_BODY: + case RM_BODY_EDGES: + { + xc = _body_xc + alpha * _body_ux + beta * _body_vx; + yc = _body_yc + alpha * _body_uy + beta * _body_vy; + w = (original->xmax - original->xmin) * _body_window_scaling; + h = (original->ymax - original->ymin) * _body_window_scaling; + } + break; + + default: + cerr << "Undefined registration mode." << endl; + abort(); + } + + result->xmin = xc - 0.5 * w; + result->ymin = yc - 0.5 * h; + result->xmax = xc + 0.5 * w; + result->ymax = yc + 0.5 * h; + + ASSERT(result->xmin < result->xmax && result->ymin < result->ymax); +} + +int PiReferential::register_edge(int registration_mode, int edge_type) { + + if(edge_type >= RichImage::first_edge_tag && + edge_type < RichImage::first_edge_tag + RichImage::nb_edge_tags) { + + int e = edge_type - RichImage::first_edge_tag; + + switch(registration_mode) { + case PiReferential::RM_HEAD_NO_POLARITY: + case PiReferential::RM_BELLY_NO_POLARITY: + break; + + case PiReferential::RM_HEAD: + case PiReferential::RM_BELLY: + case PiReferential::RM_HEAD_BELLY: + case PiReferential::RM_BODY: + if(_horizontal_polarity < 0) { + e = (RichImage::nb_edge_tags - e) % RichImage::nb_edge_tags; + } + break; + + case PiReferential::RM_HEAD_BELLY_EDGES: + if(_horizontal_polarity < 0) { + e = (RichImage::nb_edge_tags - e) % RichImage::nb_edge_tags; + } + e += _head_belly_edge_shift; + break; + + case PiReferential::RM_BODY_EDGES: + if(_horizontal_polarity < 0) { + e = (RichImage::nb_edge_tags - e) % RichImage::nb_edge_tags; + } + e += _body_edge_shift; + break; + + default: + cerr << "INCONSISTENCY" << endl; + abort(); + } + + e = e % RichImage::nb_edge_tags; + + return RichImage::first_edge_tag + e; + + } + + else return edge_type; +} + +void PiReferential::draw(RGBImage *image, int level) { + int x1, y1, x2, y2, x3, y3, x4, y4; + + if(level >= 2) { + + // Draw the RM_BODY reference frame + + x1 = int(_body_xc + _body_ux + _body_vx); + y1 = int(_body_yc + _body_uy + _body_vy); + x2 = int(_body_xc - _body_ux + _body_vx); + y2 = int(_body_yc - _body_uy + _body_vy); + x3 = int(_body_xc - _body_ux - _body_vx); + y3 = int(_body_yc - _body_uy - _body_vy); + x4 = int(_body_xc + _body_ux - _body_vx); + y4 = int(_body_yc + _body_uy - _body_vy); + + draw_frame(image, RM_BODY, x1, y1, x2, y2, x3, y3, x4, y4); + } + + if(level >= 1) { + + // Draw the RM_BELLY reference frame + + x1 = int(_belly_xc + _belly_ux + _belly_vx); + y1 = int(_belly_yc + _belly_uy + _belly_vy); + x2 = int(_belly_xc - _belly_ux + _belly_vx); + y2 = int(_belly_yc - _belly_uy + _belly_vy); + x3 = int(_belly_xc - _belly_ux - _belly_vx); + y3 = int(_belly_yc - _belly_uy - _belly_vy); + x4 = int(_belly_xc + _belly_ux - _belly_vx); + y4 = int(_belly_yc + _belly_uy - _belly_vy); + + draw_frame(image, RM_BELLY, x1, y1, x2, y2, x3, y3, x4, y4); + + // Draw the RM_HEAD_BELLY reference frame + + x1 = int(_head_belly_xc + _head_belly_ux + _head_belly_vx); + y1 = int(_head_belly_yc + _head_belly_uy + _head_belly_vy); + x2 = int(_head_belly_xc - _head_belly_ux + _head_belly_vx); + y2 = int(_head_belly_yc - _head_belly_uy + _head_belly_vy); + x3 = int(_head_belly_xc - _head_belly_ux - _head_belly_vx); + y3 = int(_head_belly_yc - _head_belly_uy - _head_belly_vy); + x4 = int(_head_belly_xc + _head_belly_ux - _head_belly_vx); + y4 = int(_head_belly_yc + _head_belly_uy - _head_belly_vy); + + draw_frame(image, RM_HEAD_BELLY, x1, y1, x2, y2, x3, y3, x4, y4); + } + + // Draw the RM_HEAD reference frame + + x1 = int(_head_xc + _head_ux + _head_vx); + y1 = int(_head_yc + _head_uy + _head_vy); + x2 = int(_head_xc - _head_ux + _head_vx); + y2 = int(_head_yc - _head_uy + _head_vy); + x3 = int(_head_xc - _head_ux - _head_vx); + y3 = int(_head_yc - _head_uy - _head_vy); + x4 = int(_head_xc + _head_ux - _head_vx); + y4 = int(_head_yc + _head_uy - _head_vy); + + draw_frame(image, RM_HEAD, x1, y1, x2, y2, x3, y3, x4, y4); +} + +void PiReferential::print_registration_mode(ostream *out, int registration_mode) { + switch(registration_mode) { + case RM_HEAD: + (*out) << "RM_HEAD"; + break; + case RM_HEAD_NO_POLARITY: + (*out) << "RM_HEAD_NO_POLARITY"; + break; + case RM_BELLY: + (*out) << "RM_BELLY"; + break; + case RM_BELLY_NO_POLARITY: + (*out) << "RM_BELLY_NO_POLARITY"; + break; + case RM_HEAD_BELLY: + (*out) << "RM_HEAD_BELLY"; + break; + case RM_HEAD_BELLY_EDGES: + (*out) << "RM_HEAD_BELLY_EDGES"; + break; + case RM_BODY: + (*out) << "RM_BODY"; + break; + case RM_BODY_EDGES: + (*out) << "RM_BODY_EDGES"; + break; + default: + abort(); + } +} diff --git a/pi_referential.h b/pi_referential.h new file mode 100644 index 0000000..1dcb007 --- /dev/null +++ b/pi_referential.h @@ -0,0 +1,133 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +/* + + This class factorizes the information from a PoseCell needed by a + PiFeature to be evaluated on an image. Since there can be in there + costly operations such as trigonometric mappings, it provides a + substantial cost optimization. + +*/ + +#ifndef PI_REFERENTIAL_H +#define PI_REFERENTIAL_H + +#include "rectangle.h" +#include "pose_cell.h" +#include "rgb_image.h" + +class PiReferential { + // The best scale so that the head size is + // ~global.reference_head_size. This is an integer scale as defined + // for the RichImage + int _common_scale; + + scalar_t _horizontal_polarity; + + // The head frame in _common_scale. The vectors are of length the _RADIUS_ of the head + scalar_t _head_xc, _head_yc, _head_radius; + scalar_t _head_window_scaling; + scalar_t _head_ux, _head_uy, _head_vx, _head_vy; + scalar_t _head_ux_nopolarity, _head_uy_nopolarity, _head_vx_nopolarity, _head_vy_nopolarity; + + // The body frame in that _common_scale. The vectors are of length the radii of the ellipse + scalar_t _body_xc, _body_yc; + scalar_t _body_radius_1, _body_radius_2, _body_tilt; + scalar_t _body_ux, _body_uy, _body_vx, _body_vy; + scalar_t _body_window_scaling; + int _body_edge_shift; + + // The belly frame is defined by the body location and head scale + scalar_t _belly_xc, _belly_yc; + scalar_t _belly_ux, _belly_uy, _belly_vx, _belly_vy; + scalar_t _belly_ux_nopolarity, _belly_uy_nopolarity, _belly_vx_nopolarity, _belly_vy_nopolarity; + scalar_t _belly_window_scaling; + + // The head-belly frame is defined by the head location and the body + // center location + scalar_t _head_belly_xc, _head_belly_yc; + scalar_t _head_belly_ux, _head_belly_uy, _head_belly_vx, _head_belly_vy; + int _head_belly_edge_shift; + + void draw_frame(RGBImage *image, + int registration_mode, + int x1, int y1, + int x2, int y2, + int x3, int y3, + int x4, int y4); + +public: + PiReferential(PoseCell *cell); + + enum { + // A frame centered on the head, of size four times the head radius + // and, flipped verically if the body center is on the left of the + // head center + RM_HEAD, + + // Same as above, without the flipping + RM_HEAD_NO_POLARITY, + // A frame centered on the body center, of size size times the + // head rardius, flipped vertically if the body center is on the + // left of the head center + RM_BELLY, + + // Same as above, without the flipping + RM_BELLY_NO_POLARITY, + + // A frame centered on the middle point between the head center + // and the body center, of size twice the distance head center - + // body center in the head-body direction, and of four times the + // head radius in the other + RM_HEAD_BELLY, + + // Same as above with rotation of the edges + RM_HEAD_BELLY_EDGES, + + // Not finished yet + RM_BODY, + RM_BODY_EDGES + }; + + int common_scale(); + + // The rectangle coordinates are in the reference frames. For the + // head for instance , [-1,1] x [-1,1] corresponds to the head + // bounding box + + void register_rectangle(int registration_mode, + Rectangle *original, + Rectangle *result); + + int register_edge(int registration_type, int edge_type); + + void draw(RGBImage *image, int level); + + void draw_window(RGBImage *image, + int registration_mode, Rectangle *window, + int filled); + + void draw_edge_and_scale(RGBImage *image, + int registration_mode, Rectangle *window, + int _tag, int _edge_scale); + + static void print_registration_mode(ostream *out, int registration_mode); +}; + +#endif diff --git a/pose.cc b/pose.cc new file mode 100644 index 0000000..ed6c705 --- /dev/null +++ b/pose.cc @@ -0,0 +1,190 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include + +#include "pose.h" + +Pose::Pose() { + memset(this, 0, sizeof(*this)); +} + +void Pose::horizontal_flip(scalar_t scene_width) { + _head_xc = scene_width - 1 - _head_xc; + _body_xc = scene_width - 1 - _body_xc; +} + +void Pose::translate(scalar_t dx, scalar_t dy) { + _bounding_box_xmin += dx; + _bounding_box_ymin += dy; + _bounding_box_xmax += dx; + _bounding_box_ymax += dy; + _head_xc += dx; + _head_yc += dy; + _body_xc += dx; + _body_yc += dy; +} + +void Pose::scale(scalar_t factor) { + _bounding_box_xmin *= factor; + _bounding_box_ymin *= factor; + _bounding_box_xmax *= factor; + _bounding_box_ymax *= factor; + _head_xc *= factor; + _head_yc *= factor; + _head_radius *= factor; + _body_xc *= factor; + _body_yc *= factor; +} + +const scalar_t tolerance_scale_ratio_for_hit = 1.5; +const scalar_t tolerance_distance_factor_for_hit = 1.0; + +bool Pose::hit(int level, Pose *pose) { + + // Head size + + if(_head_radius/pose->_head_radius > tolerance_scale_ratio_for_hit || + pose->_head_radius/_head_radius > tolerance_scale_ratio_for_hit) + return false; + + scalar_t sq_delta = _head_radius * pose->_head_radius * sq(tolerance_distance_factor_for_hit); + + // Head location + + if(sq(_head_xc - pose->_head_xc) + sq(_head_yc - pose->_head_yc) > sq_delta) + return false; + + if(level == 0) return true; + + // Belly location + + if(sq(_body_xc - pose->_body_xc) + sq(_body_yc - pose->_body_yc) > 4 * sq_delta) + return false; + + if(level == 1) return true; + + cerr << "Hit criterion is undefined for level " << level << endl; + + abort(); +} + +const scalar_t tolerance_scale_ratio_for_collide = 1.2; +const scalar_t tolerance_distance_factor_for_collide = 0.25; + +bool Pose::collide(int level, Pose *pose) { + + // Head size + + if(_head_radius/pose->_head_radius > tolerance_scale_ratio_for_collide || + pose->_head_radius/_head_radius > tolerance_scale_ratio_for_collide) + return false; + + scalar_t sq_delta = _head_radius * pose->_head_radius * sq(tolerance_distance_factor_for_collide); + + // Head location + + if(sq(_head_xc - pose->_head_xc) + sq(_head_yc - pose->_head_yc) <= sq_delta) + return true; + + if(level == 0) return false; + + // Belly location + + if(sq(_body_xc - pose->_body_xc) + sq(_body_yc - pose->_body_yc) <= sq_delta) + return true; + + if(level == 1) return false; + + cerr << "Colliding criterion is undefined for level " << level << endl; + + abort(); +} + +void Pose::draw(int thickness, int r, int g, int b, int level, RGBImage *image) { + // Draw the head circle + + image->draw_ellipse(thickness, r, g, b, + _head_xc, _head_yc, _head_radius, _head_radius, 0); + + // int vx = int(cos(_head_tilt) * _head_radius); + // int vy = int(sin(_head_tilt) * _head_radius); + // image->draw_line(thickness, r, g, b, _head_xc, _head_yc, _head_xc + vx, _head_yc + vy); + + if(level == 1) { + + // image->draw_line(thickness, r, g, b, + // int(_body_xc) - delta, int(_body_yc), + // int(_body_xc) + delta, int(_body_yc)); + + // image->draw_line(thickness, r, g, b, + // int(_body_xc), int(_body_yc) - delta, + // int(_body_xc), int(_body_yc) + delta); + + scalar_t vx = _body_xc - _head_xc, vy = _body_yc - _head_yc; + scalar_t l = sqrt(vx * vx + vy * vy); + vx /= l; + vy /= l; + + scalar_t body_radius = 12 + thickness / 2; + + if(l > _head_radius + thickness + body_radius) { + image->draw_line(thickness, r, g, b, + _head_xc + vx * (_head_radius + thickness/2), + _head_yc + vy * (_head_radius + thickness/2), + _body_xc - vx * (body_radius - thickness/2), + _body_yc - vy * (body_radius - thickness/2)); + } + + // An ugly way to make a filled disc + for(scalar_t u = 0; u < body_radius; u += thickness / 2) { + image->draw_ellipse(thickness, r, g, b, _body_xc, _body_yc, u, u, 0); + } + + } +} + +void Pose::print(ostream *out) { + (*out) << " _head_xc " << _head_xc << endl; + (*out) << " _head_yc " << _head_yc << endl; + (*out) << " _head_radius " << _head_radius << endl; + (*out) << " _body_xc " << _body_xc << endl; + (*out) << " _body_yc " << _body_yc << endl; +} + +void Pose::write(ostream *out) { + write_var(out, &_bounding_box_xmin); + write_var(out, &_bounding_box_ymin); + write_var(out, &_bounding_box_xmax); + write_var(out, &_bounding_box_ymax); + write_var(out, &_head_xc); write_var(out, &_head_yc); + write_var(out, &_head_radius); + write_var(out, &_body_xc); + write_var(out, &_body_yc); +} + +void Pose::read(istream *in) { + read_var(in, &_bounding_box_xmin); + read_var(in, &_bounding_box_ymin); + read_var(in, &_bounding_box_xmax); + read_var(in, &_bounding_box_ymax); + read_var(in, &_head_xc); read_var(in, &_head_yc); + read_var(in, &_head_radius); + read_var(in, &_body_xc); + read_var(in, &_body_yc); +} diff --git a/pose.h b/pose.h new file mode 100644 index 0000000..acd7884 --- /dev/null +++ b/pose.h @@ -0,0 +1,49 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef POSE_H +#define POSE_H + +#include "rgb_image.h" +#include "image.h" + +class Pose { +public: + scalar_t _bounding_box_xmin, _bounding_box_ymin; + scalar_t _bounding_box_xmax, _bounding_box_ymax; + scalar_t _head_xc, _head_yc, _head_radius; + scalar_t _body_xc, _body_yc; + + Pose(); + + bool hit(int level, Pose *pose); + bool collide(int level, Pose *pose); + + void horizontal_flip(scalar_t scene_width); + void translate(scalar_t dx, scalar_t dy); + void scale(scalar_t factor); + + void draw(int thickness, int r, int g, int b, int level, RGBImage *image); + + void print(ostream *out); + + void write(ostream *out); + void read(istream *in); +}; + +#endif diff --git a/pose_cell.cc b/pose_cell.cc new file mode 100644 index 0000000..4e5e7e2 --- /dev/null +++ b/pose_cell.cc @@ -0,0 +1,63 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "pose_cell.h" +#include "global.h" +#include "tools.h" + +bool PoseCell::contains(Pose *pose) { + return + _head_xc.contains(pose->_head_xc) && + _head_yc.contains(pose->_head_yc) && + _head_radius.contains(pose->_head_radius) && + _body_xc.contains(pose->_body_xc) && + _body_yc.contains(pose->_body_yc); +} + +bool PoseCell::negative_for_train(Pose *pose) { + const scalar_t r_tolerance = 3; + scalar_t r = pose->_head_radius; + return + r <= _head_radius.min / r_tolerance || + r >= _head_radius.max * r_tolerance || + pose->_head_xc <= _head_xc.min - r || + pose->_head_xc >= _head_xc.max + r || + pose->_head_yc <= _head_yc.min - r || + pose->_head_yc >= _head_yc.max + r; +} + +void PoseCell::get_centroid(Pose *pose) { + pose->_bounding_box_xmin = -1; + pose->_bounding_box_ymin = -1; + pose->_bounding_box_xmax = -1; + pose->_bounding_box_ymax = -1; + pose->_head_radius = sqrt(_head_radius.min * _head_radius.max); + pose->_head_xc = _head_xc.middle(); + pose->_head_yc = _head_yc.middle(); + pose->_body_xc = _body_xc.middle(); + pose->_body_yc = _body_yc.middle(); +} + +void PoseCell::print(ostream *out) { + (*out) << " _head_xc " << _head_xc << endl; + (*out) << " _head_yc " << _head_yc << endl; + (*out) << " _head_radius " << _head_radius << endl; + (*out) << " _head_tilt " << _head_tilt << endl; + (*out) << " _body_xc " << _body_xc << endl; + (*out) << " _body_yc " << _body_yc << endl; +} diff --git a/pose_cell.h b/pose_cell.h new file mode 100644 index 0000000..b970756 --- /dev/null +++ b/pose_cell.h @@ -0,0 +1,47 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef POSE_CELL_H +#define POSE_CELL_H + +#include "pose.h" +#include "interval.h" + +class PoseCell { +public: + + Interval _head_xc, _head_yc, _head_radius, _head_tilt; + Interval _body_xc, _body_yc; + + // The cell contains the pose + + bool contains(Pose *pose); + + // The pose is far enough from the cell to accept the cell as a + // negative sample + + bool negative_for_train(Pose *pose); + + // Copies into pose the average pose of that cell + + void get_centroid(Pose *pose); + + void print(ostream *out); +}; + +#endif diff --git a/pose_cell_hierarchy.cc b/pose_cell_hierarchy.cc new file mode 100644 index 0000000..0816f3a --- /dev/null +++ b/pose_cell_hierarchy.cc @@ -0,0 +1,392 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "pose_cell_hierarchy.h" +#include "gaussian.h" + +PoseCellHierarchy::PoseCellHierarchy() { + _body_cells = 0; +} + +PoseCellHierarchy::PoseCellHierarchy(LabelledImagePool *train_pool) { + _nb_levels = global.nb_levels; + _min_head_radius = global.min_head_radius; + _max_head_radius = global.max_head_radius; + _root_cell_nb_xy_per_scale = global.root_cell_nb_xy_per_scale; + + LabelledImage *image; + int nb_total_targets = 0; + for(int i = 0; i < train_pool->nb_images(); i++) { + image = train_pool->grab_image(i); + // We are going to symmetrize + nb_total_targets += 2 * image->nb_targets(); + train_pool->release_image(i); + } + + RelativeBodyPoseCell targets[nb_total_targets]; + + int u = 0; + for(int i = 0; i < train_pool->nb_images(); i++) { + image = train_pool->grab_image(i); + + PoseCellSet cell_set; + add_root_cells(image, &cell_set); + + for(int t = 0; t < image->nb_targets(); t++) { + Pose pose = *image->get_target_pose(t); + Pose coarse; + + cell_set.get_containing_cell(&pose)->get_centroid(&coarse); + + targets[u]._body_xc.set((pose._body_xc - coarse._head_xc) / coarse._head_radius); + targets[u]._body_yc.set((pose._body_yc - coarse._head_yc) / coarse._head_radius); + u++; + + pose.horizontal_flip(image->width()); + + cell_set.get_containing_cell(&pose)->get_centroid(&coarse); + + targets[u]._body_xc.set((pose._body_xc - coarse._head_xc) / coarse._head_radius); + targets[u]._body_yc.set((pose._body_yc - coarse._head_yc) / coarse._head_radius); + u++; + } + + train_pool->release_image(i); + } + + scalar_t fattening = 1.1; + + Interval body_rxc, body_ryc; + + body_rxc.set(&targets[0]._body_xc); + body_ryc.set(&targets[0]._body_yc); + + for(int t = 0; t < nb_total_targets; t++) { + body_rxc.swallow(&targets[t]._body_xc); + body_ryc.swallow(&targets[t]._body_yc); + } + + body_rxc.min *= fattening; + body_rxc.max *= fattening; + body_ryc.min *= fattening; + body_ryc.max *= fattening; + + scalar_t body_rxc_min = body_resolution * floor(body_rxc.min / body_resolution); + int nb_body_rxc = int(ceil((body_rxc.max - body_rxc_min) / body_resolution)); + + scalar_t body_ryc_min = body_resolution * floor(body_ryc.min / body_resolution); + int nb_body_ryc = int(ceil((body_ryc.max - body_ryc_min) / body_resolution)); + + (*global.log_stream) << "body_rxc = " << body_rxc << endl + << "body_rxc_min = " << body_rxc_min << endl + << "body_rxc_min + nb_body_rxc * body_resolution = " << body_rxc_min + nb_body_rxc * body_resolution << endl + << endl + << "body_ryc = " << body_ryc << endl + << "body_ryc_min = " << body_ryc_min << endl + << "body_ryc_min + nb_body_ryc * body_resolution = " << body_ryc_min + nb_body_ryc * body_resolution << endl; + + int used[nb_body_rxc * nb_body_rxc]; + + for(int k = 0; k < nb_body_rxc * nb_body_ryc; k++) { + used[k] = 1; + } + + // An ugly way to compute the convexe enveloppe + + for(scalar_t alpha = 0; alpha < M_PI * 2; alpha += (2 * M_PI) / 100) { + scalar_t vx = cos(alpha), vy = sin(alpha); + scalar_t rho = 0; + + for(int t = 0; t < nb_total_targets; t++) { + rho = min(rho, vx * targets[t]._body_xc.middle() + vy * targets[t]._body_yc.middle()); + } + + rho *= fattening; + + for(int j = 0; j < nb_body_ryc; j++) { + for(int i = 0; i < nb_body_rxc; i++) { + if( + vx * (scalar_t(i + 0) * body_resolution + body_rxc_min) + + vy * (scalar_t(j + 0) * body_resolution + body_ryc_min) < rho + && + vx * (scalar_t(i + 1) * body_resolution + body_rxc_min) + + vy * (scalar_t(j + 0) * body_resolution + body_ryc_min) < rho + && + vx * (scalar_t(i + 0) * body_resolution + body_rxc_min) + + vy * (scalar_t(j + 1) * body_resolution + body_ryc_min) < rho + && + vx * (scalar_t(i + 1) * body_resolution + body_rxc_min) + + vy * (scalar_t(j + 1) * body_resolution + body_ryc_min) < rho + ) { + used[i + j * nb_body_rxc] = 0; + } + } + } + } + + _nb_body_cells = 0; + for(int j = 0; j < nb_body_ryc; j++) { + for(int i = 0; i < nb_body_rxc; i++) { + if(used[i + nb_body_rxc * j]) { + _nb_body_cells++; + } + } + } + + _body_cells = new RelativeBodyPoseCell[_nb_body_cells]; + + for(int j = 0; j < nb_body_ryc; j++) { + for(int i = 0; i < nb_body_rxc; i++) { + if(used[i + nb_body_rxc * j]) { + if(sq(scalar_t(i) * body_resolution + body_resolution/2 + body_rxc_min) + + sq(scalar_t(j) * body_resolution + body_resolution/2 + body_ryc_min) <= 1) { + (*global.log_stream) << "*"; + } else { + (*global.log_stream) << "X"; + } + } else { + (*global.log_stream) << "."; + } + } + (*global.log_stream) << endl; + } + + int k = 0; + for(int j = 0; j < nb_body_ryc; j++) { + for(int i = 0; i < nb_body_rxc; i++) { + + if(used[i + nb_body_rxc * j]) { + + RelativeBodyPoseCell mother; + + scalar_t x = scalar_t(i) * body_resolution + body_rxc_min; + scalar_t y = scalar_t(j) * body_resolution + body_ryc_min; + + mother._body_xc.set(x, x + body_resolution); + mother._body_yc.set(y, y + body_resolution); + + // scalar_t dist_min = body_resolution; + scalar_t dist_min = 1e6; + + int nb_got; + + Gaussian dist_body_radius_1, dist_body_radius_2, dist_body_tilt; + + do { + + nb_got = 0; + + for(int t = 0; t < nb_total_targets; t++) { + + scalar_t dist = + sqrt(sq(targets[t]._body_xc.middle() - x - body_resolution / 2) + + sq(targets[t]._body_yc.middle() - y - body_resolution / 2)); + + if(dist <= dist_min) { + dist_body_radius_1.add_sample(targets[t]._body_radius_1.middle()); + dist_body_radius_2.add_sample(targets[t]._body_radius_2.middle()); + dist_body_tilt.add_sample(targets[t]._body_tilt.middle()); + nb_got++; + } + + } + + dist_min *= 2.0; + } while(nb_got < min(100, nb_total_targets)); + + scalar_t zeta = 4; + + mother._body_radius_1.set(dist_body_radius_1.expectation() - + zeta * dist_body_radius_1.standard_deviation(), + dist_body_radius_1.expectation() + + zeta * dist_body_radius_1.standard_deviation()); + + mother._body_radius_2.set(dist_body_radius_2.expectation() - + zeta * dist_body_radius_2.standard_deviation(), + dist_body_radius_2.expectation() + + zeta * dist_body_radius_2.standard_deviation()); + + mother._body_tilt.set(dist_body_tilt.expectation() - + zeta * dist_body_tilt.standard_deviation(), + dist_body_tilt.expectation() + + zeta * dist_body_tilt.standard_deviation()); + + _body_cells[k++] = mother; + } + } + } + + (*global.log_stream) << _nb_body_cells << " body cells." << endl; +} + +PoseCellHierarchy::~PoseCellHierarchy() { + delete[] _body_cells; +} + +int PoseCellHierarchy::nb_levels() { + return _nb_levels; +} + +void PoseCellHierarchy::get_containing_cell(Image *image, int level, + Pose *pose, PoseCell *result_cell) { + PoseCellSet cell_set; + + for(int l = 0; l < level + 1; l++) { + cell_set.erase_content(); + if(l == 0) { + add_root_cells(image, &cell_set); + } else { + add_subcells(l, result_cell, &cell_set); + } + + *result_cell = *(cell_set.get_containing_cell(pose)); + } +} + +void PoseCellHierarchy::add_root_cells(Image *image, PoseCellSet *cell_set) { + + const int nb_scales = int((log(_max_head_radius) - log(_min_head_radius)) / log(2) * + global.nb_scales_per_power_of_two); + + scalar_t alpha = log(_min_head_radius); + scalar_t beta = log(2) / scalar_t(global.nb_scales_per_power_of_two); + + for(int s = 0; s < nb_scales; s++) { + scalar_t cell_xy_size = exp(alpha + scalar_t(s) * beta) / global.root_cell_nb_xy_per_scale; + PoseCell cell; + cell._head_radius.min = exp(alpha + scalar_t(s) * beta); + cell._head_radius.max = exp(alpha + scalar_t(s+1) * beta); + cell._head_tilt.min = -M_PI; + cell._head_tilt.max = M_PI; + for(scalar_t y = 0; y < image->height(); y += cell_xy_size) + for(scalar_t x = 0; x < image->width(); x += cell_xy_size) { + cell._head_xc.min = x; + cell._head_xc.max = x + cell_xy_size; + cell._head_yc.min = y; + cell._head_yc.max = y + cell_xy_size; + cell._body_xc.min = cell._head_xc.min - pseudo_infty; + cell._body_xc.max = cell._head_xc.max + pseudo_infty; + cell._body_yc.min = cell._head_yc.min - pseudo_infty; + cell._body_yc.max = cell._head_yc.max + pseudo_infty; + cell_set->add_cell(&cell); + } + } +} + +void PoseCellHierarchy::add_subcells(int level, PoseCell *root, + PoseCellSet *cell_set) { + + switch(level) { + + case 1: + { + // Here we split the body-center coordinate cell part + PoseCell cell = *root; + scalar_t r = sqrt(cell._head_radius.min * cell._head_radius.max); + scalar_t x = (cell._head_xc.min + cell._head_xc.max) / 2.0; + scalar_t y = (cell._head_yc.min + cell._head_yc.max) / 2.0; + for(int k = 0; k < _nb_body_cells; k++) { + cell._body_xc.min = (_body_cells[k]._body_xc.min * r) + x; + cell._body_xc.max = (_body_cells[k]._body_xc.max * r) + x; + cell._body_yc.min = (_body_cells[k]._body_yc.min * r) + y; + cell._body_yc.max = (_body_cells[k]._body_yc.max * r) + y; + cell_set->add_cell(&cell); + } + } + break; + + default: + { + cerr << "Inconsistent level in PoseCellHierarchy::add_subcells" << endl; + abort(); + } + break; + } +} + + +int PoseCellHierarchy::nb_incompatible_poses(LabelledImagePool *pool) { + PoseCell target_cell; + PoseCellSet cell_set; + LabelledImage *image; + + int nb_errors = 0; + + for(int i = 0; i < pool->nb_images(); i++) { + image = pool->grab_image(i); + + for(int t = 0; t < image->nb_targets(); t++) { + cell_set.erase_content(); + + int error_level = -1; + + for(int l = 0; error_level < 0 && l < _nb_levels; l++) { + cell_set.erase_content(); + + if(l == 0) { + add_root_cells(image, &cell_set); + } else { + add_subcells(l, &target_cell, &cell_set); + } + + int nb_compliant = 0; + + for(int c = 0; c < cell_set.nb_cells(); c++) { + if(cell_set.get_cell(c)->contains(image->get_target_pose(t))) { + target_cell = *(cell_set.get_cell(c)); + nb_compliant++; + } + } + + if(nb_compliant != 1) { + error_level = l; + } + } + + if(error_level >= 0) { + nb_errors++; + } + } + + pool->release_image(i); + } + + return nb_errors; +} + +void PoseCellHierarchy::write(ostream *os) { + write_var(os, &_min_head_radius); + write_var(os, &_max_head_radius); + write_var(os, &_root_cell_nb_xy_per_scale); + write_var(os, &_nb_body_cells); + for(int k = 0; k < _nb_body_cells; k++) + write_var(os, &_body_cells[k]); +} + +void PoseCellHierarchy::read(istream *is) { + delete[] _body_cells; + read_var(is, &_min_head_radius); + read_var(is, &_max_head_radius); + read_var(is, &_root_cell_nb_xy_per_scale); + read_var(is, &_nb_body_cells); + delete[] _body_cells; + _body_cells = new RelativeBodyPoseCell[_nb_body_cells]; + for(int k = 0; k < _nb_body_cells; k++) { + read_var(is, &_body_cells[k]); + } +} diff --git a/pose_cell_hierarchy.h b/pose_cell_hierarchy.h new file mode 100644 index 0000000..51f4e6e --- /dev/null +++ b/pose_cell_hierarchy.h @@ -0,0 +1,66 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef POSE_CELL_HIERARCHY_H +#define POSE_CELL_HIERARCHY_H + +#include "pose_cell_set.h" +#include "labelled_image_pool.h" + +struct RelativeBodyPoseCell { + Interval _body_xc, _body_yc, _body_radius_1, _body_radius_2, _body_tilt; +}; + +class PoseCellHierarchy { + static const scalar_t pseudo_infty = 10000; + + static const scalar_t body_resolution = 0.5; + + static const int nb_radius_1 = 16; + static const int nb_radius_2 = 16; + static const int nb_tilts = 64; + + int _nb_levels; + scalar_t _min_head_radius; + scalar_t _max_head_radius; + int _root_cell_nb_xy_per_scale; + + int _nb_body_cells; + + RelativeBodyPoseCell *_body_cells; + +public: + PoseCellHierarchy(); + PoseCellHierarchy(LabelledImagePool *train_pool); + virtual ~PoseCellHierarchy(); + + virtual int nb_levels(); + virtual void get_containing_cell(Image *image, int level, + Pose *pose, PoseCell *result_cell); + + virtual void add_root_cells(Image *image, PoseCellSet *cell_set); + // level is the level to build, hence should be greater than 1 + virtual void add_subcells(int level, PoseCell *root, PoseCellSet *cell_set); + + virtual int nb_incompatible_poses(LabelledImagePool *pool); + + virtual void write(ostream *os); + virtual void read(istream *is); +}; + +#endif diff --git a/pose_cell_hierarchy_reader.cc b/pose_cell_hierarchy_reader.cc new file mode 100644 index 0000000..1d3b5b5 --- /dev/null +++ b/pose_cell_hierarchy_reader.cc @@ -0,0 +1,25 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "pose_cell_hierarchy_reader.h" + +PoseCellHierarchy *read_hierarchy(istream *is) { + PoseCellHierarchy *result = new PoseCellHierarchy(); + result->read(is); + return result; +} diff --git a/pose_cell_hierarchy_reader.h b/pose_cell_hierarchy_reader.h new file mode 100644 index 0000000..77ae99d --- /dev/null +++ b/pose_cell_hierarchy_reader.h @@ -0,0 +1,26 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef POSE_CELL_HIERARCHY_READER_H +#define POSE_CELL_HIERARCHY_READER_H + +#include "pose_cell_hierarchy.h" + +PoseCellHierarchy *read_hierarchy(istream *is); + +#endif diff --git a/pose_cell_scored_set.cc b/pose_cell_scored_set.cc new file mode 100644 index 0000000..253c3ee --- /dev/null +++ b/pose_cell_scored_set.cc @@ -0,0 +1,122 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "pose_cell_scored_set.h" +#include "global.h" +#include "fusion_sort.h" + +PoseCellScoredSet::PoseCellScoredSet() { + _scores = new scalar_t[_max_nb]; +} + +PoseCellScoredSet::~PoseCellScoredSet() { + delete[] _scores; +} + +void PoseCellScoredSet::add_cell_with_score(PoseCell *cell, scalar_t score) { + _scores[_nb_added] = score; + add_cell(cell); +} + +void PoseCellScoredSet::decimate_hit(int level) { + if(_nb_added == 0) return; + + Pose *poses = new Pose[_nb_added]; + int *indexes = new int[_nb_added]; + int *sorted_indexes = new int[_nb_added]; + + for(int c = 0; c < _nb_added; c++) { + _cells[c].get_centroid(poses + c); + indexes[c] = c; + } + + indexed_fusion_dec_sort(_nb_added, indexes, sorted_indexes, _scores); + + int nb_remaining = _nb_added, current = 0; + + while(current < nb_remaining) { + int e = current + 1; + for(int d = current + 1; d < nb_remaining; d++) + if(!poses[sorted_indexes[current]].hit(level, poses + sorted_indexes[d])) + sorted_indexes[e++] = sorted_indexes[d]; + nb_remaining = e; + current++; + } + + PoseCell *tmp_cells = new PoseCell[max_nb_cells]; + scalar_t *tmp_scores = new scalar_t[max_nb_cells]; + + for(int n = 0; n < nb_remaining; n++) { + tmp_cells[n] = _cells[sorted_indexes[n]]; + tmp_scores[n] = _scores[sorted_indexes[n]]; + } + + delete[] _cells; + delete[] _scores; + _cells = tmp_cells; + _scores = tmp_scores; + _nb_added = nb_remaining; + + delete[] poses; + delete[] indexes; + delete[] sorted_indexes; +} + +void PoseCellScoredSet::decimate_collide(int level) { + if(_nb_added == 0) return; + + Pose *poses = new Pose[_nb_added]; + int *indexes = new int[_nb_added]; + int *sorted_indexes = new int[_nb_added]; + + for(int c = 0; c < _nb_added; c++) { + _cells[c].get_centroid(poses + c); + indexes[c] = c; + } + + indexed_fusion_dec_sort(_nb_added, indexes, sorted_indexes, _scores); + + int nb_remaining = _nb_added, current = 0; + + while(current < nb_remaining) { + int e = current + 1; + for(int d = current + 1; d < nb_remaining; d++) + if(!poses[sorted_indexes[current]].collide(level, poses + sorted_indexes[d])) + sorted_indexes[e++] = sorted_indexes[d]; + nb_remaining = e; + current++; + } + + PoseCell *tmp_cells = new PoseCell[max_nb_cells]; + scalar_t *tmp_scores = new scalar_t[max_nb_cells]; + + for(int n = 0; n < nb_remaining; n++) { + tmp_cells[n] = _cells[sorted_indexes[n]]; + tmp_scores[n] = _scores[sorted_indexes[n]]; + } + + delete[] _cells; + delete[] _scores; + _cells = tmp_cells; + _scores = tmp_scores; + _nb_added = nb_remaining; + + delete[] poses; + delete[] indexes; + delete[] sorted_indexes; +} diff --git a/pose_cell_scored_set.h b/pose_cell_scored_set.h new file mode 100644 index 0000000..e3bc877 --- /dev/null +++ b/pose_cell_scored_set.h @@ -0,0 +1,46 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef POSE_CELL_SCORED_SET_H +#define POSE_CELL_SCORED_SET_H + +#include "pose_cell.h" +#include "labelled_image.h" + +#include "pose_cell_set.h" + +class PoseCellScoredSet : public PoseCellSet { + scalar_t *_scores; + +public: + + PoseCellScoredSet(); + ~PoseCellScoredSet(); + + inline scalar_t get_score(int k) { + ASSERT(k >= 0 && k < _nb_added); + return _scores[k]; + } + + void add_cell_with_score(PoseCell *cell, scalar_t score); + + void decimate_hit(int level); + void decimate_collide(int level); +}; + +#endif diff --git a/pose_cell_set.cc b/pose_cell_set.cc new file mode 100644 index 0000000..62d04dd --- /dev/null +++ b/pose_cell_set.cc @@ -0,0 +1,51 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "pose_cell_set.h" +#include "global.h" + +PoseCellSet::PoseCellSet() { + _nb_added = 0; + _max_nb = max_nb_cells; + _cells = new PoseCell[_max_nb]; +} + +PoseCellSet::~PoseCellSet() { + delete[] _cells; +} + +void PoseCellSet::erase_content() { + _nb_added = 0; +} + +PoseCell *PoseCellSet::get_containing_cell(Pose *p) { + for(int c = 0; c < _nb_added; c++) { + if(_cells[c].contains(p)) return _cells + c; + } + return 0; +} + +void PoseCellSet::add_cell(PoseCell *cell) { + if(_nb_added < _max_nb) { + _cells[_nb_added] = *cell; + _nb_added++; + } else { + cerr << "Too many pose cells!" << endl; + abort(); + } +} diff --git a/pose_cell_set.h b/pose_cell_set.h new file mode 100644 index 0000000..b0a5fac --- /dev/null +++ b/pose_cell_set.h @@ -0,0 +1,54 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef POSE_CELL_SET_H +#define POSE_CELL_SET_H + +#include "pose_cell.h" +#include "labelled_image.h" + +class PoseCellSet { +protected: + // This is a bit ugly. Notice that all the code is written in such a + // way that we never have more than one such PoseCellSet in memory + // at any moment + static const int max_nb_cells = 500000; + + int _max_nb; + int _nb_added; + PoseCell *_cells; + +public: + + PoseCellSet(); + ~PoseCellSet(); + + inline int nb_cells() { return _nb_added; } + + inline PoseCell *get_cell(int k) { + ASSERT(k >= 0 && k < _nb_added); + return _cells + k; + } + + PoseCell *get_containing_cell(Pose *p); + + void erase_content(); + void add_cell(PoseCell *cell); +}; + +#endif diff --git a/progress_bar.cc b/progress_bar.cc new file mode 100644 index 0000000..b7800eb --- /dev/null +++ b/progress_bar.cc @@ -0,0 +1,93 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include +#include "progress_bar.h" + +const int ProgressBar::_width = 80; + +ProgressBar::ProgressBar() : _visible(false), _value_max(-1) { } + +void ProgressBar::set_visible(bool visible) { + _visible = visible; +} + +void ProgressBar::init(ostream *out, scalar_t value_max) { + _value_max = value_max; + _last_step = -1; + time(&_initial_time); + refresh(out, 0); +} + +void ProgressBar::refresh(ostream *out, scalar_t value) { + if(_visible && _value_max > 0) { + int step = int((value * 40) / _value_max); + + if(1 || step > _last_step) { + char buffer[_width + 1], date_buffer[buffer_size]; + int i, j; + j = sprintf(buffer, "Timer: "); + + for(i = 0; i < step; i++) buffer[j + i] = 'X'; + for(; i < 40; i++) buffer[j + i] = (i%4 == 0) ? '+' : '-'; + j += i; + + time_t current_time; time(¤t_time); + int rt = int(((current_time - _initial_time)/scalar_t(value)) * scalar_t(_value_max - value)); + + if(rt > 0) { + if(rt > 3600 * 24) { + time_t current; + time(¤t); + current += rt; + strftime(date_buffer, buffer_size, "%a %b %e %H:%M", localtime(¤t)); + j += snprintf(buffer + j, _width - j - 1, " (end ~ %s)", date_buffer); + } else { + int hours = rt/3600, min = (rt%3600)/60, sec = rt%60; + if(hours > 0) + j += snprintf(buffer + j, _width - j - 1, " (~%dh%dmin left)", hours, min); + else if(min > 0) + j += snprintf(buffer + j, _width - j - 1, " (~%dmin%ds left)", min, sec); + else + j += snprintf(buffer + j, _width - j - 1, " (~%ds left)", sec); + } + } + + for(; j < _width; j++) buffer[j] = ' '; + buffer[j] = '\0'; + (*out) << buffer << "\r"; + out->flush(); + _last_step = step; + } + } +} + +void ProgressBar::finish(ostream *out) { + if(_visible) { + char buffer[_width + 1]; + int j; + time_t current_time; time(¤t_time); + int rt = int(current_time - _initial_time); + int min = rt/60, sec = rt%60; + j = sprintf(buffer, "Timer: Total %dmin%ds", min, sec); + for(; j < _width; j++) buffer[j] = ' '; + buffer[j] = '\0'; + (*out) << buffer << endl; + out->flush(); + } +} diff --git a/progress_bar.h b/progress_bar.h new file mode 100644 index 0000000..50aa7c4 --- /dev/null +++ b/progress_bar.h @@ -0,0 +1,48 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +/* + + This class displays a progress bar in text mode and computes a rough + estimate of the remaining processing time. + +*/ + +#ifndef PROGRESS_BAR_H +#define PROGRESS_BAR_H + +#include + +using namespace std; + +#include "misc.h" + +class ProgressBar { + bool _visible; + scalar_t _value_max, _last_step; + time_t _initial_time; + const static int _width; +public: + ProgressBar(); + void set_visible(bool visible); + void init(ostream *out, scalar_t value_max); + void refresh(ostream *out, scalar_t value); + void finish(ostream *out); +}; + +#endif diff --git a/rectangle.h b/rectangle.h new file mode 100644 index 0000000..b8fd645 --- /dev/null +++ b/rectangle.h @@ -0,0 +1,28 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef RECTANGLE_H +#define RECTANGLE_H + +#include "misc.h" + +struct Rectangle { + scalar_t xmin, ymin, xmax, ymax; +}; + +#endif diff --git a/rgb_image.cc b/rgb_image.cc new file mode 100644 index 0000000..1fd38cb --- /dev/null +++ b/rgb_image.cc @@ -0,0 +1,566 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include +#include "jpeg_misc.h" + +#include "rgb_image.h" + +void RGBImage::allocate() { + _bit_plans = new unsigned char **[RGB_DEPTH]; + _bit_lines = new unsigned char *[RGB_DEPTH * _height]; + _bit_map = new unsigned char [_width * _height * RGB_DEPTH]; + for(int k = 0; k < RGB_DEPTH; k++) _bit_plans[k] = _bit_lines + k * _height; + for(int k = 0; k < RGB_DEPTH * _height; k++) _bit_lines[k] = _bit_map + k * _width; +} + +void RGBImage::deallocate() { + delete[] _bit_plans; + delete[] _bit_lines; + delete[] _bit_map; +} + +RGBImage::RGBImage() : _bit_plans(0), _bit_lines(0), _bit_map(0) { } + +RGBImage::RGBImage(int width, int height) : _width(width), _height(height) { + allocate(); + memset(_bit_map, 0, _width * _height * RGB_DEPTH * sizeof(unsigned char)); +} + +RGBImage::RGBImage(RGBImage *image, scalar_t scale) { + _width = int(scale * image->_width); + _height = int(scale * image->_height); + + allocate(); + + for(int y = 0; y < _height; y++) { + for(int x = 0; x < _width; x++) { + + const int delta = 10; + int sr = 0, sg = 0, sb = 0, t = 0; + int xo, yo; + + for(int yy = y * delta; yy < (y + 1) * delta; yy++) { + for(int xx = x * delta; xx < (x + 1) * delta; xx++) { + xo = (image->_width * xx)/(_width * delta); + yo = (image->_height * yy)/(_height * delta); + if(xo >= 0 && xo < image->_width && yo >= 0 && yo < image->_height) { + sr += image->_bit_plans[RED][yo][xo]; + sg += image->_bit_plans[GREEN][yo][xo]; + sb += image->_bit_plans[BLUE][yo][xo]; + t++; + } + } + } + + if(t > 0) { + _bit_plans[RED][y][x] = sr / t; + _bit_plans[GREEN][y][x] = sg / t; + _bit_plans[BLUE][y][x] = sb / t; + } else { + _bit_plans[RED][y][x] = 0; + _bit_plans[GREEN][y][x] = 0; + _bit_plans[BLUE][y][x] = 0; + } + + } + } +} + +RGBImage::~RGBImage() { + deallocate(); +} + +void RGBImage::write_ppm(const char *filename) { + FILE *outfile; + + if ((outfile = fopen (filename, "wb")) == 0) { + fprintf (stderr, "Can't open %s for reading\n", filename); + exit(1); + } + + fprintf(outfile, "P6\n%d %d\n255\n", _width, _height); + + char *raw = new char[_width * _height * 3]; + + int k = 0; + for(int y = 0; y < _height; y++) for(int x = 0; x < _width; x++) { + raw[k++] = _bit_map[x + _width * (y + _height * RED)]; + raw[k++] = _bit_map[x + _width * (y + _height * GREEN)]; + raw[k++] = _bit_map[x + _width * (y + _height * BLUE)]; + } + + fwrite((void *) raw, sizeof(unsigned char), _width * _height * 3, outfile); + fclose(outfile); + + delete[] raw; +} + +void RGBImage::read_ppm(const char *filename) { + const int buffer_size = 1024; + FILE *infile; + char buffer[buffer_size]; + int max; + + deallocate(); + + if((infile = fopen (filename, "r")) == 0) { + fprintf (stderr, "Can't open %s for reading\n", filename); + exit(1); + } + + fgets(buffer, buffer_size, infile); + + if(strncmp(buffer, "P6", 2) == 0) { + + do { + fgets(buffer, buffer_size, infile); + } while((buffer[0] < '0') || (buffer[0] > '9')); + sscanf(buffer, "%d %d", &_width, &_height); + fgets(buffer, buffer_size, infile); + sscanf(buffer, "%d", &max); + + allocate(); + + unsigned char *raw = new unsigned char[_width * _height * RGB_DEPTH]; + fread(raw, sizeof(unsigned char), _width * _height * RGB_DEPTH, infile); + + int k = 0; + for(int y = 0; y < _height; y++) for(int x = 0; x < _width; x++) { + _bit_plans[RED][y][x] = raw[k++]; + _bit_plans[GREEN][y][x] = raw[k++]; + _bit_plans[BLUE][y][x] = raw[k++]; + } + + delete[] raw; + + } else if(strncmp(buffer, "P5", 2) == 0) { + + do { + fgets(buffer, buffer_size, infile); + } while((buffer[0] < '0') || (buffer[0] > '9')); + sscanf(buffer, "%d %d", &_width, &_height); + fgets(buffer, buffer_size, infile); + sscanf(buffer, "%d", &max); + + allocate(); + + unsigned char *pixbuf = new unsigned char[_width * _height]; + fread(buffer, sizeof(unsigned char), _width * _height, infile); + + int k = 0, l = 0; + for(int y = 0; y < _height; y++) for(int x = 0; x < _width; x++) { + unsigned char c = pixbuf[k++]; + _bit_map[l++] = c; + _bit_map[l++] = c; + _bit_map[l++] = c; + } + + delete[] pixbuf; + + } else { + cerr << "Can not read ppm of type [" << buffer << "] from " << filename << ".\n"; + exit(1); + } +} + +void RGBImage::read_png(const char *name) { + // This is the number of bytes the read_png routine will read to + // decide if the file is a PNG or not. According to the png + // documentation, it can be 1 to 8 bytes, 8 being the max and the + // best. + + const int header_size = 8; + + png_byte header[header_size]; + png_bytep *row_pointers; + + deallocate(); + + // open file + FILE *fp = fopen(name, "rb"); + if (!fp) { + cerr << "Unable to open file " << name << " for reading.\n"; + exit(1); + } + + // read header + fread(header, 1, header_size, fp); + if (png_sig_cmp(header, 0, header_size)) { + cerr << "File " << name << " does not look like PNG.\n"; + fclose(fp); + exit(1); + } + + // create png pointer + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); + if (!png_ptr) { + cerr << "png_create_read_struct failed\n"; + fclose(fp); + exit(1); + } + + // create png info struct + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, (png_infopp) 0, (png_infopp) 0); + cerr << "png_create_info_struct failed\n"; + fclose(fp); + exit(1); + } + + // get image info + png_init_io(png_ptr, fp); + png_set_sig_bytes(png_ptr, header_size); + png_read_info(png_ptr, info_ptr); + + _width = info_ptr->width; + _height = info_ptr->height; + + png_byte bit_depth, color_type, channels; + color_type = info_ptr->color_type; + bit_depth = info_ptr->bit_depth; + channels = info_ptr->channels; + + if(bit_depth != 8) { + cerr << "Can only read 8-bits PNG images." << endl; + exit(1); + } + + // allocate image pointer + row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * _height); + for (int y = 0; y < _height; y++) + row_pointers[y] = (png_byte*) malloc(info_ptr->rowbytes); + + allocate(); + + // read image + png_read_image(png_ptr, row_pointers); + + // send image to red, green and blue buffers + switch (color_type) { + case PNG_COLOR_TYPE_GRAY: + { + unsigned char pixel = 0; + for (int y = 0; y < _height; y++) for (int x = 0; x < _width; x++) { + pixel = row_pointers[y][x]; + _bit_plans[RED][y][x] = pixel; + _bit_plans[GREEN][y][x] = pixel; + _bit_plans[BLUE][y][x] = pixel; + } + } + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA: + cerr << "PNG type GRAY_ALPHA not supported.\n"; + exit(1); + break; + + case PNG_COLOR_TYPE_PALETTE: + cerr << "PNG type PALETTE not supported.\n"; + exit(1); + break; + + case PNG_COLOR_TYPE_RGB: + { + if(channels != RGB_DEPTH) { + cerr << "Unsupported number of channels for RGB type\n"; + break; + } + int k; + for (int y = 0; y < _height; y++) { + k = 0; + for (int x = 0; x < _width; x++) { + _bit_plans[RED][y][x] = row_pointers[y][k++]; + _bit_plans[GREEN][y][x] = row_pointers[y][k++]; + _bit_plans[BLUE][y][x] = row_pointers[y][k++]; + } + } + } + break; + + case PNG_COLOR_TYPE_RGB_ALPHA: + cerr << "PNG type RGB_ALPHA not supported.\n"; + exit(1); + break; + + default: + cerr << "Unknown PNG type\n"; + exit(1); + } + + // release memory + png_destroy_read_struct(&png_ptr, &info_ptr, 0); + + for (int y = 0; y < _height; y++) free(row_pointers[y]); + free(row_pointers); + + fclose(fp); +} + +void RGBImage::write_png(const char *name) { + png_bytep *row_pointers; + + // create file + FILE *fp = fopen(name, "wb"); + + if (!fp) { + cerr << "Unable to create image '" << name << "'\n"; + exit(1); + } + + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); + + if (!png_ptr) { + cerr << "png_create_write_struct failed\n"; + fclose(fp); + exit(1); + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + cerr << "png_create_info_struct failed\n"; + fclose(fp); + exit(1); + } + + png_init_io(png_ptr, fp); + + png_set_IHDR(png_ptr, info_ptr, _width, _height, + 8, 2, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + png_write_info(png_ptr, info_ptr); + + // allocate memory + row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * _height); + for (int y = 0; y < _height; y++) + row_pointers[y] = (png_byte*) malloc(info_ptr->rowbytes); + + int k; + for (int y = 0; y < _height; y++) { + k = 0; + for (int x = 0; x < _width; x++) { + row_pointers[y][k++] = _bit_map[x + _width * (y + _height * RED)]; + row_pointers[y][k++] = _bit_map[x + _width * (y + _height * GREEN)]; + row_pointers[y][k++] = _bit_map[x + _width * (y + _height * BLUE)]; + } + } + + png_write_image(png_ptr, row_pointers); + png_write_end(png_ptr, 0); + + png_destroy_write_struct(&png_ptr, &info_ptr); + + // cleanup heap allocation + for (int y = 0; y < _height; y++) free(row_pointers[y]); + free(row_pointers); + + fclose(fp); +} + +void RGBImage::write_jpg(const char *filename, int quality) { + struct jpeg_compress_struct cinfo; + struct my_error_mgr jerr; + FILE *outfile; /* target file */ + JSAMPARRAY buffer; /* Output row buffer */ + + jpeg_create_compress (&cinfo); + + if ((outfile = fopen (filename, "wb")) == 0) { + fprintf (stderr, "Can't open %s\n", filename); + exit(1); + } + + cinfo.err = jpeg_std_error (&jerr.pub); + jerr.pub.error_exit = my_error_exit; + + if (setjmp (jerr.setjmp_buffer)) { + jpeg_destroy_compress (&cinfo); + fclose (outfile); + exit(1); + } + + jpeg_stdio_dest (&cinfo, outfile); + + cinfo.image_width = _width; + cinfo.image_height = _height; + cinfo.input_components = RGB_DEPTH; + + cinfo.in_color_space = JCS_RGB; + + jpeg_set_defaults (&cinfo); + jpeg_set_quality (&cinfo, quality, TRUE); + jpeg_start_compress (&cinfo, TRUE); + int y = 0; + buffer = + (*cinfo.mem->alloc_sarray) ((j_common_ptr) & cinfo, JPOOL_IMAGE, + _width * RGB_DEPTH, 1); + while (int(cinfo.next_scanline) < _height) { + for(int d = 0; d < RGB_DEPTH; d++) + for(int x = 0; x < _width; x++) + buffer[0][x * RGB_DEPTH + d] = + (JSAMPLE) ((_bit_map[x + _width * (y + _height * d)] * (MAXJSAMPLE + 1)) / 255); + jpeg_write_scanlines (&cinfo, buffer, 1); + y++; + } + + jpeg_finish_compress (&cinfo); + fclose (outfile); + + jpeg_destroy_compress (&cinfo); +} + +void RGBImage::read_jpg(const char *filename) { + struct jpeg_decompress_struct cinfo; + struct my_error_mgr jerr; + FILE *infile; + JSAMPARRAY buffer; + + deallocate(); + + if ((infile = fopen (filename, "rb")) == 0) { + fprintf (stderr, "can't open %s\n", filename); + return; + } + + cinfo.err = jpeg_std_error (&jerr.pub); + jerr.pub.error_exit = my_error_exit; + + if (setjmp (jerr.setjmp_buffer)) { + jpeg_destroy_decompress (&cinfo); + fclose (infile); + delete[] _bit_map; + _width = 0; + _height = 0; + _bit_map = 0; + return; + } + + jpeg_create_decompress (&cinfo); + jpeg_stdio_src (&cinfo, infile); + jpeg_read_header (&cinfo, TRUE); + jpeg_start_decompress (&cinfo); + + _width = cinfo.output_width; + _height = cinfo.output_height; + int depth = cinfo.output_components; + + allocate(); + + buffer = + (*cinfo.mem->alloc_sarray) ((j_common_ptr) & cinfo, JPOOL_IMAGE, + _width * depth, 1); + + int y = 0; + while (cinfo.output_scanline < cinfo.output_height) { + jpeg_read_scanlines (&cinfo, buffer, 1); + if(depth == 1) { + for(int d = 0; d < RGB_DEPTH; d++) + for(int x = 0; x < _width; x++) + _bit_plans[d][y][x] = + (unsigned char) ((buffer[0][x * depth] * 255) / (MAXJSAMPLE + 1)); + } else { + for(int d = 0; d < depth; d++) + for(int x = 0; x < _width; x++) + _bit_plans[d][y][x] = + (unsigned char) ((buffer[0][x * depth + d] * 255) / (MAXJSAMPLE + 1)); + } + y++; + } + + jpeg_finish_decompress (&cinfo); + jpeg_destroy_decompress (&cinfo); + + fclose (infile); +} + +void RGBImage::draw_line(int thickness, + unsigned char r, unsigned char g, unsigned char b, + scalar_t x0, scalar_t y0, scalar_t x1, scalar_t y1) { + int l = 0; + int dx, dy, h, v; + int ix0 = int(x0 + 0.5), iy0 = int(y0 + 0.5), ix1 = int(x1 + 0.5), iy1 = int(y1 + 0.5); + + if(ix0 < ix1) { dx = 1; h = ix1 - ix0; } else { dx = -1; h = ix0 - ix1; } + if(iy0 < iy1) { dy = 1; v = iy1 - iy0; } else { dy = -1; v = iy0 - iy1; } + + int x = ix0, y = iy0; + + if(h > v) { + for(int i = 0; i < h + 1; i++) { + for(int ex = - thickness / 2 - 1; ex < (thickness + 1) / 2 + 1; ex++) { + for(int ey = - thickness / 2 - 1; ey < (thickness + 1) / 2 + 1; ey++) { + if(ex * ex + ey * ey <= thickness * thickness / 4) { + int xx = x + ex, yy = y + ey; + if(xx >= 0 && xx < _width && yy >= 0 && yy < _height) + set_pixel(xx, yy, r, g, b); + } + } + } + + x += dx; l += v; + if(l > 0) { y += dy; l -= h; } + } + + } else { + + for(int i = 0; i < v + 1; i++) { + for(int ex = - thickness / 2 - 1; ex < (thickness + 1) / 2 + 1; ex++) { + for(int ey = - thickness / 2 - 1; ey < (thickness + 1) / 2 + 1; ey++) { + if(ex * ex + ey * ey <= thickness * thickness / 4) { + int xx = x + ex, yy = y + ey; + if(xx >= 0 && xx < _width && yy >= 0 && yy < _height) + set_pixel(xx, yy, r, g, b); + } + } + } + + y += dy; l -= h; + if(l < 0) { x += dx; l += v; } + } + + } + +} + +void RGBImage::draw_ellipse(int thickness, + unsigned char r, unsigned char g, unsigned char b, + scalar_t xc, scalar_t yc, scalar_t radius_1, scalar_t radius_2, scalar_t tilt) { + scalar_t ux1 = cos(tilt) * radius_1, uy1 = sin(tilt) * radius_1; + scalar_t ux2 = - sin(tilt) * radius_2, uy2 = cos(tilt) * radius_2; + + const int nb_points_to_draw = 80; + scalar_t x, y, px = 0, py = 0; + + for(int i = 0; i <= nb_points_to_draw; i++) { + scalar_t alpha = (M_PI * 2 * scalar_t(i)) / scalar_t(nb_points_to_draw); + + x = xc + cos(alpha) * ux1 + sin(alpha) * ux2; + y = yc + cos(alpha) * uy1 + sin(alpha) * uy2; + + if(i > 0) { + draw_line(thickness, r, g, b, px, py, x, y); + } + + px = x; py = y; + } +} diff --git a/rgb_image.h b/rgb_image.h new file mode 100644 index 0000000..9105534 --- /dev/null +++ b/rgb_image.h @@ -0,0 +1,78 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +// A simple color image class + +#ifndef RGB_IMAGE_H +#define RGB_IMAGE_H + +#include "misc.h" + +class RGBImage { +protected: + int _width, _height; + unsigned char ***_bit_plans, **_bit_lines, *_bit_map; + static const int RED = 0; + static const int GREEN = 1; + static const int BLUE = 2; + static const int RGB_DEPTH = 3; + + void allocate(); + void deallocate(); + +public: + + RGBImage(); + RGBImage(int width, int height); + RGBImage(RGBImage *image, scalar_t scale); + virtual ~RGBImage(); + + inline int width() const { return _width; } + inline int height() const { return _height; } + + inline void set_pixel(int x, int y, unsigned char r, unsigned char g, unsigned char b) { + ASSERT(x >= 0 && x < _width && y >= 0 && y < _height); + _bit_plans[RED][y][x] = r; + _bit_plans[GREEN][y][x] = g; + _bit_plans[BLUE][y][x] = b; + } + + inline unsigned char pixel(int x, int y, int d) { + ASSERT(x >= 0 && x < _width && y >= 0 && y < _height && d >= 0 && d < RGB_DEPTH); + return _bit_plans[d][y][x]; + } + + virtual void read_ppm(const char *filename); + virtual void write_ppm(const char *filename); + + virtual void read_png(const char *filename); + virtual void write_png(const char *filename); + + virtual void read_jpg(const char *filename); + virtual void write_jpg(const char *filename, int quality); + + virtual void draw_line(int thickness, + unsigned char r, unsigned char g, unsigned char b, + scalar_t x0, scalar_t y0, scalar_t x1, scalar_t y1); + + virtual void draw_ellipse(int thickness, + unsigned char r, unsigned char g, unsigned char b, + scalar_t xc, scalar_t yc, scalar_t radius_1, scalar_t radius_2, scalar_t tilt); +}; + +#endif diff --git a/rgb_image_subpixel.cc b/rgb_image_subpixel.cc new file mode 100644 index 0000000..3338f50 --- /dev/null +++ b/rgb_image_subpixel.cc @@ -0,0 +1,86 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "rgb_image_subpixel.h" + +RGBImageSubpixel::RGBImageSubpixel(int width, int height) : RGBImage(width * _scale, height* _scale) { } + +RGBImageSubpixel::RGBImageSubpixel(RGBImage *image) : RGBImage(image->width() * _scale, image->height() * _scale) { + for(int y = 0; y < _height; y++) { + for(int x = 0; x < _width; x++) { + set_pixel(x, y, + image->pixel(x / _scale, y / _scale, RGBImage::RED), + image->pixel(x / _scale, y / _scale, RGBImage::GREEN), + image->pixel(x / _scale, y / _scale, RGBImage::BLUE)); + } + } +} + +RGBImageSubpixel::~RGBImageSubpixel() { } + +void RGBImageSubpixel::read_ppm(const char *filename) { + abort(); +} + +void RGBImageSubpixel::write_ppm(const char *filename) { + abort(); +} + + +void RGBImageSubpixel::read_png(const char *filename) { + abort(); +} + +void RGBImageSubpixel::write_png(const char *filename) { + RGBImage tmp(_width / _scale, _height / _scale); + for(int y = 0; y < _height / _scale; y++) { + for(int x = 0; x < _width / _scale; x++) { + int sr = 0, sg = 0, sb = 0; + for(int yy = y * _scale; yy < (y + 1) * _scale; yy++) { + for(int xx = x * _scale; xx < (x + 1) * _scale; xx++) { + sr += int(_bit_plans[RED][yy][xx]); + sg += int(_bit_plans[GREEN][yy][xx]); + sb += int(_bit_plans[BLUE][yy][xx]); + } + } + + tmp.set_pixel(x, y, + sr / (_scale * _scale), sg / (_scale * _scale), sb / (_scale * _scale)); + } + } + tmp.write_png(filename); +} + + +void RGBImageSubpixel::read_jpg(const char *filename) { + abort(); +} + +void RGBImageSubpixel::write_jpg(const char *filename, int quality) { + abort(); +} + + +void RGBImageSubpixel::draw_line(int thickness, + unsigned char r, unsigned char g, unsigned char b, + scalar_t x0, scalar_t y0, scalar_t x1, scalar_t y1) { + RGBImage::draw_line(int(thickness * _scale), + r, g, b, + x0 * _scale + _scale/2, y0 * _scale + _scale/2, + x1 * _scale + _scale/2, y1 * _scale + _scale/2); +} diff --git a/rgb_image_subpixel.h b/rgb_image_subpixel.h new file mode 100644 index 0000000..393068b --- /dev/null +++ b/rgb_image_subpixel.h @@ -0,0 +1,52 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +// A simple color image class + +#ifndef RGB_IMAGE_SUBPIXEL_H +#define RGB_IMAGE_SUBPIXEL_H + +#include "misc.h" +#include "rgb_image.h" + +class RGBImageSubpixel : public RGBImage { + static const int _scale = 8; +public: + + RGBImageSubpixel(int width, int height); + RGBImageSubpixel(RGBImage *image); + virtual ~RGBImageSubpixel(); + + inline int width() const { return _width / _scale; } + inline int height() const { return _height / _scale; } + + virtual void read_ppm(const char *filename); + virtual void write_ppm(const char *filename); + + virtual void read_png(const char *filename); + virtual void write_png(const char *filename); + + virtual void read_jpg(const char *filename); + virtual void write_jpg(const char *filename, int quality); + + virtual void draw_line(int thickness, + unsigned char r, unsigned char g, unsigned char b, + scalar_t x0, scalar_t y0, scalar_t x1, scalar_t y1); +}; + +#endif diff --git a/rich_image.cc b/rich_image.cc new file mode 100644 index 0000000..f3b4b54 --- /dev/null +++ b/rich_image.cc @@ -0,0 +1,453 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include + +#include "rich_image.h" + +#define PIXEL_DELTA(a, b) ((a) >= (b) ? (a) - (b) : (b) - (a)) + +static inline bool edge( unsigned char v0, unsigned char v1, + unsigned char v2, unsigned char v3, unsigned char v4, unsigned char v5, + unsigned char v6, unsigned char v7) { + unsigned char g = PIXEL_DELTA(v3, v4); + + return + g > 8 && + g > PIXEL_DELTA(v0, v3) && + g > PIXEL_DELTA(v1, v4) && + g > PIXEL_DELTA(v2, v3) && + g > PIXEL_DELTA(v4, v5) && + g > PIXEL_DELTA(v3, v6) && + g > PIXEL_DELTA(v4, v7); +} + +void RichImage::free() { + delete[] _edge_map; + _edge_map = 0; + delete[] _line_edge_map; + _line_edge_map = 0; + delete[] _tag_edge_map; + _tag_edge_map = 0; + delete[] _scale_edge_map; + _scale_edge_map = 0; + delete[] _width_at_scale; + _width_at_scale = 0; + delete[] _height_at_scale; + _height_at_scale = 0; +} + +void RichImage::compute_one_scale_edge_maps(int width, int height, + unsigned int ***scale_edge_map, + unsigned char *pixel_map, + unsigned int *sum_pixel_map, + unsigned int *sum_sq_pixel_map) { + + unsigned char d00, d01, d02, d03, d04, d05, d06, d07; + unsigned char d08, d09, d10, d11, d12, d13, d14, d15; + unsigned char *local_pixel_map; + unsigned int *local_sum_pixel_map, *local_sum_sq_pixel_map; + + // Compute the integral images + + { + unsigned int *sp = sum_pixel_map, *ssp = sum_sq_pixel_map; + unsigned char *p = pixel_map; + for(int x = 0; x < width; x++) { + *sp++ = 0; + *ssp++ = 0; + p++; + } + + for(int y = 1; y < height; y++) { + *sp++ = 0; + *ssp++ = 0; + p++; + for(int x = 1; x < width; x++) { + *sp = *(sp - width) + *(sp - 1) - *(sp - width - 1) + ((unsigned int) (*p)); + *ssp = *(ssp - width) + *(ssp - 1) - *(ssp - width - 1) + sq((unsigned int) (*p)); + sp++; + ssp++; + p++; + } + } + } + + const unsigned int var_square_size = 16; + + int k00 = - 2 + width * (- 2); + int k01 = - 1 + width * (- 2); + int k02 = + 0 + width * (- 2); + int k03 = + 1 + width * (- 2); + int k04 = - 2 + width * (- 1); + int k05 = - 1 + width * (- 1); + int k06 = + 0 + width * (- 1); + int k07 = + 1 + width * (- 1); + int k08 = - 2 + width * (+ 0); + int k09 = - 1 + width * (+ 0); + int k10 = + 0 + width * (+ 0); + int k11 = + 1 + width * (+ 0); + int k12 = - 2 + width * (+ 1); + int k13 = - 1 + width * (+ 1); + int k14 = + 0 + width * (+ 1); + int k15 = + 1 + width * (+ 1); + + unsigned int *edge_map0 = scale_edge_map[0][0]; + unsigned int *edge_map1 = scale_edge_map[1][0]; + unsigned int *edge_map2 = scale_edge_map[2][0]; + unsigned int *edge_map3 = scale_edge_map[3][0]; + unsigned int *edge_map4 = scale_edge_map[4][0]; + unsigned int *edge_map5 = scale_edge_map[5][0]; + unsigned int *edge_map6 = scale_edge_map[6][0]; + unsigned int *edge_map7 = scale_edge_map[7][0]; + unsigned int *variance_map = scale_edge_map[8][0]; + + int a = -1 - width, b = - width, c = -1, d = 0; + + local_pixel_map = pixel_map; + local_sum_pixel_map = sum_pixel_map; + local_sum_sq_pixel_map = sum_sq_pixel_map; + + const int gray_bin_width = 256 / nb_gray_tags; + + for(int y = 0; y < height; y++) { + + for(int x = 0; x < width; x++) { + + if(x == 0 || y == 0) { + for(int e = 0; e < _nb_tags; e++) + scale_edge_map[e][0][d] = 0; + } else { + for(int e = 0; e < _nb_tags; e++) + scale_edge_map[e][0][d] = + scale_edge_map[e][0][b] + + scale_edge_map[e][0][c] - + scale_edge_map[e][0][a]; + } + + scale_edge_map[first_gray_tag + + (local_pixel_map[0] / gray_bin_width)][0][d]++; + + if(x - int(var_square_size/2) >= 0 && + x + int(var_square_size/2) < width && + y - int(var_square_size/2) >= 0 && + y + int(var_square_size/2) < height) { + + unsigned int s = + + local_sum_pixel_map[ - var_square_size/2 + width * ( - var_square_size / 2)] + + local_sum_pixel_map[ + var_square_size/2 + width * ( + var_square_size / 2)] + - local_sum_pixel_map[ - var_square_size/2 + width * ( + var_square_size / 2)] + - local_sum_pixel_map[ + var_square_size/2 + width * ( - var_square_size / 2)]; + + unsigned int s_sq = + + local_sum_sq_pixel_map[ - var_square_size/2 + width * ( - var_square_size / 2)] + + local_sum_sq_pixel_map[ + var_square_size/2 + width * ( + var_square_size / 2)] + - local_sum_sq_pixel_map[ - var_square_size/2 + width * ( + var_square_size / 2)] + - local_sum_sq_pixel_map[ + var_square_size/2 + width * ( - var_square_size / 2)]; + + if(sq(var_square_size) * s_sq - sq(s) >= + 100 * sq(var_square_size) * (sq(var_square_size) - 1)) { + + d00 = local_pixel_map[k00]; + d01 = local_pixel_map[k01]; + d02 = local_pixel_map[k02]; + d03 = local_pixel_map[k03]; + + d04 = local_pixel_map[k04]; + d05 = local_pixel_map[k05]; + d06 = local_pixel_map[k06]; + d07 = local_pixel_map[k07]; + + d08 = local_pixel_map[k08]; + d09 = local_pixel_map[k09]; + d10 = local_pixel_map[k10]; + d11 = local_pixel_map[k11]; + + d12 = local_pixel_map[k12]; + d13 = local_pixel_map[k13]; + d14 = local_pixel_map[k14]; + d15 = local_pixel_map[k15]; + + /* + + #0 #1 #2 #3 + + XXXXXX .XXXXX ...XXX .....X + XXXXXX ..XXXX ...XXX ....XX + ...... ...XXX ...XXX ...XXX + ...... ....XX ...XXX ..XXXX + + #4 #5 #6 #7 + + ...... X..... XXX... XXXXX. + ...... XX.... XXX... XXXX.. + XXXXXX XXX... XXX... XXX... + XXXXXX XXXX.. XXX... XX.... + + */ + + if(edge(d04, d08, d01, d05, d09, d13, d06, d10)) { + if(d05 < d09) edge_map0[d]++; + else edge_map4[d]++; + } + + if(edge(d02, d07, d00, d05, d10, d15, d08, d13)) { + if(d05 < d10) edge_map7[d]++; + else edge_map3[d]++; + } + + if(edge(d01, d02, d04, d05, d06, d07, d09, d10)) { + if(d05 < d06) edge_map6[d]++; + else edge_map2[d]++; + } + + if(edge(d01, d04, d03, d06, d09, d12, d11, d14)) { + if(d06 < d09) edge_map1[d]++; + else edge_map5[d]++; + } + + variance_map[d]++; + } + } + + a++; b++; c++; d++; + local_pixel_map++; + local_sum_pixel_map++; + local_sum_sq_pixel_map++; + } + } +} + +void RichImage::compute_rich_structure() { + free(); + + unsigned char *pixel_maps; + unsigned char **line_pixel_maps; + unsigned char ***scale_pixel_maps; + + _nb_scales = + int(global.nb_scales_per_power_of_two + * log(scalar_t(min(_width, _height))/scalar_t(_image_size_min)) / log(2)) + 1; + + _width_at_scale = new int[_nb_scales]; + _height_at_scale = new int[_nb_scales]; + int total_surface = 0, total_lines = 0; + + // Compute the sizes of the image at various scales + + scalar_t rho = exp(log(0.5) / scalar_t(global.nb_scales_per_power_of_two)); + scalar_t factor = 1.0; + + for(int s = 0; s < _nb_scales; s++) { + _width_at_scale[s] = int(scalar_t(_width) * factor); + _height_at_scale[s] = int(scalar_t(_height) * factor); + total_surface += _width_at_scale[s] * _height_at_scale[s]; + total_lines += _height_at_scale[s]; + factor *= rho; + } + + // Allocate the memory for the various scales and set up the pointer + // arrays to speed-up accesses + + pixel_maps = new unsigned char[total_surface]; + memset(pixel_maps, 0, sizeof(unsigned char) * total_surface); + line_pixel_maps = new unsigned char *[total_lines]; + scale_pixel_maps = new unsigned char **[_nb_scales]; + + int sum_surfaces, sum_heights; + + sum_surfaces = 0; + sum_heights = 0; + + for(int s = 0; s < _nb_scales; s++) { + scale_pixel_maps[s] = line_pixel_maps + sum_heights; + for(int y = 0; y < _height_at_scale[s]; y++) { + scale_pixel_maps[s][y] = pixel_maps + sum_surfaces; + sum_surfaces += _width_at_scale[s]; + } + sum_heights += _height_at_scale[s]; + } + + _edge_map = new unsigned int[total_surface * _nb_tags]; + memset(_edge_map, 0, sizeof(unsigned int) * total_surface * _nb_tags); + _line_edge_map = new unsigned int *[total_lines * _nb_tags]; + _tag_edge_map = new unsigned int **[_nb_tags * _nb_scales]; + _scale_edge_map = new unsigned int ***[_nb_scales]; + + sum_surfaces = 0; + sum_heights = 0; + + for(int s = 0; s < _nb_scales; s++) { + _scale_edge_map[s] = _tag_edge_map + s * _nb_tags; + for(int t = 0; t < _nb_tags; t++) { + _scale_edge_map[s][t] = _line_edge_map + sum_heights; + for(int y = 0; y < _height_at_scale[s]; y++) { + _scale_edge_map[s][t][y] = _edge_map + sum_surfaces; + sum_surfaces += _width_at_scale[s]; + } + sum_heights += _height_at_scale[s]; + } + } + + // Compute the scaled images per se + + for(int s = 0; s < _nb_scales; s++) { + + if(s < global.nb_scales_per_power_of_two) { + + if(s == 0) { + + // The first image is not rescaled + + for(int y = 0; y < _height_at_scale[s]; y++) + for(int x = 0; x < _width_at_scale[s]; x++) + scale_pixel_maps[s][y][x] = _content[x + _width * y]; + + } else { + + // The nb_scales_per_power_of_two - 1 first images are + // generated with a 'complex' scaling, and then we just + // downscale by a factor of two + + const int ld = 3; + + int delta_column[_width_at_scale[s] << ld]; + for(int xx = 0; xx < _width_at_scale[s] << ld; xx++) + delta_column[xx] = (xx * _width_at_scale[0]) / (_width_at_scale[s] << ld); + + unsigned char *line[_height_at_scale[s] << ld]; + for(int yy = 0; yy < _height_at_scale[s] << ld; yy++) + line[yy] = scale_pixel_maps[0][(yy * _height_at_scale[0]) / (_height_at_scale[s] << ld)]; + + for(int y = 0; y < _height_at_scale[s]; y++) { + unsigned char *dest_line = scale_pixel_maps[s][y]; + for(int x = 0; x < _width_at_scale[s]; x++) { + int u = 0; + for(int yy = (y << ld); yy < ((y+1) << ld); yy++) + for(int xx = (x << ld); xx < ((x+1) << ld); xx++) + u += line[yy][delta_column[xx]]; + dest_line[x] = (u >> (2 * ld)); + } + } + } + } else { + + // Other scales are computed by halfing one of the already + // computed scales + + int r = s - global.nb_scales_per_power_of_two; + for(int y = 0; y < _height_at_scale[s]; y++) + for(int x = 0; x < _width_at_scale[s]; x++) + scale_pixel_maps[s][y][x] = + (int(scale_pixel_maps[r][y*2 + 0][x*2 + 0]) + + int(scale_pixel_maps[r][y*2 + 0][x*2 + 1]) + + int(scale_pixel_maps[r][y*2 + 1][x*2 + 0]) + + int(scale_pixel_maps[r][y*2 + 1][x*2 + 1])) >> 2; + } + + } + + // Allocate the memory for the edge maps at various scales and set + // up the pointer arrays to speed-up accesses + + unsigned int *sum_pixel_map = new unsigned int[_width * _height]; + unsigned int *sum_sq_pixel_map = new unsigned int[_width * _height]; + + for(int s = 0; s < _nb_scales; s++) + compute_one_scale_edge_maps(_width_at_scale[s], + _height_at_scale[s], + _scale_edge_map[s], + scale_pixel_maps[s][0], + sum_pixel_map, + sum_sq_pixel_map); + + delete[] sum_pixel_map; + delete[] sum_sq_pixel_map; + + delete[] pixel_maps; + delete[] line_pixel_maps; + delete[] scale_pixel_maps; +} + +void RichImage::crop(int xmin, int ymin, int width, int height) { + free(); + Image::crop(xmin, ymin, width, height); +} + +RichImage::RichImage() : Image() { + _width_at_scale = 0; + _height_at_scale = 0; + _edge_map = 0; + _line_edge_map = 0; + _tag_edge_map = 0; + _scale_edge_map = 0; +} + +RichImage::RichImage(int width, int height) : Image(width, height) { + _width_at_scale = 0; + _height_at_scale = 0; + _edge_map = 0; + _line_edge_map = 0; + _tag_edge_map = 0; + _scale_edge_map = 0; +} + +RichImage::~RichImage() { + delete[] _edge_map; + delete[] _line_edge_map; + delete[] _tag_edge_map; + delete[] _scale_edge_map; + delete[] _width_at_scale; + delete[] _height_at_scale; +} + +void RichImage::write(ostream *out) { + Image::write(out); +} + +void RichImage::read(istream *in) { + Image::read(in); +} + +void RichImage::write_tag_png(const char *filename) { + const int nb_cols= 4; + const int nb_rows = ((nb_tags() + nb_cols - 1) / nb_cols); + + int height_total = 0; + for(int s = 0; s < _nb_scales; s++) height_total += _height_at_scale[s]; + + RGBImage image(_width * nb_cols, height_total * nb_rows); + + for(int y = 0; y < image.height(); y++) for(int x = 0; x < image.width(); x++) + image.set_pixel(x, y, 0, 255, 0); + + int sum_height = 0; + + for(int s = 0; s < _nb_scales; s++) { + for(int t = 0; t < nb_tags(); t++) { + int x0 = (t%nb_cols) * _width, y0 = sum_height + (t/nb_cols) * _height_at_scale[s]; + for(int y = 0; y < _height_at_scale[s]; y++) for(int x = 0; x < _width_at_scale[s]; x++) { + int c = 255 - min(255, max(0, int(255 * nb_tags_in_window(s, t, x, y, x+1, y+1)))); + image.set_pixel(x0 + x, y0 + y, c, c, c); + } + } + sum_height += nb_rows * _height_at_scale[s]; + } + + image.write_png(filename); +} diff --git a/rich_image.h b/rich_image.h new file mode 100644 index 0000000..c8f7a74 --- /dev/null +++ b/rich_image.h @@ -0,0 +1,105 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +/* + + This class implements the multi-scale basic edge features on the + images. The heavy machinery and ugly coding style is motivated by + performance. + +*/ + +#ifndef RICH_IMAGE_H +#define RICH_IMAGE_H + +#include "image.h" +#include "rgb_image.h" +#include "misc.h" +#include "global.h" + +class RichImage : public Image { + + static const int _image_size_min = 8; + + int _nb_scales; + + int *_width_at_scale, *_height_at_scale; + + unsigned int *_edge_map; + + unsigned int **_line_edge_map; + unsigned int ***_tag_edge_map; + unsigned int ****_scale_edge_map; + + void free(); + + void compute_one_scale_edge_maps(int width, int height, + unsigned int ***scale_edge_map, + unsigned char *pixel_map, + unsigned int *sum_pixel_map, + unsigned int *sum_sq_pixel_map); + +public: + + // We have 8 edge orientations + static const int nb_edge_tags = 8; + static const int first_edge_tag = 0; + + // The variance tag is 1 if the variance of the gray levels in the + // 16x16 square is greater than 10 + static const int variance_tag = 8; + + // We quantify the grayscales into 8 bins + static const int nb_gray_tags = 8; + static const int first_gray_tag = 9; + + static const int _nb_tags = nb_edge_tags + 1 + nb_gray_tags; + + inline int nb_tags() { return _nb_tags; } + inline int nb_scales() { return _nb_scales; } + + inline int nb_tags_in_window(int scale, int tag, int xmin, int ymin, int xmax, int ymax) { + if(scale < 0 || scale >= _nb_scales) return 0; + if(xmin < 0) xmin = 0; + if(xmax >= _width_at_scale[scale]) xmax = _width_at_scale[scale] - 1; + if(ymin < 0) ymin = 0; + if(ymax >= _height_at_scale[scale]) ymax = _height_at_scale[scale] - 1; + if(xmin < xmax && ymin < ymax) { + unsigned int **tmp = _scale_edge_map[scale][tag]; + return tmp[ymax][xmax] + tmp[ymin][xmin] - tmp[ymax][xmin] - tmp[ymin][xmax]; + } else + return 0; + } + + RichImage(); + RichImage(int width, int height); + virtual ~RichImage(); + + virtual void compute_rich_structure(); + virtual void crop(int xmin, int ymin, int width, int height); + + virtual void write(ostream *out); + + // read does _NOT_ build the rich structure. One has to call + // compute_rich_structure() for that! + virtual void read(istream *in); + + virtual void write_tag_png(const char *filename); +}; + +#endif diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..3a90f56 --- /dev/null +++ b/run.sh @@ -0,0 +1,124 @@ +#!/bin/bash + +######################################################################### +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the version 3 of the GNU General Public License # +# as published by the Free Software Foundation. # +# # +# This program is distributed in the hope that it will be useful, but # +# WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # +# General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +# Written by Francois Fleuret, (C) IDIAP # +# Contact for comments & bug reports # +######################################################################### + +MAIN_URL="http://www.idiap.ch/folded-ctf" + +# Compiling + +make -j -k + +echo + +# Generating the pool file + +DATA_PATH=./rmk-data +POOL_NAME=${DATA_PATH}/rmk.pool + +if [[ -d ${DATA_PATH} ]]; then + + if [[ -f ${POOL_NAME} ]]; then + + echo "The pool file exists." + + else + + echo "Can not find the pool file, checking the data integrity." + + md5sum -c ${DATA_PATH}/list.md5 + + if [[ $? != 0 ]]; then + echo "The data set is corrupted. You can download it" >&2 + echo "from ${MAIN_URL}" >&2 + exit 1 + fi + + echo "Generating the pool file." + + ./list_to_pool ${DATA_PATH}/full.lst ${DATA_PATH} ${POOL_NAME}.wrk + + if [[ $? == 0 ]]; then + mv ${POOL_NAME}.wrk ${POOL_NAME} + else + \rm ${POOL_NAME}.wrk 2> /dev/null + echo "Pool generation failed." >&2 + exit 1 + fi + + fi + +else + + echo "Can not find the RateMyKitten images in ${DATA_PATH}. You can" >&2 + echo "download them from ${MAIN_URL}" >&2 + exit 1 + +fi + +# Running the computation per se + +RESULT_DIR=./results + +if [[ ! -d ${RESULT_DIR} ]]; then + mkdir ${RESULT_DIR} +fi + +for SEED in {0..9}; do + + for MODE in hb h+b; do + + EXPERIMENT_RESULT_DIR="${RESULT_DIR}/${MODE}-${SEED}" + + mkdir ${EXPERIMENT_RESULT_DIR} 2> /dev/null + + if [[ $? == 0 ]]; then + + OPTS="--random-seed=${SEED} --wanted-true-positive-rate=0.75" + + if [[ $MODE == "h+b" ]]; then + OPTS="${OPTS} --force-head-belly-independence=yes" + fi + + if [[ $1 == "light" ]]; then + OPTS="${OPTS} --nb-classifiers-per-level=1 --nb-weak-learners-per-classifier=10" + OPTS="${OPTS} --proportion-for-train=0.1 --proportion-for-validation=0.1 --proportion-for-test=0.1" + fi + + ./folding \ + --niceness=15 \ + --pool-name=${POOL_NAME} \ + --nb-levels=2 \ + --nb-classifiers-per-level=25 --nb-weak-learners-per-classifier=100 \ + --result-path=${EXPERIMENT_RESULT_DIR} \ + --detector-name=${EXPERIMENT_RESULT_DIR}/default.det \ + ${OPTS} \ + open-pool \ + train-detector \ + compute-thresholds \ + write-detector \ + sequence-test-detector | tee -a ${EXPERIMENT_RESULT_DIR}/stdout + + else + + echo "${EXPERIMENT_RESULT_DIR} exists, aborting experiment." + + fi + + done + +done diff --git a/sample_set.cc b/sample_set.cc new file mode 100644 index 0000000..bf323e6 --- /dev/null +++ b/sample_set.cc @@ -0,0 +1,65 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "sample_set.h" + +SampleSet::SampleSet(int nb_features, int nb_samples) { + _nb_features = nb_features; + _nb_samples = nb_samples; + _shared_feature_values = new SharedResponses(_nb_features, _nb_samples); + _shared_feature_values->grab(); + + _labels = new int[_nb_samples]; + _feature_values = new scalar_t *[_nb_samples]; + for(int s = 0; s < _nb_samples; s++) + _feature_values[s] = _shared_feature_values->_responses + _nb_features * s; + +} + +SampleSet::SampleSet(SampleSet *father, int nb, int *indexes) { + _nb_features = father->_nb_features; + _nb_samples = nb; + _shared_feature_values = father->_shared_feature_values; + _shared_feature_values->grab(); + + _labels = new int[_nb_samples]; + _feature_values = new scalar_t *[_nb_samples]; + for(int s = 0; s < _nb_samples; s++) { + _feature_values[s] = father->_feature_values[indexes[s]]; + _labels[s] = father->_labels[indexes[s]]; + } +} + +SampleSet::~SampleSet() { + _shared_feature_values->release(); + delete[] _feature_values; + delete[] _labels; +} + +void SampleSet::set_sample(int n, + PiFeatureFamily *pi_feature_family, + RichImage *image, + PoseCell *cell, int label) { + ASSERT(n >= 0 && n < _nb_samples); + _labels[n] = label; + PiReferential referential(cell); + for(int f = 0; f < _nb_features; f++) { + _feature_values[n][f] = pi_feature_family->get_feature(f)->response(image, &referential); + ASSERT(!isnan(_feature_values[n][f])); + } +} diff --git a/sample_set.h b/sample_set.h new file mode 100644 index 0000000..ed870e8 --- /dev/null +++ b/sample_set.h @@ -0,0 +1,62 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef SAMPLE_SET_H +#define SAMPLE_SET_H + +#include "pose_cell.h" +#include "pi_feature_family.h" +#include "shared_responses.h" + +class SampleSet { + int _nb_features; + int _nb_samples; + SharedResponses *_shared_feature_values; + scalar_t **_feature_values; + int *_labels; + +public: + + inline int nb_samples() { return _nb_samples; } + inline int nb_features() { return _nb_features; } + + inline int label(int n_sample) { + ASSERT(n_sample >= 0 && n_sample < _nb_samples); + return _labels[n_sample]; + } + + inline scalar_t feature_value(int n_sample, int n_feature) { + ASSERT(n_sample >= 0 && n_sample < _nb_samples && + n_feature >= 0 && n_feature < _nb_features); + ASSERT(!isnan(_feature_values[n_sample][n_feature])); + return _feature_values[n_sample][n_feature]; + } + + SampleSet(int nb_features, int nb_samples); + SampleSet(SampleSet *father, int nb, int *indexes); + + ~SampleSet(); + + void set_sample(int n, + PiFeatureFamily *pi_feature_family, + RichImage *image, + PoseCell *cell, + int label); +}; + +#endif diff --git a/shared.cc b/shared.cc new file mode 100644 index 0000000..6986ad6 --- /dev/null +++ b/shared.cc @@ -0,0 +1,34 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "shared.h" + +Shared::Shared() : _nb_refs(0) { } + +Shared::~Shared() { + ASSERT(_nb_refs == 0); +} + +void Shared::grab() { + _nb_refs++; +} + +void Shared::release() { + ASSERT(_nb_refs > 0); + if(--_nb_refs == 0) delete this; +} diff --git a/shared.h b/shared.h new file mode 100644 index 0000000..b17e6f0 --- /dev/null +++ b/shared.h @@ -0,0 +1,39 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +// A tiny class to implement shared objects and lazy deletion + +// When you create a reference to such an object, call grab(), and +// when you destroy that reference, call release() which will delete +// it if no reference remains. Never delete it yourself! + +#ifndef SHARED_H +#define SHARED_H + +#include "misc.h" + +class Shared { + int _nb_refs; +public: + Shared(); + virtual ~Shared(); + void grab(); + void release(); +}; + +#endif diff --git a/shared_responses.cc b/shared_responses.cc new file mode 100644 index 0000000..5d96ece --- /dev/null +++ b/shared_responses.cc @@ -0,0 +1,27 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "shared_responses.h" + +SharedResponses::SharedResponses(int nb_features, int nb_samples) { + _responses = new scalar_t[nb_samples * nb_features]; +} + +SharedResponses::~SharedResponses() { + delete[] _responses; +} diff --git a/shared_responses.h b/shared_responses.h new file mode 100644 index 0000000..28b9a62 --- /dev/null +++ b/shared_responses.h @@ -0,0 +1,31 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef SHARED_RESPONSES_H +#define SHARED_RESPONSES_H + +#include "shared.h" + +class SharedResponses : public Shared { +public: + scalar_t *_responses; + SharedResponses(int nb_features, int nb_samples); + ~SharedResponses(); +}; + +#endif diff --git a/storable.cc b/storable.cc new file mode 100644 index 0000000..4e57ae1 --- /dev/null +++ b/storable.cc @@ -0,0 +1,42 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "storable.h" + +#include +#include + +Storable::~Storable() { } + +void Storable::read(const char *name) { + ifstream in(name); + if(in.fail()) { + cerr << "Can not open " << name << " for reading." << endl; + exit(1); + } + read(&in); +} + +void Storable::write(const char *name) { + ofstream out(name); + if(out.fail()) { + cerr << "Can not open " << name << " for writing." << endl; + exit(1); + } + write(&out); +} diff --git a/storable.h b/storable.h new file mode 100644 index 0000000..d9fca51 --- /dev/null +++ b/storable.h @@ -0,0 +1,37 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef STORABLE_H +#define STORABLE_H + +#include + +using namespace std; + +class Storable { +public: + virtual ~Storable(); + + virtual void read(istream *is) = 0; + virtual void write(ostream *os) = 0; + + virtual void read(const char *name); + virtual void write(const char *name); +}; + +#endif diff --git a/tools.cc b/tools.cc new file mode 100644 index 0000000..1834cd8 --- /dev/null +++ b/tools.cc @@ -0,0 +1,108 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#include "misc.h" +#include "tools.h" +#include "fusion_sort.h" + +scalar_t robust_sampling(int nb, scalar_t *weights, int nb_to_sample, int *sampled) { + ASSERT(nb > 0); + if(nb == 1) { + for(int k = 0; k < nb_to_sample; k++) sampled[k] = 0; + return weights[0]; + } else { + scalar_t *pair_weights = new scalar_t[(nb+1)/2]; + for(int k = 0; k < nb/2; k++) + pair_weights[k] = weights[2 * k] + weights[2 * k + 1]; + if(nb%2) + pair_weights[(nb+1)/2 - 1] = weights[nb-1]; + scalar_t result = robust_sampling((nb+1)/2, pair_weights, nb_to_sample, sampled); + for(int k = 0; k < nb_to_sample; k++) { + int s = sampled[k]; + // There is a bit of a trick for the isolated sample in the odd + // case. Since the corresponding pair weight is the same as the + // one sample alone, the test is always true and the isolated + // sample will be taken for sure. + if(drand48() * pair_weights[s] <= weights[2 * s]) + sampled[k] = 2 * s; + else + sampled[k] = 2 * s + 1; + } + delete[] pair_weights; + return result; + } +} + +void print_roc_small_pos(ostream *out, + int nb_pos, scalar_t *pos_responses, + int nb_neg, scalar_t *neg_responses, + scalar_t fas_factor) { + + scalar_t *sorted_pos_responses = new scalar_t[nb_pos]; + + fusion_sort(nb_pos, pos_responses, sorted_pos_responses); + + int *bins = new int[nb_pos + 1]; + for(int k = 0; k <= nb_pos; k++) bins[k] = 0; + + for(int k = 0; k < nb_neg; k++) { + scalar_t r = neg_responses[k]; + + if(r < sorted_pos_responses[0]) + bins[0]++; + + else if(r >= sorted_pos_responses[nb_pos - 1]) + bins[nb_pos]++; + + else { + int a = 0; + int b = nb_pos - 1; + int c = 0; + + while(a < b - 1) { + c = (a + b) / 2; + if(r < sorted_pos_responses[c]) + b = c; + else + a = c; + } + + // Beware of identical positive responses + while(c < nb_pos && r >= sorted_pos_responses[c]) + c++; + + bins[c]++; + } + } + + int s = nb_neg; + for(int k = 0; k < nb_pos; k++) { + s -= bins[k]; + if(k == 0 || sorted_pos_responses[k-1] < sorted_pos_responses[k]) { + (*out) << (scalar_t(s) / scalar_t(nb_neg)) * fas_factor + << " " + << scalar_t(nb_pos - k)/scalar_t(nb_pos) + << " " + << sorted_pos_responses[k] + << endl; + } + } + + delete[] bins; + delete[] sorted_pos_responses; +} diff --git a/tools.h b/tools.h new file mode 100644 index 0000000..ec35551 --- /dev/null +++ b/tools.h @@ -0,0 +1,32 @@ + +/////////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the version 3 of the GNU General Public License // +// as published by the Free Software Foundation. // +// // +// This program is distributed in the hope that it will be useful, but // +// WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +// General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Written by Francois Fleuret, (C) IDIAP // +// Contact for comments & bug reports // +/////////////////////////////////////////////////////////////////////////// + +#ifndef TOOLS_H +#define TOOLS_H + +#include +#include "misc.h" + +scalar_t robust_sampling(int nb, scalar_t *weights, int nb_to_sample, int *sampled); + +void print_roc_small_pos(ostream *out, + int nb_pos, scalar_t *pos_responses, + int nb_neg, scalar_t *neg_responses, + scalar_t fas_factor); + +#endif -- 2.20.1