initial commit
BIN
IMAGES/blob1.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
IMAGES/bubblets.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
IMAGES/chat1.jpg
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
IMAGES/fleur1.jpg
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
IMAGES/fleur2.jpg
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
IMAGES/joint_J1.pgm
Normal file
BIN
IMAGES/joint_J2.pgm
Normal file
BIN
IMAGES/joint_J3.pgm
Normal file
BIN
IMAGES/joint_J4.pgm
Normal file
BIN
IMAGES/joint_J5.pgm
Normal file
BIN
IMAGES/joint_J6.pgm
Normal file
BIN
IMAGES/joint_J7.pgm
Normal file
BIN
IMAGES/joint_K1.pgm
Normal file
BIN
IMAGES/joint_K2.pgm
Normal file
BIN
IMAGES/joint_K3.pgm
Normal file
BIN
IMAGES/joint_K4.pgm
Normal file
BIN
IMAGES/joint_K5.pgm
Normal file
BIN
IMAGES/joint_K6.pgm
Normal file
BIN
IMAGES/joint_K7.pgm
Normal file
BIN
IMAGES/joint_K8.pgm
Normal file
BIN
IMAGES/noisy_fingerprint.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
IMAGES/paper1.jpg
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
IMAGES/test.pgm
Normal file
45
Makefile
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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 ();
|
||||
}
|