From 012508e5234b1e21f902f7bc460f2de7bcbcdf01 Mon Sep 17 00:00:00 2001 From: Phuntsok Drak-pa Date: Sat, 27 Apr 2019 15:45:39 +0200 Subject: [PATCH] Beginning refactoring, adding triangles to the list of possible shapes --- include/genimg/methods.hh | 43 ++++++++++++++++------ include/genimg/parseargs.hh | 25 ++++++++----- include/genimg/shapes.hh | 63 ++++++++++++++++++++++++++++++++ src/main.cc | 20 +++++----- src/methods.cc | 60 ++++++++++++++++++++++-------- src/parseargs.cc | 73 ++++++++++++++++++++++--------------- src/shapes.cc | 72 ++++++++++++++++++++++++++++++++++++ 7 files changed, 281 insertions(+), 75 deletions(-) create mode 100644 include/genimg/shapes.hh create mode 100644 src/shapes.cc diff --git a/include/genimg/methods.hh b/include/genimg/methods.hh index e705a9f..70815c9 100644 --- a/include/genimg/methods.hh +++ b/include/genimg/methods.hh @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include "shapes.hh" +#include #include #include #include @@ -19,11 +19,13 @@ class ImageManipulator { /// \brief Load image from input, and prepare for output ImageManipulator(std::string const t_input_path, std::string const t_output_path, - int const iterations); + int const iterations, + Shape::ShapeType const t_shape); /// \brief Basically makes views from image ImageManipulator(cv::Mat const& t_origin_image, - int const iterations, + int const t_iterations, + Shape::ShapeType const t_shape, int const t_x, int const t_y, int const t_width, @@ -67,41 +69,59 @@ class ImageManipulator { /// \brief Calculates the euclidian distance between two images [[nodiscard]] auto euclidian_distance(cv::Mat const& t_img) const noexcept -> double; + /// \brief Creates and returns a random color [[nodiscard]] auto random_color() const noexcept; + /// \brief Generates random square coordinates - [[nodiscard]] auto get_square_values() const noexcept; + [[deprecated]] [[nodiscard]] auto get_square_values() const noexcept; + /// \brief Generates controlled random square coordinates - [[nodiscard]] auto get_controlled_square_values() const noexcept; + [[deprecated]] [[nodiscard]] auto get_controlled_square_values() const + noexcept; + /// \brief Generates a candidate for image generation improvement - [[nodiscard]] auto create_candidate(bool const t_controlled_size) const; + [[nodiscard]] auto create_candidate(bool const t_controlled_size); + /// \brief Generates organized views of the reference image for method 5 [[nodiscard]] auto generate_tiles(int const t_cols, int const t_rows) const; + /// \brief Gets all colors from the reference image void get_color_set(); + /// \brief Threaded helper for \ref get_color_set void threaded_get_color(int const t_h); + /// \brief Draw a square on an image - void draw_square(cv::Mat& t_img, - cv::Point const& t_top_left, - int const t_size, - cv::Scalar const& t_color) const; + [[deprecated]] void draw_square(cv::Mat& t_img, + cv::Point const& t_top_left, + int const t_size, + cv::Scalar const& t_color) const; + + void draw_shape(cv::Mat& t_img, cv::Scalar&& t_color); + /// \brief Update this object’s generated image void update_gen_image(cv::Mat const& t_img, double const t_diff); + /// \brief Merges tiles generated by method5 void merge_tiles(std::vector> const& t_tiles); + /// \brief First method as described in the /// [report](https://labs.phundrak.fr/phundrak/genetic-images/blob/master/report/report.pdf) void method1(); + /// \brief Second method as described in the /// [report](https://labs.phundrak.fr/phundrak/genetic-images/blob/master/report/report.pdf) void method2(); + /// \brief Third method as described in the /// [report](https://labs.phundrak.fr/phundrak/genetic-images/blob/master/report/report.pdf) void method3(); + /// \brief Fourth method as described in the /// [report](https://labs.phundrak.fr/phundrak/genetic-images/blob/master/report/report.pdf) void method4(bool const t_controlled_size); + /// \brief Fifth method as described in the /// [report](https://labs.phundrak.fr/phundrak/genetic-images/blob/master/report/report.pdf) void method5(bool const t_controlled_size, @@ -116,6 +136,7 @@ class ImageManipulator { cv::Mat generated_image_{ reference_.size().height, reference_.size().width, CV_8UC3, cv::Scalar(0, 0, 0)}; /*!< Working, generated image */ + Shape shape_{Shape::ShapeType::Square}; mutable std::mutex colors_mutex_{}; /*!< Thread mutex for color set generation */ std::string const output_path_{}; /*!< Write path for the generated image */ diff --git a/include/genimg/parseargs.hh b/include/genimg/parseargs.hh index 873ae46..ecd40d4 100644 --- a/include/genimg/parseargs.hh +++ b/include/genimg/parseargs.hh @@ -1,15 +1,20 @@ #pragma once #include -#include +#include "shapes.hh" + +struct ParsedArgs { + std::filesystem::path input_path; + std::filesystem::path output_path; + Shape::ShapeType shape; + int iterations; + int method; + int cols; + int rows; + int submethod; + bool controlled_size; + bool verbose; +}; /// \brief Parses the arguments passed to the program -[[nodiscard]] auto parse_args(int, char**) -> std::tuple; +[[nodiscard]] auto parse_args(int, char**) -> ParsedArgs; diff --git a/include/genimg/shapes.hh b/include/genimg/shapes.hh new file mode 100644 index 0000000..abc9f5b --- /dev/null +++ b/include/genimg/shapes.hh @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include + +class Shape { + public: + static constexpr int MAX_POINTS{4}; + enum class ShapeType { Square, Triangle }; + + /// \brief Default constructor + Shape() = delete; + + Shape(Shape::ShapeType const t_type); + + /// \brief Copy constructor + Shape(const Shape& other) = default; + + /// \brief Move constructor + Shape(Shape&& other) noexcept; + + /// \brief Destructor + virtual ~Shape() noexcept = default; + + /// \brief Copy assignment operator + Shape& operator=(const Shape& other) = delete; + + /// \brief Move assignment operator + Shape& operator=(Shape&& other) noexcept = delete; + + /// \brief Generates a shape's points + void operator()(cv::Point&& t_max_pos, + int const t_max_size, + int const t_min_size = 0) noexcept; + + [[nodiscard]] auto get_points() const noexcept + -> std::array const& + { + return points_; + } + + /// \brief Returns the type of shape described by the object + [[nodiscard]] auto get_type() const noexcept -> ShapeType const& + { + return type_; + } + + [[nodiscard]] auto get_nb_points() const noexcept { return nb_points_; } + + protected: + private: + void create_square_points(cv::Point const& t_top_left, + int const t_size) noexcept; + void create_triangle_points(cv::Point const& t_top_left, + int const t_size) noexcept; + + + ShapeType const type_{ShapeType::Square}; + std::array points_{ + cv::Point{0, 0}, cv::Point{0, 0}, cv::Point{0, 0}, cv::Point{0, 0}}; + int nb_points_{Shape::MAX_POINTS}; +}; diff --git a/src/main.cc b/src/main.cc index 9a940f1..ba0fcaf 100644 --- a/src/main.cc +++ b/src/main.cc @@ -7,16 +7,18 @@ int main(int ac, char** av) { std::srand(std::time(nullptr)); - auto const [input_file, output_file, iterations, method, cols, rows, - submethod, controlled_size, verbose] - = parse_args(ac, av); - spdlog::set_level(verbose ? spdlog::level::debug : spdlog::level::info); + auto const arguments = parse_args(ac, av); + spdlog::set_level(arguments.verbose ? spdlog::level::debug + : spdlog::level::info); spdlog::set_pattern("[thread %t] %+"); - spdlog::debug("Input file:\t{}", input_file.native()); - spdlog::debug("Output file:\t{}", output_file.native()); - spdlog::debug("Iterations:\t{}", iterations); + spdlog::debug("Input file:\t{}", arguments.input_path.native()); + spdlog::debug("Output file:\t{}", arguments.output_path.native()); + spdlog::debug("Iterations:\t{}", arguments.iterations); - ImageManipulator image_process{input_file, output_file, iterations}; - image_process.exec_method(method, controlled_size, cols, rows, submethod); + ImageManipulator image_process{arguments.input_path, arguments.output_path, + arguments.iterations, arguments.shape}; + image_process.exec_method(arguments.method, arguments.controlled_size, + arguments.cols, arguments.rows, + arguments.submethod); image_process.write_file(); } diff --git a/src/methods.cc b/src/methods.cc index f4bd6da..02bf8bd 100644 --- a/src/methods.cc +++ b/src/methods.cc @@ -1,6 +1,5 @@ #include "methods.hh" #include -#include #include #include #include @@ -22,6 +21,7 @@ ImageManipulator::ImageManipulator(const ImageManipulator& other) : colors_{other.colors_}, reference_{other.reference_}, generated_image_{other.generated_image_}, + shape_{other.shape_}, output_path_{other.output_path_}, diff_{other.diff_}, total_iterations_{other.total_iterations_}, @@ -41,6 +41,7 @@ ImageManipulator::ImageManipulator(ImageManipulator&& other) noexcept : colors_{std::move(other.colors_)}, reference_{std::move(other.reference_)}, generated_image_{std::move(other.generated_image_)}, + shape_{std::move(other.shape_)}, output_path_{std::move(other.output_path_)}, diff_{std::move(other.diff_)}, total_iterations_{other.total_iterations_}, @@ -60,8 +61,10 @@ ImageManipulator::ImageManipulator(ImageManipulator&& other) noexcept */ ImageManipulator::ImageManipulator(std::string const t_input_path, std::string const t_output_path, - int const t_iterations) + int const t_iterations, + Shape::ShapeType const t_shape) : reference_{cv::imread(t_input_path, cv::IMREAD_COLOR)}, + shape_{Shape{t_shape}}, output_path_{t_output_path}, total_iterations_{t_iterations} { @@ -84,6 +87,7 @@ ImageManipulator::ImageManipulator(std::string const t_input_path, */ ImageManipulator::ImageManipulator(cv::Mat const& t_origin_image, int const t_iterations, + Shape::ShapeType const t_shape, int const t_x, int const t_y, int const t_width, @@ -91,6 +95,7 @@ ImageManipulator::ImageManipulator(cv::Mat const& t_origin_image, : reference_{t_origin_image( cv::Range{t_y, std::min(t_y + t_height, t_origin_image.rows)}, cv::Range{t_x, std::min(t_x + t_width, t_origin_image.cols)})}, + shape_{Shape{t_shape}}, total_iterations_{t_iterations} { if (!reference_.data) { @@ -229,21 +234,39 @@ void ImageManipulator::exec_method(int const t_nb_method, * \return Optional pair of cv::Mat and double */ [[nodiscard]] auto ImageManipulator::create_candidate( - bool const t_controlled_size = false) const + bool const t_controlled_size = false) { - auto temp_image = generated_image_.clone(); - auto const [rand_x, rand_y, size] = t_controlled_size - ? get_controlled_square_values() - : get_square_values(); + auto temp_img = generated_image_.clone(); + auto create_shape = [&]() { + return shape_(cv::Point{reference_.size().width, reference_.size().height}, + std::min(reference_.size().width, reference_.size().height)); + }; + auto create_controlled_shape = [&]() { + float const coef = static_cast(remaining_iter_) + / static_cast(total_iterations_); + int const min_size = static_cast( + (static_cast( + std::min(reference_.size().width, reference_.size().height)) + / 2.0f) + * coef); + int const max_size = min_size * 2 + 1; + return shape_(cv::Point{reference_.size().width, reference_.size().height}, + max_size, min_size); + }; auto const& color = colors_[rand() % colors_.size()]; - draw_square( - temp_image, cv::Point{rand_x, rand_y}, size, - cv::Scalar{static_cast(color[0]), static_cast(color[1]), - static_cast(color[2])}); - auto new_diff = euclidian_distance(temp_image); + if (t_controlled_size) { + create_shape(); + } + else { + create_controlled_shape(); + } + draw_shape(temp_img, cv::Scalar{static_cast(color[0]), + static_cast(color[1]), + static_cast(color[2])}); + auto new_diff = euclidian_distance(temp_img); return (new_diff < diff_) ? std::optional>{std::make_pair( - std::move(temp_image), new_diff)} + std::move(temp_img), new_diff)} : std::nullopt; } @@ -270,8 +293,9 @@ void ImageManipulator::exec_method(int const t_nb_method, int const height = (index_y != t_rows - 1) ? tile_height : tile_height + reference_.rows % tile_height; - tile_col.emplace_back(reference_, total_iterations_, index_x * tile_width, - index_y * tile_height, width, height); + tile_col.emplace_back(reference_, total_iterations_, shape_.get_type(), + index_x * tile_width, index_y * tile_height, width, + height); } tiles.push_back(tile_col); } @@ -341,6 +365,12 @@ void ImageManipulator::draw_square(cv::Mat& t_img, fillConvexPoly(t_img, points.data(), 4, t_color); } +void ImageManipulator::draw_shape(cv::Mat& t_img, cv::Scalar&& t_color) +{ + fillConvexPoly(t_img, shape_.get_points().data(), shape_.get_nb_points(), + t_color); +} + /** * Updates the object’s current generated image and difference with its * reference by replacing them with the arguments passed in this function. This diff --git a/src/parseargs.cc b/src/parseargs.cc index 8ac44e6..7bbee2b 100644 --- a/src/parseargs.cc +++ b/src/parseargs.cc @@ -42,29 +42,29 @@ void processFilenames(po::variables_map const& t_vm, * \param[in] t_av Arguments passed to the program * \return Tuple of path, path, int, int, int, int, int, bool and bool */ -[[nodiscard]] auto parse_args(int t_ac, char** t_av) - -> std::tuple +[[nodiscard]] auto parse_args(int t_ac, char** t_av) -> ParsedArgs { + ParsedArgs ret{}; po::options_description desc("Allowed options"); - desc.add_options() - ("help,h", "Display this help message") - ("input,i", po::value(), "Input image") - ("output,o", po::value(), - "Image output path (default: \"output_\" + input path)") - ("iterations,n", po::value(), "Number of iterations (default: 2000)") - ("method,m", po::value(), "Method number to be used (default: 1)") - ("cols,c", po::value(), - "For method 5 only, number of columns the reference image should be " - "divided into. If the value is equal to 0, then it will be assumed " - "there will be as many rows as there are collumns. (default: 0)") - ("rows,r", po::value(), - "For method 5 only, number of rows the reference image should be " - "divided into. (default: 1)") - ("submethod,S", po::value(), - "Sub-method that will be used to generate the individual tiles from " - "method 5. (default: 1)") - ("size,s", "Enables controlled size of the random shapes") - ("verbose,v", "Enables verbosity"); + desc.add_options()("help,h", "Display this help message")( + "input,i", po::value(), "Input image")( + "output,o", po::value(), + "Image output path (default: \"output_\" + input path)")( + "iterations,n", po::value(), "Number of iterations (default: 2000)")( + "method,m", po::value(), "Method number to be used (default: 1)")( + "form,f", po::value(), "Select shape (1:square, 2:triangle)")( + "cols,c", po::value(), + "For method 5 only, number of columns the reference image should be " + "divided into. If the value is equal to 0, then it will be assumed " + "there will be as many rows as there are collumns. (default: 0)")( + "rows,r", po::value(), + "For method 5 only, number of rows the reference image should be " + "divided into. (default: 1)")( + "submethod,S", po::value(), + "Sub-method that will be used to generate the individual tiles from " + "method 5. (default: 1)")("size,s", + "Enables controlled size of the random shapes")( + "verbose,v", "Enables verbosity"); po::variables_map vm; po::store(po::parse_command_line(t_ac, t_av, desc), vm); po::notify(vm); @@ -78,13 +78,26 @@ void processFilenames(po::variables_map const& t_vm, = vm.count("output") ? vm["output"].as() : input_path.filename(); processFilenames(vm, input_path, output_path); - return std::make_tuple( - input_path, output_path, - vm.count("iterations") ? vm["iterations"].as() : DEFAULT_ITERATIONS, - vm.count("method") ? vm["method"].as() : 1, - vm.count("cols") ? vm["cols"].as() : 0, - vm.count("rows") ? vm["rows"].as() : 1, - vm.count("submethod") ? vm["submethod"].as() : 1, - vm.count("size"), - vm.count("verbose")); + ret.input_path = input_path; + ret.output_path = output_path; + ret.iterations = vm.count("iterations") ? vm["iterations"].as() + : DEFAULT_ITERATIONS; + ret.method = vm.count("method") ? vm["method"].as() : 1; + switch (vm.count("form") ? vm["form"].as() : 1) { + case 2: + ret.shape= Shape::ShapeType::Triangle; + break; + case 1: + [[fallthrough]]; + default: + ret.shape = Shape::ShapeType::Square; + break; + } + + ret.cols = vm.count("cols") ? vm["cols"].as() : 0; + ret.rows = vm.count("rows") ? vm["rows"].as() : 1; + ret.submethod = vm.count("submethod") ? vm["submethod"].as() : 1; + ret.controlled_size = vm.count("size"); + ret.verbose = vm.count("verbose"); + return ret; } diff --git a/src/shapes.cc b/src/shapes.cc new file mode 100644 index 0000000..5963f22 --- /dev/null +++ b/src/shapes.cc @@ -0,0 +1,72 @@ +#include "shapes.hh" +#include +#include + +using point_arr = std::array; + +Shape::Shape(Shape::ShapeType const t_type) : type_{t_type} +{ + switch (t_type) { + case ShapeType::Triangle: { + nb_points_ = 3; + break; + } + case ShapeType::Square: + [[fallthrough]]; + default: + nb_points_ = 4; + break; + } +} + +Shape::Shape(Shape&& other) noexcept + : type_{std::move(other.type_)}, + points_{std::move(other.points_)}, + nb_points_{std::move(other.nb_points_)} +{ +} + +/** + * Generates all the needed points for the corresponding shape described in + * \ref type_. + * + * \param t_max_pos Bottom-rightmost point of the image the shape is generated + * for + * \return Array of points describing the shape + */ +void Shape::operator()(cv::Point&& t_max_pos, + int const t_max_size, + int const t_min_size) noexcept +{ + int const size = (rand() % (t_max_size - t_min_size)) + t_min_size; + cv::Point const top_left + = {rand() % (t_max_pos.x - size), rand() % (t_max_pos.y - size)}; + if (type_ == ShapeType::Triangle) { + create_triangle_points(top_left, size); + } + else { // ShapeType::Square + create_square_points(top_left, size); + } +} + + +void Shape::create_triangle_points(cv::Point const& t_top_left, + int const t_size) noexcept +{ + bool top_left = rand() % 1 == 0; + points_ = { + cv::Point{top_left ? t_top_left.x : t_top_left.x + t_size, t_top_left.y}, + cv::Point{top_left ? t_top_left.x + t_size : t_top_left.x, + t_top_left.y + t_size}, + cv::Point{t_top_left.x + rand() % t_size, t_top_left.y + t_size}, + cv::Point{0, 0}}; +} + +void Shape::create_square_points(cv::Point const& t_top_left, + int const t_size) noexcept +{ + points_ = {cv::Point{t_top_left.x, t_top_left.y}, + cv::Point{t_top_left.x, t_top_left.y + t_size}, + cv::Point{t_top_left.x + t_size, t_top_left.y}, + cv::Point{t_top_left.x + t_size, t_top_left.y}}; +}