====== Redimensionnement d'une image par filtrage ======
L'objectif de ce TD est d'effectuer le redimensionnement d'une image en appliquant un filtre limitant ainsi les défauts qui peuvent apparaître lorsqu'une image est redimensionnée, notamment les effets d'escalier ou de pixelisation qui peuvent apparaître lors d'un redimensionnement par simple interpolation.
===== Bibliothèque de manipulation d'images =====
Pour lire et écrire des images, nous vous proposons d'utiliser une bibliothèque pour accéder aux images de types .png et .jpeg.
Il est demandé de supporter au moins deux formats d'image. Dans l'absolu, il est souhaitable d'avoir le format .bmp, le format .png ainsi que le format .jpeg. Un plus serait d'avoir le format jpeg 2000.
==== Installation d'une bibliothèque ====
Il vous est proposé d'utiliser la bibliothèque **[[http://cimg.eu|CImg]]**. Cette bibliothèque vous permettra de lire et écrire les fichier ainsi que de manipuler les images.
Vous installerez la bibliothèque dans votre environnement.
==== Validation de l'installation ====
Vous validerez l'installation en exécutant le code de test suivant:
#include "CImg.h"
using namespace cimg_library;
int main() {
CImg image("lena.jpg"), visu(500,400,1,3,0);
const unsigned char red[] = { 255,0,0 }, green[] = { 0,255,0 }, blue[] = { 0,0,255 };
image.blur(2.5);
CImgDisplay main_disp(image,"Click a point"), draw_disp(visu,"Intensity profile");
while (!main_disp.is_closed() && !draw_disp.is_closed()) {
main_disp.wait();
if (main_disp.button() && main_disp.mouse_y()>=0) {
const int y = main_disp.mouse_y();
visu.fill(0).draw_graph(image.get_crop(0,y,0,0,image.width()-1,y,0,0),red,1,1,0,255,0);
visu.draw_graph(image.get_crop(0,y,0,1,image.width()-1,y,0,1),green,1,1,0,255,0);
visu.draw_graph(image.get_crop(0,y,0,2,image.width()-1,y,0,2),blue,1,1,0,255,0).display(draw_disp);
}
}
return 0;
}
Ce code vous permet de voir comment charger une image de type jpeg, comment afficher une image et comment accéder à un pixel de cette image.
//Lors de la soutenance, vous montrerez une exécution de ce code
//
==== Analyse et structure de la bibliothèque ====
A partir de la documentation, effectuer rapidement une analyse et une hiérarchie des classes qui sont utiles pour :
- la représentation des images,
- la représentation des pixels dans les images (puisque nous allons manipuler directement ces pixels dans la seconde partie de cet exercice)
- la visualisation des images.
//Vous effectuerez un schéma récapitulant l'ensemble des ces informations ainsi qu'un ou deux transparents pour les présenter lors de la soutenance.//
===== Redimensionnement d'une image =====
Nous nous proposons d'agrandir une image par un facteur défini par un nombre à virgule flottante ($> 1.0$). Nous allons étudier dans la suite du sujets plusieurs techniques de sampling permettant de limiter les artéfacts induits par le redimensionnement.
Agrandir l'image correspond à ajouter des lignes et des colonnes à la matrice représentant l'image actuelle. Supposons que nous ayons une matrice de 4x4, c'est-à-dire 4 lignes de 4 colonnes, que nous appliquons un coefficient de $f=2.75$, nous obtiendrons donc une matrice de 11 lignes et 11 colonnes. Nous allons donc avoir $x^d$ (resp. $y^d$) qui va varier entre 0 et 10. Quand nous calculons la valeur de $x^d$ (resp. $y^d$), nous devons calculer cette position dans le référentiel initial $x^o$ qui sera égal à $\frac{x^d}{f}$. Si nous considérons que $x^o = 6$, $y^o = 3$, les $(x^d, y^d)$ associés auront les valeurs : $2.18$ et $1.09$. Ce point sera donc compris entre les points $(2,1)$, $(2,2)$, $(3,1)$ et $(3,2)$ dans le référentiel de la matrice initial. Il faut donc estimer la couleur du point $(x_d, y_d)$ à partir des couleurs des points $(2,1)$, $(2,2)$, $(3,1)$ et $(3,2)$
==== Redimensionnement d'une image par la technique des plus proches voisins ===
L'idée la plus simple est de considérer que ce pixel possède la couleur de son plus proche voisin. Dans ce cas, si nous introduisons un nouveau pixel $(x,y)$ situé entre $(x_i,y_i)$ et $(x_{i+1}, y_{i+1})$, tel que défini dans le schéma suivant :
{{:in204:interpolation_visualisation.png?600|}}
la couleur du point sera la couleur de son plus proche voisin, soit $(x_i, y_i)$, soit $(x_i, y_{i+1})$, soit $(x_{i+1}, y_i)$, soit $(x_{i+1}, y_{i+1})$.
**Réaliser la fonction qui agrandit l'image en utilisant la technique précédent et effectuer des redimensionnements sur des images existantes avec un facteur allant de $1.5$ à $5$ par pas de $0.75$.**
===== Redimensionnement d'une image par interpolation bilinéaire =====
La technique précédente est simple à mettre en œuvre mais n'est pas très satisfaisante en terme de qualité de rendu, dès que le facteur d'agrandissement est élevé. Nous nous intéressons donc à d'autres techniques permettant de calculer la couleur du point $(x,y)$ qui est introduit lors de la fonction d'agrandissement. Le schéma suivant montre les techniques d'interpolation habituelles selon leur version en une dimension et leur extension à deux dimensions.
{{:in204:comparison_of_1d_and_2d_interpolation.svg.png?400|}}
L'algorithme précédent correspond au 2D-nearest-neighbour. Nous souhaitons désormais fait une interpolation bilinéaire, il s'agit de faire d'abord une interpolation linéaire sur l'axe des $x$ puis une interpolation sur l'ax
e des $y$.
**Implanter une fonction qui effectue le redimensionnement de l'image en appliquant une interpolation billinéaire. Tester cette fonction sur la même image et pour les mêmes facteurs que pour l'interpolation par plus proche voisin.**
====== Rendre l'interpolation générique ======
==== Phase 1 ====
Dans les deux cas précédents, l'interpolation dépend des valeurs des quatres voisins de l'élément $(x^o, y^o)$ que sont les éléments $(x_i, y_j)$, $(x_{i+1}, y_i)$, $(x_i, y_{i, j+1})$, $(x_{i+1}, y_{j+1})$. La seule modification entre la technique d'interpolation des plus proches voisins et l'interpolation biliénaire est la fonction qui calcule la valeur de la couleur de l'élément $(x^o, y^o)$ à partir des éléments $(x_i, y_j)$, $(x_{i+1}, y_i)$, $(x_i, y_{i, j+1})$, $(x_{i+1}, y_{j+1})$.
Nous pouvons imaginer dans ce cas de définir une classe qui implante un algorithme générique qui appel à une fonction virtuelle et deux classes héritant de cette classe générique qui définit la fonction générique virtuelle d'interpolation.
class resize_image_base
{
public:
void resize(const CImg& source, CImg& destination);
Color estimate_color(const CImg& source, float x_o, float y_o) = 0;
}
class resize_nearest_neighbour: resize_image_base
{
public:
Color estimate_color(const CImg& source, float x_o, float y_o);
}
class resize_bilinear_interpolation: resize_image_base
{
public:
Color estimate_color(const CImg& source, float x_o, float y_o);
}
void resize_image_base::resize(const CImg& source, CImg& destination)
{
// L'image destination est l'image qui contiendra les pixels de l'image source après redimensionnement.
// La taille de l'image destination permet de calculer le facteur de redimensionnement nécessaire.
// Cet algorithme appelle pour chaque point (x^d, y^d) dans "destination", la fonction "estimate_color".
}
color resize_nearest_neighbour::estimate_color(const CImg& source, float x_o, float y_o)
{
// Implantation du code calculant la couleur associée au point (x^o, y^o) pour l'implantation par
// plus proche voisin.
}
color resize_bilinear_interpolation::estimate_color(const CImg& source, float x_o, float y_o)
{
// Implantation du code calculant la couleur associée au point (x^o, y^o) pour l'implantation par
// plus proche voisin.
}
** Effectuer la transformation de votre code pour le mettre sous la forme d'une telle classe. **
==== Phase 2 ====
Comment généraliser cette approche à des fonctions plus complexes nécessitant non pas les quatres plus proches voisins mais par exemple une matrice 4x4 définis par $(x_{i-1}\ldots x_{i+2}, y_{i-1},\ldots y_{i+2})$ pour notamment l'interpolation cubique, sachant que $x_i, y_i$ est égal à $(\lfloor(x^o), \lfloor(y^o))$.