initial commit

This commit is contained in:
papush 2021-12-13 08:59:59 +01:00
commit 78c2612c7d
31 changed files with 1893 additions and 0 deletions

BIN
IMAGES/blob1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
IMAGES/bubblets.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
IMAGES/chat1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
IMAGES/fleur1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
IMAGES/fleur2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
IMAGES/joint_J1.pgm Normal file

Binary file not shown.

BIN
IMAGES/joint_J2.pgm Normal file

Binary file not shown.

BIN
IMAGES/joint_J3.pgm Normal file

Binary file not shown.

BIN
IMAGES/joint_J4.pgm Normal file

Binary file not shown.

BIN
IMAGES/joint_J5.pgm Normal file

Binary file not shown.

BIN
IMAGES/joint_J6.pgm Normal file

Binary file not shown.

BIN
IMAGES/joint_J7.pgm Normal file

Binary file not shown.

BIN
IMAGES/joint_K1.pgm Normal file

Binary file not shown.

BIN
IMAGES/joint_K2.pgm Normal file

Binary file not shown.

BIN
IMAGES/joint_K3.pgm Normal file

Binary file not shown.

BIN
IMAGES/joint_K4.pgm Normal file

Binary file not shown.

BIN
IMAGES/joint_K5.pgm Normal file

Binary file not shown.

BIN
IMAGES/joint_K6.pgm Normal file

Binary file not shown.

BIN
IMAGES/joint_K7.pgm Normal file

Binary file not shown.

BIN
IMAGES/joint_K8.pgm Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
IMAGES/paper1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
IMAGES/test.pgm Normal file

Binary file not shown.

45
Makefile Normal file
View File

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

691
astico2d.cpp Normal file
View File

