diff --git a/modules/adas/tools/CMakeLists.txt b/modules/adas/tools/CMakeLists.txt index bc3ebfde0..3cf5d7763 100644 --- a/modules/adas/tools/CMakeLists.txt +++ b/modules/adas/tools/CMakeLists.txt @@ -1,33 +1,2 @@ -set(name fcw_train) -set(the_target opencv_${name}) -set(the_module opencv_adas) - -ocv_check_dependencies(${OPENCV_MODULE_${the_module}_DEPS}) - -if(NOT OCV_DEPENDENCIES_FOUND) - return() -endif() - -project(${the_target}) - -ocv_include_directories("${OpenCV_SOURCE_DIR}/include/opencv") -ocv_include_modules(${OPENCV_MODULE_${the_module}_DEPS}) - -file(GLOB ${the_target}_SOURCES ${CMAKE_CURRENT_LIST_DIR}/*.cpp) - -add_executable(${the_target} ${${the_target}_SOURCES}) - -ocv_target_link_libraries(${the_target} ${OPENCV_MODULE_${the_module}_DEPS}) - -set_target_properties(${the_target} PROPERTIES - DEBUG_POSTFIX "${OPENCV_DEBUG_POSTFIX}" - ARCHIVE_OUTPUT_DIRECTORY ${LIBRARY_OUTPUT_PATH} - RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH} - INSTALL_NAME_DIR lib - OUTPUT_NAME ${the_target}) - -if(ENABLE_SOLUTION_FOLDERS) - set_target_properties(${the_target} PROPERTIES FOLDER "applications") -endif() - -install(TARGETS ${the_target} RUNTIME DESTINATION bin COMPONENT main) +add_subdirectory(fcw_train) +add_subdirectory(fcw_detect) diff --git a/modules/adas/tools/fcw_detect/CMakeLists.txt b/modules/adas/tools/fcw_detect/CMakeLists.txt new file mode 100644 index 000000000..9ddf61d92 --- /dev/null +++ b/modules/adas/tools/fcw_detect/CMakeLists.txt @@ -0,0 +1,34 @@ +set(name fcw_detect) +set(the_target opencv_${name}) + +set(OPENCV_${the_target}_DEPS opencv_xobjdetect) + +ocv_check_dependencies(${OPENCV_${the_target}_DEPS}) + +if(NOT OCV_DEPENDENCIES_FOUND) + return() +endif() + +project(${the_target}) + +ocv_include_directories("${OpenCV_SOURCE_DIR}/include/opencv") +ocv_include_modules(${OPENCV_${the_target}_DEPS}) + +file(GLOB ${the_target}_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) + +add_executable(${the_target} ${${the_target}_SOURCES}) + +target_link_libraries(${the_target} ${OPENCV_${the_target}_DEPS}) + +set_target_properties(${the_target} PROPERTIES + DEBUG_POSTFIX "${OPENCV_DEBUG_POSTFIX}" + ARCHIVE_OUTPUT_DIRECTORY ${LIBRARY_OUTPUT_PATH} + RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH} + INSTALL_NAME_DIR lib + OUTPUT_NAME ${the_target}) + +if(ENABLE_SOLUTION_FOLDERS) + set_target_properties(${the_target} PROPERTIES FOLDER "applications") +endif() + +install(TARGETS ${the_target} RUNTIME DESTINATION bin COMPONENT main) diff --git a/modules/adas/tools/fcw_detect/fcw_detect.cpp b/modules/adas/tools/fcw_detect/fcw_detect.cpp new file mode 100644 index 000000000..b670d6c13 --- /dev/null +++ b/modules/adas/tools/fcw_detect/fcw_detect.cpp @@ -0,0 +1,92 @@ +#include +using std::string; + +#include +using std::vector; + +#include +using std::cerr; +using std::endl; + +#include +using cv::Rect; +using cv::Size; +using cv::Mat; +using cv::Mat_; +using cv::Vec3b; + +#include +using cv::imread; +using cv::imwrite; + +#include +using cv::CommandLineParser; +using cv::FileStorage; + +#include +using cv::xobjdetect::ICFDetector; + +static Mat visualize(const Mat &image, const vector &objects) +{ + CV_Assert(image.type() == CV_8UC3); + Mat_ img = image.clone(); + for( size_t j = 0; j < objects.size(); ++j ) + { + Rect obj = objects[j]; + int x = obj.x; + int y = obj.y; + int width = obj.width; + int height = obj.height; + for( int i = y; i <= y + height; ++i ) { + img(i, x) = Vec3b(255, 0, 0); + img(i, x + width) = Vec3b(255, 0, 0); + } + for( int i = x; i <= x + width; ++i) { + img(y, i) = Vec3b(255, 0, 0); + img(y + height, i) = Vec3b(255, 0, 0); + } + } + return img; +} + +int main(int argc, char *argv[]) +{ + const string keys = + "{help | | print this message}" + "{model_filename | model.xml | filename for reading model}" + "{image_path | test.png | path to image for detection}" + "{out_image_path | out.png | path to image for output}" + "{threshold | 0.0 | threshold for cascade}" + ; + + CommandLineParser parser(argc, argv, keys); + parser.about("FCW detection"); + + if( parser.has("help") || argc == 1) + { + parser.printMessage(); + return 0; + } + + string model_filename = parser.get("model_filename"); + string image_path = parser.get("image_path"); + string out_image_path = parser.get("out_image_path"); + float threshold = parser.get("threshold"); + + + if( !parser.check() ) + { + parser.printErrors(); + return 1; + } + + ICFDetector detector; + FileStorage fs(model_filename, FileStorage::READ); + fs["icfdetector"] >> detector; + fs.release(); + vector objects; + Mat img = imread(image_path); + detector.detect(img, objects, 1.1, Size(40, 40), + Size(300, 300), threshold); + imwrite(out_image_path, visualize(img, objects)); +} diff --git a/modules/adas/tools/fcw_train.cpp b/modules/adas/tools/fcw_train.cpp deleted file mode 100644 index 77f922fef..000000000 --- a/modules/adas/tools/fcw_train.cpp +++ /dev/null @@ -1,195 +0,0 @@ -#include -#include - -#include -using std::string; - -#include -using std::vector; - -#include -using std::ifstream; -using std::getline; - -#include -using std::stringstream; - -#include -using std::cerr; -using std::endl; - -#include -using cv::Rect; - -#include - -using cv::xobjdetect::ICFDetectorParams; -using cv::xobjdetect::ICFDetector; -using cv::xobjdetect::WaldBoost; -using cv::xobjdetect::WaldBoostParams; -using cv::Mat; - -static bool read_pos_int(const char *str, int *n) -{ - int pos = 0; - if( sscanf(str, "%d%n", n, &pos) != 1 || str[pos] != '\0' || *n <= 0 ) - { - return false; - } - return true; -} - -static bool read_model_size(char *str, int *rows, int *cols) -{ - int pos = 0; - if( sscanf(str, "%dx%d%n", rows, cols, &pos) != 2 || str[pos] != '\0' || - *rows <= 0 || *cols <= 0) - { - return false; - } - return true; -} - -static bool read_overlap(const char *str, double *overlap) -{ - int pos = 0; - if( sscanf(str, "%lf%n", overlap, &pos) != 1 || str[pos] != '\0' || - *overlap < 0 || *overlap > 1) - { - return false; - } - return true; -} - -static bool read_labels(const string& path, - vector& filenames, vector< vector >& labels) -{ - string labels_path = path + "/gt.txt"; - string filename, line; - int x1, y1, x2, y2; - char delim; - ifstream ifs(labels_path.c_str()); - if( !ifs.good() ) - return false; - - while( getline(ifs, line) ) - { - stringstream stream(line); - stream >> filename; - filenames.push_back(path + "/" + filename); - vector filename_labels; - while( stream >> x1 >> y1 >> x2 >> y2 >> delim ) - { - filename_labels.push_back(Rect(x1, y1, x2, y2)); - } - labels.push_back(filename_labels); - filename_labels.clear(); - } - return true; -} - - -int main(int argc, char *argv[]) -{ - if( argc == 1 ) - { - printf("Usage: %s OPTIONS, where OPTIONS are:\n" - "\n" - "--path - path to dir with data and labels\n" - " (labels should have name gt.txt)\n" - "\n" - "--feature_count - number of features to generate\n" - "\n" - "--weak_count - number of weak classifiers in cascade\n" - "\n" - "--model_size - model size in pixels\n" - "\n" - "--overlap - number from [0, 1], means maximum\n" - " overlap with objects while sampling background\n" - "\n" - "--model_filename - filename for saving model\n", - argv[0]); - return 0; - } - - string path, model_path; - ICFDetectorParams params; - for( int i = 1; i < argc; ++i ) - { - if( !strcmp("--path", argv[i]) ) - { - i += 1; - path = argv[i]; - } - else if( !strcmp("--feature_count", argv[i]) ) - { - i += 1; - if( !read_pos_int(argv[i], ¶ms.feature_count) ) - { - fprintf(stderr, "Error reading feature count from `%s`\n", - argv[i]); - return 1; - } - } - else if( !strcmp("--weak_count", argv[i]) ) - { - i += 1; - if( !read_pos_int(argv[i], ¶ms.weak_count) ) - { - fprintf(stderr, "Error reading weak count from `%s`\n", - argv[i]); - return 1; - } - } - else if( !strcmp("--model_size", argv[i]) ) - { - i += 1; - if( !read_model_size(argv[i], ¶ms.model_n_rows, - ¶ms.model_n_cols) ) - { - fprintf(stderr, "Error reading model size from `%s`\n", - argv[i]); - return 1; - } - } - else if( !strcmp("--overlap", argv[i]) ) - { - i += 1; - if( !read_overlap(argv[i], ¶ms.overlap) ) - { - fprintf(stderr, "Error reading overlap from `%s`\n", - argv[i]); - return 1; - } - } - else if( !strcmp("--model_filename", argv[i]) ) - { - i += 1; - model_path = argv[i]; - } - else - { - fprintf(stderr, "Error: unknown argument `%s`\n", argv[i]); - return 1; - } - - } - - try - { - ICFDetector detector; - vector filenames; - vector< vector > labels; - read_labels(path, filenames, labels); - - detector.train(filenames, labels, params); - } - catch( const char *err ) - { - cerr << err << endl; - } - catch( ... ) - { - cerr << "Unknown error\n" << endl; - } -} diff --git a/modules/adas/tools/fcw_train/CMakeLists.txt b/modules/adas/tools/fcw_train/CMakeLists.txt new file mode 100644 index 000000000..51754a000 --- /dev/null +++ b/modules/adas/tools/fcw_train/CMakeLists.txt @@ -0,0 +1,34 @@ +set(name fcw_train) +set(the_target opencv_${name}) + +set(OPENCV_${the_target}_DEPS opencv_xobjdetect) + +ocv_check_dependencies(${OPENCV_${the_target}_DEPS}) + +if(NOT OCV_DEPENDENCIES_FOUND) + return() +endif() + +project(${the_target}) + +ocv_include_directories("${OpenCV_SOURCE_DIR}/include/opencv") +ocv_include_modules(${OPENCV_${the_target}_DEPS}) + +file(GLOB ${the_target}_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) + +add_executable(${the_target} ${${the_target}_SOURCES}) + +target_link_libraries(${the_target} ${OPENCV_${the_target}_DEPS}) + +set_target_properties(${the_target} PROPERTIES + DEBUG_POSTFIX "${OPENCV_DEBUG_POSTFIX}" + ARCHIVE_OUTPUT_DIRECTORY ${LIBRARY_OUTPUT_PATH} + RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH} + INSTALL_NAME_DIR lib + OUTPUT_NAME ${the_target}) + +if(ENABLE_SOLUTION_FOLDERS) + set_target_properties(${the_target} PROPERTIES FOLDER "applications") +endif() + +install(TARGETS ${the_target} RUNTIME DESTINATION bin COMPONENT main) diff --git a/modules/adas/tools/fcw_train/fcw_train.cpp b/modules/adas/tools/fcw_train/fcw_train.cpp new file mode 100644 index 000000000..6f28960f6 --- /dev/null +++ b/modules/adas/tools/fcw_train/fcw_train.cpp @@ -0,0 +1,100 @@ +#include +#include + +#include +using std::string; + +#include +using std::vector; + +#include +using std::ifstream; +using std::getline; + +#include +using std::stringstream; + +#include +using std::cerr; +using std::endl; + +#include +using cv::Rect; +using cv::Size; +#include +using cv::imread; +#include +using cv::CommandLineParser; +using cv::FileStorage; + +#include + +using cv::xobjdetect::ICFDetectorParams; +using cv::xobjdetect::ICFDetector; +using cv::xobjdetect::WaldBoost; +using cv::xobjdetect::WaldBoostParams; +using cv::Mat; + +static bool read_model_size(const char *str, int *rows, int *cols) +{ + int pos = 0; + if( sscanf(str, "%dx%d%n", rows, cols, &pos) != 2 || str[pos] != '\0' || + *rows <= 0 || *cols <= 0) + { + return false; + } + return true; +} + +int main(int argc, char *argv[]) +{ + const string keys = + "{help | | print this message}" + "{pos_path | pos | path to training object samples}" + "{bg_path | bg | path to background images}" + "{feature_count | 10000 | number of features to generate}" + "{weak_count | 100 | number of weak classifiers in cascade}" + "{model_size | 40x40 | model size in pixels}" + "{model_filename | model.xml | filename for saving model}" + ; + + CommandLineParser parser(argc, argv, keys); + parser.about("FCW trainer"); + + if( parser.has("help") || argc == 1) + { + parser.printMessage(); + return 0; + } + + string pos_path = parser.get("pos_path"); + string bg_path = parser.get("bg_path"); + string model_filename = parser.get("model_filename"); + + cerr << pos_path << endl; + cerr << bg_path << endl; + + ICFDetectorParams params; + params.feature_count = parser.get("feature_count"); + params.weak_count = parser.get("weak_count"); + + string model_size = parser.get("model_size"); + if( !read_model_size(model_size.c_str(), ¶ms.model_n_rows, + ¶ms.model_n_cols) ) + { + cerr << "Error reading model size from `" << model_size << "`" << endl; + return 1; + } + + if( !parser.check() ) + { + parser.printErrors(); + return 1; + } + + ICFDetector detector; + detector.train(pos_path, bg_path, params); + FileStorage fs(model_filename, FileStorage::WRITE); + fs << "icfdetector" << detector; + fs.release(); +} diff --git a/modules/xobjdetect/include/opencv2/xobjdetect.hpp b/modules/xobjdetect/include/opencv2/xobjdetect.hpp index d523c1b2d..110ac105a 100644 --- a/modules/xobjdetect/include/opencv2/xobjdetect.hpp +++ b/modules/xobjdetect/include/opencv2/xobjdetect.hpp @@ -58,7 +58,7 @@ namespace xobjdetect channels — output array for computed channels */ -void computeChannels(InputArray image, OutputArrayOfArrays channels); +void computeChannels(InputArray image, std::vector& channels); class CV_EXPORTS ACFFeatureEvaluator : public Algorithm { @@ -69,6 +69,8 @@ public: /* Set window position */ virtual void setPosition(Size position) = 0; + virtual void assertChannels() = 0; + /* Evaluate feature with given index for current channels and window position */ virtual int evaluate(size_t feature_ind) const = 0; @@ -103,7 +105,7 @@ struct CV_EXPORTS WaldBoostParams int weak_count; float alpha; - WaldBoostParams(): weak_count(100), alpha(0.01f) + WaldBoostParams(): weak_count(100), alpha(0.02f) {} }; @@ -122,8 +124,8 @@ public: Returns feature indices chosen for cascade. Feature enumeration starts from 0 */ - virtual std::vector train(const Mat& data, - const Mat& labels) = 0; + virtual std::vector train(const Mat& /*data*/, + const Mat& /*labels*/) {return std::vector();} /* Predict object class given object that can compute object features @@ -133,10 +135,21 @@ public: is from class +1 */ virtual float predict( - const Ptr& feature_evaluator) const = 0; + const Ptr& /*feature_evaluator*/) const + {return 0.0f;} + + /* Write WaldBoost to FileStorage */ + virtual void write(FileStorage& /*fs*/) const {} + /* Read WaldBoost */ + virtual void read(const FileNode& /*node*/) {} }; +void write(FileStorage& fs, String&, const WaldBoost& waldboost); + +void read(const FileNode& node, WaldBoost& w, + const WaldBoost& default_value = WaldBoost()); + CV_EXPORTS Ptr createWaldBoost(const WaldBoostParams& params = WaldBoostParams()); @@ -146,32 +159,63 @@ struct CV_EXPORTS ICFDetectorParams int weak_count; int model_n_rows; int model_n_cols; - double overlap; ICFDetectorParams(): feature_count(UINT_MAX), weak_count(100), - model_n_rows(40), model_n_cols(40), overlap(0.0) + model_n_rows(40), model_n_cols(40) {} }; class CV_EXPORTS ICFDetector { public: + + ICFDetector(): waldboost_(), features_() {} + /* Train detector - image_filenames — filenames of images for training + pos_path — path to folder with images of objects - labelling — vector of object bounding boxes per every image + bg_path — path to folder with background images params — parameters for detector training */ - void train(const std::vector& image_filenames, - const std::vector >& labelling, + void train(const String& pos_path, + const String& bg_path, ICFDetectorParams params = ICFDetectorParams()); - /* Save detector in file, return true on success, false otherwise */ - bool save(const std::string& filename); + /* Detect object on image + + image — image for detection + + object — output array of bounding boxes + + scaleFactor — scale between layers in detection pyramid + + minSize — min size of objects in pixels + + maxSize — max size of objects in pixels + */ + void detect(const Mat& image, std::vector& objects, + double scaleFactor, Size minSize, Size maxSize, float threshold); + + /* Write detector to FileStorage */ + void write(FileStorage &fs) const; + + /* Read detector */ + void read(const FileNode &node); + +private: + Ptr waldboost_; + std::vector features_; + int model_n_rows_; + int model_n_cols_; }; +CV_EXPORTS void write(FileStorage& fs, String&, const ICFDetector& detector); + +CV_EXPORTS void read(const FileNode& node, ICFDetector& d, + const ICFDetector& default_value = ICFDetector()); + } /* namespace xobjdetect */ } /* namespace cv */ diff --git a/modules/xobjdetect/include/opencv2/xobjdetect/private.hpp b/modules/xobjdetect/include/opencv2/xobjdetect/private.hpp index 4201263cc..3229e10f1 100644 --- a/modules/xobjdetect/include/opencv2/xobjdetect/private.hpp +++ b/modules/xobjdetect/include/opencv2/xobjdetect/private.hpp @@ -47,6 +47,26 @@ public: */ float predict(int value) const; + /* Write stump in FileStorage */ + void write(FileStorage& fs) const + { + fs << "{" + << "threshold" << threshold_ + << "polarity" << polarity_ + << "pos_value" << pos_value_ + << "neg_value" << neg_value_ + << "}"; + } + + /* Read stump */ + void read(const FileNode& node) + { + threshold_ = (int)node["threshold"]; + polarity_ = (int)node["polarity"]; + pos_value_ = (float)node["pos_value"]; + neg_value_ = (float)node["neg_value"]; + } + private: /* Stump decision threshold */ int threshold_; @@ -56,6 +76,10 @@ private: float pos_value_, neg_value_; }; +void read(const FileNode& node, Stump& s, const Stump& default_value=Stump()); + +void write(FileStorage& fs, String&, const Stump& s); + } /* namespace xobjdetect */ } /* namespace cv */ diff --git a/modules/xobjdetect/src/acffeature.cpp b/modules/xobjdetect/src/acffeature.cpp index 1c642f661..addffae40 100644 --- a/modules/xobjdetect/src/acffeature.cpp +++ b/modules/xobjdetect/src/acffeature.cpp @@ -64,6 +64,7 @@ public: } virtual void setChannels(InputArrayOfArrays channels); + virtual void assertChannels(); virtual void setPosition(Size position); virtual int evaluate(size_t feature_ind) const; virtual void evaluateAll(OutputArray feature_values) const; @@ -77,16 +78,56 @@ private: Size position_; }; + +static bool isNull(const Mat_ &m) +{ + bool null_data = true; + for( int row = 0; row < m.rows; ++row ) + { + for( int col = 0; col < m.cols; ++col ) + if( m.at(row, col) ) + null_data = false; + } + return null_data; +} + +void ACFFeatureEvaluatorImpl::assertChannels() +{ + bool null_data = true; + for( size_t i = 0; i < channels_.size(); ++i ) + null_data &= isNull(channels_[i]); + CV_Assert(!null_data); +} + void ACFFeatureEvaluatorImpl::setChannels(cv::InputArrayOfArrays channels) { channels_.clear(); vector ch; channels.getMatVector(ch); CV_Assert(ch.size() == 10); + + /*int min_val = 100500, max_val = -1; + for( size_t i = 0; i < ch.size(); ++i ) + { + const Mat &channel = ch[i]; + for( int row = 0; row < channel.rows; ++row ) + for( int col = 0; col < channel.cols; ++col ) + { + int val = (int)channel.at(row, col); + if( val < min_val ) + min_val = val; + else if( val > max_val ) + max_val = val; + } + } + + cout << "SET " << min_val << " " << max_val << endl; + */ + for( size_t i = 0; i < ch.size(); ++i ) { const Mat &channel = ch[i]; - Mat_ acf_channel(channel.rows / 4, channel.cols / 4); + Mat_ acf_channel = Mat_::zeros(channel.rows / 4, channel.cols / 4); for( int row = 0; row < channel.rows; row += 4 ) { for( int col = 0; col < channel.cols; col += 4 ) @@ -94,18 +135,23 @@ void ACFFeatureEvaluatorImpl::setChannels(cv::InputArrayOfArrays channels) int sum = 0; for( int cell_row = row; cell_row < row + 4; ++cell_row ) for( int cell_col = col; cell_col < col + 4; ++cell_col ) + { + //cout << channel.rows << " " << channel.cols << endl; + //cout << cell_row << " " << cell_col << endl; sum += (int)channel.at(cell_row, cell_col); + } acf_channel(row / 4, col / 4) = sum; } } - channels_.push_back(acf_channel); + + channels_.push_back(acf_channel.clone()); } } void ACFFeatureEvaluatorImpl::setPosition(Size position) { - position_ = position; + position_ = Size(position.width / 4, position.height / 4); } int ACFFeatureEvaluatorImpl::evaluate(size_t feature_ind) const @@ -117,7 +163,7 @@ int ACFFeatureEvaluatorImpl::evaluate(size_t feature_ind) const int x = feature.x; int y = feature.y; int n = feature.z; - return channels_[n].at(y, x); + return channels_[n].at(y + position_.width, x + position_.height); } void ACFFeatureEvaluatorImpl::evaluateAll(OutputArray feature_values) const @@ -127,7 +173,7 @@ void ACFFeatureEvaluatorImpl::evaluateAll(OutputArray feature_values) const { feature_vals(0, i) = evaluate(i); } - feature_values.setTo(feature_vals); + feature_values.assign(feature_vals); } Ptr @@ -159,14 +205,15 @@ vector generateFeatures(Size window_size, int count) return features; } -void computeChannels(cv::InputArray image, cv::OutputArrayOfArrays channels_) +void computeChannels(InputArray image, vector& channels) { Mat src(image.getMat().rows, image.getMat().cols, CV_32FC3); image.getMat().convertTo(src, CV_32FC3, 1./255); Mat_ grad; - Mat gray; + Mat luv, gray; cvtColor(src, gray, CV_RGB2GRAY); + cvtColor(src, luv, CV_RGB2Luv); Mat_ row_der, col_der; Sobel(gray, row_der, CV_32F, 0, 1); @@ -174,7 +221,7 @@ void computeChannels(cv::InputArray image, cv::OutputArrayOfArrays channels_) magnitude(row_der, col_der, grad); - Mat_ hist(grad.rows, grad.cols); + Mat_ hist = Mat_::zeros(grad.rows, grad.cols); const float to_deg = 180 / 3.1415926f; for (int row = 0; row < grad.rows; ++row) { for (int col = 0; col < grad.cols; ++col) { @@ -182,12 +229,22 @@ void computeChannels(cv::InputArray image, cv::OutputArrayOfArrays channels_) if (angle < 0) angle += 180; int ind = (int)(angle / 30); - hist(row, col)[ind] = grad(row, col); + + // If angle == 180, prevent index overflow + if (ind == 6) + ind = 5; + + hist(row, col)[ind] = grad(row, col) * 255; } } - vector channels; - channels.push_back(gray); + channels.clear(); + + Mat luv_channels[3]; + split(luv, luv_channels); + for( int i = 0; i < 3; ++i ) + channels.push_back(luv_channels[i]); + channels.push_back(grad); vector hist_channels; @@ -195,8 +252,6 @@ void computeChannels(cv::InputArray image, cv::OutputArrayOfArrays channels_) for( size_t i = 0; i < hist_channels.size(); ++i ) channels.push_back(hist_channels[i]); - - channels_.setTo(channels); } } /* namespace xobjdetect */ diff --git a/modules/xobjdetect/src/icfdetector.cpp b/modules/xobjdetect/src/icfdetector.cpp index 9859ec2eb..0d2607af6 100644 --- a/modules/xobjdetect/src/icfdetector.cpp +++ b/modules/xobjdetect/src/icfdetector.cpp @@ -39,6 +39,17 @@ the use of this software, even if advised of the possibility of such damage. */ +#include +using std::ostringstream; + +#include +using std::setfill; +using std::setw; + +#include +using std::cout; +using std::endl; + #include "precomp.hpp" using std::vector; @@ -52,64 +63,54 @@ namespace cv namespace xobjdetect { -static bool overlap(const Rect& r, const vector& gt) -{ - for( size_t i = 0; i < gt.size(); ++i ) - if( (r & gt[i]).area() ) - return true; - return false; -} - -void ICFDetector::train(const vector& image_filenames, - const vector< vector >& labelling, +void ICFDetector::train(const String& pos_path, + const String& bg_path, ICFDetectorParams params) { + vector pos_filenames; + glob(pos_path + "/*.png", pos_filenames); + + vector bg_filenames; + glob(bg_path + "/*.png", bg_filenames); + + model_n_rows_ = params.model_n_rows; + model_n_cols_ = params.model_n_cols; + Size model_size(params.model_n_cols, params.model_n_rows); vector samples; /* positive samples + negative samples */ Mat sample, resized_sample; int pos_count = 0; - for( size_t i = 0; i < image_filenames.size(); ++i, ++pos_count ) - { - Mat img = imread(String(image_filenames[i].c_str())); - for( size_t j = 0; j < labelling[i].size(); ++j ) - { - Rect r = labelling[i][j]; - if( r.x > img.cols || r.y > img.rows ) - continue; - sample = img.colRange(max(r.x, 0), min(r.width, img.cols)) - .rowRange(max(r.y, 0), min(r.height, img.rows)); - - resize(sample, resized_sample, model_size); - - samples.push_back(resized_sample); - } + for( size_t i = 0; i < pos_filenames.size(); ++i, ++pos_count ) + { + cout << setw(6) << (i + 1) << "/" << pos_filenames.size() << "\r"; + Mat img = imread(pos_filenames[i]); + resize(img, resized_sample, model_size); + samples.push_back(resized_sample.clone()); } + cout << "\n"; int neg_count = 0; RNG rng; - for( size_t i = 0; i < image_filenames.size(); ++i ) + for( size_t i = 0; i < bg_filenames.size(); ++i ) { - Mat img = imread(String(image_filenames[i].c_str())); - for( int j = 0; j < (int)(pos_count / image_filenames.size() + 1); ) + cout << setw(6) << (i + 1) << "/" << bg_filenames.size() << "\r"; + Mat img = imread(bg_filenames[i]); + for( int j = 0; j < 50; + ++j, ++neg_count) { Rect r; - r.x = rng.uniform(0, img.cols); - r.width = rng.uniform(r.x + 1, img.cols); + r.x = rng.uniform(0, img.cols); r.width = rng.uniform(r.x + 1, img.cols); r.y = rng.uniform(0, img.rows); r.height = rng.uniform(r.y + 1, img.rows); - if( !overlap(r, labelling[i]) ) - { - sample = img.colRange(r.x, r.width).rowRange(r.y, r.height); - //resize(sample, resized_sample); - samples.push_back(resized_sample); - ++neg_count; - ++j; - } + sample = img.colRange(r.x, r.width).rowRange(r.y, r.height); + resize(sample, resized_sample, model_size); + samples.push_back(resized_sample.clone()); } } + cout << "\n"; Mat_ labels(1, pos_count + neg_count); for( int i = 0; i < pos_count; ++i) @@ -120,30 +121,131 @@ void ICFDetector::train(const vector& image_filenames, vector features = generateFeatures(model_size); Ptr feature_evaluator = createACFFeatureEvaluator(features); - Mat_ data((int)features.size(), (int)samples.size()); - Mat_ feature_col; + Mat_ data = Mat_::zeros((int)features.size(), (int)samples.size()); + Mat_ feature_col(1, (int)samples.size()); vector channels; for( int i = 0; i < (int)samples.size(); ++i ) { + cout << setw(6) << i << "/" << samples.size() << "\r"; computeChannels(samples[i], channels); feature_evaluator->setChannels(channels); + //feature_evaluator->assertChannels(); feature_evaluator->evaluateAll(feature_col); - for( int j = 0; j < feature_col.rows; ++j ) - data(i, j) = feature_col(0, j); + + CV_Assert(feature_col.cols == (int)features.size()); + + for( int j = 0; j < feature_col.cols; ++j ) + data(j, i) = feature_col(0, j); } + cout << "\n"; + samples.clear(); WaldBoostParams wparams; wparams.weak_count = params.weak_count; - wparams.alpha = 0.001f; + wparams.alpha = 0.02f; + + waldboost_ = createWaldBoost(wparams); + vector indices = waldboost_->train(data, labels); + cout << "indices: "; + for( size_t i = 0; i < indices.size(); ++i ) + cout << indices[i] << " "; + cout << endl; + + features_.clear(); + for( size_t i = 0; i < indices.size(); ++i ) + features_.push_back(features[indices[i]]); +} - Ptr waldboost = createWaldBoost(wparams); - waldboost->train(data, labels); +void ICFDetector::write(FileStorage& fs) const +{ + fs << "{"; + fs << "model_n_rows" << model_n_rows_; + fs << "model_n_cols" << model_n_cols_; + fs << "waldboost" << *waldboost_; + fs << "features" << "["; + for( size_t i = 0; i < features_.size(); ++i ) + { + fs << features_[i]; + } + fs << "]"; + fs << "}"; +} + +void ICFDetector::read(const FileNode& node) +{ + waldboost_ = Ptr(createWaldBoost(WaldBoostParams())); + node["model_n_rows"] >> model_n_rows_; + node["model_n_cols"] >> model_n_cols_; + node["waldboost"] >> *waldboost_; + FileNode features = node["features"]; + features_.clear(); + Point3i p; + for( FileNodeIterator n = features.begin(); n != features.end(); ++n ) + { + (*n) >> p; + features_.push_back(p); + } +} + +void ICFDetector::detect(const Mat& img, vector& objects, + double scaleFactor, Size minSize, Size maxSize, float threshold) +{ + float scale_from = min(model_n_cols_ / (float)maxSize.width, + model_n_rows_ / (float)maxSize.height); + float scale_to = max(model_n_cols_ / (float)minSize.width, + model_n_rows_ / (float)minSize.height); + objects.clear(); + Ptr evaluator = createACFFeatureEvaluator(features_); + Mat rescaled_image; + int step = 8; + vector channels; + for( float scale = scale_from; scale < scale_to + 0.001; scale *= scaleFactor ) + { + cout << "scale " << scale << endl; + int new_width = img.cols * scale; + new_width -= new_width % 4; + int new_height = img.rows * scale; + new_height -= new_height % 4; + + resize(img, rescaled_image, Size(new_width, new_height)); + computeChannels(rescaled_image, channels); + evaluator->setChannels(channels); + for( int row = 0; row <= rescaled_image.rows - model_n_rows_; row += step) + { + for( int col = 0; col <= rescaled_image.cols - model_n_cols_; + col += step ) + { + evaluator->setPosition(Size(row, col)); + float value = waldboost_->predict(evaluator); + if( value > threshold ) + { + int x = (int)(col / scale); + int y = (int)(row / scale); + int width = (int)(model_n_cols_ / scale); + int height = (int)(model_n_rows_ / scale); + cout << value << " " << x << " " << y << " " << width << " " + << height << endl; + objects.push_back(Rect(x, y, width, height)); + } + } + } + + } +} + +void write(FileStorage& fs, String&, const ICFDetector& detector) +{ + detector.write(fs); } -bool ICFDetector::save(const string&) +void read(const FileNode& node, ICFDetector& d, + const ICFDetector& default_value) { - return true; + if( node.empty() ) + d = default_value; + else + d.read(node); } } /* namespace xobjdetect */ diff --git a/modules/xobjdetect/src/precomp.hpp b/modules/xobjdetect/src/precomp.hpp index 38228cc69..811329940 100644 --- a/modules/xobjdetect/src/precomp.hpp +++ b/modules/xobjdetect/src/precomp.hpp @@ -45,6 +45,8 @@ the use of this software, even if advised of the possibility of such damage. #include #include +#include + #include #include diff --git a/modules/xobjdetect/src/stump.cpp b/modules/xobjdetect/src/stump.cpp index cba594d5f..d575d4c61 100644 --- a/modules/xobjdetect/src/stump.cpp +++ b/modules/xobjdetect/src/stump.cpp @@ -65,6 +65,7 @@ int Stump::train(const Mat& data, const Mat& labels, const Mat& weights) { CV_Assert(labels.rows == 1 && labels.cols == data.cols); CV_Assert(weights.rows == 1 && weights.cols == data.cols); + CV_Assert(data.cols > 1); /* Assert that data and labels have int type */ /* Assert that weights have float type */ @@ -199,5 +200,19 @@ float Stump::predict(int value) const return polarity_ * (value - threshold_) > 0 ? pos_value_ : neg_value_; } + +void read(const FileNode& node, Stump& s, const Stump& default_value) +{ + if( node.empty() ) + s = default_value; + else + s.read(node); +} + +void write(FileStorage& fs, String&, const Stump& s) +{ + s.write(fs); +} + } /* namespace xobjdetect */ } /* namespace cv */ diff --git a/modules/xobjdetect/src/waldboost.cpp b/modules/xobjdetect/src/waldboost.cpp index e7da5dd85..6b52c766c 100644 --- a/modules/xobjdetect/src/waldboost.cpp +++ b/modules/xobjdetect/src/waldboost.cpp @@ -45,6 +45,11 @@ using std::swap; using std::vector; +#include +using std::cout; +using std::endl; + + namespace cv { namespace xobjdetect @@ -64,6 +69,10 @@ public: virtual float predict( const Ptr& feature_evaluator) const; + virtual void write(FileStorage& fs) const; + + virtual void read(const FileNode& node); + private: /* Parameters for cascade training */ WaldBoostParams params_; @@ -73,19 +82,97 @@ private: std::vector thresholds_; }; -vector WaldBoostImpl::train(const Mat& data, const Mat& labels) +static int count(const Mat_ &m, int elem) +{ + int res = 0; + for( int row = 0; row < m.rows; ++row ) + for( int col = 0; col < m.cols; ++col ) + if( m(row, col) == elem) + res += 1; + return res; +} + +void write(FileStorage& fs, String&, const WaldBoost& waldboost) +{ + waldboost.write(fs); +} + +void read(const FileNode& node, WaldBoost& w, + const WaldBoost& default_value) +{ + if( node.empty() ) + w = default_value; + else + w.read(node); +} + +void WaldBoostImpl::read(const FileNode& node) { - CV_Assert(labels.rows == 1 && labels.cols == data.cols); + FileNode params = node["waldboost_params"]; + params_.weak_count = (int)(params["weak_count"]); + params_.alpha = (float)(params["alpha"]); - int pos_count = 0, neg_count = 0; - for( int col = 0; col < labels.cols; ++col ) + FileNode stumps = node["waldboost_stumps"]; + stumps_.clear(); + for( FileNodeIterator n = stumps.begin(); n != stumps.end(); ++n ) { - if( labels.at(0, col) == +1 ) - pos_count += 1; - else - neg_count += 1; + Stump s; + *n >> s; + stumps_.push_back(s); } + FileNode thresholds = node["waldboost_thresholds"]; + thresholds_.clear(); + for( FileNodeIterator n = thresholds.begin(); n != thresholds.end(); ++n ) + { + float t; + *n >> t; + thresholds_.push_back(t); + } +} + +void WaldBoostImpl::write(FileStorage& fs) const +{ + fs << "{"; + fs << "waldboost_params" << "{" + << "weak_count" << params_.weak_count + << "alpha" << params_.alpha + << "}"; + + fs << "waldboost_stumps" << "["; + for( size_t i = 0; i < stumps_.size(); ++i ) + fs << stumps_[i]; + fs << "]"; + + fs << "waldboost_thresholds" << "["; + for( size_t i = 0; i < thresholds_.size(); ++i ) + fs << thresholds_[i]; + fs << "]"; + fs << "}"; + +} + +vector WaldBoostImpl::train(const Mat& data_, const Mat& labels_) +{ + CV_Assert(labels_.rows == 1 && labels_.cols == data_.cols); + CV_Assert(data_.rows >= params_.weak_count); + + Mat labels, data; + data_.copyTo(data); + labels_.copyTo(labels); + + bool null_data = true; + for( int row = 0; row < data.rows; ++row ) + { + for( int col = 0; col < data.cols; ++col ) + if( data.at(row, col) ) + null_data = false; + } + CV_Assert(!null_data); + + int pos_count = count(labels, +1); + int neg_count = count(labels, -1); + Mat_ weights(labels.rows, labels.cols); float pos_weight = 1.0f / (2 * pos_count); float neg_weight = 1.0f / (2 * neg_count); @@ -97,6 +184,9 @@ vector WaldBoostImpl::train(const Mat& data, const Mat& labels) weights.at(0, col) = neg_weight; } + vector feature_indices_pool; + for( int ind = 0; ind < data.rows; ++ind ) + feature_indices_pool.push_back(ind); vector feature_indices; Mat_ trace = Mat_::zeros(labels.rows, labels.cols); @@ -104,10 +194,14 @@ vector WaldBoostImpl::train(const Mat& data, const Mat& labels) thresholds_.clear(); for( int i = 0; i < params_.weak_count; ++i) { + cout << "stage " << i << endl; Stump s; int feature_ind = s.train(data, labels, weights); + cout << "feature_ind " << feature_ind << endl; stumps_.push_back(s); - feature_indices.push_back(feature_ind); + int ind = feature_indices_pool[feature_ind]; + feature_indices_pool.erase(feature_indices_pool.begin() + feature_ind); + feature_indices.push_back(ind); // Recompute weights for( int col = 0; col < weights.cols; ++col ) @@ -118,6 +212,14 @@ vector WaldBoostImpl::train(const Mat& data, const Mat& labels) weights.at(0, col) *= exp(-label * h); } + // Erase row for feature in data + Mat fixed_data; + fixed_data.push_back(data.rowRange(0, feature_ind)); + fixed_data.push_back(data.rowRange(feature_ind + 1, data.rows)); + + data = fixed_data; + + // Normalize weights float z = (float)sum(weights)[0]; for( int col = 0; col < weights.cols; ++col) @@ -125,12 +227,12 @@ vector WaldBoostImpl::train(const Mat& data, const Mat& labels) weights.at(0, col) /= z; } - // Sort trace Mat indices; sortIdx(trace, indices, cv::SORT_EVERY_ROW | cv::SORT_ASCENDING); Mat new_weights = Mat_::zeros(weights.rows, weights.cols); Mat new_labels = Mat_::zeros(labels.rows, labels.cols); + Mat new_data = Mat_::zeros(data.rows, data.cols); Mat new_trace; for( int col = 0; col < new_weights.cols; ++col ) { @@ -138,26 +240,60 @@ vector WaldBoostImpl::train(const Mat& data, const Mat& labels) weights.at(0, indices.at(0, col)); new_labels.at(0, col) = labels.at(0, indices.at(0, col)); + for( int row = 0; row < new_data.rows; ++row ) + { + new_data.at(row, col) = + data.at(row, indices.at(0, col)); + } } sort(trace, new_trace, cv::SORT_EVERY_ROW | cv::SORT_ASCENDING); // Compute threshold for trace + /* int col = 0; for( int pos_i = 0; - pos_i < pos_count * params_.alpha && col < weights.cols; + pos_i < pos_count * params_.alpha && col < new_labels.cols; ++col ) { - if( labels.at(0, col) == +1 ) + if( new_labels.at(0, col) == +1 ) ++pos_i; } + */ + + int cur_pos = 0, cur_neg = 0; + int max_col = 0; + for( int col = 0; col < new_labels.cols - 1; ++col ) { + if (new_labels.at(0, col) == +1 ) + ++cur_pos; + else + ++cur_neg; + + float p_neg = cur_neg / (float)neg_count; + float p_pos = cur_pos / (float)pos_count; + if( params_.alpha * p_neg > p_pos ) + max_col = col; + } - thresholds_.push_back(new_trace.at(0, col)); + thresholds_.push_back(new_trace.at(0, max_col)); + cout << "threshold " << *(thresholds_.end() - 1) << endl; + + cout << "col " << max_col << " size " << data.cols << endl; // Drop samples below threshold - new_trace.colRange(col, new_trace.cols).copyTo(trace); - new_weights.colRange(col, new_weights.cols).copyTo(weights); - new_labels.colRange(col, new_labels.cols).copyTo(labels); + new_data.colRange(max_col, new_data.cols).copyTo(data); + new_trace.colRange(max_col, new_trace.cols).copyTo(trace); + new_weights.colRange(max_col, new_weights.cols).copyTo(weights); + new_labels.colRange(max_col, new_labels.cols).copyTo(labels); + + pos_count = count(labels, +1); + neg_count = count(labels, -1); + cout << "pos_count " << pos_count << "; neg_count " << neg_count << endl; + + if( data.cols < 2 || neg_count == 0) + { + break; + } } return feature_indices; } @@ -166,6 +302,7 @@ float WaldBoostImpl::predict( const Ptr& feature_evaluator) const { float trace = 0; + CV_Assert(stumps_.size() == thresholds_.size()); for( size_t i = 0; i < stumps_.size(); ++i ) { int value = feature_evaluator->evaluate(i);