code cleanup, started report, new verbose option

This commit is contained in:
Phuntsok Drak-pa 2019-03-24 19:43:25 +01:00
parent 13e59c2dc4
commit eb1046603d
15 changed files with 2670 additions and 62 deletions

View File

@ -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)

View File

@ -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).

5
doc/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
!Doxyfile

2494
doc/Doxyfile Normal file

File diff suppressed because it is too large Load Diff

BIN
img/mahakala-monochrome.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -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_ */

View File

@ -1,5 +1,5 @@
#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_ */

View File

@ -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_ */

3
report/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
auto/
_minted*/
*.tex

91
report/report.org Normal file
View File

@ -0,0 +1,91 @@
#+TITLE: Création dimages 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 dun 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. Lalgorithme commence par créer une
image vide aux dimensions identiques à limage de référence, puis applique une
de ces formes aléatoires. Si la ressemblance de limage ainsi générée augmente
par rapport à sa version précédente par rapport à limage 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 limage. Sauf indication contraire, jai utilisé dans
limplémentation de chaque méthode des carrés comme forme déléments appliqués
aléatoirement à limage.
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 limage de référence, ~W~ étant le vecteur de
pixels de limage 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 dun 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
doptimisation ~-O3~ et ~-flto~.
Voici également ci-dessous la liste des options et arguments possibles
concernant lexé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
Jai tout dabord implémenté la méthode naïve afin davoir 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 mattendais, cette méthode de
génération dimages est très lente, principalement dû au fait que lalgorithme
en létat essaiera dappliquer des couleurs nexistant pas dans limage de
référence, voire complètement à lopposées de la palette de couleurs de limage
de référence.
Voici la ligne de commande utilisée depuis le répertoire ~build~ afin de pouvoir
obtenir un temps dexé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 ditérations réussies | nombre dexécutions | temps dexé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 dexécution croît en même temps que le
nombre daméliorations nécessaires à apporter à limage à améliorer, dû à la
nature aléatoire de lalgorithme. Cependant, on constate également une
croissance importante du temps dexécution suivant également ce nombre
ditérations réussies.

BIN
report/report.pdf Normal file

Binary file not shown.

View File

@ -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));
}

View File

@ -1,14 +1,16 @@
#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());

View File

@ -1,4 +1,4 @@
#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);
}
}
}

View File

@ -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);
}