| | |
| | |
| |
|
| | #include "cluster.h" |
| | #include <dlib/console_progress_indicator.h> |
| | #include <dlib/image_io.h> |
| | #include <dlib/data_io.h> |
| | #include <dlib/image_transforms.h> |
| | #include <dlib/misc_api.h> |
| | #include <dlib/dir_nav.h> |
| | #include <dlib/clustering.h> |
| | #include <dlib/svm.h> |
| | #include <dlib/statistics.h> |
| |
|
| | |
| |
|
| | using namespace std; |
| | using namespace dlib; |
| |
|
| | |
| |
|
| | struct assignment |
| | { |
| | unsigned long c; |
| | double dist; |
| | unsigned long idx; |
| |
|
| | bool operator<(const assignment& item) const |
| | { return dist < item.dist; } |
| | }; |
| |
|
| | std::vector<assignment> angular_cluster ( |
| | std::vector<matrix<double,0,1> > feats, |
| | const unsigned long num_clusters |
| | ) |
| | { |
| | DLIB_CASSERT(feats.size() != 0, "The dataset can't be empty"); |
| | for (unsigned long i = 0; i < feats.size(); ++i) |
| | { |
| | DLIB_CASSERT(feats[i].size() == feats[0].size(), "All feature vectors must have the same length."); |
| | } |
| |
|
| | |
| | const matrix<double,0,1> m = mean(mat(feats)); |
| |
|
| | |
| | |
| | for (auto& f : feats) |
| | { |
| | f = normalize(f-m); |
| | } |
| |
|
| | |
| | std::vector<matrix<double,0,1> > centers; |
| | pick_initial_centers(num_clusters, centers, feats, linear_kernel<matrix<double,0,1> >(), 0.05); |
| | find_clusters_using_angular_kmeans(feats, centers); |
| |
|
| | |
| | std::vector<assignment> assignments; |
| | for (unsigned long i = 0; i < feats.size(); ++i) |
| | { |
| | assignment temp; |
| | temp.c = nearest_center(centers, feats[i]); |
| | temp.dist = length(feats[i] - centers[temp.c]); |
| | temp.idx = i; |
| | assignments.push_back(temp); |
| | } |
| | return assignments; |
| | } |
| | std::vector<assignment> chinese_cluster ( |
| | std::vector<matrix<double,0,1> > feats, |
| | unsigned long &num_clusters |
| | ) |
| | { |
| | DLIB_CASSERT(feats.size() != 0, "The dataset can't be empty"); |
| | for (unsigned long i = 0; i < feats.size(); ++i) |
| | { |
| | DLIB_CASSERT(feats[i].size() == feats[0].size(), "All feature vectors must have the same length."); |
| | } |
| |
|
| | |
| | |
| | const matrix<double,0,1> m = mean(mat(feats)); |
| |
|
| | for (auto& f : feats) |
| | { |
| | f = normalize(f-m); |
| | } |
| |
|
| | |
| | |
| | running_stats<double> 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])); |
| | } |
| | } |
| |
|
| | |
| | std::vector<sample_pair> 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<unsigned long> labels; |
| | num_clusters = chinese_whispers(edges, labels); |
| |
|
| | std::vector<assignment> 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; |
| | } |
| |
|
| | |
| |
|
| | bool compare_first ( |
| | const std::pair<double,image_dataset_metadata::image>& a, |
| | const std::pair<double,image_dataset_metadata::image>& b |
| | ) |
| | { |
| | return a.first < b.first; |
| | } |
| |
|
| | |
| |
|
| | double mean_aspect_ratio ( |
| | const image_dataset_metadata::dataset& data |
| | ) |
| | { |
| | double sum = 0; |
| | double cnt = 0; |
| | for (unsigned long i = 0; i < data.images.size(); ++i) |
| | { |
| | for (unsigned long j = 0; j < data.images[i].boxes.size(); ++j) |
| | { |
| | rectangle rect = data.images[i].boxes[j].rect; |
| | if (rect.area() == 0 || data.images[i].boxes[j].ignore) |
| | continue; |
| | sum += rect.width()/(double)rect.height(); |
| | ++cnt; |
| | } |
| | } |
| |
|
| | if (cnt != 0) |
| | return sum/cnt; |
| | else |
| | return 0; |
| | } |
| |
|
| | |
| |
|
| | bool has_non_ignored_boxes (const image_dataset_metadata::image& img) |
| | { |
| | for (auto&& b : img.boxes) |
| | { |
| | if (!b.ignore) |
| | return true; |
| | } |
| | return false; |
| | } |
| |
|
| | |
| |
|
| | int cluster_dataset( |
| | const dlib::command_line_parser& parser |
| | ) |
| | { |
| | |
| | if (parser.number_of_arguments() != 1) |
| | { |
| | cerr << "The --cluster option requires you to give one XML file on the command line." << endl; |
| | return EXIT_FAILURE; |
| | } |
| |
|
| | unsigned long num_clusters = get_option(parser, "cluster", 0); |
| | const unsigned long chip_size = get_option(parser, "size", 8000); |
| |
|
| | image_dataset_metadata::dataset data; |
| |
|
| | image_dataset_metadata::load_image_dataset_metadata(data, parser[0]); |
| | set_current_dir(get_parent_directory(file(parser[0]))); |
| |
|
| | const double aspect_ratio = mean_aspect_ratio(data); |
| |
|
| | dlib::array<array2d<rgb_pixel> > images; |
| | std::vector<matrix<double,0,1> > feats; |
| | console_progress_indicator pbar(data.images.size()); |
| | |
| | cout << "Loading image data..." << endl; |
| | for (unsigned long i = 0; i < data.images.size(); ++i) |
| | { |
| | pbar.print_status(i); |
| | if (!has_non_ignored_boxes(data.images[i])) |
| | continue; |
| |
|
| | array2d<rgb_pixel> img, chip; |
| | load_image(img, data.images[i].filename); |
| |
|
| | for (unsigned long j = 0; j < data.images[i].boxes.size(); ++j) |
| | { |
| | if (data.images[i].boxes[j].ignore || data.images[i].boxes[j].rect.area() < 10) |
| | continue; |
| | drectangle rect = data.images[i].boxes[j].rect; |
| | rect = set_aspect_ratio(rect, aspect_ratio); |
| | extract_image_chip(img, chip_details(rect, chip_size), chip); |
| | feats.push_back(extract_fhog_features(chip)); |
| | images.push_back(chip); |
| | } |
| | } |
| |
|
| | if (feats.size() == 0) |
| | { |
| | cerr << "No non-ignored object boxes found in the XML dataset. You can't cluster an empty dataset." << endl; |
| | return EXIT_FAILURE; |
| | } |
| |
|
| | cout << "\nClustering objects..." << endl; |
| | std::vector<assignment> assignments; |
| | if (num_clusters) { |
| | assignments = angular_cluster(feats, num_clusters); |
| | } else { |
| | assignments = chinese_cluster(feats, num_clusters); |
| | } |
| |
|
| |
|
| | |
| | for (unsigned long c = 0; c < num_clusters; ++c) |
| | { |
| | |
| | |
| | |
| | |
| | |
| | std::vector<std::pair<double,image_dataset_metadata::image> > idata(data.images.size()); |
| | unsigned long idx = 0; |
| | for (unsigned long i = 0; i < data.images.size(); ++i) |
| | { |
| | idata[i].first = std::numeric_limits<double>::infinity(); |
| | idata[i].second.filename = data.images[i].filename; |
| | idata[i].second.width = data.images[i].width; |
| | idata[i].second.height = data.images[i].height; |
| | if (!has_non_ignored_boxes(data.images[i])) |
| | continue; |
| |
|
| | for (unsigned long j = 0; j < data.images[i].boxes.size(); ++j) |
| | { |
| | idata[i].second.boxes.push_back(data.images[i].boxes[j]); |
| |
|
| | if (data.images[i].boxes[j].ignore || data.images[i].boxes[j].rect.area() < 10) |
| | continue; |
| |
|
| | |
| | |
| | |
| | if (assignments[idx].c == c) |
| | idata[i].first = std::min(idata[i].first, assignments[idx].dist); |
| | else |
| | idata[i].second.boxes.back().ignore = true; |
| |
|
| | ++idx; |
| | } |
| | } |
| |
|
| | |
| | std::sort(idata.begin(), idata.end(), compare_first); |
| | image_dataset_metadata::dataset cdata; |
| | cdata.comment = data.comment + "\n\n This file contains objects which were clustered into group " + |
| | cast_to_string(c+1) + " of " + cast_to_string(num_clusters) + " groups with a chip size of " + |
| | cast_to_string(chip_size) + " by imglab."; |
| | cdata.name = data.name; |
| | for (unsigned long i = 0; i < idata.size(); ++i) |
| | { |
| | |
| | if (idata[i].first != std::numeric_limits<double>::infinity()) |
| | cdata.images.push_back(idata[i].second); |
| | } |
| |
|
| | string outfile = "cluster_"+pad_int_with_zeros(c+1, 3) + ".xml"; |
| | cout << "Saving " << outfile << endl; |
| | save_image_dataset_metadata(cdata, outfile); |
| | } |
| |
|
| | |
| | |
| | std::sort(assignments.begin(), assignments.end()); |
| | for (unsigned long c = 0; c < num_clusters; ++c) |
| | { |
| | dlib::array<array2d<rgb_pixel> > temp; |
| | for (unsigned long i = 0; i < assignments.size(); ++i) |
| | { |
| | if (assignments[i].c == c) |
| | temp.push_back(images[assignments[i].idx]); |
| | } |
| | #ifdef DLIB_JXL_SUPPORT |
| | if (parser.option("jxl")) |
| | { |
| | string outfile = "cluster_"+pad_int_with_zeros(c+1, 3) + ".jxl"; |
| | cout << "Saving " << outfile << endl; |
| | const float jxl_quality = std::stof(parser.option("jxl").argument()); |
| | save_jxl(tile_images(temp), outfile, jxl_quality); |
| | } |
| | else |
| | #endif |
| | #ifdef DLIB_WEBP_SUPPORT |
| | if (parser.option("webp")) |
| | { |
| | string outfile = "cluster_"+pad_int_with_zeros(c+1, 3) + ".webp"; |
| | cout << "Saving " << outfile << endl; |
| | const float webp_quality = std::stof(parser.option("webp").argument()); |
| | save_webp(tile_images(temp), outfile, webp_quality); |
| | } |
| | else |
| | #endif |
| | { |
| | string outfile = "cluster_"+pad_int_with_zeros(c+1, 3) + ".jpg"; |
| | cout << "Saving " << outfile << endl; |
| | save_jpeg(tile_images(temp), outfile); |
| | } |
| | } |
| |
|
| |
|
| | return EXIT_SUCCESS; |
| | } |
| |
|
| | |
| |
|
| |
|