code cleanup, started report, new verbose option

parent 13e59c2d
......@@ -17,6 +17,7 @@ conan_basic_setup()
enable_cxx_compiler_flag_if_supported("-Wall")
enable_cxx_compiler_flag_if_supported("-pedantic")
enable_cxx_compiler_flag_if_supported("-O3")
enable_cxx_compiler_flag_if_supported("-flto")
# include_directories(<PUBLIC HEADER DIRECTORIES>)
set(TGT genetic-image)
......
......@@ -53,7 +53,7 @@ use it, first install clang-7 and lldb 7, then run this:
conan profile new default --detect
conan profile update settings.compiler=clang default
conan profile update settings.compiler.version=7.0 default
conan profile update settings.compiler.libcxx=libstdc++ default
conan profile update settings.compiler.libcxx=libstdc++11 default
conan profile update env.CC=/bin/clang default
conan profile update env.CXX=/bin/clang++ default
#+end_src
......@@ -65,7 +65,7 @@ Then, To build and run the program, go to the root of the project and run this:
#+begin_src shell
mkdir build && cd build
conan install .. --build missing
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .. -G Ninja
cmake -DCMAKE_CXX_COMPILER=clang++ .. -G Ninja
cmake --build .
#+end_src
If you want to use another profile than your default one, you should run the
......@@ -74,6 +74,15 @@ following line instead of the second line:
conan install .. --build missing --profile <your_profile>
#+end_src
If you do not wish to build your project with Ninja but with another generator,
such as Unix Makefiles, simply replace ~Ninja~ in the second to last ~cmake~
command with the name of your generator. For instance:
#+begin_src shell
cmake -DCMAKE_CXX_COMPILER=clang++ .. -G "Unix Makefiles"
#+end_src
You can still build your project by running ~cmake --build .~ or by running
~make~ manually.
This project was built and tested using clang-7, lldb and gdb on Void Linux
(kernel 4.19) and Arch Linux (kernel 5.0).
......
# Ignore everything in this directory
*
# Except this file
!.gitignore
!Doxyfile
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -5,13 +5,10 @@
#include <opencv2/highgui/highgui.hpp>
#include <spdlog/spdlog.h>
#include <string>
#include <tuple>
#include <random>
#include <utility>
std::tuple<cv::Mat, cv::Mat> init_image(std::string const &);
std::pair<cv::Mat, cv::Mat> init_image(std::string const &);
double euclidian_distance(cv::Mat const &, cv::Mat const &);
cv::Scalar random_color(std::mt19937 &);
#endif /* GENETIC_IMAGE_INCLUDE_GENIMG_COMMON_HH_ */
#ifndef GENETIC_IMAGE_INCLUDE_GENIMG_METHOD1_METHOD1_HH_
#define GENETIC_IMAGE_INCLUDE_GENIMG_METHOD1_METHOD1_HH_
#ifndef GENETIC_IMAGE_INCLUDE_GENIMG_METHODS_HH_
#define GENETIC_IMAGE_INCLUDE_GENIMG_METHODS_HH_
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
......@@ -12,4 +12,4 @@ void method1(cv::Mat &t_reference, cv::Mat &t_output, int t_iterations,
void method2(cv::Mat &t_reference, cv::Mat &t_output, int t_iterations,
std::mt19937 &t_gen);
#endif /* GENETIC_IMAGE_INCLUDE_GENIMG_METHOD1_METHOD1_HH_ */
#endif /* GENETIC_IMAGE_INCLUDE_GENIMG_METHODS_HH_ */
......@@ -2,10 +2,9 @@
#define GENETIC_IMAGE_INCLUDE_GENIMG_PARSEARGS_HH_
#include <filesystem>
#include <string>
#include <tuple>
std::tuple<std::filesystem::path, std::filesystem::path, bool, int, int>
std::tuple<std::filesystem::path, std::filesystem::path, bool, int, int, bool>
parse_args(int, char **);
#endif /* GENETIC_IMAGE_INCLUDE_GENIMG_PARSEARGS_HH_ */
auto/
_minted*/
*.tex
\ No newline at end of file
#+TITLE: Création d’images par algorithme génétique avec référence
#+SUBTITLE: Rapport de projet
#+AUTHOR: Lucien Cartier-Tilet
#+EMAIL: phundrak@phundrak.fr
#+CREATOR: Lucien Cartier-Tilet
#+LANGUAGE: fr
#+LATEX_CLASS: article
#+LaTeX_CLASS_OPTIONS: [a4paper,twoside]
#+LATEX_HEADER: \usepackage{xltxtra,fontspec,xunicode}\usepackage[total={6.5in,9.5in}]{geometry}\setromanfont[Numbers=Lowercase]{Charis SIL}
#+LATEX_HEADER: \usepackage{xcolor} \usepackage{hyperref}
#+LATEX_HEADER: \hypersetup{colorlinks=true,linkbordercolor=red,linkcolor=blue,pdfborderstyle={/S/U/W 1}}
#+STARTUP: latexpreview
* Sujet
Le sujet de ce projet est la création d’un logiciel pouvant recréer une image
fournie grâce à des générations aléatoires et successives de formes aux,
positions, couleurs et taille aléatoires. L’algorithme commence par créer une
image vide aux dimensions identiques à l’image de référence, puis applique une
de ces formes aléatoires. Si la ressemblance de l’image ainsi générée augmente
par rapport à sa version précédente par rapport à l’image de référence, alors
cette modification est conservée, sinon elle est annulée. Répéter jusqu’à
satisfaction.
* Les méthodes utilisées
Plusieurs approches au problème sont possibles, allant de la simple
implémentation naïve du problème à des moyen pouvant au moins décupler la
vitesse de génération de l’image. Sauf indication contraire, j’ai utilisé dans
l’implémentation de chaque méthode des carrés comme forme d’éléments appliqués
aléatoirement à l’image.
Pour évaluer la ressemblance entre deux image, j’évalue une distance euclidienne
entre le vecteur de leurs pixels qui peut se résumer à ceci :
#+begin_export latex
$$\sqrt{\sum_{i=0}^{n} V_{i}^{2}+W_{i}^{2}}$$
#+end_export
~V~ étant le vecteur de pixels de l’image de référence, ~W~ étant le vecteur de
pixels de l’image générée, et ~n~ la taille de ces deux vecteurs.
Les tests de temps sont réalisés sur un Thinkpad x220, disposant d’un processeur
Intel® Core™ i5-2540M à 2.6GHz, composé de deux cœurs supportant chacun deux
threads, et de 4Go de RAM. Le programme est compilé avec les options
d’optimisation ~-O3~ et ~-flto~.
Voici également ci-dessous la liste des options et arguments possibles
concernant l’exécution du logiciel.
#+begin_src text
$ ./bin/genetic-image -h
Allowed options:
-h [ --help ] Display this help message
-i [ --input ] arg Input image
-o [ --output ] arg Image or video output path (default: input path +
"_output")
-m [ --method ] arg Method number to be used (default: 1)
-n [ --iterations ] arg Number of iterations (default: 5000)
-v [ --video ] Enable video output
#+end_src
** Méthode naïve
J’ai tout d’abord implémenté la méthode naïve afin d’avoir une référence en
matière de temps. Cette dernière est implémentée dans ~src/methods.cc~ avec la
fonction ~method1()~. Comme ce à quoi je m’attendais, cette méthode de
génération d’images est très lente, principalement dû au fait que l’algorithme
en l’état essaiera d’appliquer des couleurs n’existant pas dans l’image de
référence, voire complètement à l’opposées de la palette de couleurs de l’image
de référence.
Voici la ligne de commande utilisée depuis le répertoire ~build~ afin de pouvoir
obtenir un temps d’exécution :
#+begin_src shell
perf stat -r nombreDExécutions -B ./bin/genetic-image \
-i ../img/mahakala-monochrome.jpg -o output.png -n 200 -m 1
#+end_src
| / | < | < |
| nombre d’itérations réussies | nombre d’exécutions | temps d’exécution moyen |
|------------------------------+---------------------+-------------------------|
| 10 | 100 | 0.09447s (±0.02%) |
| 50 | 100 | 1.1331s (±2.85%) |
| 100 | 50 | |
| 200 | 20 | |
| 500 | 10 | |
| 1000 | 5 | |
Naturellement, la variation en temps d’exécution croît en même temps que le
nombre d’améliorations nécessaires à apporter à l’image à améliorer, dû à la
nature aléatoire de l’algorithme. Cependant, on constate également une
croissance importante du temps d’exécution suivant également ce nombre
d’itérations réussies.
......@@ -3,18 +3,18 @@
#include <cmath>
#include <cstdlib>
std::tuple<cv::Mat, cv::Mat> init_image(std::string const &t_input_file) {
std::pair<cv::Mat, cv::Mat> init_image(std::string const &t_input_file) {
cv::Mat input_image = cv::imread(t_input_file, cv::IMREAD_COLOR);
if (!input_image.data) {
spdlog::critical("Could not open or find image!\n");
exit(-1);
}
spdlog::info("Image loaded!");
spdlog::info("Width:\t\t{}", input_image.size().width);
spdlog::info("Height:\t{}", input_image.size().height);
spdlog::debug("Image loaded!");
spdlog::debug("Width:\t\t{}", input_image.size().width);
spdlog::debug("Height:\t{}", input_image.size().height);
cv::Mat process_image(input_image.size().height, input_image.size().width,
CV_8UC3, cv::Scalar(0, 0, 0));
return std::make_tuple(std::move(input_image), process_image);
return std::make_pair(std::move(input_image), process_image);
}
double euclidian_distance(cv::Mat const &t_img1, cv::Mat const &t_img2) {
......@@ -28,8 +28,3 @@ double euclidian_distance(cv::Mat const &t_img1, cv::Mat const &t_img2) {
euclidian = std::sqrt(euclidian);
return euclidian;
}
cv::Scalar random_color(std::mt19937 &t_gen) {
static std::uniform_int_distribution<> dis(0, 255);
return cv::Scalar(dis(t_gen), dis(t_gen), dis(t_gen));
}
#include "common.hh"
#include "method1.hh"
#include "methods.hh"
#include "parseargs.hh"
#include <iostream>
int main(int ac, char **av) {
auto const [input_file, output_file, video_output, iterations, method] =
parse_args(ac, av);
spdlog::info("Input file:\t{}", input_file.native());
spdlog::info("Output file:\t{}", output_file.native());
spdlog::info("Video output:\t{}", video_output);
spdlog::info("Iterations:\t{}", iterations);
auto const [input_file, output_file, video_output, iterations, method,
verbose] = parse_args(ac, av);
spdlog::set_level(verbose ? spdlog::level::debug : spdlog::level::info);
spdlog::debug("Input file:\t{}", input_file.native());
spdlog::debug("Output file:\t{}", output_file.native());
spdlog::debug("Video output:\t{}", video_output);
spdlog::debug("Iterations:\t{}", iterations);
auto [input_image, process_image] = init_image(input_file.native());
std::random_device rd;
std::mt19937 gen(rd());
......
#include "method1.hh"
#include "methods.hh"
#include "common.hh"
#include "drawing.hh"
#include <algorithm>
......@@ -13,34 +13,21 @@ using randint = std::uniform_int_distribution<>;
using Color = std::array<uchar, 3>;
using ColorSet = std::vector<Color>;
namespace methods_private {
cv::Scalar randomColor(std::mt19937 &t_gen) {
static std::uniform_int_distribution<> dis(0, 255);
return cv::Scalar(dis(t_gen), dis(t_gen), dis(t_gen));
}
void newSquare1(cv::Mat &t_process_img, std::mt19937 &t_gen,
randint &t_rand_pos) {
const int square_size = t_rand_pos(t_gen);
auto square_top_left = cv::Point{t_rand_pos(t_gen), t_rand_pos(t_gen)};
draw_shape(t_process_img, square_top_left, square_size, random_color(t_gen),
draw_shape(t_process_img, square_top_left, square_size, randomColor(t_gen),
Shapes::Square);
}
void method1(cv::Mat &t_reference, cv::Mat &t_output, int t_iterations,
std::mt19937 &t_gen) {
auto diff = euclidian_distance(t_reference, t_output);
auto const max_size =
std::max(t_reference.size().width, t_reference.size().height);
randint dist(0, max_size);
spdlog::info("Beginning method1, initial difference: {}", diff);
while (t_iterations > 0) {
auto temp_image = t_output.clone();
newSquare1(temp_image, t_gen, dist);
if (auto new_diff = euclidian_distance(t_reference, temp_image);
new_diff < diff) {
diff = new_diff;
temp_image.copyTo(t_output);
--t_iterations;
spdlog::info("Iteration {}: diff {}", t_iterations, diff);
}
}
}
void threadedGetColor(cv::Mat &t_reference, ColorSet &t_colors, int t_h) {
if (t_h > t_reference.size().height)
return;
......@@ -62,8 +49,9 @@ ColorSet getColorSet(cv::Mat &t_reference) {
for (int h = 0; h < t_reference.size().height; h += thread_nbr) {
std::vector<std::thread> thread_list{};
for (int i = 0; i < thread_nbr; ++i) {
thread_list.push_back(std::thread(threadedGetColor, std::ref(t_reference),
std::ref(res), h + i));
thread_list.push_back(std::thread(methods_private::threadedGetColor,
std::ref(t_reference), std::ref(res),
h + i));
}
for (auto &th : thread_list)
th.join();
......@@ -85,26 +73,48 @@ void newSquare2(cv::Mat &t_process_img, std::mt19937 &t_gen,
Shapes::Square);
}
} // namespace methods_private
void method1(cv::Mat &t_reference, cv::Mat &t_output, int t_iterations,
std::mt19937 &t_gen) {
auto diff = euclidian_distance(t_reference, t_output);
auto const max_size =
std::max(t_reference.size().width, t_reference.size().height);
randint dist(0, max_size);
spdlog::debug("Beginning method1, initial difference: {}", diff);
while (t_iterations > 0) {
auto temp_image = t_output.clone();
methods_private::newSquare1(temp_image, t_gen, dist);
if (auto new_diff = euclidian_distance(t_reference, temp_image);
new_diff < diff) {
diff = new_diff;
temp_image.copyTo(t_output);
--t_iterations;
spdlog::debug("Iteration {}: diff {}", t_iterations, diff);
}
}
}
void method2(cv::Mat &t_reference, cv::Mat &t_output, int t_iterations,
std::mt19937 &t_gen) {
auto diff = euclidian_distance(t_reference, t_output);
auto const max_size =
std::max(t_reference.size().width, t_reference.size().height);
randint dist(0, max_size);
spdlog::info("Beginning method2, initial difference: {}", diff);
auto const colors = getColorSet(t_reference);
spdlog::info("Running {} threads.", thread_nbr);
spdlog::info("{} colors detected.", colors.size());
spdlog::debug("Beginning method2, initial difference: {}", diff);
auto const colors = methods_private::getColorSet(t_reference);
spdlog::debug("Running {} threads.", thread_nbr);
spdlog::debug("{} colors detected.", colors.size());
randint rand_color(0, colors.size());
while (t_iterations > 0) {
auto temp_image = t_output.clone();
newSquare2(temp_image, t_gen, colors, dist, rand_color);
methods_private::newSquare2(temp_image, t_gen, colors, dist, rand_color);
if (auto new_diff = euclidian_distance(t_reference, temp_image);
new_diff < diff) {
diff = new_diff;
temp_image.copyTo(t_output);
--t_iterations;
spdlog::info("Iteration {}: diff {}", t_iterations, diff);
spdlog::debug("Iteration {}: diff {}", t_iterations, diff);
}
}
}
......@@ -21,7 +21,7 @@ void processFilenames(po::variables_map const &vm, path const &t_input,
}
}
std::tuple<path, path, bool, int, int> parse_args(int t_ac, char **t_av) {
std::tuple<path, path, bool, int, int, bool> parse_args(int t_ac, char **t_av) {
po::options_description desc("Allowed options");
desc.add_options()
("help,h", "Display this help message")
......@@ -30,7 +30,8 @@ std::tuple<path, path, bool, int, int> parse_args(int t_ac, char **t_av) {
"Image or video output path (default: input path + \"_output\")")
("method,m", po::value<int>(), "Method number to be used (default: 1)")
("iterations,n", po::value<int>(), "Number of iterations (default: 5000)")
("video,v", "Enable video output");
("video,V", "Enable video output")
("verbose,v", "Enables verbosity");
po::variables_map vm;
po::store(po::parse_command_line(t_ac, t_av, desc), vm);
po::notify(vm);
......@@ -49,5 +50,6 @@ std::tuple<path, path, bool, int, int> parse_args(int t_ac, char **t_av) {
output_path,
vm.count("video") ? true : false,
vm.count("iterations") ? vm["iterations"].as<int>() : DEFAULT_ITERATIONS,
vm.count("method") ? vm["method"].as<int>() : 1);
vm.count("method") ? vm["method"].as<int>() : 1,
vm.count("verbose") ? true : false);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment