genetic-images/src/methods.cc

542 lines
18 KiB
C++
Raw Normal View History

#include "methods.hh"
2019-03-20 19:15:53 +00:00
#include <algorithm>
2019-03-21 01:49:00 +00:00
#include <array>
#include <cmath>
2019-03-25 11:24:19 +00:00
#include <cstdlib>
2019-04-14 01:58:49 +00:00
#include <functional>
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-04-02 08:37:14 +00:00
#include <utility>
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
*/
ImageManipulator::ImageManipulator(const ImageManipulator& other)
: colors_{other.colors_},
reference_{other.reference_},
generated_image_{other.generated_image_},
output_path_{other.output_path_},
diff_{other.diff_},
total_iterations_{other.total_iterations_},
remaining_iter_{other.remaining_iter_},
width_{other.width_},
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
*/
ImageManipulator::ImageManipulator(ImageManipulator&& other) noexcept
: colors_{std::move(other.colors_)},
reference_{std::move(other.reference_)},
generated_image_{std::move(other.generated_image_)},
output_path_{std::move(other.output_path_)},
diff_{std::move(other.diff_)},
total_iterations_{other.total_iterations_},
remaining_iter_{other.remaining_iter_},
width_{other.width_},
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)
2019-04-14 01:58:49 +00:00
: 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);
}
}
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
*/
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 //////////////////////////////////////////////////////////////////
2019-04-14 01:58:49 +00:00
/**
* 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);
}
2019-04-14 01:58:49 +00:00
/**
* 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
{
return ImageManipulator{std::move(other)};
}
// 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-14 01:58:49 +00:00
int const t_cols = 1,
int const t_rows = 0,
int const t_submethod = 1)
2019-04-02 08:37:14 +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: {
2019-04-14 01:58:49 +00:00
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
}
2019-04-14 01:58:49 +00:00
/**
* Write the generated image as a file to the specified path stored in the
* object
*/
void ImageManipulator::write_file() const
2019-04-02 08:37:14 +00:00
{
cv::imwrite(output_path_, generated_image_);
}
///////////////////////////////////////////////////////////////////////////////
// Private //
///////////////////////////////////////////////////////////////////////////////
// methods ////////////////////////////////////////////////////////////////////
2019-04-14 01:58:49 +00:00
/**
* 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
{
double euclidian = 0.0;
for (auto itr1 = reference_.begin<uchar>(), itr2 = t_img.begin<uchar>();
itr1 != reference_.end<uchar>() && itr2 != t_img.end<uchar>();
++itr1, ++itr2) {
euclidian += std::pow(*itr1 - *itr2, 2);
2019-04-02 08:37:14 +00:00
}
return std::sqrt(euclidian);
2019-04-02 08:37:14 +00:00
}
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
{
return cv::Scalar(rand() % 255, rand() % 255, rand() % 255);
}
2019-04-14 01:58:49 +00:00
/**
* 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;
int rand_y = rand() % reference_.size().height;
int size = rand()
% std::min(reference_.size().width - rand_x,
reference_.size().height - rand_y);
2019-04-02 08:37:14 +00:00
return std::tuple<int, int, int>(rand_x, rand_y, size);
}
2019-04-14 01:58:49 +00:00
/**
* 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
2019-04-02 08:54:01 +00:00
{
int rand_x = rand() % reference_.size().width;
int rand_y = rand() % reference_.size().height;
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);
2019-04-02 08:54:01 +00:00
int const max_size = min_size * 2 + 1;
int size = rand() % (max_size - min_size) + min_size;
return std::tuple<int, int, int>(rand_x, rand_y, size);
}
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
*/
[[nodiscard]] auto ImageManipulator::create_candidate(
bool const t_controlled_size = false) const
2019-04-02 08:37:14 +00:00
{
auto temp_image = generated_image_.clone();
auto const [rand_x, rand_y, size] = t_controlled_size
? get_controlled_square_values()
: get_square_values();
auto const& color = colors_[rand() % colors_.size()];
draw_square(
temp_image, cv::Point{rand_x, rand_y}, size,
cv::Scalar{static_cast<double>(color[0]), static_cast<double>(color[1]),
static_cast<double>(color[2])});
auto new_diff = euclidian_distance(temp_image);
return (new_diff < diff_)
2019-04-02 08:37:14 +00:00
? std::optional<std::pair<cv::Mat, double>>{std::make_pair(
2019-04-07 22:57:54 +00:00
std::move(temp_image), new_diff)}
2019-04-02 08:37:14 +00:00
: std::nullopt;
}
/**
* 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
{
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()
2019-03-28 11:26:05 +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));
}
for (auto& th : thread_list) {
th.join();
}
2019-03-28 11:26:05 +00:00
}
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 t_h)
2019-03-28 11:26:05 +00:00
{
if (t_h > reference_.size().height) {
2019-03-28 11:26:05 +00:00
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_)) {
colors_mutex_.lock();
colors_.push_back(std::move(temp));
colors_mutex_.unlock();
2019-03-28 11:26:05 +00:00
}
}
2019-03-21 01:49:00 +00:00
}
2019-04-14 01:58:49 +00:00
/**
* 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,
cv::Scalar const& t_color) const
2019-03-28 11:26:05 +00:00
{
auto points = std::make_unique<cv::Point[]>(4);
points[0] = t_top_left;
points[1] = cv::Point{t_top_left.x, t_top_left.y + t_size};
points[2] = cv::Point{t_top_left.x + t_size, t_top_left.y + t_size};
points[3] = cv::Point{t_top_left.x + t_size, t_top_left.y};
fillConvexPoly(t_img, points.get(), 4, t_color);
2019-03-21 01:49:00 +00:00
}
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
*/
void ImageManipulator::update_gen_image(cv::Mat const& t_img,
double const t_diff)
{
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(
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()
2019-03-28 11:26:05 +00:00
{
spdlog::debug("Beginning method1, initial difference: {}", diff_);
spdlog::debug("nb_total_iter: {}, nb_remaining_iter: {}", total_iterations_,
remaining_iter_);
while (remaining_iter_ > 0 && diff_ > 0.0) {
auto temp_image = generated_image_.clone();
auto const [rand_x, rand_y, size] = get_square_values();
draw_square(temp_image, cv::Point{rand_x, rand_y}, size, random_color());
if (auto const new_diff = euclidian_distance(temp_image);
new_diff < diff_) {
update_gen_image(temp_image, new_diff);
2019-03-28 11:26:05 +00:00
}
}
}
void ImageManipulator::method2()
2019-03-28 11:26:05 +00:00
{
spdlog::debug("Beginning method2, initial difference: {}", diff_);
spdlog::debug("nb_total_iter: {}, nb_remaining_iter: {}", total_iterations_,
remaining_iter_);
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-28 11:26:05 +00:00
}
}
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
{
spdlog::debug("Beginning method3, initial difference: {}", diff_);
spdlog::debug("nb_total_iter: {}, nb_remaining_iter: {}", total_iterations_,
remaining_iter_);
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-28 11:26:05 +00:00
}
}
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
{
spdlog::debug("Beginning method4, initial difference: {}", diff_);
spdlog::debug("nb_total_iter: {}, nb_remaining_iter: {}", total_iterations_,
remaining_iter_);
spdlog::debug("Running on {} threads", thread_nbr);
get_color_set();
spdlog::debug("{} colors detected", colors_.size());
while (remaining_iter_ > 0 && diff_ > 0.0) {
2019-04-08 00:48:25 +00:00
std::vector<std::future<std::optional<std::pair<cv::Mat, double>>>>
results{};
std::vector<std::pair<cv::Mat, double>> values{};
for (size_t i = 0; i < thread_nbr; ++i) {
results.push_back(std::async(std::launch::async,
&ImageManipulator::create_candidate, this,
t_controlled_size));
}
for (auto& elem : results) {
if (auto res = elem.get(); res.has_value() && res->second < diff_) {
values.push_back(*res);
}
}
if (values.size() > 0) {
2019-04-14 01:58:49 +00:00
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
*/
void ImageManipulator::method5(bool const t_controlled_size,
2019-04-14 01:58:49 +00:00
int const t_cols,
int const t_rows,
int const t_submethod)
{
spdlog::debug("Beginning method5, initial difference: {}", diff_);
spdlog::debug("Running on {} threads", thread_nbr);
2019-04-14 01:58:49 +00:00
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); });
}
2019-04-08 00:48:25 +00:00
}
2019-04-14 01:58:49 +00:00
for (auto& th : thread_list) {
th.join();
}
merge_tiles(tiles);
2019-04-08 00:48:25 +00:00
}