#include "dlib/data_io.h" #include "dlib/string.h" #include "metadata_editor.h" #include "convert_pascal_xml.h" #include "convert_pascal_v1.h" #include "convert_idl.h" #include "cluster.h" #include "flip_dataset.h" #include #include #include #include #include #include #include #include #include #include const char* VERSION = "1.13"; using namespace std; using namespace dlib; // ---------------------------------------------------------------------------------------- void create_new_dataset ( const command_line_parser& parser ) { using namespace dlib::image_dataset_metadata; const std::string filename = parser.option("c").argument(); // make sure the file exists so we can use the get_parent_directory() command to // figure out it's parent directory. make_empty_file(filename); const std::string parent_dir = get_parent_directory(file(filename)); unsigned long depth = 0; if (parser.option("r")) depth = 30; dataset meta; meta.name = "imglab dataset"; meta.comment = "Created by imglab tool."; for (unsigned long i = 0; i < parser.number_of_arguments(); ++i) { try { const string temp = strip_path(file(parser[i]), parent_dir); meta.images.push_back(image(temp)); } catch (dlib::file::file_not_found&) { // then parser[i] should be a directory std::vector files = get_files_in_directory_tree(parser[i], match_endings(".png .PNG .jpeg .JPEG .jpg .JPG .bmp .BMP .dng .DNG .gif .GIF"), depth); sort(files.begin(), files.end()); for (unsigned long j = 0; j < files.size(); ++j) { meta.images.push_back(image(strip_path(files[j], parent_dir))); } } } save_image_dataset_metadata(meta, filename); } // ---------------------------------------------------------------------------------------- int split_dataset ( const command_line_parser& parser ) { if (parser.number_of_arguments() != 1) { cerr << "The --split option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } const std::string label = parser.option("split").argument(); dlib::image_dataset_metadata::dataset data, data_with, data_without; load_image_dataset_metadata(data, parser[0]); data_with.name = data.name; data_with.comment = data.comment; data_without.name = data.name; data_without.comment = data.comment; for (unsigned long i = 0; i < data.images.size(); ++i) { auto&& temp = data.images[i]; bool has_the_label = false; // check for the label we are looking for for (unsigned long j = 0; j < temp.boxes.size(); ++j) { if (temp.boxes[j].label == label) { has_the_label = true; break; } } if (has_the_label) data_with.images.push_back(temp); else data_without.images.push_back(temp); } save_image_dataset_metadata(data_with, left_substr(parser[0],".") + "_with_"+label + ".xml"); save_image_dataset_metadata(data_without, left_substr(parser[0],".") + "_without_"+label + ".xml"); return EXIT_SUCCESS; } // ---------------------------------------------------------------------------------------- void print_all_labels ( const dlib::image_dataset_metadata::dataset& data ) { std::set labels; for (unsigned long i = 0; i < data.images.size(); ++i) { for (unsigned long j = 0; j < data.images[i].boxes.size(); ++j) { labels.insert(data.images[i].boxes[j].label); } } for (std::set::iterator i = labels.begin(); i != labels.end(); ++i) { if (i->size() != 0) { cout << *i << endl; } } } // ---------------------------------------------------------------------------------------- void print_all_label_stats ( const dlib::image_dataset_metadata::dataset& data ) { std::map > area_stats, aspect_ratio; std::map image_hits; std::set labels; unsigned long num_unignored_boxes = 0; for (unsigned long i = 0; i < data.images.size(); ++i) { std::set temp; for (unsigned long j = 0; j < data.images[i].boxes.size(); ++j) { labels.insert(data.images[i].boxes[j].label); temp.insert(data.images[i].boxes[j].label); area_stats[data.images[i].boxes[j].label].add(data.images[i].boxes[j].rect.area()); aspect_ratio[data.images[i].boxes[j].label].add(data.images[i].boxes[j].rect.width()/ (double)data.images[i].boxes[j].rect.height()); if (!data.images[i].boxes[j].ignore) ++num_unignored_boxes; } // count the number of images for each label for (std::set::iterator i = temp.begin(); i != temp.end(); ++i) image_hits[*i] += 1; } cout << "Number of images: "<< data.images.size() << endl; cout << "Number of different labels: "<< labels.size() << endl; cout << "Number of non-ignored boxes: " << num_unignored_boxes << endl << endl; for (std::set::iterator i = labels.begin(); i != labels.end(); ++i) { if (i->size() == 0) cout << "Unlabeled Boxes:" << endl; else cout << "Label: "<< *i << endl; cout << " number of images: " << image_hits[*i] << endl; cout << " number of occurrences: " << area_stats[*i].current_n() << endl; cout << " min box area: " << area_stats[*i].min() << endl; cout << " max box area: " << area_stats[*i].max() << endl; cout << " mean box area: " << area_stats[*i].mean() << endl; cout << " stddev box area: " << area_stats[*i].stddev() << endl; cout << " mean width/height ratio: " << aspect_ratio[*i].mean() << endl; cout << " stddev width/height ratio: " << aspect_ratio[*i].stddev() << endl; cout << endl; } } // ---------------------------------------------------------------------------------------- void rename_labels ( dlib::image_dataset_metadata::dataset& data, const std::string& from, const std::string& to ) { for (unsigned long i = 0; i < data.images.size(); ++i) { for (unsigned long j = 0; j < data.images[i].boxes.size(); ++j) { if (data.images[i].boxes[j].label == from) data.images[i].boxes[j].label = to; } } } // ---------------------------------------------------------------------------------------- void ignore_labels ( dlib::image_dataset_metadata::dataset& data, const std::string& label ) { for (unsigned long i = 0; i < data.images.size(); ++i) { for (unsigned long j = 0; j < data.images[i].boxes.size(); ++j) { if (data.images[i].boxes[j].label == label) data.images[i].boxes[j].ignore = true; } } } // ---------------------------------------------------------------------------------------- void merge_metadata_files ( const command_line_parser& parser ) { image_dataset_metadata::dataset src, dest; load_image_dataset_metadata(src, parser.option("add").argument(0)); load_image_dataset_metadata(dest, parser.option("add").argument(1)); std::map merged_data; for (unsigned long i = 0; i < dest.images.size(); ++i) merged_data[dest.images[i].filename] = dest.images[i]; // now add in the src data and overwrite anything if there are duplicate entries. for (unsigned long i = 0; i < src.images.size(); ++i) merged_data[src.images[i].filename] = src.images[i]; // copy merged data into dest dest.images.clear(); for (std::map::const_iterator i = merged_data.begin(); i != merged_data.end(); ++i) { dest.images.push_back(i->second); } save_image_dataset_metadata(dest, "merged.xml"); } // ---------------------------------------------------------------------------------------- void rotate_dataset(const command_line_parser& parser) { image_dataset_metadata::dataset metadata; const string datasource = parser[0]; load_image_dataset_metadata(metadata,datasource); double angle = get_option(parser, "rotate", 0); // Set the current directory to be the one that contains the // metadata file. We do this because the file might contain // file paths which are relative to this folder. set_current_dir(get_parent_directory(file(datasource))); const string file_prefix = "rotated_"+ cast_to_string(angle) + "_"; const string metadata_filename = get_parent_directory(file(datasource)).full_name() + directory::get_separator() + file_prefix + file(datasource).name(); array2d img, temp; for (unsigned long i = 0; i < metadata.images.size(); ++i) { file f(metadata.images[i].filename); string filename = get_parent_directory(f).full_name() + directory::get_separator() + file_prefix + to_png_name(f.name()); load_image(img, metadata.images[i].filename); const point_transform_affine tran = rotate_image(img, temp, angle*pi/180); if (parser.option("jpg")) { filename = to_jpg_name(filename); save_jpeg(temp, filename,JPEG_QUALITY); } else { save_png(temp, filename); } for (unsigned long j = 0; j < metadata.images[i].boxes.size(); ++j) { const rectangle rect = metadata.images[i].boxes[j].rect; rectangle newrect; newrect += tran(rect.tl_corner()); newrect += tran(rect.tr_corner()); newrect += tran(rect.bl_corner()); newrect += tran(rect.br_corner()); // now make newrect have the same area as the starting rect. double ratio = std::sqrt(rect.area()/(double)newrect.area()); newrect = centered_rect(newrect, newrect.width()*ratio, newrect.height()*ratio); metadata.images[i].boxes[j].rect = newrect; // rotate all the object parts std::map::iterator k; for (k = metadata.images[i].boxes[j].parts.begin(); k != metadata.images[i].boxes[j].parts.end(); ++k) { k->second = tran(k->second); } } metadata.images[i].filename = filename; } save_image_dataset_metadata(metadata, metadata_filename); } // ---------------------------------------------------------------------------------------- int resample_dataset(const command_line_parser& parser) { if (parser.number_of_arguments() != 1) { cerr << "The --resample option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } const size_t obj_size = get_option(parser,"cropped-object-size",100*100); const double margin_scale = get_option(parser,"crop-size",2.5); // cropped image will be this times wider than the object. const unsigned long min_object_size = get_option(parser,"min-object-size",1); const bool one_object_per_image = parser.option("one-object-per-image"); dlib::image_dataset_metadata::dataset data, resampled_data; std::ostringstream sout; sout << "\nThe --resample parameters which generated this dataset were:" << endl; sout << " cropped-object-size: "<< obj_size << endl; sout << " crop-size: "<< margin_scale << endl; sout << " min-object-size: "<< min_object_size << endl; if (one_object_per_image) sout << " one_object_per_image: true" << endl; resampled_data.comment = data.comment + sout.str(); resampled_data.name = data.name + " RESAMPLED"; load_image_dataset_metadata(data, parser[0]); locally_change_current_dir chdir(get_parent_directory(file(parser[0]))); dlib::rand rnd; const size_t image_size = std::round(std::sqrt(obj_size*margin_scale*margin_scale)); const chip_dims cdims(image_size, image_size); console_progress_indicator pbar(data.images.size()); for (unsigned long i = 0; i < data.images.size(); ++i) { // don't even bother loading images that don't have objects. if (data.images[i].boxes.size() == 0) continue; pbar.print_status(i); array2d img, chip; load_image(img, data.images[i].filename); // figure out what chips we want to take from this image for (unsigned long j = 0; j < data.images[i].boxes.size(); ++j) { const rectangle rect = data.images[i].boxes[j].rect; if (data.images[i].boxes[j].ignore || rect.area() < min_object_size) continue; const auto max_dim = std::max(rect.width(), rect.height()); const double rand_scale_perturb = 1 - 0.3*(rnd.get_random_double()-0.5); const rectangle crop_rect = centered_rect(rect, max_dim*margin_scale*rand_scale_perturb, max_dim*margin_scale*rand_scale_perturb); const rectangle_transform tform = get_mapping_to_chip(chip_details(crop_rect, cdims)); extract_image_chip(img, chip_details(crop_rect, cdims), chip); image_dataset_metadata::image dimg; // Now transform the boxes to the crop and also mark them as ignored if they // have already been cropped out or are outside the crop. for (size_t k = 0; k < data.images[i].boxes.size(); ++k) { image_dataset_metadata::box box = data.images[i].boxes[k]; // ignore boxes outside the cropped image if (crop_rect.intersect(box.rect).area() == 0) continue; // mark boxes we include in the crop as ignored. Also mark boxes that // aren't totally within the crop as ignored. if (crop_rect.contains(grow_rect(box.rect,10)) && (!one_object_per_image || k==j)) data.images[i].boxes[k].ignore = true; else box.ignore = true; if (box.rect.area() < min_object_size) box.ignore = true; box.rect = tform(box.rect); for (auto&& p : box.parts) p.second = tform.get_tform()(p.second); dimg.boxes.push_back(box); } // Put a 64bit hash of the image data into the name to make sure there are no // file name conflicts. std::ostringstream sout; sout << hex << murmur_hash3_128bit(&chip[0][0], chip.size()*sizeof(chip[0][0])).second; dimg.filename = data.images[i].filename + "_RESAMPLED_"+sout.str()+".png"; if (parser.option("jpg")) { dimg.filename = to_jpg_name(dimg.filename); save_jpeg(chip,dimg.filename, JPEG_QUALITY); } else { save_png(chip,dimg.filename); } resampled_data.images.push_back(dimg); } } save_image_dataset_metadata(resampled_data, parser[0] + ".RESAMPLED.xml"); return EXIT_SUCCESS; } // ---------------------------------------------------------------------------------------- int tile_dataset(const command_line_parser& parser) { if (parser.number_of_arguments() != 1) { cerr << "The --tile option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } string out_image = parser.option("tile").argument(); string ext = right_substr(out_image,"."); if (ext != "png" && ext != "jpg") { cerr << "The output image file must have either .png or .jpg extension." << endl; return EXIT_FAILURE; } const unsigned long chip_size = get_option(parser, "size", 8000); dlib::image_dataset_metadata::dataset data; load_image_dataset_metadata(data, parser[0]); locally_change_current_dir chdir(get_parent_directory(file(parser[0]))); dlib::array > images; console_progress_indicator pbar(data.images.size()); for (unsigned long i = 0; i < data.images.size(); ++i) { // don't even bother loading images that don't have objects. if (data.images[i].boxes.size() == 0) continue; pbar.print_status(i); array2d img; load_image(img, data.images[i].filename); // figure out what chips we want to take from this image std::vector dets; for (unsigned long j = 0; j < data.images[i].boxes.size(); ++j) { if (data.images[i].boxes[j].ignore) continue; rectangle rect = data.images[i].boxes[j].rect; dets.push_back(chip_details(rect, chip_size)); } // Now grab all those chips at once. dlib::array > chips; extract_image_chips(img, dets, chips); // and put the chips into the output. for (unsigned long j = 0; j < chips.size(); ++j) images.push_back(chips[j]); } chdir.revert(); if (ext == "png") save_png(tile_images(images), out_image); else save_jpeg(tile_images(images), out_image); return EXIT_SUCCESS; } // ---------------------------------------------------------------------------------------- int main(int argc, char** argv) { try { command_line_parser parser; parser.add_option("h","Displays this information."); parser.add_option("v","Display version."); parser.set_group_name("Creating XML files"); parser.add_option("c","Create an XML file named listing a set of images.",1); parser.add_option("r","Search directories recursively for images."); parser.add_option("convert","Convert foreign image Annotations from format to the imglab format. " "Supported formats: pascal-xml, pascal-v1, idl.",1); parser.set_group_name("Viewing XML files"); parser.add_option("tile","Chip out all the objects and save them as one big image called .",1); parser.add_option("size","When using --tile or --cluster, make each extracted object contain " "about pixels (default 8000).",1); parser.add_option("l","List all the labels in the given XML file."); parser.add_option("stats","List detailed statistics on the object labels in the given XML file."); parser.add_option("files","List all the files in the given XML file."); parser.set_group_name("Editing/Transforming XML datasets"); parser.add_option("rename", "Rename all labels of to .",2); parser.add_option("parts","The display will allow image parts to be labeled. The set of allowable parts " "is defined by which should be a space separated list of parts.",1); parser.add_option("rmempty","Remove all images that don't contain non-ignored annotations and save the results to a new XML file."); parser.add_option("rmdupes","Remove duplicate images from the dataset. This is done by comparing " "the md5 hash of each image file and removing duplicate images. " ); parser.add_option("rmdiff","Set the ignored flag to true for boxes marked as difficult."); parser.add_option("rmtrunc","Set the ignored flag to true for boxes that are partially outside the image."); parser.add_option("sort-num-objects","Sort the images listed an XML file so images with many objects are listed first."); parser.add_option("sort","Alphabetically sort the images in an XML file."); parser.add_option("shuffle","Randomly shuffle the order of the images listed in an XML file."); parser.add_option("seed", "When using --shuffle, set the random seed to the string .",1); parser.add_option("split", "Split the contents of an XML file into two separate files. One containing the " "images with objects labeled and another file with all the other images. ",1); parser.add_option("add", "Add the image metadata from into . If any of the image " "tags are in both files then the ones in are deleted and replaced with the " "image tags from . The results are saved into merged.xml and neither or " " files are modified.",2); parser.add_option("flip", "Read an XML image dataset from the XML file and output a left-right flipped " "version of the dataset and an accompanying flipped XML file named flipped_. " "We also adjust object part labels after flipping so that the new flipped dataset " "has the same average part layout as the source dataset." ,1); parser.add_option("flip-basic", "This option is just like --flip, except we don't adjust any object part labels after flipping. " "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 " "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); parser.add_option("rm-other-labels","Remove all boxes not labeled and save the results to a new XML file.",1); parser.add_option("rmignore","Remove all boxes marked ignore and save the results to a new XML file."); parser.add_option("rm-if-overlaps","Remove all boxes labeled if they overlap any box not labeled and save the results to a new XML file.",1); parser.add_option("jpg", "When saving images to disk, write them as jpg files instead of png."); parser.set_group_name("Cropping sub images"); parser.add_option("resample", "Crop out images that are centered on each object in the dataset. " "The output is a new XML dataset."); parser.add_option("cropped-object-size", "When doing --resample, make the cropped objects contain about pixels (default 10000).",1); parser.add_option("min-object-size", "When doing --resample, skip objects that have fewer than pixels in them (default 1).",1); parser.add_option("crop-size", "When doing --resample, the entire cropped image will be times wider than the object (default 2.5).",1); parser.add_option("one-object-per-image", "When doing --resample, only include one non-ignored object per image (i.e. the central object)."); parser.parse(argc, argv); const char* singles[] = {"h","c","r","l","files","convert","parts","rmdiff", "rmtrunc", "rmdupes", "seed", "shuffle", "split", "add", "flip-basic", "flip", "rotate", "tile", "size", "cluster", "resample", "min-object-size", "rmempty", "crop-size", "cropped-object-size", "rmlabel", "rm-other-labels", "rm-if-overlaps", "sort-num-objects", "one-object-per-image", "jpg", "rmignore", "sort"}; parser.check_one_time_options(singles); const char* c_sub_ops[] = {"r", "convert"}; parser.check_sub_options("c", c_sub_ops); parser.check_sub_option("shuffle", "seed"); const char* resample_sub_ops[] = {"min-object-size", "crop-size", "cropped-object-size", "one-object-per-image"}; parser.check_sub_options("resample", resample_sub_ops); const char* size_parent_ops[] = {"tile", "cluster"}; parser.check_sub_options(size_parent_ops, "size"); parser.check_incompatible_options("c", "l"); parser.check_incompatible_options("c", "files"); parser.check_incompatible_options("c", "rmdiff"); parser.check_incompatible_options("c", "rmempty"); parser.check_incompatible_options("c", "rmlabel"); parser.check_incompatible_options("c", "rm-other-labels"); parser.check_incompatible_options("c", "rmignore"); parser.check_incompatible_options("c", "rm-if-overlaps"); parser.check_incompatible_options("c", "rmdupes"); parser.check_incompatible_options("c", "rmtrunc"); parser.check_incompatible_options("c", "add"); parser.check_incompatible_options("c", "flip"); parser.check_incompatible_options("c", "flip-basic"); parser.check_incompatible_options("flip", "flip-basic"); parser.check_incompatible_options("c", "rotate"); parser.check_incompatible_options("c", "rename"); parser.check_incompatible_options("c", "ignore"); parser.check_incompatible_options("c", "parts"); parser.check_incompatible_options("c", "tile"); parser.check_incompatible_options("c", "cluster"); parser.check_incompatible_options("c", "resample"); parser.check_incompatible_options("l", "rename"); parser.check_incompatible_options("l", "ignore"); parser.check_incompatible_options("l", "add"); parser.check_incompatible_options("l", "parts"); parser.check_incompatible_options("l", "flip"); parser.check_incompatible_options("l", "flip-basic"); parser.check_incompatible_options("l", "rotate"); parser.check_incompatible_options("files", "rename"); parser.check_incompatible_options("files", "ignore"); parser.check_incompatible_options("files", "add"); parser.check_incompatible_options("files", "parts"); parser.check_incompatible_options("files", "flip"); parser.check_incompatible_options("files", "flip-basic"); parser.check_incompatible_options("files", "rotate"); parser.check_incompatible_options("add", "flip"); parser.check_incompatible_options("add", "flip-basic"); parser.check_incompatible_options("add", "rotate"); parser.check_incompatible_options("add", "tile"); parser.check_incompatible_options("flip", "tile"); parser.check_incompatible_options("flip-basic", "tile"); parser.check_incompatible_options("rotate", "tile"); parser.check_incompatible_options("cluster", "tile"); parser.check_incompatible_options("resample", "tile"); parser.check_incompatible_options("flip", "cluster"); parser.check_incompatible_options("flip-basic", "cluster"); parser.check_incompatible_options("rotate", "cluster"); parser.check_incompatible_options("add", "cluster"); parser.check_incompatible_options("flip", "resample"); parser.check_incompatible_options("flip-basic", "resample"); parser.check_incompatible_options("rotate", "resample"); parser.check_incompatible_options("add", "resample"); parser.check_incompatible_options("shuffle", "tile"); parser.check_incompatible_options("sort-num-objects", "tile"); parser.check_incompatible_options("sort", "tile"); parser.check_incompatible_options("convert", "l"); parser.check_incompatible_options("convert", "files"); parser.check_incompatible_options("convert", "rename"); parser.check_incompatible_options("convert", "ignore"); parser.check_incompatible_options("convert", "parts"); parser.check_incompatible_options("convert", "cluster"); parser.check_incompatible_options("convert", "resample"); parser.check_incompatible_options("rmdiff", "rename"); parser.check_incompatible_options("rmdiff", "ignore"); parser.check_incompatible_options("rmempty", "ignore"); parser.check_incompatible_options("rmempty", "rename"); parser.check_incompatible_options("rmlabel", "ignore"); parser.check_incompatible_options("rmlabel", "rename"); parser.check_incompatible_options("rm-other-labels", "ignore"); parser.check_incompatible_options("rm-other-labels", "rename"); parser.check_incompatible_options("rmignore", "ignore"); parser.check_incompatible_options("rmignore", "rename"); parser.check_incompatible_options("rm-if-overlaps", "ignore"); parser.check_incompatible_options("rm-if-overlaps", "rename"); parser.check_incompatible_options("rmdupes", "rename"); parser.check_incompatible_options("rmdupes", "ignore"); parser.check_incompatible_options("rmtrunc", "rename"); parser.check_incompatible_options("rmtrunc", "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("rotate", -360, 360); parser.check_option_arg_range("size", 10*10, 1000*1000); parser.check_option_arg_range("min-object-size", 1, 10000*10000); parser.check_option_arg_range("cropped-object-size", 4, 10000*10000); parser.check_option_arg_range("crop-size", 1.0, 100.0); if (parser.option("h")) { cout << "Usage: imglab [options] \n"; parser.print_options(cout); cout << endl << endl; return EXIT_SUCCESS; } if (parser.option("add")) { merge_metadata_files(parser); return EXIT_SUCCESS; } if (parser.option("flip") || parser.option("flip-basic")) { flip_dataset(parser); return EXIT_SUCCESS; } if (parser.option("rotate")) { rotate_dataset(parser); return EXIT_SUCCESS; } if (parser.option("v")) { cout << "imglab v" << VERSION << "\nCompiled: " << __TIME__ << " " << __DATE__ << "\nWritten by Davis King\n"; cout << "Check for updates at http://dlib.net\n\n"; return EXIT_SUCCESS; } if (parser.option("tile")) { return tile_dataset(parser); } if (parser.option("cluster")) { return cluster_dataset(parser); } if (parser.option("resample")) { return resample_dataset(parser); } if (parser.option("c")) { if (parser.option("convert")) { if (parser.option("convert").argument() == "pascal-xml") convert_pascal_xml(parser); else if (parser.option("convert").argument() == "pascal-v1") convert_pascal_v1(parser); else if (parser.option("convert").argument() == "idl") convert_idl(parser); } else { create_new_dataset(parser); } return EXIT_SUCCESS; } if (parser.option("rmdiff")) { if (parser.number_of_arguments() != 1) { cerr << "The --rmdiff option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } dlib::image_dataset_metadata::dataset data; load_image_dataset_metadata(data, parser[0]); for (unsigned long i = 0; i < data.images.size(); ++i) { for (unsigned long j = 0; j < data.images[i].boxes.size(); ++j) { if (data.images[i].boxes[j].difficult) data.images[i].boxes[j].ignore = true; } } save_image_dataset_metadata(data, parser[0]); return EXIT_SUCCESS; } if (parser.option("rmempty")) { if (parser.number_of_arguments() != 1) { cerr << "The --rmempty option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } dlib::image_dataset_metadata::dataset data, data2; load_image_dataset_metadata(data, parser[0]); data2 = data; data2.images.clear(); for (unsigned long i = 0; i < data.images.size(); ++i) { bool has_label = false; for (unsigned long j = 0; j < data.images[i].boxes.size(); ++j) { if (!data.images[i].boxes[j].ignore) has_label = true; } if (has_label) data2.images.push_back(data.images[i]); } save_image_dataset_metadata(data2, parser[0] + ".rmempty.xml"); return EXIT_SUCCESS; } if (parser.option("rmlabel")) { if (parser.number_of_arguments() != 1) { cerr << "The --rmlabel option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } dlib::image_dataset_metadata::dataset data; load_image_dataset_metadata(data, parser[0]); const auto label = parser.option("rmlabel").argument(); for (auto&& img : data.images) { std::vector boxes; for (auto&& b : img.boxes) { if (b.label != label) boxes.push_back(b); } img.boxes = boxes; } save_image_dataset_metadata(data, parser[0] + ".rmlabel-"+label+".xml"); return EXIT_SUCCESS; } if (parser.option("rm-other-labels")) { if (parser.number_of_arguments() != 1) { cerr << "The --rm-other-labels option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } dlib::image_dataset_metadata::dataset data; load_image_dataset_metadata(data, parser[0]); const auto labels = parser.option("rm-other-labels").argument(); // replace comma by dash to form the file name std::string strlabels = labels; std::replace(strlabels.begin(), strlabels.end(), ',', '-'); std::vector all_labels = split(labels, ","); for (auto&& img : data.images) { std::vector boxes; for (auto&& b : img.boxes) { if (std::find(all_labels.begin(), all_labels.end(), b.label) != all_labels.end()) boxes.push_back(b); } img.boxes = boxes; } save_image_dataset_metadata(data, parser[0] + ".rm-other-labels-"+ strlabels +".xml"); return EXIT_SUCCESS; } if (parser.option("rmignore")) { if (parser.number_of_arguments() != 1) { cerr << "The --rmignore option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } dlib::image_dataset_metadata::dataset data; load_image_dataset_metadata(data, parser[0]); for (auto&& img : data.images) { std::vector boxes; for (auto&& b : img.boxes) { if (!b.ignore) boxes.push_back(b); } img.boxes = boxes; } save_image_dataset_metadata(data, parser[0] + ".rmignore.xml"); return EXIT_SUCCESS; } if (parser.option("rm-if-overlaps")) { if (parser.number_of_arguments() != 1) { cerr << "The --rm-if-overlaps option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } dlib::image_dataset_metadata::dataset data; load_image_dataset_metadata(data, parser[0]); const auto label = parser.option("rm-if-overlaps").argument(); test_box_overlap overlaps(0.5); for (auto&& img : data.images) { std::vector boxes; for (auto&& b : img.boxes) { if (b.label != label) { boxes.push_back(b); } else { bool has_overlap = false; for (auto&& b2 : img.boxes) { if (b2.label != label && overlaps(b2.rect, b.rect)) { has_overlap = true; break; } } if (!has_overlap) boxes.push_back(b); } } img.boxes = boxes; } save_image_dataset_metadata(data, parser[0] + ".rm-if-overlaps-"+label+".xml"); return EXIT_SUCCESS; } if (parser.option("rmdupes")) { if (parser.number_of_arguments() != 1) { cerr << "The --rmdupes option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } dlib::image_dataset_metadata::dataset data, data_out; std::set hashes; load_image_dataset_metadata(data, parser[0]); data_out = data; data_out.images.clear(); for (unsigned long i = 0; i < data.images.size(); ++i) { ifstream fin(data.images[i].filename.c_str(), ios::binary); string hash = md5(fin); if (hashes.count(hash) == 0) { hashes.insert(hash); data_out.images.push_back(data.images[i]); } } save_image_dataset_metadata(data_out, parser[0]); return EXIT_SUCCESS; } if (parser.option("rmtrunc")) { if (parser.number_of_arguments() != 1) { cerr << "The --rmtrunc option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } dlib::image_dataset_metadata::dataset data; load_image_dataset_metadata(data, parser[0]); { locally_change_current_dir chdir(get_parent_directory(file(parser[0]))); for (unsigned long i = 0; i < data.images.size(); ++i) { array2d img; load_image(img, data.images[i].filename); const rectangle area = get_rect(img); for (unsigned long j = 0; j < data.images[i].boxes.size(); ++j) { if (!area.contains(data.images[i].boxes[j].rect)) data.images[i].boxes[j].ignore = true; } } } save_image_dataset_metadata(data, parser[0]); return EXIT_SUCCESS; } if (parser.option("l")) { if (parser.number_of_arguments() != 1) { cerr << "The -l option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } dlib::image_dataset_metadata::dataset data; load_image_dataset_metadata(data, parser[0]); print_all_labels(data); return EXIT_SUCCESS; } if (parser.option("files")) { if (parser.number_of_arguments() != 1) { cerr << "The --files option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } dlib::image_dataset_metadata::dataset data; load_image_dataset_metadata(data, parser[0]); for (size_t i = 0; i < data.images.size(); ++i) cout << data.images[i].filename << "\n"; return EXIT_SUCCESS; } if (parser.option("split")) { return split_dataset(parser); } if (parser.option("shuffle")) { if (parser.number_of_arguments() != 1) { cerr << "The --shuffle option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } dlib::image_dataset_metadata::dataset data; load_image_dataset_metadata(data, parser[0]); const string default_seed = cast_to_string(time(0)); const string seed = get_option(parser, "seed", default_seed); dlib::rand rnd(seed); randomize_samples(data.images, rnd); save_image_dataset_metadata(data, parser[0]); return EXIT_SUCCESS; } if (parser.option("sort-num-objects")) { if (parser.number_of_arguments() != 1) { cerr << "The --sort-num-objects option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } dlib::image_dataset_metadata::dataset data; load_image_dataset_metadata(data, parser[0]); std::sort(data.images.rbegin(), data.images.rend(), [](const image_dataset_metadata::image& a, const image_dataset_metadata::image& b) { return a.boxes.size() < b.boxes.size(); }); save_image_dataset_metadata(data, parser[0]); return EXIT_SUCCESS; } if (parser.option("sort")) { if (parser.number_of_arguments() != 1) { cerr << "The --sort option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } dlib::image_dataset_metadata::dataset data; load_image_dataset_metadata(data, parser[0]); std::sort(data.images.begin(), data.images.end(), [](const image_dataset_metadata::image& a, const image_dataset_metadata::image& b) { return a.filename < b.filename; }); save_image_dataset_metadata(data, parser[0]); return EXIT_SUCCESS; } if (parser.option("stats")) { if (parser.number_of_arguments() != 1) { cerr << "The --stats option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } dlib::image_dataset_metadata::dataset data; load_image_dataset_metadata(data, parser[0]); print_all_label_stats(data); return EXIT_SUCCESS; } if (parser.option("rename")) { if (parser.number_of_arguments() != 1) { cerr << "The --rename option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } dlib::image_dataset_metadata::dataset data; load_image_dataset_metadata(data, parser[0]); for (unsigned long i = 0; i < parser.option("rename").count(); ++i) { rename_labels(data, parser.option("rename").argument(0,i), parser.option("rename").argument(1,i)); } save_image_dataset_metadata(data, parser[0]); return EXIT_SUCCESS; } if (parser.option("ignore")) { if (parser.number_of_arguments() != 1) { cerr << "The --ignore option requires you to give one XML file on the command line." << endl; return EXIT_FAILURE; } dlib::image_dataset_metadata::dataset data; load_image_dataset_metadata(data, parser[0]); for (unsigned long i = 0; i < parser.option("ignore").count(); ++i) { ignore_labels(data, parser.option("ignore").argument()); } save_image_dataset_metadata(data, parser[0]+".ignored.xml"); return EXIT_SUCCESS; } if (parser.number_of_arguments() == 1) { metadata_editor editor(parser[0]); if (parser.option("parts")) { std::vector parts = split(parser.option("parts").argument()); for (unsigned long i = 0; i < parts.size(); ++i) { editor.add_labelable_part_name(parts[i]); } } editor.wait_until_closed(); return EXIT_SUCCESS; } cout << "Invalid command, give -h to see options." << endl; return EXIT_FAILURE; } catch (exception& e) { cerr << e.what() << endl; return EXIT_FAILURE; } } // ----------------------------------------------------------------------------------------