@ -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<int>(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<cv::Vec3b>(y,x)[0] = couls[c][2];
img_coul.at<cv::Vec3b>(y,x)[1] = couls[c][1];
img_coul.at<cv::Vec3b>(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<cv::Vec3b>(y,x)[0] = 255 - img.at<cv::Vec3b>(y,x)[0];
img.at<cv::Vec3b>(y,x)[1] = 255 - img.at<cv::Vec3b>(y,x)[1];
img.at<cv::Vec3b>(y,x)[2] = 255 - img.at<cv::Vec3b>(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<cv::Vec3b>(y,x)[0] = 64;
dest.at<cv::Vec3b>(y,x)[1] = 64;
dest.at<cv::Vec3b>(y,x)[2] = 64;
continue;
}
dest.at<cv::Vec3b>(y,x)[0] = src.at<cv::Vec3b>(y0,x0)[0];
dest.at<cv::Vec3b>(y,x)[1] = src.at<cv::Vec3b>(y0,x0)[1];
dest.at<cv::Vec3b>(y,x)[2] = src.at<cv::Vec3b>(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<int>(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<int>(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<cv::Vec3b>(y,x)[2],
cV = src.at<cv::Vec3b>(y,x)[1],
cB = src.at<cv::Vec3b>(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

136
astico2d.hpp Normal file
View File

@ -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 <iostream>
#include <iomanip>
#include <cstring>
#include <opencv2/opencv.hpp>
// 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 <windows.h>
# else
# include <sys/time.h>
# 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);
};

165
demo1.cpp Normal file
View File

@ -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<n°-de-la-planche>-<vos-noms>.cpp
- écrivez ci-dessous vos NOMS Prénoms et la date de la version :
<NOM1 Prénom1> [et <NOM2 Prénom2>] - version du <date>
*/
#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<int>(y,x);
if (g > 0) {
img_niv.at<int>(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<int>(y,x);
if (g > 0) {
img_niv.at<int>(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<int>(y,x);
if (g > 0) {
img_niv.at<int>(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 ();
}

244
demo2.cpp Normal file
View File

@ -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<n°-de-la-planche>-<vos-noms>.cpp
- écrivez ci-dessous vos NOMS Prénoms et la date de la version :
<NOM1 Prénom1> [et <NOM2 Prénom2>] - version du <date>
*/
#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<cv::Vec3b>(y,x)[0] = coulB;
img_coul.at<cv::Vec3b>(y,x)[1] = coulG;
img_coul.at<cv::Vec3b>(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<cv::Vec3b>(y,x)[0] = 255 - img_coul.at<cv::Vec3b>(y,x)[0];
img_coul.at<cv::Vec3b>(y,x)[1] = 255 - img_coul.at<cv::Vec3b>(y,x)[1];
img_coul.at<cv::Vec3b>(y,x)[2] = 255 - img_coul.at<cv::Vec3b>(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<int>(y,x) = img_niv.at<int>(y,x) - img_niv.at<int>(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<int>(y,x);
// Attention img_coul est en B, G, R
if (g >= 0) {
img_coul.at<cv::Vec3b>(y,x)[0] = 0;
img_coul.at<cv::Vec3b>(y,x)[1] = g % 256; // Vert
img_coul.at<cv::Vec3b>(y,x)[2] = 0;
} else {
img_coul.at<cv::Vec3b>(y,x)[0] = 0;
img_coul.at<cv::Vec3b>(y,x)[1] = 0;
img_coul.at<cv::Vec3b>(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<int>(nature_dessin)+1;
nature_dessin = static_cast<NatureDessin>(suiv);
if (nature_dessin == D_LAST) nature_dessin = D_BLANC;
std::cout << "Nature dessin : "
<< nom_dessins[static_cast<int>(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 ();
}

83
index.html Normal file
View File

@ -0,0 +1,83 @@
<!DOCTYPE html>
<html>
<head>
<title>Astico2D</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="http://pageperso.lif.univ-mrs.fr/~edouard.thiel/styleET2019c.css" />
<link rel="icon" type="image/x-icon" href="http://pageperso.lif.univ-mrs.fr/~edouard.thiel/favicon.ico">
</head>
<body>
<div id="bandeau">
<div class="titre">
Astico2D
</div>
<div class="droite">
<span class="legende">Lien court :</span>
<a href="http://j.mp/astico2d">j.mp/astico2d</a>
</div>
</div>
<div class="mono">
<p class="centrer sauter gras">
Atelier Simple de Transformations d'Images au Clavier avec OpenCV en 2D
</p>
<p class="centrer sauter">
<a href="https://creativecommons.org/licenses/by-sa/4.0/deed.fr">CC BY-SA</a> -
2021, version 1.0 -
<a href="http://pageperso.lif.univ-mrs.fr/~edouard.thiel/">Edouard Thiel</a>
</p>
<p class="justifier">
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 <a href="https://opencv.org">OpenCV</a>,
disponible pour les systèmes Linux, windows et MacOS.
</p>
<table class="table1">
<tr>
<td><a href="../astico2d.tgz">astico2d.tgz</a></td>
<td>Téléchargez les sources prêts à compiler, avec quelques images de test.</td>
</tr>
<tr>
<td><a href="install-astico2d.txt">Installation</a></td>
<td>La documentation pour installer et compiler.</td>
</tr>
<tr>
<td><a href="../capture1-astico2d.png">Capture</a></td>
<td>Une capture d'écran.</td>
</tr>
</table>
<p class="justifier">
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.
</p>
<p class="justifier">
Des petits exemples commentés sont fournis, qui montrent comment écrire
un programme utilisant Astico2D pour programmer des algorithmes de
transformations d'image :
<a href="demo1.cpp">demo1.cpp</a>, <a href="demo2.cpp">demo2.cpp</a>.
Ces exemples peuvent être directement copiés comme fichier de départ
pour des TP d'imagerie.
</p>
</div> <!-- mono -->
<div id="footer">
<p>Mis à jour le 14/09/2021</p>
</div>
</body>
</html>

168
install-astico2d.txt Normal file
View File

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

361
tp8-andré-colin.cpp Normal file
View File

@ -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<n°-de-la-planche>-<vos-noms>.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<int> weights;
std::vector<cv::Point> offsets;
std::string name;
ES(size_t w, size_t h, std::vector<int> 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<int>(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<int>(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>([&](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>([&](int &val, const int *position) {
cv::Point p {position[1], position[0]};
if (val != tmp.at<int>(p)) {
img.at<int>(p) = iter;
continuer = true;
}
});
erosion(tmp, es);
iter++;
} while (continuer);
}
void reconstruction(cv::Mat &img, ES &es) {
int max = 0;
img.forEach<int>([&](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>([&](int &val, const int *position) {
cv::Point p {position[1], position[0]};
if (val >= i) {
img.at<int>(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 <typename F>
void hit(cv::Mat &img, const TOR_mask &mask, F func) {
cv::Mat tmp = img.clone();
tmp.forEach<int>([&](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<int>(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 ();
}