method 5 DONE

This commit is contained in:
Phuntsok Drak-pa 2019-04-14 03:58:49 +02:00
parent 51bac1bdca
commit 8a34a40600
4 changed files with 326 additions and 118 deletions

View File

@ -1,29 +1,28 @@
#pragma once
#include <filesystem>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <spdlog/spdlog.h>
#include <string>
#include <thread>
#include <tuple>
#include <vector>
class ImageManipulator {
public:
ImageManipulator() = delete;
ImageManipulator(const ImageManipulator& other);
ImageManipulator(ImageManipulator&& other) noexcept;
[[nodiscard]] auto operator=(const ImageManipulator& other)
-> ImageManipulator;
[[nodiscard]] auto operator=(ImageManipulator&& other) noexcept
-> ImageManipulator;
// Load image from input, and prepare for output
ImageManipulator(std::filesystem::path const t_input_path,
std::filesystem::path const t_output_path,
/// \brief Copy contructor
ImageManipulator(const ImageManipulator& other);
/// \brief Move constructor
ImageManipulator(ImageManipulator&& other) noexcept;
/// \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);
/// \brief Basically makes views from image
ImageManipulator(cv::Mat const& t_origin_image,
int const iterations,
int const t_x,
@ -31,57 +30,96 @@ class ImageManipulator {
int const t_width,
int const t_height);
/// \brief Copy assignment operator
[[nodiscard]] auto operator=(const ImageManipulator& other)
-> ImageManipulator;
/// \brief Move assignment operator
[[nodiscard]] auto operator=(ImageManipulator&& other) noexcept
-> ImageManipulator;
/// \brief Execute the nth method on the current object
void exec_method(int const t_nb_method,
bool const t_controlled_size,
int const t_cols,
int const t_rows,
int const t_submethod);
/// \brief Write the generated image to the output path
void write_file() const;
/// \brief Returns a reference to the generated image
[[nodiscard]] auto const& get_generated_image() const noexcept
{
return generated_image_;
}
//! Destructor
/// \brief Destructor
virtual ~ImageManipulator() noexcept = default;
protected:
private:
/// \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;
/// \brief Generates controlled random square coordinates
[[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 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 t_h);
void adjust_size(cv::Point& t_top_left, int const size) noexcept;
/// \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;
/// \brief Update this objects generated image
void update_gen_image(cv::Mat const& t_img, double const t_diff);
void merge_tiles(std::vector<std::vector<ImageManipulator>> 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,
int cols,
int const cols,
int const rows,
int const submethod);
std::vector<std::array<uchar, 3>> colors_
= std::vector<std::array<uchar, 3>>{};
cv::Mat const reference_;
= std::vector<std::array<uchar, 3>>{}; /*!< Color set from reference */
cv::Mat const reference_; /*!< Reference image */
cv::Mat generated_image_
= cv::Mat{reference_.size().height, reference_.size().width, CV_8UC3,
cv::Scalar(0, 0, 0)};
mutable std::mutex colors_mutex_ = std::mutex{};
std::string const output_path_{""};
double diff_ = 0.0;
int const total_iterations_ = 0;
int remaining_iter_ = total_iterations_;
int const width_ = reference_.size().width;
int const height_ = reference_.size().height;
cv::Scalar(0, 0, 0)}; /*!< Working, generated image */
mutable std::mutex colors_mutex_
= std::mutex{}; /*!< Thread mutex for color set generation */
std::string const output_path_{""}; /*!< Write path for the generated image */
double diff_ = euclidian_distance(generated_image_); /*!< Euclidian difference
between \ref reference_ and \ref generated_image_ */
int const total_iterations_ = 0; /*!< Number of iterations to perform */
int remaining_iter_
= total_iterations_; /*!< Remaining iterations to perform */
int const width_ = reference_.size().width; /*!< Width of the image */
int const height_ = reference_.size().height; /*!< Height of the image */
};

View File

@ -3,6 +3,7 @@
#include <filesystem>
#include <tuple>
/// \brief Parses the arguments passed to the program
[[nodiscard]] auto parse_args(int, char**) -> std::tuple<std::filesystem::path,
std::filesystem::path,
int,

View File

@ -3,56 +3,21 @@
#include <array>
#include <cmath>
#include <cstdlib>
#include <functional>
#include <future>
#include <iostream>
#include <optional>
#include <thread>
#include <utility>
auto const thread_nbr = std::thread::hardware_concurrency();
///////////////////////////////////////////////////////////////////////////////
// class implementation //
///////////////////////////////////////////////////////////////////////////////
// constructors ///////////////////////////////////////////////////////////////
ImageManipulator::ImageManipulator(std::filesystem::path const t_input_path,
std::filesystem::path const t_output_path,
int const t_iterations)
: reference_{cv::imread(t_input_path.native(), cv::IMREAD_COLOR)},
output_path_{t_output_path.native()},
diff_{euclidian_distance(generated_image_)},
total_iterations_{t_iterations}
{
if (!reference_.data) {
spdlog::critical("Could not open or find image!\n");
exit(-1);
}
spdlog::debug("Image loaded!");
spdlog::debug("Width:\t{}", reference_.size().width);
spdlog::debug("Height:\t{}", reference_.size().height);
}
ImageManipulator::ImageManipulator(cv::Mat const& t_origin_image,
int const t_iterations,
int const t_x,
int const t_y,
int const t_width,
int const t_height)
: 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)})},
diff_{euclidian_distance(generated_image_)},
total_iterations_{t_iterations}
{
if (!reference_.data) {
spdlog::critical("Could not open or find image!\n");
exit(-1);
}
spdlog::debug("Image loaded!");
spdlog::debug("Width:\t{}", reference_.size().width);
spdlog::debug("Height:\t{}", reference_.size().height);
}
/**
* Copy constructor of \ref ImageManipulator, will copy all of its members
* except for its mutex.
*
* \param[in] other Element to copy
*/
ImageManipulator::ImageManipulator(const ImageManipulator& other)
: colors_{other.colors_},
reference_{other.reference_},
@ -66,6 +31,12 @@ ImageManipulator::ImageManipulator(const ImageManipulator& other)
{
}
/**
* Move constructor of \ref ImageManipulator, will move all of the inputs
* members except for its mutex, a new one will be made.
*
* \param[in] other Element to move
*/
ImageManipulator::ImageManipulator(ImageManipulator&& other) noexcept
: colors_{std::move(other.colors_)},
reference_{std::move(other.reference_)},
@ -79,13 +50,78 @@ ImageManipulator::ImageManipulator(ImageManipulator&& other) noexcept
{
}
/**
* Creates an instance of \ref ImageManipulator based on an input path and an
* output path. It will load the input image from its first argument, and will
* write an output image when asked at the path passed as its second argument.
*
* \param[in] t_input_path Path for the input, reference image
* \param[in] t_output_path Path to the output image to write
*/
ImageManipulator::ImageManipulator(std::string const t_input_path,
std::string const t_output_path,
int const t_iterations)
: reference_{cv::imread(t_input_path, cv::IMREAD_COLOR)},
output_path_{t_output_path},
total_iterations_{t_iterations}
{
if (!reference_.data) {
spdlog::critical("Could not open or find image!\n");
exit(-1);
}
}
/**
* Creates a view of the input image, and will generate an image based only on
* that view.
*
* \param[in] t_origin_image Image to create a view from
* \param[in] t_iterations Number of iterations to perform on this view
* \param[in] t_x X value of the views origin (top left)
* \param[in] t_y Y value of the views origin (top left)
* \param[in] t_width Width of the view from its origin
* \param[in] t_height Height of the view from its origin
*/
ImageManipulator::ImageManipulator(cv::Mat const& t_origin_image,
int const t_iterations,
int const t_x,
int const t_y,
int const t_width,
int const t_height)
: 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)})},
total_iterations_{t_iterations}
{
if (!reference_.data) {
spdlog::critical("Could not open or find image!\n");
exit(-1);
}
}
// operators //////////////////////////////////////////////////////////////////
/**
* Copy assignment operator, will copy all of the inputs members except for
* its mutex, a new one will be generated.
*
* \param[in] other Element to copy
* \return ImageManipulator
*/
[[nodiscard]] auto ImageManipulator::operator=(const ImageManipulator& other)
-> ImageManipulator
{
return ImageManipulator(other);
}
/**
* Move assignment operator, will move all of the inputs members except for
* its mutex, a new one will be generated.
*
* \param[in] other Element to move
* \return ImageManipulator
*/
[[nodiscard]] auto ImageManipulator::operator=(
ImageManipulator&& other) noexcept -> ImageManipulator
{
@ -93,11 +129,25 @@ ImageManipulator::ImageManipulator(ImageManipulator&& other) noexcept
}
// public methods /////////////////////////////////////////////////////////////
/**
* Execute one of the methods as described in the report. If a non-valid
* method is called, the program will be terminated. The argument
* `t_controlled_size` allows the program to have some control over the random
* size of the squares that will be generated. The arguments `t_cols`, `t_rows`
* and `submethod` are relevant to the fifth method.
*
* \param[in] t_nb_method Method identifier
* \param[in] t_controlled_size Control over the squares size
* \param[in] t_cols Number of columns the reference should be divided into
* \param[in] t_rows Number of rows the reference should be divided into
* \param[in] t_submethod
*/
void ImageManipulator::exec_method(int const t_nb_method,
bool const t_controlled_size = false,
int const cols = 1,
int const rows = 0,
int const submethod = 1)
int const t_cols = 1,
int const t_rows = 0,
int const t_submethod = 1)
{
switch (t_nb_method) {
case 1: {
@ -117,7 +167,7 @@ void ImageManipulator::exec_method(int const t_nb_method,
break;
}
case 5: {
method5(t_controlled_size, cols, rows, submethod);
method5(t_controlled_size, t_cols, t_rows, t_submethod);
break;
}
default:
@ -126,6 +176,10 @@ void ImageManipulator::exec_method(int const t_nb_method,
}
}
/**
* Write the generated image as a file to the specified path stored in the
* object
*/
void ImageManipulator::write_file() const
{
cv::imwrite(output_path_, generated_image_);
@ -133,6 +187,13 @@ void ImageManipulator::write_file() const
// private methods ////////////////////////////////////////////////////////////
/**
* Calculates the euclidian distance between the reference image and the image
* passed as an argument
*
* \param t_img Image with which the distance is computed
* \return double
*/
[[nodiscard]] auto ImageManipulator::euclidian_distance(
cv::Mat const& t_img) const noexcept -> double
{
@ -145,11 +206,19 @@ void ImageManipulator::write_file() const
return std::sqrt(euclidian);
}
/**
* \return cv::Scalar
*/
[[nodiscard]] auto ImageManipulator::random_color() const noexcept
{
return cv::Scalar(rand() % 255, rand() % 255, rand() % 255);
}
/**
* Generates random x/y coordinates for a square and the size of said square.
*
* \return Tuple of three ints
*/
[[nodiscard]] auto ImageManipulator::get_square_values() const noexcept
{
int rand_x = rand() % reference_.size().width;
@ -160,6 +229,13 @@ void ImageManipulator::write_file() const
return std::tuple<int, int, int>(rand_x, rand_y, size);
}
/**
* Generates random x/y coordinates for a squares origin (top left), and a
* random size whichs max and minimal value are controled as mentionned in the
* [report](https://labs.phundrak.fr/phundrak/genetic-images/blob/master/report/report.pdf).
*
* \return Tuple of three ints
*/
[[nodiscard]] auto ImageManipulator::get_controlled_square_values() const
noexcept
{
@ -177,6 +253,15 @@ void ImageManipulator::write_file() const
return std::tuple<int, int, int>(rand_x, rand_y, size);
}
/**
* Creates a temporary image on which a random square is drawn. If its
* euclidian distance with the reference image proves to be an improvement from
* the latest improvement before, then both the image and the distance are
* returned. Otherwise, nothing is returned.
*
* \param[in] t_controlled_size Enables controlled square size
* \return Optional pair of cv::Mat and double
*/
[[nodiscard]] auto ImageManipulator::create_candidate(
bool const t_controlled_size = false) const
{
@ -196,6 +281,33 @@ void ImageManipulator::write_file() const
: std::nullopt;
}
[[nodiscard]] auto ImageManipulator::generate_tiles(int const t_cols,
int const t_rows) const
{
std::vector<std::vector<ImageManipulator>> tiles{};
int const tile_width = reference_.cols / t_cols;
int const tile_height = reference_.rows / t_rows;
for (int index_x = 0; index_x < t_cols; ++index_x) {
std::vector<ImageManipulator> tile_col{};
for (int index_y = 0; index_y < t_rows; ++index_y) {
int const width = (index_x != t_cols - 1)
? tile_width
: tile_width + reference_.cols % tile_width;
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);
}
tiles.push_back(tile_col);
}
return tiles;
}
/**
* Will analyse the reference image and will store each color found in member
* variable \ref colors_. Works on multithreading.
*/
void ImageManipulator::get_color_set()
{
for (int h = 0; h < reference_.size().height; h += thread_nbr) {
@ -211,6 +323,12 @@ void ImageManipulator::get_color_set()
colors_.shrink_to_fit();
}
/**
* Will search for every color found in its designated column. If a new color
* is found, pauses all its other similar threads, adds the new color in \ref
* colors_, then resumes the other threads. Helper function for \ref
* get_color_set
*/
void ImageManipulator::threaded_get_color(int t_h)
{
if (t_h > reference_.size().height) {
@ -229,19 +347,15 @@ void ImageManipulator::threaded_get_color(int t_h)
}
}
void ImageManipulator::adjust_size(cv::Point& t_top_left,
int const size) noexcept
{
int const shape_total_width = t_top_left.x + size;
int const shape_total_height = t_top_left.y + size;
if (int const diff = shape_total_height + height_; diff > 0) {
t_top_left.x += diff + 1;
}
if (int const diff = shape_total_width + width_; diff > 0) {
t_top_left.x += diff + 1;
}
}
/**
* Draw a square on the image passed as its argument, following its passed
* coordinates and with the passed color.
*
* \param[out] t_img Image to draw the square to
* \param[in] t_top_left Origin of the square
* \param[in] t_size Size of the square
* \param[in] t_color Color of the square
*/
void ImageManipulator::draw_square(cv::Mat& t_img,
cv::Point const& t_top_left,
int const t_size,
@ -255,6 +369,16 @@ void ImageManipulator::draw_square(cv::Mat& t_img,
fillConvexPoly(t_img, points.get(), 4, t_color);
}
/**
* Updates the objects current generated image and difference with its
* reference by replacing them with the arguments passed in this function. This
* function should only be called if the passed elements are improving the
* generated image and reduce the euclidian distance between said image and its
* reference.
*
* \param[in] t_img Image to replace \ref generated_image_
* \param[in] t_diff New euclidian distance
*/
void ImageManipulator::update_gen_image(cv::Mat const& t_img,
double const t_diff)
{
@ -264,6 +388,22 @@ void ImageManipulator::update_gen_image(cv::Mat const& t_img,
spdlog::debug("remaining iter: {}\tdiff: {}", remaining_iter_, diff_);
}
void ImageManipulator::merge_tiles(
std::vector<std::vector<ImageManipulator>> t_tiles)
{
std::vector<cv::Mat> columns{};
for (auto const& col : t_tiles) {
std::vector<cv::Mat> column_arr{};
cv::Mat column_img{};
for (auto const& tile : col) {
column_arr.push_back(tile.get_generated_image());
}
vconcat(column_arr, column_img);
columns.push_back(std::move(column_img));
}
hconcat(columns, generated_image_);
}
void ImageManipulator::method1()
{
spdlog::debug("Beginning method1, initial difference: {}", diff_);
@ -311,6 +451,9 @@ void ImageManipulator::method3()
}
}
/**
* \param[in] t_controlled_size Enables control over the random squares size
*/
void ImageManipulator::method4(bool const t_controlled_size)
{
spdlog::debug("Beginning method4, initial difference: {}", diff_);
@ -334,45 +477,50 @@ void ImageManipulator::method4(bool const t_controlled_size)
}
}
if (values.size() > 0) {
size_t best = 0;
for (size_t i = 0; i < values.size(); ++i) {
if (values[i].second < values[best].second) {
best = i;
}
}
update_gen_image(values[best].first, values[best].second);
auto const pos
= std::min_element(std::begin(values), std::end(values),
[](const auto& elem1, const auto& elem2) {
return elem1.second < elem2.second;
});
update_gen_image(pos->first, pos->second);
// size_t best = 0;
// for (size_t i = 0; i < values.size(); ++i) {
// if (values[i].second < values[best].second) {
// best = i;
// }
// }
// update_gen_image(values[best].first, values[best].second);
}
}
}
/**
* \param[in] t_controlled_size Enables control over the random squares size
* \param[in] t_cols Number of colomns the reference should be divided into
* \param[in] t_rows Number of rows the reference should be divided into
* \param[in] t_submethod Method to be used on each tile
*/
void ImageManipulator::method5(bool const t_controlled_size,
int cols,
int const rows,
int const submethod)
int const t_cols,
int const t_rows,
int const t_submethod)
{
spdlog::debug("Beginning method5, initial difference: {}", diff_);
spdlog::debug("nb_total_iter: {}, nb_remaining_iter: {}", total_iterations_,
remaining_iter_);
spdlog::debug("Running on {} threads", thread_nbr);
if (cols == 0) {
cols = rows;
}
std::vector<std::vector<ImageManipulator>> tiles{};
int const tile_width = reference_.cols / cols;
int const tile_height = reference_.rows / rows;
spdlog::debug("tile: width:{}\theight:{}", tile_width, tile_height);
spdlog::debug("image: width:{}\theight:{}", reference_.cols, reference_.rows);
for (int i = 0; i < rows; ++i) {
std::vector<ImageManipulator> tile_row{};
for (int j = 0; j < cols; ++j) {
tile_row.emplace_back(
reference_, total_iterations_, i * tile_height, j * tile_width,
(i != rows - 1) ? tile_height
: tile_height + reference_.cols % tile_height,
(j != cols - 1) ? tile_width
: tile_width + reference_.cols % tile_width);
}
tiles.push_back(tile_row);
}
auto tiles = generate_tiles((t_cols != 0) ? t_cols : t_rows, t_rows);
spdlog::debug("{} tiles", tiles.size());
std::vector<std::thread> thread_list{};
for (auto& row : tiles) {
for (auto& tile : row) {
thread_list.emplace_back(
[&]() { tile.exec_method(t_submethod, t_controlled_size); });
}
}
for (auto& th : thread_list) {
th.join();
}
merge_tiles(tiles);
}

