From 4ff365a530844bd315dbddb550c39a81b060369c Mon Sep 17 00:00:00 2001 From: martin Date: Sat, 29 Feb 2020 14:31:28 +0000 Subject: [PATCH] imglab: chinese ("automatic") clustering, keyboard shortcuts for zooming (#2007) * imglab: add support for using chinese whispers for more automatic clustering * widgets: refactor out zooming from wheel handling * tools/imglab/src/metadata_editor.cpp imglab: add keyboard shortcuts for zooming --- dlib/gui_widgets/widgets.cpp | 54 ++++++++++++++++--------- dlib/gui_widgets/widgets.h | 6 +++ tools/imglab/src/cluster.cpp | 60 +++++++++++++++++++++++++++- tools/imglab/src/main.cpp | 4 +- tools/imglab/src/metadata_editor.cpp | 10 +++++ 5 files changed, 111 insertions(+), 23 deletions(-) diff --git a/dlib/gui_widgets/widgets.cpp b/dlib/gui_widgets/widgets.cpp index c9e175019..4b110cd53 100644 --- a/dlib/gui_widgets/widgets.cpp +++ b/dlib/gui_widgets/widgets.cpp @@ -7065,25 +7065,9 @@ namespace dlib // ---------------------------------------------------------------------------------------- void image_display:: - on_wheel_up ( - unsigned long state + zoom_in ( ) { - // disable mouse wheel if the user is drawing a rectangle - if (drawing_rect) - return; - - // if CONTROL is not being held down - if ((state & base_window::CONTROL) == 0) - { - scrollable_region::on_wheel_up(state); - return; - } - - if (rect.contains(lastx,lasty) == false || hidden || !enabled) - return; - - if (zoom_in_scale < 100 && zoom_out_scale == 1) { const point mouse_loc(lastx, lasty); @@ -7119,7 +7103,7 @@ namespace dlib // ---------------------------------------------------------------------------------------- void image_display:: - on_wheel_down ( + on_wheel_up ( unsigned long state ) { @@ -7130,14 +7114,22 @@ namespace dlib // if CONTROL is not being held down if ((state & base_window::CONTROL) == 0) { - scrollable_region::on_wheel_down(state); + scrollable_region::on_wheel_up(state); return; } if (rect.contains(lastx,lasty) == false || hidden || !enabled) return; + zoom_in(); + } + +// ---------------------------------------------------------------------------------------- + void image_display:: + zoom_out ( + ) + { if (zoom_in_scale != 1) { const point mouse_loc(lastx, lasty); @@ -7170,6 +7162,30 @@ namespace dlib } } +// ---------------------------------------------------------------------------------------- + + void image_display:: + on_wheel_down ( + unsigned long state + ) + { + // disable mouse wheel if the user is drawing a rectangle + if (drawing_rect) + return; + + // if CONTROL is not being held down + if ((state & base_window::CONTROL) == 0) + { + scrollable_region::on_wheel_down(state); + return; + } + + if (rect.contains(lastx,lasty) == false || hidden || !enabled) + return; + + zoom_out(); + } + // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // image_window member functions diff --git a/dlib/gui_widgets/widgets.h b/dlib/gui_widgets/widgets.h index c6e60a6e4..bd1df2b6b 100644 --- a/dlib/gui_widgets/widgets.h +++ b/dlib/gui_widgets/widgets.h @@ -3493,6 +3493,12 @@ namespace dlib bool overlay_editing_is_enabled ( ) const { auto_mutex M(m); return overlay_editing_enabled; } + void zoom_in ( + ); + + void zoom_out ( + ); + private: void draw ( diff --git a/tools/imglab/src/cluster.cpp b/tools/imglab/src/cluster.cpp index 23b289a7f..58484eb69 100644 --- a/tools/imglab/src/cluster.cpp +++ b/tools/imglab/src/cluster.cpp @@ -10,6 +10,7 @@ #include #include #include +#include // ---------------------------------------------------------------------------------------- @@ -72,6 +73,56 @@ std::vector angular_cluster ( } return assignments; } +std::vector chinese_cluster ( + std::vector > feats, + unsigned long &num_clusters + ) +{ + // try to find a good value to select if we should add a vertex in the graph + matrix m; + for (unsigned long i = 0; i < feats.size(); ++i) + m += feats[i]; + m /= feats.size(); + + for (unsigned long i = 0; i < feats.size(); ++i) + { + feats[i] -= m; + double len = length(feats[i]); + if (len != 0) + feats[i] /= len; + } + + running_stats rs; + for (size_t i = 0; i < feats.size(); ++i) { + for (size_t j = i; j < feats.size(); ++j) { + rs.add(length(feats[i] - feats[j])); + } + } + + // add vertices for chinese whispers to find clusters + std::vector edges; + for (size_t i = 0; i < feats.size(); ++i) { + for (size_t j = i; j < feats.size(); ++j) { + if (length(feats[i] - feats[j]) < rs.mean()) { + edges.push_back(sample_pair(i, j, length(feats[i] - feats[j]))); + } + } + } + + std::vector labels; + num_clusters = chinese_whispers(edges, labels); + + std::vector assignments; + for (unsigned long i = 0; i < feats.size(); ++i) + { + assignment temp; + temp.c = labels[i]; + temp.dist = length(feats[i]); + temp.idx = i; + assignments.push_back(temp); + } + return assignments; +} // ---------------------------------------------------------------------------------------- @@ -134,7 +185,7 @@ int cluster_dataset( return EXIT_FAILURE; } - const unsigned long num_clusters = get_option(parser, "cluster", 2); + unsigned long num_clusters = get_option(parser, "cluster", 0); const unsigned long chip_size = get_option(parser, "size", 8000); image_dataset_metadata::dataset data; @@ -177,7 +228,12 @@ int cluster_dataset( } cout << "\nClustering objects..." << endl; - std::vector assignments = angular_cluster(feats, num_clusters); + std::vector assignments; + if (num_clusters) { + assignments = angular_cluster(feats, num_clusters); + } else { + assignments = chinese_cluster(feats, num_clusters); + } // Now output each cluster to disk as an XML file. diff --git a/tools/imglab/src/main.cpp b/tools/imglab/src/main.cpp index 9fd4731c9..bfd00ccbf 100644 --- a/tools/imglab/src/main.cpp +++ b/tools/imglab/src/main.cpp @@ -588,7 +588,7 @@ int main(int argc, char** argv) "The parts are instead simply mirrored to the flipped dataset.", 1); parser.add_option("rotate", "Read an XML image dataset and output a copy that is rotated counter clockwise by degrees. " "The output is saved to an XML file prefixed with rotated_.",1); - parser.add_option("cluster", "Cluster all the objects in an XML file into different clusters and save " + parser.add_option("cluster", "Cluster all the objects in an XML file into different clusters (pass 0 to find automatically) and save " "the results as cluster_###.xml and cluster_###.jpg files.",1); parser.add_option("ignore", "Mark boxes labeled as as ignored. The resulting XML file is output as a separate file and the original is not modified.",1); parser.add_option("rmlabel","Remove all boxes labeled and save the results to a new XML file.",1); @@ -704,7 +704,7 @@ int main(int argc, char** argv) parser.check_incompatible_options("box-images", "ignore"); const char* convert_args[] = {"pascal-xml","pascal-v1","idl"}; parser.check_option_arg_range("convert", convert_args); - parser.check_option_arg_range("cluster", 2, 999); + parser.check_option_arg_range("cluster", 0, 999); parser.check_option_arg_range("rotate", -360, 360); parser.check_option_arg_range("size", 10*10, 1000*1000); parser.check_option_arg_range("min-object-size", 1, 10000*10000); diff --git a/tools/imglab/src/metadata_editor.cpp b/tools/imglab/src/metadata_editor.cpp index 384857861..84d946452 100644 --- a/tools/imglab/src/metadata_editor.cpp +++ b/tools/imglab/src/metadata_editor.cpp @@ -343,6 +343,16 @@ on_keydown ( last_keyboard_jump_pos_update = 0; } + if (key == '=') + { + display.zoom_in(); + } + + if (key == '-') + { + display.zoom_out(); + } + if (key == 'd' && (state&base_window::KBD_MOD_ALT)) { remove_selected_images();