commit 78c2612c7d482843f9af28b3f71a48b235b64f3f Author: papush Date: Mon Dec 13 08:59:59 2021 +0100 initial commit diff --git a/IMAGES/blob1.png b/IMAGES/blob1.png new file mode 100644 index 0000000..8dea5aa Binary files /dev/null and b/IMAGES/blob1.png differ diff --git a/IMAGES/bubblets.png b/IMAGES/bubblets.png new file mode 100644 index 0000000..03ebf20 Binary files /dev/null and b/IMAGES/bubblets.png differ diff --git a/IMAGES/chat1.jpg b/IMAGES/chat1.jpg new file mode 100644 index 0000000..35dac6c Binary files /dev/null and b/IMAGES/chat1.jpg differ diff --git a/IMAGES/fleur1.jpg b/IMAGES/fleur1.jpg new file mode 100644 index 0000000..7b1b274 Binary files /dev/null and b/IMAGES/fleur1.jpg differ diff --git a/IMAGES/fleur2.jpg b/IMAGES/fleur2.jpg new file mode 100644 index 0000000..c6a393b Binary files /dev/null and b/IMAGES/fleur2.jpg differ diff --git a/IMAGES/joint_J1.pgm b/IMAGES/joint_J1.pgm new file mode 100644 index 0000000..2403e56 Binary files /dev/null and b/IMAGES/joint_J1.pgm differ diff --git a/IMAGES/joint_J2.pgm b/IMAGES/joint_J2.pgm new file mode 100644 index 0000000..33ad4b7 Binary files /dev/null and b/IMAGES/joint_J2.pgm differ diff --git a/IMAGES/joint_J3.pgm b/IMAGES/joint_J3.pgm new file mode 100644 index 0000000..e0eb325 Binary files /dev/null and b/IMAGES/joint_J3.pgm differ diff --git a/IMAGES/joint_J4.pgm b/IMAGES/joint_J4.pgm new file mode 100644 index 0000000..8f49597 Binary files /dev/null and b/IMAGES/joint_J4.pgm differ diff --git a/IMAGES/joint_J5.pgm b/IMAGES/joint_J5.pgm new file mode 100644 index 0000000..b760e62 Binary files /dev/null and b/IMAGES/joint_J5.pgm differ diff --git a/IMAGES/joint_J6.pgm b/IMAGES/joint_J6.pgm new file mode 100644 index 0000000..a9b8766 Binary files /dev/null and b/IMAGES/joint_J6.pgm differ diff --git a/IMAGES/joint_J7.pgm b/IMAGES/joint_J7.pgm new file mode 100644 index 0000000..2e9a143 Binary files /dev/null and b/IMAGES/joint_J7.pgm differ diff --git a/IMAGES/joint_K1.pgm b/IMAGES/joint_K1.pgm new file mode 100644 index 0000000..54166f8 Binary files /dev/null and b/IMAGES/joint_K1.pgm differ diff --git a/IMAGES/joint_K2.pgm b/IMAGES/joint_K2.pgm new file mode 100644 index 0000000..9a526a9 Binary files /dev/null and b/IMAGES/joint_K2.pgm differ diff --git a/IMAGES/joint_K3.pgm b/IMAGES/joint_K3.pgm new file mode 100644 index 0000000..6a4d733 Binary files /dev/null and b/IMAGES/joint_K3.pgm differ diff --git a/IMAGES/joint_K4.pgm b/IMAGES/joint_K4.pgm new file mode 100644 index 0000000..d2c8703 Binary files /dev/null and b/IMAGES/joint_K4.pgm differ diff --git a/IMAGES/joint_K5.pgm b/IMAGES/joint_K5.pgm new file mode 100644 index 0000000..788644e Binary files /dev/null and b/IMAGES/joint_K5.pgm differ diff --git a/IMAGES/joint_K6.pgm b/IMAGES/joint_K6.pgm new file mode 100644 index 0000000..ebb59ea Binary files /dev/null and b/IMAGES/joint_K6.pgm differ diff --git a/IMAGES/joint_K7.pgm b/IMAGES/joint_K7.pgm new file mode 100644 index 0000000..53672ce Binary files /dev/null and b/IMAGES/joint_K7.pgm differ diff --git a/IMAGES/joint_K8.pgm b/IMAGES/joint_K8.pgm new file mode 100644 index 0000000..259f609 Binary files /dev/null and b/IMAGES/joint_K8.pgm differ diff --git a/IMAGES/noisy_fingerprint.png b/IMAGES/noisy_fingerprint.png new file mode 100644 index 0000000..7580391 Binary files /dev/null and b/IMAGES/noisy_fingerprint.png differ diff --git a/IMAGES/paper1.jpg b/IMAGES/paper1.jpg new file mode 100644 index 0000000..bcc483f Binary files /dev/null and b/IMAGES/paper1.jpg differ diff --git a/IMAGES/test.pgm b/IMAGES/test.pgm new file mode 100644 index 0000000..5c0a305 Binary files /dev/null and b/IMAGES/test.pgm differ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ce09d64 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +# Makefile pour Astico2D : +# Atelier Simple de Transformations d'Images au Clavier avec OpenCV en 2D +# +# CC BY-SA Edouard.Thiel@univ-amu.fr - 22/08/2021 + +# Version Linux et WSL +# +# Pour installer OpenCV sous Ubuntu, taper : +# sudo apt install g++ make libopencv-dev + +SHELL = /bin/bash +CC = g++ +RM = rm -f + +# Version de OpenCV pour pkg-config : +# - mettre "opencv4" sur Ubuntu >= 20.04 +# - mettre "opencv" sur Ubuntu <= 18.04 et WSL +OPENCV = opencv + +# Options de compilation +CFLAGS = -Wall --std=c++14 $$(pkg-config $(OPENCV) --cflags) +LIBS = $$(pkg-config $(OPENCV) --libs) + +# Fichiers à compiler : +# chaque fichier .cpp produira un exécutable du même nom, sauf astico2d.cpp +CFILES := $(wildcard *.cpp) +CFILES := $(filter-out astico2d.cpp, $(CFILES)) +EXECS := $(CFILES:%.cpp=%) + +# Règle pour produire tous les exécutables. +# Tapez "make -j all" pour les compiler en parallèle. +all :: $(EXECS) + +# Règle pour fabriquer les .o à partir des .cpp +%.o : %.cpp + $(CC) $(CFLAGS) -c $*.cpp + +# Règle de production de chaque exécutable +$(EXECS) : % : %.o astico2d.o + $(CC) -o $@ $^ $(LIBS) + +# Règle de nettoyage +clean :: + $(RM) *.o *~ $(EXECS) tmp*.* + diff --git a/astico2d.cpp b/astico2d.cpp new file mode 100644 index 0000000..cfc4c0b --- /dev/null +++ b/astico2d.cpp @@ -0,0 +1,691 @@ +/* + Module d'interface utilisateur pour Astico2D : + Atelier Simple de Transformations d'Images au Clavier avec OpenCV en 2D + + CC BY-SA Edouard.Thiel@univ-amu.fr - 04/09/2021 - v1.0 +*/ + +#include "astico2d.hpp" + + +//---------------------------- C O U L E U R S -------------------------------- + +void representer_en_couleurs_vga (cv::Mat &img_niv, cv::Mat &img_coul) +{ + CHECK_MAT_TYPE(img_niv, CV_32SC1); + CHECK_MAT_TYPE(img_coul, CV_8UC3); + + unsigned char couls[16][3] = { // R, G, B + { 0, 0, 0 }, // 0 black -> 0 uniquement + { 30, 30, 230 }, // 1 blue -> 1, 15, 29, ... + { 30, 200, 30 }, // 2 green -> 2, 16, 30, ... + { 30, 200, 200 }, // 3 cyan -> 3, 17, 31, ... + { 200, 30, 30 }, // 4 red -> 4, 18, 32, ... + { 200, 30, 200 }, // 5 magenta -> 5, 19, 33, ... + { 200, 130, 50 }, // 6 brown -> 6, 20, 34, ... + { 200, 200, 200 }, // 7 light gray -> 7, 21, 35, ... + { 110, 110, 140 }, // 8 dark gray -> 8, 22, 36, ... + { 84, 130, 252 }, // 9 light blue -> 9, 23, 37, ... + { 84, 252, 84 }, // 10 light green -> 10, 24, 38, ... + { 84, 252, 252 }, // 11 light cyan -> 11, 25, 39, ... + { 252, 84, 84 }, // 12 light red -> 12, 26, 40, ... + { 252, 84, 252 }, // 13 light magenta -> 13, 27, 41, ... + { 252, 252, 84 }, // 14 yellow -> 14, 28, 42, ... + { 252, 252, 252 }, // 15 white -> 255 uniquement + }; + + for (int y = 0; y < img_niv.rows; y++) + for (int x = 0; x < img_niv.cols; x++) + { + int g = img_niv.at(y,x), c = 0; + if (g == 255) c = 15; // seul 255 est blanc + else if (g != 0) c = 1 + abs(g-1) % 14; // seul 0 est noir + // Attention img_coul est en B, G, R -> inverser les canaux + img_coul.at(y,x)[0] = couls[c][2]; + img_coul.at(y,x)[1] = couls[c][1]; + img_coul.at(y,x)[2] = couls[c][0]; + } +} + + +void inverser_couleurs (cv::Mat &img) +{ + CHECK_MAT_TYPE(img, CV_8UC3); + + for (int y = 0; y < img.rows; y++) + for (int x = 0; x < img.cols; x++) + { + img.at(y,x)[0] = 255 - img.at(y,x)[0]; + img.at(y,x)[1] = 255 - img.at(y,x)[1]; + img.at(y,x)[2] = 255 - img.at(y,x)[2]; + } +} + + +//--------------------------------- L O U P E --------------------------------- + +void Loupe::reborner (cv::Mat &res1, cv::Mat &res2) +{ + int bon_zoom = zoom >= 1 ? zoom : 1; + + int h = res2.rows / bon_zoom; + int w = res2.cols / bon_zoom; + + if (zoom_x0 < 0) zoom_x0 = 0; + zoom_x1 = zoom_x0 + w; + if (zoom_x1 > res1.cols) { + zoom_x1 = res1.cols; + zoom_x0 = zoom_x1 - w; + if (zoom_x0 < 0) zoom_x0 = 0; + } + + if (zoom_y0 < 0) zoom_y0 = 0; + zoom_y1 = zoom_y0 + h; + if (zoom_y1 > res1.rows) { + zoom_y1 = res1.rows; + zoom_y0 = zoom_y1 - h; + if (zoom_y0 < 0) zoom_y0 = 0; + } +} + + +void Loupe::deplacer (cv::Mat &res1, cv::Mat &res2, int dx, int dy) +{ + zoom_x0 += dx; zoom_y0 += dy; + zoom_x1 += dx; zoom_y1 += dy; + reborner (res1, res2); +} + + +int Loupe::calculer_zoom_optimal (cv::Mat &src) +{ + // On autorise l'image à dépasser de 10% + int zh = opt_h * 110 / (src.rows * 100), + zw = opt_w * 110 / (src.cols * 100); + int z = zh < zw ? zh : zw; + if (z < 1) z = 1; else if (z > zoom_max) z = zoom_max; + return z; +} + + +void Loupe::calculer_portion_optimale (cv::Mat &src, int &ph, int &pw) +{ + ph = ((src.rows * zoom - 1) / vue_inc +1) * vue_inc; + pw = ((src.cols * zoom - 1) / vue_inc +1) * vue_inc; + if (ph < vue_min) ph = vue_min; else if (ph > opt_h) ph = opt_h; + if (pw < vue_min) pw = vue_min; else if (pw > opt_w) pw = opt_w; +} + + +void Loupe::dessiner_rect (cv::Mat &src, cv::Mat &dest) +{ + dest = src.clone(); + if (zoom == 0) return; + cv::Point p0 = cv::Point(zoom_x0, zoom_y0), + p1 = cv::Point(zoom_x1, zoom_y1); + cv::rectangle(dest, p0, p1, cv::Scalar (255, 255, 255), 3, 4); + cv::rectangle(dest, p0, p1, cv::Scalar ( 0, 0, 255), 1, 4); +} + + +void Loupe::dessiner_portion (cv::Mat &src, cv::Mat &dest) +{ + CHECK_MAT_TYPE(src, CV_8UC3); + + int bon_zoom = zoom >= 1 ? zoom : 1; + + for (int y = 0; y < dest.rows; y++) + for (int x = 0; x < dest.cols; x++) + { + int x0 = zoom_x0 + x / bon_zoom; + int y0 = zoom_y0 + y / bon_zoom; + + if (x0 < 0 || x0 >= src.cols || y0 < 0 || y0 >= src.rows) { + dest.at(y,x)[0] = 64; + dest.at(y,x)[1] = 64; + dest.at(y,x)[2] = 64; + continue; + } + dest.at(y,x)[0] = src.at(y0,x0)[0]; + dest.at(y,x)[1] = src.at(y0,x0)[1]; + dest.at(y,x)[2] = src.at(y0,x0)[2]; + } +} + + +void Loupe::afficher_tableau_niveaux (cv::Mat &src, int ex, int ey, int rx, int ry) +{ + CHECK_MAT_TYPE(src, CV_32SC1); + + int bon_zoom = zoom >= 1 ? zoom : 1; + int cx = zoom_x0 + ex / bon_zoom; + int cy = zoom_y0 + ey / bon_zoom; + int x1 = cx-rx, x2 = cx+rx, y1 = cy-ry, y2 = cy+ry; + + int vmin = 0, vmax = 0; + for (int y = y1; y <= y2; y++) { + for (int x = x1; x <= x2; x++) { + if (x < 0 || x >= src.cols || y < 0 || y >= src.rows) + continue; + int v = src.at(y,x); + if (v < vmin) vmin = v; + else if (v > vmax) vmax = v; + } + } + int n1 = ceil(log10(abs(vmin)+1)), n2 = ceil(log10(abs(vmax)+1)); + if (vmin < 0) n1++; // signe + if (vmax < 0) n2++; + int n = std::max(n1,n2); // nb de chiffres + + std::cout << "Valeurs autour de " << cx << "," << cy << " :" << std::endl; + + for (int y = y1; y <= y2; y++) { + for (int x = x1; x <= x2; x++) { + if (x < 0 || x >= src.cols || y < 0 || y >= src.rows) + std::cout << std::setw(n+1) << "* " ; // hors de l'image + else std::cout << std::setw(n) << src.at(y,x) << " "; + } + std::cout << std::endl; + } + std::cout << std::endl; +} + + +void Loupe::afficher_tableau_couleurs (cv::Mat &src, int ex, int ey, int rx, int ry) +{ + CHECK_MAT_TYPE(src, CV_8UC3); + + int bon_zoom = zoom >= 1 ? zoom : 1; + int cx = zoom_x0 + ex / bon_zoom; + int cy = zoom_y0 + ey / bon_zoom; + int x1 = cx-rx, x2 = cx+rx, y1 = cy-ry, y2 = cy+ry; + + int n = 3; // nb de chiffres + + std::cout << "Valeurs autour de " << cx << "," << cy << " :" << std::endl; + + for (int y = y1; y <= y2; y++) { + for (int x = x1; x <= x2; x++) { + if (x < 0 || x >= src.cols || y < 0 || y >= src.rows) + // hors de l'image + std::cout << "(" + << std::setw(n) << "* " << "," + << std::setw(n) << "* " << "," + << std::setw(n) << "* " << ") "; + else { + int cR = src.at(y,x)[2], + cV = src.at(y,x)[1], + cB = src.at(y,x)[0]; + std::cout << "(" + << std::setw(n) << cR << "," + << std::setw(n) << cV << "," + << std::setw(n) << cB << ") "; + } + } + std::cout << std::endl; + } + std::cout << std::endl; +} + + +//----------------------------- A S T I C O 2 D ------------------------------- + + +Astico2D::Astico2D (int &argc, char **&argv) : temps() +{ + if (analyser_arguments (argc, argv) != 0) { init_ok = false ; return; } + if (lire_image_entree () != 0) { init_ok = false ; return; } + creer_images_resultat (); + creer_fenetres (); +} + + +int Astico2D::run () +{ + if (! init_ok) return 1; + afficher_touches_clavier (); + traiter_evenements (); + return (enregistrer_image_resultat () == 0) ? 0 : 1; +} + + +void Astico2D::afficher_usage (const char *nom_prog) { + std::cout << "Usage: " << nom_prog + << " [-mag w h] [-thr seuil] [-o image_res] image_src" + << " [autres options]" + << std::endl; +} + + +int Astico2D::analyser_arguments (int &argc, char **&argv) +{ + nom_out2 = NULL; + + char *nom_prog = argv[0]; + argc -= 1; argv += 1; + + while (argc > 0) { + if (!strcmp(argv[0], "-mag")) { + if (argc-1 < 3) { afficher_usage(nom_prog); return 1; } + zoom_w = atoi(argv[1]); + zoom_h = atoi(argv[2]); + argc -= 3; argv += 3; + } else if (!strcmp(argv[0], "-thr")) { + if (argc-1 < 2) { afficher_usage(nom_prog); return 1; } + seuil_niv = atoi(argv[1]); + argc -= 2; argv += 2; + } else if (!strcmp(argv[0], "-o")) { + if (argc-1 < 2) { afficher_usage(nom_prog); return 1; } + nom_out2 = argv[1]; + argc -= 2; argv += 2; + } else break; + } + + if (argc < 1) { afficher_usage(nom_prog); return 1; } + nom_in1 = argv[0]; + argc -= 1; argv += 1; + + // Les arguments supplémentaires restent dans argc,argv et peuvent être + // analysés dans le constructeur d'une classe fille après l'appel du + // constructeur de la classe mère. + return 0; +} + + +int Astico2D::lire_image_entree () +{ + img_src = cv::imread (nom_in1, cv::IMREAD_COLOR); // produit du 8UC3 + if (img_src.empty()) { + std::cout << "Erreur de lecture de \"" << nom_in1 << "\"," + << " ou format incompatible de l'image." << std::endl; + return -1; + } + return 0; +} + + +int Astico2D::enregistrer_image_resultat () +{ + if (!nom_out2) return 0; + + int res = cv::imwrite (nom_out2, img_coul); + if (res) std::cout << "Enregistrement reussi"; + else std::cout << "Erreur d'enregistrement"; + std::cout << " dans \"" << nom_out2 << "\"." << std::endl; + return res ? 0 : -1; +} + + +void Astico2D::creer_images_resultat () +{ + img_res1 = cv::Mat(img_src.rows, img_src.cols, CV_8UC3); + img_res2 = cv::Mat(zoom_h, zoom_w, CV_8UC3); + img_niv = cv::Mat(img_src.rows, img_src.cols, CV_32SC1); + img_coul = cv::Mat(img_src.rows, img_src.cols, CV_8UC3); + loupe.reborner(img_res1, img_res2); +} + + +void Astico2D::creer_slider (const char* nom_slider, int *valeur_courante, + int valeur_max) +{ + cv::createTrackbar (nom_slider, "ImageSrc", valeur_courante, valeur_max, + onDefaultTrackbarSlide, this); +} + + +void Astico2D::modifier_slider (const char* nom_slider, int nouvelle_valeur) +{ + cv::setTrackbarPos (nom_slider, "ImageSrc", nouvelle_valeur); +} + + +void Astico2D::diminuer_slider (const char* nom_slider, int valeur_min) +{ + int nouv = cv::getTrackbarPos (nom_slider, "ImageSrc") - 1; + if (nouv >= valeur_min) modifier_slider (nom_slider, nouv); +} + + +void Astico2D::augmenter_slider (const char* nom_slider, int valeur_max) +{ + int nouv = cv::getTrackbarPos (nom_slider, "ImageSrc") + 1; + if (nouv <= valeur_max) modifier_slider (nom_slider, nouv); +} + + +void Astico2D::creer_fenetres () +{ + cv::namedWindow ("ImageSrc", cv::WINDOW_AUTOSIZE); + cv::createTrackbar ("Zoom", "ImageSrc", &loupe.zoom, loupe.zoom_max, + onZoomSlide, this); + creer_slider ("Seuil", &seuil_niv, seuil_max); + cv::setMouseCallback ("ImageSrc", onMouseEventSrc, this); + + cv::namedWindow ("Loupe", cv::WINDOW_AUTOSIZE); + cv::setMouseCallback ("Loupe", onMouseEventLoupe, this); +} + + +void Astico2D::traiter_evenements () +{ + for (;;) { + + if (recalc == R_TOUTES) + { + if (mode_transfo == M_NIVEAUX) { + //std::cout << "Calcul img_niv" << std::endl; + cv::Mat img_gry; + cv::cvtColor (img_src, img_gry, cv::COLOR_BGR2GRAY); + cv::threshold (img_gry, img_gry, seuil_niv, 255, cv::THRESH_BINARY); + img_gry.convertTo (img_niv, CV_32SC1,1., 0.); + } else { + img_coul = img_src.clone(); + } + + if (mode_transfo != M_AUCUN && touche_transfo != 0) { + //std::cout << "Calcul tranformations pour '" + // << char(touche_transfo) << "'" << std::endl; + double t1 = temps.mesurer (); + // RQ : cet appel doit produire une image couleur dans img_coul. + effectuer_transformations (); + double t2 = temps.mesurer (); + if (MESURER_TEMPS) { + std::cout << "Duree transformations : " + << std::to_string(t2-t1) << " s" << std::endl; + } + } else if (mode_transfo == M_NIVEAUX) { + representer_en_couleurs_vga (img_niv, img_coul); + } + } + + if (recalc == R_TOUTES || recalc == R_LOUPE) { + //std::cout << "Calcul images loupe" << std::endl; + loupe.dessiner_rect (img_coul, img_res1); + loupe.dessiner_portion (img_coul, img_res2); + cv::imshow ("ImageSrc", img_res1); + cv::imshow ("Loupe" , img_res2); + } + + recalc = R_RIEN; + + // Attente du prochain événement sur toutes les fenêtres, avec un + // timeout de 15ms pour détecter les changements de flags + int key = cv::waitKey (15); + + // Gestion des événements clavier avec une callback "maison" que l'on + // appelle nous-même. Les Callbacks souris et slider sont directement + // appelées par waitKey lors de l'attente. + if (onKeyPressEvent (key, this) < 0) break; + } +} + + +// Callbacks des sliders + +void Astico2D::onZoomSlide (int pos, void *data) +{ + Astico2D *ao = (Astico2D*) data; + ao->loupe.reborner (ao->img_res1, ao->img_res2); + ao->recalc = R_LOUPE; +} + +void Astico2D::onDefaultTrackbarSlide (int pos, void *data) +{ + Astico2D *ao = (Astico2D*) data; + ao->recalc = R_TOUTES; +} + + +// Callback pour la souris + +void Astico2D::onMouseEventSrc (int event, int x, int y, int flags, void *data) +{ + Astico2D *ao = (Astico2D*) data; + switch (event) { + case cv::EVENT_LBUTTONDOWN : + ao->clic_x = x; + ao->clic_y = y; + ao->clic_n = 1; + break; + case cv::EVENT_MOUSEMOVE : + // std::cout << "mouse move " << x << "," << y << std::endl; + if (ao->clic_n == 1) { + ao->loupe.deplacer (ao->img_res1, ao->img_res2, + x - ao->clic_x, y - ao->clic_y); + ao->clic_x = x; + ao->clic_y = y; + ao->recalc = R_LOUPE; + } + break; + case cv::EVENT_LBUTTONUP : + ao->clic_n = 0; + break; + } +} + + +void Astico2D::onMouseEventLoupe (int event, int x, int y, int flags, void *data) +{ + Astico2D *ao = (Astico2D*) data; + switch (event) { + case cv::EVENT_LBUTTONDOWN : + if (ao->mode_transfo == M_NIVEAUX) + ao->loupe.afficher_tableau_niveaux (ao->img_niv, x, y, 5, 4); + else ao->loupe.afficher_tableau_couleurs (ao->img_coul, x, y, 2, 3); + break; + } +} + + +// Gestion du clavier + +void Astico2D::afficher_touches_clavier () +{ + std::cout << + "Touches du clavier :\n" + " a affiche cette aide\n" + " Esc quitte (et enregistre image_res)\n" + " hHlL change la hauteur ou largeur de la loupe\n" + " jJkK deplace la loupe\n" + " zZ change le zoom\n" + " g agrandit la loupe et adapte le zoom\n" + " i inverse les couleurs de l'image d'origine\n" + " os affiche l'image d'origine ou seuillee\n" + " eE change le seuil\n"; +} + + +int Astico2D::onKeyPressEvent (int key, void *data) +{ + Astico2D *ao = (Astico2D*) data; + if (key < 0) return 0; // aucune touche pressée + key &= 255; // pour comparer avec un char + + switch (key) { + case 'a' : + ao->afficher_touches_clavier(); + break; + case 27 : // ESC pour quitter + return -1; + case 'h' : + case 'H' : + case 'l' : + case 'L' : { + int h = ao->img_res2.rows, w = ao->img_res2.cols, + a = ao->loupe.vue_min, b = ao->loupe.vue_max, + i = ao->loupe.vue_inc; + if (key == 'h') h = (h >= a+i) ? h-i : a; + else if (key == 'H') h = (h <= b-i) ? h+i : b; + else if (key == 'l') w = (w >= a+i) ? w-i : a; + else if (key == 'L') w = (w <= b-i) ? w+i : b; + std::cout << "Taille loupe " << w << "x" << h << std::endl; + ao->img_res2 = cv::Mat(h, w, CV_8UC3); + ao->loupe.reborner(ao->img_res1, ao->img_res2); + ao->recalc = R_LOUPE; + break; + } + case 'j' : + ao->loupe.deplacer (ao->img_res1, ao->img_res2, 0, -1); + ao->recalc = R_LOUPE; + break; + case 'J' : + ao->loupe.deplacer (ao->img_res1, ao->img_res2, 0, 1); + ao->recalc = R_LOUPE; + break; + case 'k' : + ao->loupe.deplacer (ao->img_res1, ao->img_res2, -1, 0); + ao->recalc = R_LOUPE; + break; + case 'K' : + ao->loupe.deplacer (ao->img_res1, ao->img_res2, 1, 0); + ao->recalc = R_LOUPE; + break; + case 'z' : + ao->diminuer_slider ("Zoom", 1); + break; + case 'Z' : + ao->augmenter_slider ("Zoom", ao->loupe.zoom_max); + break; + case 'g' : { + int z = ao->loupe.calculer_zoom_optimal (ao->img_src); + ao->modifier_slider ("Zoom", z); + int ph, pw; + ao->loupe.calculer_portion_optimale (ao->img_src, ph, pw); + std::cout << "Taille loupe " << pw << "x" << ph + <<" zoom " << z << std::endl; + ao->img_res2 = cv::Mat(ph, pw, CV_8UC3); + ao->loupe.reborner(ao->img_res1, ao->img_res2); + ao->recalc = R_LOUPE; + break; + } + case 'i' : + std::cout << "Couleurs inversees" << std::endl; + inverser_couleurs(ao->img_src); + ao->recalc = R_TOUTES; + break; + case 'o' : + std::cout << "Image originale" << std::endl; + ao->recalc = R_TOUTES; + ao->mode_transfo = M_AUCUN; + break; + case 's' : + std::cout << "Image seuillee" << std::endl; + ao->recalc = R_TOUTES; + ao->mode_transfo = M_NIVEAUX; + ao->touche_transfo = 0; + break; + case 'e' : + ao->diminuer_slider ("Seuil", 0); + break; + case 'E' : + ao->augmenter_slider ("Seuil", ao->seuil_max); + break; + + default : + if (ao->traiter_touche_clavier (key)) { + ao->recalc = R_TOUTES; + } + break; + } + return 1; +} + + +//------------------------ M E S U R E T E M P S ---------------------------- + +// Mesure du temps. +// Code extrait de EZ-Draw version 1.2 (LGPL), Edouard.Thiel@univ-amu.fr + +#if MESURER_TEMPS + +MesureTemps::MesureTemps () +{ +# ifdef _WIN32 +_init_timeofday (); +# endif +} + + +// Return the elapsed time since the Epoch in seconds,microseconds +double MesureTemps::mesurer () +{ + struct timeval tp; + _gettimeofday (&tp); + return (double) tp.tv_sec + (double) tp.tv_usec / (double) 1E6; +} + + +# ifdef _WIN32 + +// Initialize time computation. + +void MesureTemps::_init_timeofday (void) +{ + LARGE_INTEGER freq; + + _perf_freq = 0; + _get_systemtime (&_start_time); + QueryPerformanceCounter (&_start_count); + + if (QueryPerformanceFrequency (&freq) && freq.QuadPart > 0) + _perf_freq = 1.0 / freq.QuadPart; +} + + +// Emulate gettimeofday() with GetSystemTimeAsFileTime(). +// GetSystemTimeAsFileTime() has an accuracy of 15ms (10ms on XP). +// GetSystemTimePreciseAsFileTime() has an accuracy of 1ms but needs +// windows >= 8, and seems rather expensive. + +void MesureTemps::_get_systemtime (struct timeval *t) +{ + ULARGE_INTEGER ul; + FILETIME ft; + double dt; + if (t == NULL) return; + GetSystemTimeAsFileTime (&ft); + ul.LowPart = ft.dwLowDateTime; + ul.HighPart = ft.dwHighDateTime; + dt = ul.QuadPart / 10000000.0 - 11644473600.0; + t->tv_sec = dt; + t->tv_usec = (dt - t->tv_sec) * 1000000.0; +} + + +// Emulate gettimeofday() with QueryPerformanceCounter(). +// This method seems to be more accurate than _get_systemtime() +// but leads to a drift. + +void MesureTemps::_get_countertime (struct timeval *t) +{ + LARGE_INTEGER now; + double dt; + QueryPerformanceCounter (&now); + dt = (now.QuadPart - _start_count.QuadPart) * _perf_freq; + *t = _start_time; + t->tv_sec += dt; + t->tv_usec += (dt - (int)dt) * 1000000; + t->tv_sec += t->tv_usec / 1000000; + t->tv_usec %= 1000000; +} + +# endif // _WIN32 + + +// Compute elapsed time since the Epoch. + +void MesureTemps::_gettimeofday (struct timeval *t) +{ + if (t == NULL) return; +#ifdef _WIN32 + if (_perf_freq == 0) + _get_systemtime (t); + else _get_countertime (t); +#else + gettimeofday (t, NULL); +#endif +} + +#endif // MESURER_TEMPS + diff --git a/astico2d.hpp b/astico2d.hpp new file mode 100644 index 0000000..3dcd129 --- /dev/null +++ b/astico2d.hpp @@ -0,0 +1,136 @@ +/* + Module d'interface utilisateur pour Astico2D : + Atelier Simple de Transformations d'Images au Clavier avec OpenCV en 2D + + CC BY-SA Edouard.Thiel@univ-amu.fr - 03/09/2021 - v1.0 +*/ + +#include +#include +#include +#include + + +// Mesure du temps d'exécution ; mettre à 0 en cas de problème de compilation +#define MESURER_TEMPS 1 + + +#if MESURER_TEMPS +# ifdef _WIN32 +# include +# else +# include +# endif + +class MesureTemps { + public: + MesureTemps (); + double mesurer (); + + private: +# ifdef _WIN32 + struct timeval _start_time; // Initial date + LARGE_INTEGER _start_count; // Counter to compute time + double _perf_freq; // Frequency to compute time + void _init_timeofday (void); + void _get_systemtime (struct timeval *t); + void _get_countertime (struct timeval *t); +# endif + void _gettimeofday (struct timeval *t); +}; +#else // MESURER_TEMPS +class MesureTemps { + public: + MesureTemps () {} + double mesurer () { return 0.; }; +}; +#endif // MESURER_TEMPS + + +// Cette macro permet de vérifier si une image a le type attendu + +#define CHECK_MAT_TYPE(mat, format_type) \ + if (mat.type() != int(format_type)) \ + throw std::runtime_error(std::string(__func__) +\ + ": format non géré '" + std::to_string(mat.type()) +\ + "' pour la matrice '" # mat "'") + + +// Gestion des couleurs + +void representer_en_couleurs_vga (cv::Mat &img_niv, cv::Mat &img_coul); +void inverser_couleurs (cv::Mat &img); + + +// Classe Loupe pour afficher une portion de l'image avec un zoom + +class Loupe { + public: + int zoom = 5, zoom_max = 20; + int zoom_x0 = 0, zoom_y0 = 0, zoom_x1 = 100, zoom_y1 = 100; + int vue_min = 200, vue_max = 2000, vue_inc = 100; + int opt_w = 1600, opt_h = 1000; + + void reborner (cv::Mat &res1, cv::Mat &res2); + void deplacer (cv::Mat &res1, cv::Mat &res2, int dx, int dy); + int calculer_zoom_optimal (cv::Mat &src); + void calculer_portion_optimale (cv::Mat &src, int &ph, int &pw); + void dessiner_rect (cv::Mat &src, cv::Mat &dest); + void dessiner_portion (cv::Mat &src, cv::Mat &dest); + void afficher_tableau_niveaux (cv::Mat &src, int ex, int ey, int rx, int ry); + void afficher_tableau_couleurs (cv::Mat &src, int ex, int ey, int rx, int ry); +}; + + +// Classe Astico2D de gestion de l'interface utilisateur + +class Astico2D { + + public: + int init_ok = true; + char *nom_in1, *nom_out2; + cv::Mat img_src, img_res1, img_res2, img_niv, img_coul; + Loupe loupe; + int seuil_niv = 127, seuil_max = 255; + int clic_x = 0; + int clic_y = 0; + int clic_n = 0; + int zoom_w = 600, zoom_h = 500; + + enum RecalculsImages { R_RIEN, R_LOUPE, R_TOUTES }; + RecalculsImages recalc = R_TOUTES; + + char touche_transfo = 0; + enum ModeTransfo { M_AUCUN, M_NIVEAUX, M_COULEURS }; + ModeTransfo mode_transfo = M_AUCUN; + + MesureTemps temps; + + Astico2D (int &argc, char **&argv); + int run (); + + void afficher_usage (const char *nom_prog); + virtual void afficher_touches_clavier (); + int analyser_arguments (int &argc, char **&argv); + int lire_image_entree (); + int enregistrer_image_resultat (); + void creer_images_resultat (); + void creer_slider (const char* nom_slider, int *valeur_courante, + int valeur_max); + void modifier_slider (const char* nom_slider, int nouvelle_valeur); + void diminuer_slider (const char* nom_slider, int valeur_min); + void augmenter_slider (const char* nom_slider, int valeur_max); + + void creer_fenetres (); + void traiter_evenements (); + virtual bool traiter_touche_clavier (char key) { return false; }; + virtual void effectuer_transformations () {}; + + private: + static void onZoomSlide (int pos, void *data); + static void onDefaultTrackbarSlide (int pos, void *data); + static void onMouseEventSrc (int event, int x, int y, int flags, void *data); + static void onMouseEventLoupe (int event, int x, int y, int flags, void *data); + static int onKeyPressEvent (int key, void *data); +}; + diff --git a/demo1.cpp b/demo1.cpp new file mode 100644 index 0000000..f78ddf8 --- /dev/null +++ b/demo1.cpp @@ -0,0 +1,165 @@ +/* + demo1.cpp - exemple d'utilisation de Astico2D + + CC BY-SA Edouard.Thiel@univ-amu.fr - 22/08/2021 + + Usage : + $ make demo1 + $ ./demo1 [-mag width height] [-thr seuil] image_in [image_out] +*/ + +/* + Pour le rendu de TP : + - renommez ce fichier tp-.cpp + - écrivez ci-dessous vos NOMS Prénoms et la date de la version : + + [et ] - version du +*/ + + +#include "astico2d.hpp" + + +//----------------------- T R A N S F O R M A T I O N S ----------------------- + +// Placez ici vos fonctions de transformations à la place de ces exemples + + +void transformer_bandes_horizontales (cv::Mat &img_niv) +{ + CHECK_MAT_TYPE(img_niv, CV_32SC1); + + for (int y = 0; y < img_niv.rows; y++) + for (int x = 0; x < img_niv.cols; x++) + { + int g = img_niv.at(y,x); + if (g > 0) { + img_niv.at(y,x) = y; + } + } +} + + +void transformer_bandes_verticales (cv::Mat &img_niv) +{ + CHECK_MAT_TYPE(img_niv, CV_32SC1); + + for (int y = 0; y < img_niv.rows; y++) + for (int x = 0; x < img_niv.cols; x++) + { + int g = img_niv.at(y,x); + if (g > 0) { + img_niv.at(y,x) = x; + } + } +} + + +void transformer_bandes_diagonales (cv::Mat &img_niv) +{ + CHECK_MAT_TYPE(img_niv, CV_32SC1); + + for (int y = 0; y < img_niv.rows; y++) + for (int x = 0; x < img_niv.cols; x++) + { + int g = img_niv.at(y,x); + if (g > 0) { + img_niv.at(y,x) = x+y; + } + } +} + + +//----------------------------- I N T E R F A C E ----------------------------- + + +class MonApp : public Astico2D { + + public: + // Déclarez ici d'autres membres éventuels + + + MonApp (int argc, char **argv) : + Astico2D (argc, argv) + // initialisez ici vos classes membre éventuelles + { + if (!init_ok) return; // erreur dans l'initialisation + + // Autres actions du constructeur + } + + + void afficher_touches_clavier () override + { + // Ceci affiche l'aide de base + Astico2D::afficher_touches_clavier (); + + // Indiquez ici les touches du clavier et vos transformations + std::cout << + " 1 dessine des bandes horizontales\n" + " 2 dessine des bandes verticales\n" + " 3 dessine des bandes diagonales\n" + << std::endl; + } + + + bool traiter_touche_clavier (char key) override + { + switch (key) { + + // Gérez ici les touches de flags. + + // Rajoutez ici les touches pour effectuer_transformations. + case '1' : + case '2' : + case '3' : + // On mémorise la touche pressée + touche_transfo = key; + // On précise le mode : M_NIVEAUX ou M_COULEURS + mode_transfo = M_NIVEAUX; + break; + + default : return false; // touche non traitée + } + return true; // touche traitée + } + + + void effectuer_transformations () override + { + // Appelez ici vos transformations selon touche_transfo et mode_transfo : + // - si mode_transfo est M_NIVEAUX, l'image d'entrée est l'image seuillée + // img_niv, de type CV_32SC1 ; à la fin, pour l'affichage, il faut la + // convertir en couleur dans img_coul, de type CV_8UC3, par exemple avec + // representer_en_couleurs_vga. + // - si mode_transfo est M_COULEURS, travaillez directement sur img_coul, + // de type CV_8UC3, elle sera affichée telle quelle. + + switch (touche_transfo) { + case '1' : + transformer_bandes_horizontales (img_niv); + representer_en_couleurs_vga (img_niv, img_coul); + break; + case '2' : + transformer_bandes_verticales (img_niv); + representer_en_couleurs_vga (img_niv, img_coul); + break; + case '3' : + transformer_bandes_diagonales (img_niv); + representer_en_couleurs_vga (img_niv, img_coul); + break; + } + } + +}; + + +//---------------------------------- M A I N ---------------------------------- + + +int main (int argc, char **argv) +{ + MonApp app (argc, argv); + return app.run (); +} + diff --git a/demo2.cpp b/demo2.cpp new file mode 100644 index 0000000..800f3b5 --- /dev/null +++ b/demo2.cpp @@ -0,0 +1,244 @@ +/* + demo2.cpp - exemple d'utilisation de Astico2D + + CC BY-SA Edouard.Thiel@univ-amu.fr - 22/08/2021 + + Usage : + $ make demo2 + $ ./demo2 [-mag width height] [-thr seuil] image_in [image_out] +*/ + +/* + Pour le rendu de TP : + - renommez ce fichier tp-.cpp + - écrivez ci-dessous vos NOMS Prénoms et la date de la version : + + [et ] - version du +*/ + + +#include "astico2d.hpp" + + +//----------------------- T R A N S F O R M A T I O N S ----------------------- + +// Placez ici vos fonctions de transformations à la place de ces exemples + + +enum NatureDessin { D_BLANC, D_NOIR, D_BLEU, D_INV, D_LAST }; +char const *nom_dessins[4] = {"blanc", "noir", "bleu", "inverse"}; + + +void dessiner_damier_couleur (cv::Mat &img_coul, int taille, + int coulR, int coulG, int coulB) +{ + CHECK_MAT_TYPE(img_coul, CV_8UC3); + + for (int y = 0; y < img_coul.rows; y++) + for (int x = 0; x < img_coul.cols; x++) + { + int numero_case_x = x / taille; + int numero_case_y = y / taille; + if ((numero_case_x + numero_case_y) % 2) { + // Attention img_coul est en B, G, R + img_coul.at(y,x)[0] = coulB; + img_coul.at(y,x)[1] = coulG; + img_coul.at(y,x)[2] = coulR; + } + } +} + + +void dessiner_damier_inverse (cv::Mat &img_coul, int taille) +{ + CHECK_MAT_TYPE(img_coul, CV_8UC3); + + for (int y = 0; y < img_coul.rows; y++) + for (int x = 0; x < img_coul.cols; x++) + { + int numero_case_x = x / taille; + int numero_case_y = y / taille; + if ((numero_case_x + numero_case_y) % 2) { + img_coul.at(y,x)[0] = 255 - img_coul.at(y,x)[0]; + img_coul.at(y,x)[1] = 255 - img_coul.at(y,x)[1]; + img_coul.at(y,x)[2] = 255 - img_coul.at(y,x)[2]; + } + } +} + + +void dessiner_damier (cv::Mat &img_coul, int taille, NatureDessin nat) +{ + switch (nat) { + case D_BLANC : + dessiner_damier_couleur (img_coul, taille, 255, 255, 255); + break; + case D_NOIR : + dessiner_damier_couleur (img_coul, taille, 0, 0, 0); + break; + case D_BLEU : + dessiner_damier_couleur (img_coul, taille, 0, 0, 255); + break; + case D_INV : + dessiner_damier_inverse (img_coul, taille); + break; + + default : + std::cout << "Erreur, nature de dessin non prevu" << std::endl; + } +} + + +void calculer_differences_en_x (cv::Mat &img_niv) +{ + CHECK_MAT_TYPE(img_niv, CV_32SC1); + + // Remarque : l'image img_niv étant seuillée (valeurs 0 ou 255), le + // résultat sera dans { -255, 0, 255 }. + for (int y = img_niv.rows-1; y > 0; y--) + for (int x = img_niv.cols-1; x > 0; x--) + { + img_niv.at(y,x) = img_niv.at(y,x) - img_niv.at(y,x-1); + } +} + + +void representer_en_rouge_ou_vert (cv::Mat &img_niv, cv::Mat &img_coul) +{ + CHECK_MAT_TYPE(img_niv, CV_32SC1); + CHECK_MAT_TYPE(img_coul, CV_8UC3); + + // Palette rouge dans les valeurs négatives, vert dans les valeurs positives + for (int y = 0; y < img_niv.rows; y++) + for (int x = 0; x < img_niv.cols; x++) + { + int g = img_niv.at(y,x); + // Attention img_coul est en B, G, R + if (g >= 0) { + img_coul.at(y,x)[0] = 0; + img_coul.at(y,x)[1] = g % 256; // Vert + img_coul.at(y,x)[2] = 0; + } else { + img_coul.at(y,x)[0] = 0; + img_coul.at(y,x)[1] = 0; + img_coul.at(y,x)[2] = (-g) % 256 ; // Rouge + } + } +} + + +//----------------------------- I N T E R F A C E ----------------------------- + + +class MonApp : public Astico2D { + + public: + // Déclarez ici d'autres membres éventuels + int taille = 20, taille_max = 100; + NatureDessin nature_dessin = D_BLANC; + + + MonApp (int argc, char **argv) : + Astico2D (argc, argv) + // initialisez ici vos classes membre éventuelles + { + if (!init_ok) return; // erreur dans l'initialisation + + // On crée ici un slider "Taille" qui manipulera le membre taille de 0 + // à taille_max. + creer_slider ("Taille", &taille, taille_max); + + // On affiche les arguments supplémentaires de la ligne de commande + std::cout << "Arguments restant : argc = " << argc; + if (argc > 0) { + std::cout << " argv ="; + for (int i = 0; i < argc; i++) std::cout << " " << argv[i]; + } + std::cout << std::endl; + } + + + void afficher_touches_clavier () override + { + // Ceci affiche l'aide de base + Astico2D::afficher_touches_clavier (); + + // Indiquez ici les touches du clavier et vos transformations + std::cout << + " 1 dessine un damier sur l'image seuillee\n" + " tT deplace le slider Taille\n" + " n change la nature du dessin\n" + << std::endl; + } + + + bool traiter_touche_clavier (char key) override + { + switch (key) { + + // Gérez ici les touches de flags. + case 't' : diminuer_slider ("Taille", 0); break; + case 'T' : augmenter_slider ("Taille", taille_max); break; + + case 'n' : { + int suiv = static_cast(nature_dessin)+1; + nature_dessin = static_cast(suiv); + if (nature_dessin == D_LAST) nature_dessin = D_BLANC; + std::cout << "Nature dessin : " + << nom_dessins[static_cast(nature_dessin)] << std::endl; + break; + } + + // Rajoutez ici les touches pour effectuer_transformations. + case '1' : + // On mémorise la touche pressée + touche_transfo = key; + // On précise le mode : M_NIVEAUX ou M_COULEURS + mode_transfo = M_COULEURS; + break; + + case '2' : + touche_transfo = key; + mode_transfo = M_NIVEAUX; + break; + + default : return false; // touche non traitée + } + return true; // touche traitée + } + + + void effectuer_transformations () override + { + // Appelez ici vos transformations selon touche_transfo et mode_transfo : + // - si mode_transfo est M_NIVEAUX, l'image d'entrée est l'image seuillée + // img_niv, de type CV_32SC1 ; à la fin, pour l'affichage, il faut la + // convertir en couleur dans img_coul, de type CV_8UC3, par exemple avec + // representer_en_couleurs_vga. + // - si mode_transfo est M_COULEURS, travaillez directement sur img_coul, + // de type CV_8UC3, elle sera affichée telle quelle. + + switch (touche_transfo) { + case '1' : // M_COULEURS + dessiner_damier (img_coul, taille, nature_dessin); + break; + + case '2' : // M_NIVEAUX + calculer_differences_en_x (img_niv); + representer_en_rouge_ou_vert (img_niv, img_coul); + break; + } + } + +}; + + +//---------------------------------- M A I N ---------------------------------- + + +int main (int argc, char **argv) +{ + MonApp app (argc, argv); + return app.run (); +} + diff --git a/index.html b/index.html new file mode 100644 index 0000000..1e2cb8d --- /dev/null +++ b/index.html @@ -0,0 +1,83 @@ + + + + Astico2D + + + + + + + +
+
+ Astico2D +
+
+ Lien court : + j.mp/astico2d +
+
+ +
+ +