View File

@ -1,6 +1,5 @@
#include "parseargs.hh"
#include <boost/program_options.hpp>
#include <cstdlib>
#include <iostream>
constexpr int DEFAULT_ITERATIONS = 2000;
@ -8,11 +7,23 @@ constexpr int DEFAULT_ITERATIONS = 2000;
using path = std::filesystem::path;
namespace po = boost::program_options;
void processFilenames(po::variables_map const& vm,
/**
* \brief Ensures correct output path
*
* Checks if an output file exists, and if yes if it has an extension. In case
* it doesnt exist, `output_` is appended at the beginning of the input
* filename. If the output path does not have an extension, the type `.png` is
* appended at the end of the path.
*
* \param[in] t_vm Arguments passed to the program
* \param[out] t_input Input path
* \param[out] t_output Output path
*/
void processFilenames(po::variables_map const& t_vm,
path const& t_input,
path& t_output)
{
if (!vm.count("output")) {
if (!t_vm.count("output")) {
t_output.replace_filename("output_"
+ std::string{t_input.filename().string()});
}
@ -21,6 +32,16 @@ void processFilenames(po::variables_map const& vm,
}
}
/**
* Parses the arguments given to the program, formats them and returns them as
* a tuple. If `-h` or `--help` or a malformed argument is passed, then the
* list of arguments and their comment will be displayed, and the program will
* exit.
*
* \param[in] t_ac Number of arguments passed to the program
* \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<path, path, int, int, int, int, int, bool, bool>
{