genetic-images/src/methods.cc

418 lines
16 KiB
C++
Raw Permalink Normal View History

#include "methods.hh"
2019-04-27 14:27:22 +00:00
2019-03-20 19:15:53 +00:00
#include <algorithm>
2019-04-08 00:48:25 +00:00
#include <future>
2019-04-02 08:37:14 +00:00
#include <optional>
2019-04-14 01:58:49 +00:00
#include <thread>
2019-03-21 01:49:00 +00:00
static auto const thread_nbr = std::thread::hardware_concurrency();
///////////////////////////////////////////////////////////////////////////////
// Public //
///////////////////////////////////////////////////////////////////////////////
2019-03-20 19:15:53 +00:00
// constructors ///////////////////////////////////////////////////////////////
2019-04-14 01:58:49 +00:00
/**
* Copy constructor of \ref ImageManipulator, will copy all of its members
* except for its mutex.
*
* \param[in] other Element to copy
*/
2019-04-27 14:27:22 +00:00
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_},
2019-04-14 01:58:49 +00:00
total_iterations_{other.total_iterations_},
2019-04-27 14:27:22 +00:00
remaining_iter_{other.remaining_iter_}, width_{other.width_},
2019-04-14 01:58:49 +00:00
height_{other.height_}
{
}
/**
* 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
*/
2019-04-27 14:27:22 +00:00
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_)},
2019-04-14 01:58:49 +00:00
output_path_{std::move(other.output_path_)},
2019-04-27 14:27:22 +00:00
diff_{std::move(other.diff_)}, total_iterations_{other.total_iterations_},
remaining_iter_{other.remaining_iter_}, width_{other.width_},
2019-04-14 01:58:49 +00:00
height_{other.height_}
{
}
/**
* 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,
Shape::ShapeType const t_shape)
2019-04-27 14:27:22 +00:00
: reference_{cv::imread(t_input_path, cv::IMREAD_COLOR)}, shape_{Shape{
t_shape}},
output_path_{t_output_path}, total_iterations_{t_iterations}
{
2019-04-27 14:27:22 +00:00
if (!reference_.data) {
spdlog::critical("Could not open or find image!\n");
exit(-1);
}
}
2019-04-14 01:58:49 +00:00
/**
* 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
*/
2019-04-27 14:27:22 +00:00
ImageManipulator::ImageManipulator(cv::Mat const &t_origin_image,
int const t_iterations,
Shape::ShapeType const t_shape,
2019-04-27 14:27:22 +00:00
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)})},
2019-04-27 14:27:22 +00:00
shape_{Shape{t_shape}}, total_iterations_{t_iterations}
{
2019-04-27 14:27:22 +00:00
if (!reference_.data) {
spdlog::critical("Could not open or find image!\n");
exit(-1);
}
}
// public methods /////////////////////////////////////////////////////////////
2019-04-14 01:58:49 +00:00
/**
* 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,
2019-04-27 14:27:22 +00:00
int const t_cols = 1, int const t_rows = 0,
2019-04-14 01:58:49 +00:00
int const t_submethod = 1)
2019-04-02 08:37:14 +00:00
{
2019-04-27 14:27:22 +00:00
switch (t_nb_method) {
case 1: method1(); break;
case 2: method2(); break;
case 3: method3(); break;
case 4: method4(t_controlled_size); break;
case 5: method5(t_controlled_size, t_cols, t_rows, t_submethod); break;
default:
spdlog::error("Requested method {} is not implemented.", t_nb_method);
std::exit(-1);
}
2019-04-02 08:37:14 +00:00
}
///////////////////////////////////////////////////////////////////////////////
// Private //
///////////////////////////////////////////////////////////////////////////////
// methods ////////////////////////////////////////////////////////////////////
2019-04-14 01:58:49 +00:00
/**
* \return cv::Scalar
*/
[[nodiscard]] auto ImageManipulator::random_color() const noexcept
2019-04-02 08:37:14 +00:00
{
2019-04-27 14:27:22 +00:00
return cv::Scalar(rand() % 255, rand() % 255, rand() % 255);
}
2019-04-27 14:27:22 +00:00
void ImageManipulator::create_shape() noexcept
{
2019-04-27 16:04:32 +00:00
shape_.update(cv::Point{reference_.size().width, reference_.size().height},
std::min(reference_.size().width, reference_.size().height));
2019-04-27 14:27:22 +00:00
}
void ImageManipulator::create_controlled_shape() noexcept
{
float const coef = static_cast<float>(remaining_iter_)
/ static_cast<float>(total_iterations_);
int const min_size
= static_cast<int>((static_cast<float>(std::min(reference_.size().width,
reference_.size().height))
/ 2.0f)
* coef);
int const max_size = min_size * 2 + 1;
2019-04-27 16:04:32 +00:00
shape_.update(cv::Point{reference_.size().width, reference_.size().height},
max_size, min_size);
2019-04-02 08:54:01 +00:00
}
2019-04-14 01:58:49 +00:00
/**
* 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
*/
2019-04-27 14:27:22 +00:00
[[nodiscard]] auto
ImageManipulator::create_candidate(bool const t_controlled_size = false)
2019-04-02 08:37:14 +00:00
{
2019-04-27 14:27:22 +00:00
auto temp_img = generated_image_.clone();
auto const &color = colors_[rand() % colors_.size()];
if (t_controlled_size) {
create_controlled_shape();
2019-04-28 15:34:46 +00:00
} else {
create_shape();
2019-04-27 14:27:22 +00:00
}
draw_shape(temp_img, cv::Scalar{static_cast<double>(color[0]),
static_cast<double>(color[1]),
static_cast<double>(color[2])});
2019-05-16 21:46:56 +00:00
const auto new_diff = cv::norm(reference_, temp_img);
2019-04-27 14:27:22 +00:00
return (new_diff < diff_)
? std::optional<std::pair<cv::Mat, double>>{std::make_pair(
std::move(temp_img), new_diff)}
: std::nullopt;
2019-04-02 08:37:14 +00:00
}
/**
* Generates views and stores them in a double vector so the tiles (views) are
* stored by column top to bottom, and within the columns left to right.
*
* \param t_cols Number of columns the reference image should be divided into
* \param t_rows Number of rows the reference image should be divided into
* \return Collection of tiles (vector<vector<ImageManipulator>>)
*/
2019-04-14 01:58:49 +00:00
[[nodiscard]] auto ImageManipulator::generate_tiles(int const t_cols,
int const t_rows) const
{
2019-04-27 14:27:22 +00:00
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_, shape_.get_type(),
index_x * tile_width, index_y * tile_height, width,
height);
}
tiles.push_back(tile_col);
}
return tiles;
2019-04-14 01:58:49 +00:00
}
/**
* Will analyse the reference image and will store each color found in member
* variable \ref colors_. Works on multithreading.
*/
void ImageManipulator::get_color_set()
2019-03-28 11:26:05 +00:00
{
2019-04-27 14:27:22 +00:00
for (int h = 0; h < reference_.size().height; h += thread_nbr) {
std::vector<std::thread> thread_list{};
for (auto i = 0u; i < thread_nbr; ++i) {
thread_list.push_back(
std::thread(&ImageManipulator::threaded_get_color, this, h + i));
}
std::for_each(thread_list.begin(), thread_list.end(),
[](auto &th) { th.join(); });
}
colors_.shrink_to_fit();
2019-03-25 11:24:19 +00:00
}
2019-04-14 01:58:49 +00:00
/**
* 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 const t_h)
2019-03-28 11:26:05 +00:00
{
2019-04-27 14:27:22 +00:00
if (t_h > reference_.size().height) {
return;
}
for (int w = 0; w < reference_.size().width; w += 3) {
std::array<uchar, 3> temp
= {reference_.at<uchar>(t_h, w), reference_.at<uchar>(t_h, w + 1),
reference_.at<uchar>(t_h, w + 2)};
auto pos = std::find(std::begin(colors_), std::end(colors_), temp);
if (pos == std::end(colors_)) {
2019-06-07 12:28:56 +00:00
std::lock_guard<std::mutex> lock(colors_mutex_);
2019-04-27 14:27:22 +00:00
colors_.push_back(std::move(temp));
}
}
2019-03-21 01:49:00 +00:00
}
2019-04-27 14:27:22 +00:00
void ImageManipulator::draw_shape(cv::Mat &t_img, cv::Scalar &&t_color)
{
2019-04-27 14:27:22 +00:00
fillConvexPoly(t_img, shape_.get_points().data(), shape_.get_nb_points(),
t_color);
}
2019-04-14 01:58:49 +00:00
/**
* 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
*/
2019-04-27 14:27:22 +00:00
void ImageManipulator::update_gen_image(cv::Mat const &t_img,
double const t_diff)
{
2019-04-27 14:27:22 +00:00
diff_ = t_diff;
t_img.copyTo(generated_image_);
--remaining_iter_;
spdlog::debug("remaining iter: {}\tdiff: {}", remaining_iter_, diff_);
}
2019-03-28 11:26:05 +00:00
/**
* Merges the tiles generated by \ref method5 into a single image. The tiles
* are organized by column top to bottom, within each they are stored in order,
* left to right. They will be merged in \ref generated_image_.
*
* \param t_tiles Collection of tiles to be merged together
*/
2019-04-14 01:58:49 +00:00
void ImageManipulator::merge_tiles(
2019-04-27 14:27:22 +00:00
std::vector<std::vector<ImageManipulator>> const &t_tiles)
2019-04-14 01:58:49 +00:00
{
2019-04-27 14:27:22 +00:00
std::vector<cv::Mat> columns{};
std::for_each(t_tiles.begin(), t_tiles.end(), [&columns](auto const &col) {
std::vector<cv::Mat> column_arr{};
cv::Mat column_img{};
std::for_each(col.begin(), col.end(), [&column_arr](auto const &tile) {
column_arr.push_back(tile.get_generated_image());
});
vconcat(column_arr, column_img);
columns.push_back(std::move(column_img));
});
hconcat(columns, generated_image_);
2019-04-14 01:58:49 +00:00
}
void ImageManipulator::method1()
2019-03-28 11:26:05 +00:00
{
2019-04-27 14:27:22 +00:00
spdlog::debug("Beginning method1, initial difference: {}", diff_);
while (remaining_iter_ > 0 && diff_ > 0.0) {
auto temp_image = generated_image_.clone();
create_shape();
draw_shape(temp_image, random_color());
2019-05-16 21:46:56 +00:00
if (auto const new_diff = cv::norm(reference_, temp_image);
2019-04-27 14:27:22 +00:00
new_diff < diff_) {
update_gen_image(temp_image, new_diff);
}
}
}
void ImageManipulator::method2()
2019-03-28 11:26:05 +00:00
{
2019-04-27 14:27:22 +00:00
spdlog::debug("Beginning method2, initial difference: {}", diff_);
spdlog::debug("Running on {} threads", thread_nbr);
get_color_set();
spdlog::debug("{} colors detected", colors_.size());
while (remaining_iter_ > 0 && diff_ > 0.0) {
if (auto result = create_candidate(); result.has_value()) {
update_gen_image(result->first, result->second);
}
}
2019-03-20 19:15:53 +00:00
}
2019-03-25 16:14:57 +00:00
void ImageManipulator::method3()
2019-03-28 11:26:05 +00:00
{
2019-04-27 14:27:22 +00:00
spdlog::debug("Beginning method3, initial difference: {}", diff_);
spdlog::debug("Running on {} threads", thread_nbr);
get_color_set();
spdlog::debug("{} colors detected", colors_.size());
while (remaining_iter_ > 0 && diff_ > 0.0) {
auto temp_image = generated_image_.clone();
if (auto result = create_candidate(true); result.has_value()) {
update_gen_image(result->first, result->second);
}
}
2019-03-25 16:14:57 +00:00
}
2019-04-08 00:48:25 +00:00
2019-04-14 01:58:49 +00:00
/**
* \param[in] t_controlled_size Enables control over the random squares size
*/
void ImageManipulator::method4(bool const t_controlled_size)
2019-04-08 00:48:25 +00:00
{
2019-04-27 14:27:22 +00:00
spdlog::debug("Beginning method4, initial difference: {}", diff_);
spdlog::debug("Running on {} threads", thread_nbr);
get_color_set();
spdlog::debug("{} colors detected", colors_.size());
while (remaining_iter_ > 0 && diff_ > 0.0) {
std::vector<std::future<std::optional<std::pair<cv::Mat, double>>>>
results{};
std::vector<std::pair<cv::Mat, double>> values{};
// launch asynchronously candidate image generation
for (size_t i = 0; i < thread_nbr; ++i) {
results.push_back(std::async(std::launch::async,
&ImageManipulator::create_candidate, this,
t_controlled_size));
}
// if candidate is a success, store it
std::for_each(results.begin(), results.end(), [&values, this](auto &elem) {
if (auto res = elem.get(); res.has_value() && res->second < this->diff_) {
values.push_back(*res);
}
});
// apply best candidate
if (values.size() > 0) {
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);
}
}
}
2019-04-14 01:58:49 +00:00
/**
* \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
*/
2019-04-27 14:27:22 +00:00
void ImageManipulator::method5(bool const t_controlled_size, int const t_cols,
int const t_rows, int const t_submethod)
{
2019-04-27 14:27:22 +00:00
spdlog::debug("Beginning method5, initial difference: {}", diff_);
spdlog::debug("Running on {} threads", thread_nbr);
auto tiles = generate_tiles((t_cols != 0) ? t_cols : t_rows, t_rows);
spdlog::debug("{} tiles", tiles.size());
std::vector<std::thread> thread_list{};
std::for_each(tiles.begin(), tiles.end(), [&](auto &row) {
std::for_each(row.begin(), row.end(), [&](auto &tile) {
thread_list.emplace_back(
[&]() { tile.exec_method(t_submethod, t_controlled_size); });
});
});
std::for_each(thread_list.begin(), thread_list.end(),
[](auto &th) { th.join(); });
merge_tiles(tiles);
2019-04-08 00:48:25 +00:00
}