/* 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 (); }