+ Atelier Simple de Transformations d'Images au Clavier avec OpenCV en 2D +

+ +

+ CC BY-SA - + 2021, version 1.0 - + Edouard Thiel +

+ +

+ Astico2D est un module qui permet d'écrire de petits programmes en C++, pour + appliquer des transformations sur une image couleur, l'inverser ou faire un + seuil en noir et blanc, afficher l'image résultat et zoomer sur une portion. + Le module s'appuie sur la librairie OpenCV, + disponible pour les systèmes Linux, windows et MacOS. +

+ + + + + + + + + + + + + + +
astico2d.tgzTéléchargez les sources prêts à compiler, avec quelques images de test.
InstallationLa documentation pour installer et compiler.
CaptureUne capture d'écran.
+ +

+ L'interface est extrêmement simple et s'appuie sur HighGui, le petit GUI + intégré à OpenCV : le but est de n'avoir rien d'autre à installer qu'un + compilateur C++, make et OpenCV pour développer. + Les opérations sont déclenchées par des touches du clavier ou la souris. + Les images en entrée doivent être de petite taille, en couleur ou noir + et blanc. +

+ +

+ Des petits exemples commentés sont fournis, qui montrent comment écrire + un programme utilisant Astico2D pour programmer des algorithmes de + transformations d'image : + demo1.cpp, demo2.cpp. + Ces exemples peuvent être directement copiés comme fichier de départ + pour des TP d'imagerie. +

