From: Francois Fleuret Date: Thu, 9 Oct 2008 08:00:45 +0000 (+0200) Subject: automatic commit X-Git-Url: https://fleuret.org/cgi-bin/gitweb/gitweb.cgi?p=folded-ctf.git;a=commitdiff_plain;h=d922ad61d35e9a6996730bec24b16f8bf7bc426c automatic commit --- 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