+ +
+ + + + + + diff --git a/install-astico2d.txt b/install-astico2d.txt new file mode 100644 index 0000000..9917843 --- /dev/null +++ b/install-astico2d.txt @@ -0,0 +1,168 @@ +Installation de OpenCV et Astico2D +================================== + +CC-BY Edouard.Thiel@univ-amu.fr - 14/09/2021 + + +Sommaire : + 0. Préambule + 1. Sur Ubuntu + 2. Sur Windows + 2.1 Sur Windows 10 avec WSL ou WSL2 + 2.2 Sur Windows 10 avec MinGW64 + 3. Sur MacOS + + +0. Préambule + --------- + + Téléchargez les sources astico2d.tgz puis décompressez l'archive. + + +1. Sur Ubuntu + ---------- + + Ouvrez un terminal puis tapez : + sudo apt install g++ make libopencv-dev + + Allez ensuite dans le répertoire de Astico2D avec la commande cd, puis tapez + make -j all + ./demo1 IMAGES/chat1.jpg + + Si lors de la compilation il y a des messages d'erreur relatifs à pkg-config + (par exemple sur Ubuntu <= 18.04), éditez le fichier Makefile, remplacez + "OPENCV = opencv4" par "OPENCV = opencv" puis relancez la compilation. + + +2. Sur Windows + ----------- + +2.1 Sur Windows 10 avec WSL ou WSL2 + ------------------------------- + + WSL (Windows Subsystem for Linux) permet d'intégrer nativement une machine + virtuelle Linux dans Windows 10, avec un choix de distributions. + + Avantage : + - facile et assez rapide à installer, et ensuite on peut installer tous + les logiciels Linux avec apt install. + Inconvénient : + - avec WSL installé on ne peut plus utiliser VirtualBox. + + Pour installer WSL ou WSL2 sous Windows 10, avec une distribution Ubuntu : + https://docs.microsoft.com/fr-fr/windows/wsl/install-win10 + + Installer aussi un serveur X pour l'affichage graphique : + https://sourceforge.net/projects/vcxsrv/ + Après installation, dans le menu Windows, lancez VcXsrv / XLaunch + puis acceptez toutes les valeurs par défaut. + Pour tester, tapez dans le terminal Ubuntu : + sudo apt install x11-apps + xclock + + Installation de OpenCV : ouvrez un terminal Ubuntu puis tapez : + sudo apt install g++ make libopencv-dev + + Dans le répertoire de Astico2D modifiez le fichier Makefile en mettant + OPENCV = opencv + + Dans le terminal Ubuntu, allez dans le répertoire de Astico2D avec la commande + cd, puis tapez + make -j all + ./demo1 IMAGES/chat1.jpg + + +2.2 Sur Windows 10 avec MinGW64 + --------------------------- + + MinGW est un ensemble d'utilitaires GNU pour windows (gcc, make, etc). + + Avantage : + - on peut développer avec gcc pour l'API Windows, y compris pour des + Windows plus anciens. + Inconvénients : + - il faudra compiler OpenCV (facile mais durée 1 à 2h selon la machine) ; + - les trackbar (sliders) de OpenCV seront un peu laids et peu pratiques. + + a) Installer Mingw-w64 : + http://www.mingw-w64.org/doku.php/download/mingw-builds + + cliquer sur Sourceforge ... Enregistrer le fichier + ouvrir mingw-w64-install.exe + Setup : laisser par défaut (Threads: posix) + Destination folder : remplacer par c:\local\mingw64 (créer les sous-répertoires) + Next, Finish. + + Dans l'aide Windows : + taper "Environnement" -> Modifier les variables d'environnement + dans la fenêtre Propriétés systèmes, Paramètres systèmes avancés, + en bas Variables d'environnement... + dans Variables système : cliquer sur Path, modifier, nouveau + rajouter : c:\local\mingw64\mingw32\bin + + Ouvrir un terminal ("invite de commande") + taper : echo %PATH% + et vérifiez que le chemin de mingw apparaît bien. + + tapez : g++ --version + pour vérifier que g++ fonctionne. + + b) Sources de OpenCV + https://github.com/opencv/opencv/releases + choisir le plus récent, Source code (zip) + Décompactez le zip sur votre bureau + on obtient par ex. : C:\Users\thiel\Desktop\opencv-4.5.3 + + c) Installer cmake + https://cmake.org/download/ + + choisir Latest Stable Release, Binary distribution, Windows x64 installer, .msi + cocher Add CMake to the system PATH for all users + Install CMake to: c:\local\CMake\ (créer le sous-répertoire) + + Exécuter cmake-gui + + d) Compilation de OpenCV + dans cmake-gui : + where is the source code : C:/Users/thiel/Desktop/opencv-4.5.3 + where to build the binaries: c:/local/opencv/build-4.5.3-mingw64 + (créer les sous-répertoires) + Configure + Generator : MinGW Makefiles, default, Finish + puis attendre... "Configuration done" + Generate + ... Generating done + Quitter + + Ouvrir un terminal + cd C:\local\opencv\build-4.5.3-mingw64 + mingw32-make + ... compilation générale, durée environ 1h ... + mingw32-make install + + Enfin, dans les variables d'environnement, dans Variables système : + cliquer sur Path, modifier, nouveau + C:\local\opencv\build-4.5.3-mingw64\install\x64\mingw\bin + + Fermer le terminal actuel, ouvrez un nouveau terminal ; + vérifiez PATH en tapant : echo %PATH% + + Tapez : opencv_version (affiche par exemple : 4.5.3) + + e) Compilation de Astico2D + Allez dans le répertoire de Astico2D + Editez Makefile-mingw, modifiez éventuellement le chemin et la version : + OPENCVPATH = C:\local\opencv\build-4.5.3-mingw64\install + VER = 453 + + Tapez : make -j all + demo1.exe IMAGES/chat1.jpg + + +3. Sur MacOS + --------- + +Si vous disposez d'un Mac, merci de m'indiquer la procédure d'installation +d'un compilateur C++ et de OpenCV pour que puisse la présenter ici. + + diff --git a/tp8-andré-colin.cpp b/tp8-andré-colin.cpp new file mode 100644 index 0000000..2ec8c9f --- /dev/null +++ b/tp8-andré-colin.cpp @@ -0,0 +1,361 @@ +/* + demo1.cpp - exemple d'utilisation de Astico2D + + CC BY-SA Edouard.Thiel@univ-amu.fr - 22/08/2021 + + Usage : + $ make demo1 + $ ./demo1 [-mag width height] [-thr seuil] image_in [image_out] +*/ + +/* + Pour le rendu de TP : + - renommez ce fichier tp-.cpp + - écrivez ci-dessous vos NOMS Prénoms et la date de la version : + + ANDRÉ Jérémy et COLIN Cyril - version du 2021-12-03 +*/ + + +#include "astico2d.hpp" + + + +struct ES { + size_t w, h; + std::vector weights; + std::vector offsets; + std::string name; + + ES(size_t w, size_t h, std::vector weights, std::string name="") + :w(w), + h(h), + weights(weights), + offsets(w * h), + name(name) { + assert(weights.size() == w * h); + for (size_t i = 0; i < weights.size(); i++) { + int x_off = (i % w) - w/2; + int y_off = (i / w) - h/2; + offsets[i] = {x_off, y_off}; + } + } + + ES() {} + + static ES Square3(int weight=1) { + return ES(3, 3, std::vector(3 * 3, weight), "Carré 3×3"); + } + + static ES Diamond3(int weight=1) { + return ES(3, 3, {0, weight, 0, weight, weight, weight, 0, weight, 0}, + "Losange 3×3"); + } + + ES ccw_rotate() const { + ES ret = *this; + for (cv::Point &offset : ret.offsets) { + int tmp = offset.x; + offset.x = -offset.y; + offset.y = tmp; + } + return ret; + } +}; + + +const int img_at_s(const cv::Mat &img, const cv::Point p, int fill=0) { + if (p.x < 0 || p.x >= img.cols + || p.y < 0 || p.y >= img.rows) + return fill; + return img.at(p); +} + +//----------------------- T R A N S F O R M A T I O N S ----------------------- + +void erosion(cv::Mat &img, const ES &es, int vide=0) { + cv::Mat orig = img.clone(); + img.forEach([&](int &val, const int *position) { + cv::Point p {position[1], position[0]}; + for (size_t i = 0; i < es.weights.size(); i++) { + int val2 = img_at_s(orig, p + es.offsets[i]); + if (es.weights[i] > 0 && val2 == vide) { + val = vide; + } + } + }); +} + + +void dilatation(cv::Mat &img, const ES &es) { + erosion(img, es, 255); +} + + +void ouverture(cv::Mat &img, ES &es, int n_iter=1) { + for (int i = 0; i < n_iter; i++) erosion(img, es); + for (int i = 0; i < n_iter; i++) dilatation(img, es); +} + + +void fermeture(cv::Mat &img, ES &es, int n_iter=1) { + for (int i = 0; i < n_iter; i++) dilatation(img, es); + for (int i = 0; i < n_iter; i++) erosion(img, es); +} + + +void debruiter(cv::Mat &img, ES &es, int n_iter=1) { + ouverture(img, es, n_iter); + fermeture(img, es, n_iter); +} + + +void debruiter2(cv::Mat &img, ES &es, int n_iter=1) { + // il semble que debruiter2 inclus debruiter + fermeture(img, es, n_iter); + ouverture(img, es, n_iter); +} + + +void squelette(cv::Mat &img, ES &es) { + std::cout << " .-.\n (o.o)\n |=|\n __|__\n //.=|=.\\\\\n // .=|=. \\\\\n \\\\ .=|=. //\n \\\\(_=_)//\n (:| |:)\n || ||\n () ()\n || ||\n || ||\n ==' '==\n" << std::endl; +} + + +void squelette2(cv::Mat &img, ES &es) { + cv::Mat tmp = img.clone(); + img.setTo(0); + cv::Mat ouv; + bool continuer; + int iter = 0; + do { + continuer = false; + ouv = tmp.clone(); + ouverture(tmp, es); + ouv.forEach([&](int &val, const int *position) { + cv::Point p {position[1], position[0]}; + if (val != tmp.at(p)) { + img.at(p) = iter; + continuer = true; + } + }); + erosion(tmp, es); + iter++; + } while (continuer); +} + + +void reconstruction(cv::Mat &img, ES &es) { + int max = 0; + img.forEach([&](int &val, const int *position) { + if (val > max) max = val; + }); + cv::Mat tmp = img.clone(); + img.setTo(0); + for (int i = max; i > 0; i--) { + /* Keep only values >= max */ + tmp.forEach([&](int &val, const int *position) { + cv::Point p {position[1], position[0]}; + if (val >= i) { + img.at(p) = 255; + } + }); + + dilatation(img, es); + } +} + + +//----------------------------- TP8 ----------------------------- + + +void debruiter_empreinte(cv::Mat &img) { + ES es(5, 3, + {0, 1, 1, 1, 0, + 1, 1, 1, 1, 1, + 0, 1, 1, 1, 0, + }); + ouverture(img, es); + fermeture(img, es); +} + + +typedef ES TOR_mask; + + +template +void hit(cv::Mat &img, const TOR_mask &mask, F func) { + cv::Mat tmp = img.clone(); + tmp.forEach([&](int &val, const int *position) { + cv::Point p {position[1], position[0]}; + for (size_t i = 0; i < mask.offsets.size(); i++) { + int val2 = img_at_s(tmp, p + mask.offsets[i]); + if ((mask.weights[i] > 0 && val2 == 0) + || (mask.weights[i] == 0 && val2 != 0)) { + return; + } + } + func(p); + }); +} + + +void amincissement(cv::Mat &img, + const TOR_mask &mask_a, const TOR_mask &mask_b, + bool run_until_stable=true) { + TOR_mask masks[8]; + { + size_t i = 0; + masks[i] = mask_a; + for (i++; i < 4; i++) { + masks[i] = masks[i - 1].ccw_rotate(); + } + masks[i] = mask_b; + for (i++; i < 8; i++) { + masks[i] = masks[i - 1].ccw_rotate(); + } + } + bool has_changed; + do { + has_changed = false; + for (size_t i = 0; i < 8; i++) { + hit(img, masks[i], [&](const cv::Point &p) { + has_changed = true; + img.at(p) = 0; + }); + } + } while (run_until_stable ? has_changed : false); +} + + +void elagage(cv::Mat &img, size_t n_iter=1) { + static const TOR_mask mask_a(3, 3, { + -1, 0, 0, + 1, 1, 0, + -1, 0, 0, + }); + static const TOR_mask mask_b(3, 3, { + 0, 0, 0, + 0, 1, 0, + 1, 0, 0, + }); + for (size_t i = 0; i < n_iter; i++) { + amincissement(img, mask_a, mask_b, false); + } +} + + +//----------------------------- I N T E R F A C E ---------------------------- + + +class MonApp : public Astico2D { + const int n_iter_min = 1; + const int n_iter_max = 50; + int n_iter = n_iter_min; + +public: + // Déclarez ici d'autres membres éventuels + + + MonApp (int argc, char **argv) : + Astico2D (argc, argv) + // initialisez ici vos classes membre éventuelles + { + if (!init_ok) return; // erreur dans l'initialisation + + // Autres actions du constructeur + creer_slider ("Nb iter", &n_iter, n_iter_max); + } + + + void afficher_touches_clavier () override + { + // Ceci affiche l'aide de base + Astico2D::afficher_touches_clavier (); + + // Indiquez ici les touches du clavier et vos transformations + std::cout << + " 1 débruiter empreinte\n" + " 2 amincissement\n" + " 3 élagage\n" + << std::endl; + } + + + bool traiter_touche_clavier (char key) override + { + switch (key) { + + // Gérez ici les touches de flags. + + // Rajoutez ici les touches pour effectuer_transformations. + case '1' : + case '2' : + case '3' : + case '4' : + case '5' : + case '6' : + case '7' : + case '8' : + case '9' : + // On mémorise la touche pressée + touche_transfo = key; + // On précise le mode : M_NIVEAUX ou M_COULEURS + mode_transfo = M_NIVEAUX; + break; + + default : return false; // touche non traitée + } + return true; // touche traitée + } + + + void effectuer_transformations () override + { + // Appelez ici vos transformations selon touche_transfo et mode_transfo : + // - si mode_transfo est M_NIVEAUX, l'image d'entrée est l'image seuillée + // img_niv, de type CV_32SC1 ; à la fin, pour l'affichage, il faut la + // convertir en couleur dans img_coul, de type CV_8UC3, par exemple avec + // representer_en_couleurs_vga. + // - si mode_transfo est M_COULEURS, travaillez directement sur img_coul, + // de type CV_8UC3, elle sera affichée telle quelle. + + ES es = ES::Diamond3(); + TOR_mask mask_a(3, 3, { + 0, 0, 0, + -1, 1, -1, + 1, 1, 1, + }); + TOR_mask mask_b(3, 3, { + -1, 0, 0, + 1, 1, 0, + 1, 1, -1, + }); + switch (touche_transfo) { + case '1' : + debruiter_empreinte (img_niv); + representer_en_couleurs_vga (img_niv, img_coul); + break; + case '2' : + amincissement (img_niv, mask_a, mask_b); + representer_en_couleurs_vga (img_niv, img_coul); + break; + case '3' : + amincissement (img_niv, mask_a, mask_b); + elagage (img_niv, n_iter); + representer_en_couleurs_vga (img_niv, img_coul); + break; + } + } + +}; + + +//---------------------------------- M A I N ---------------------------------- + + +int main (int argc, char **argv) +{ + MonApp app (argc, argv); + return app.run (); +}