Cette page peut être mise à jour, avec informations complémentaires, précisions, questions bonus, etc. Pensez à y revenir souvent.
Projet à rendre pour le 06/01/2025 à 23h59, aucun retard ne sera toléré.
Mise-à-jour 15/12/2024: suppression du paramètre max de la fonction create_picture. (Inutile puisque les valeurs doivent être ramenées entre 0 et 255.)
Mise-à-jour 20/12/2024: Ajout de fichiers pour tester la fonction d'ouverture, corrections mineures.
Un rendu de projet comprend :
Un rapport typographié précisant vos choix, les problèmes techniques qui se posent et les solutions trouvées ; il présente en introduction le contexte et le sujet du projet, et il précise en conclusion les limites de votre programme. Le rapport sera de préférence composé avec . Le soin apporté à la grammaire et à l’orthographe est largement pris en compte.
Un code abondamment commenté ; la première partie des commentaires comportera systématiquement les lignes :
@requires décrivant les préconditions : c’est-à-dire
conditions sur les paramètres pour une bonne utilisation (pas de typage
ici),@assigns listant les zones de mémoire modifiées,@ensures décrivant la propriété vraie à la sortie de la
fonction lorsque les préconditions sont respectées, le cas échéant avec
mention des comportements en cas de succès et en cas d’échec, En outre
chaque boucle while doit contenir un commentaire précisant la raison de
sa terminaison (le cas échéant). De même en cas d’appels récursifs. On
pourra préciser des informations additionnelles si des techniques
particulières méritent d’être mentionnées.Le code doit enfin compiler sans erreur (évidemment) et sans warning lorsque l’option -Wall est utilisée. Un code qui ne compile pas se verra attribuer la note de 0.
Un manuel d’utilisation de votre exécutable, même minimal, est toujours bienvenu.
Avez-vous lu tout le sujet ?
Vous devez rendre
rassemblés dans une archive tar gzippée identifiée comme
votre_prénom_votre_nom.tgz. La commande devrait ressembler à :
tar zcvf randolph_carter.tgz rapport.pdf fichiers.c autres_trucs_éventuels.c…
N’OUBLIEZ surtout PAS de mettre le nom identifiant l’archive (donc nouveau) en PREMIER.
Lisez le man ! et testez le contenu de votre archive
(une commande comme par exemple :
tar tvf randolph_carter.tgz doit lister les fichiers et
donner leur taille).
Toute tentative de fraude (plagiat, etc.) sera sanctionnée. Si plusieurs projets ont des sources trop similaires (y compris sur une partie du code uniquement), tous leurs auteurs se verront attribuer la note 0/20. En particulier, il faudra prendre soin de ne pas publier son travail sur un dépôt public (en tout cas pas avant la date de fin de rendu). On évitera également de demander (ou de donner) des conseils trop précis à ses camarades (y compris des promotions précédentes), ces conseils ayant pu être donnés à plusieurs personnes. Les rendus seront comparés deux à deux.
De même, l’usage d’intelligence artificielle générative pour produire le code et/ou le rapport est strictement interdite.
Vous devez enregistrer votre archive tgz dans le dépôt dédié au cours PRIM11 (prim11-projet-2024) en vous connectant à exam.ensiie.fr. Ce dépôt sera ouvert jusqu’au 6 janvier 2025 inclus.
Le but de ce projet est d’implémenter différents traitements sur des images en niveau de gris ou des images en couleur comme lors du TP 6.
Pour réaliser ces traitement vous allez écrire un programme permettant de :
images/Lenna_color.ppm pour lui appliquer un traitement qui
consistera à inverser les niveaux des pixels de l’image vous pourrez
sauvegarder le résultat dans un fichier
images/Lenna_color_inv.ppm.Il serait utile de créer un module pictures.[h|c]
contenant les structures de données dont vous aurez besoin ainsi que le
fonctions travaillant directement sur des images:
Vous pourrez donc dans ce module déclarer :
byte par exemple pour contenir les valeurs
codées par un octet. On pourra aussi définir une constante
MAX_BYTE 255 pour contenir la valeur max des octets.picture similaire à celle utilisée dans
le TP 6 qui contiendra
...data[((i * w + j) * c) + k].pixels.[h/c] dans lequel
pour pourrez aussi avantageusement définir des constantes symboliques
(en utilisant un enum par exemple) RED,
GREEN et BLUE pour accéder directement à ces
composantes dans les pixels.filename.[h/c] qui vous
permettra de
<dirname>/<name>.<ext> en ses composantes
<dirname>, <name> et
<ext>La structure des fichiers binaires PGM et/ou PPM est toujours la même
P6
512 512
255
...
P6 identifie un fichier PPM binaire
contenant une image couleur (ce serait P5 pour les fichiers
PGM binaires contenant des images en niveaux de gris)).512 512 correspondent ici à la largeur et la hauteur de
l’image respectivement....: Ici 255.
1 et
255.64 cela voudrait dire qu’un pixel
composé des valeurs
correspondrait à la couleur blanche, alors que si le maximum est de
255 un pixel
correspond à du gris foncé.255 dans
nos images). Néanmoins, dans ce cas il faudra la prendre en compte lors
de la lecture des fichiers et corriger les valeurs des composantes des
pixels (par
)
pour qu’elles soient comprises dans l’intervalle
au lieu de la valeur maximale indiquée dans la 3ème ligne du
fichier.# que l’on doit considérer
comme des lignes de commentaires et donc ignorer.... ou tout ce qui suit correspond
aux données binaires des pixels. Dans le cas présenté ci-dessus il y a
donc
octets à lire avant la fin du fichier.Implémentez les fonctions pour :
picture read_picture(char * filename);
filename pourra être :
*.pgm pour lire des images en niveau de gris.*.ppm pour lire des images en couleur (vu en
cours).P5 : P2. On considérera que l’on
ne peut pas lire ce type de fichier.0 ce qui est impossible.0 ce qui est impossible....) ne correspondant pas à
.int write_picture(picture p, char * filename);
filename) on renverra un code d’erreur non nul.Dans les deux cas, on fera attention à bien traiter les différents cas d’erreur (ouverture du fichier, lecture des données de l’entête, allocation mémoire, etc.).
Quelques images de test vous sont fournies :
Lenna_gray.pgm : une
version en niveaux de gris de la célèbre image “Lenna”.
Lenna_color.ppm : Une
version en couleurs
Lenna_BW.pgm : Une
version de noir et blancImplémentez des fonctions pour créer, détruire, copier, interroger et convertir des images :
picture create_picture(unsigned int width, unsigned int height, unsigned int channels);
où
void clean_picture(picture * p);
0 ou NULL.picture copy_picture(picture p)
int is_empty_picture(picture p);
int is_gray_picture(picture p);
int is_color_picture(picture p);
void info_picture(picture p);
<width> x
<height> x <channels>)” où
<width>, <height> et
<channels> sont à remplacer par leur valeurs
respectives.picture convert_to_color_picture(picture p);
p était déjà en couleur on se contentera de faire
une copiepétait une image en niveaux de gris on répétera la
composante de niveau de gris dans chacune des composantes (rouge, vert,
bleu) de l’image résultat.picture convert_to_gray_picture(picture p);
p était une image en couleur on la convertit en
niveaux de gris en utilisant la règle suivante :
.p était déjà une image en niveau de gris on se
contentera d’en faire une copie.picture * split_picture(picture p);
p ne peut pas être décomposée on se contentera de
renvoyer NULL.p est une image en niveaux de gris on renverra un
tableau ne contenant qu’un seul élément.picture merge_picture(picture red, picture green, picture blue);
red, green et blue ne sont
pas de même taille ou type) on se contentera de renvoyer une image
vide.
RVB
Red,
Green,
Blue,Ecrivez les fonctions pour :
picture brighten_picture(picture p, double factor);
,

picture melt_picture(picture p, int number);
,

Une LUT (pour Look Up Table, aussi appelée “fonction de transfert”) est une fonction qui à chaque niveau d’une composante d’un pixel dans fait correspondre un autre niveau: , avec la plupart du temps mais pas obligatoirement.
Vous pourrez avantageusement créer un sous-module
lut.[h|c] qui contiendra :
lut constitué :
set_levels_picture ci-dessous.Ce module pourra alors être utilisé dans votre module principal
pictures.c, mais ne devra pas être visible de votre
programme principal main.c.
Les opérations à réaliser avec des LUTs sont les suivantes :
picture inverse_picture(picture p); de

picture normalize_dynamic_picture(picture p);
min et
max (à rechercher dans les valeurs des composantes des
pixels), la normalisation des valeurs d’une image répartit celles-ci
entre 0 et 255.

picture set_levels_picture(picture p, byte nb_levels);

picture mult_picture(picture p1, picture p2)
p1
p2
.picture mix_picture(picture p1, picture p2, picture p3)
p1
p2
p3
Le re-échantillonnage d’image consiste à changer la taille d’une image (largeur et/ou hauteur). L’idée principale consiste à calculer la position des pixels de l’image résultat sur la grille des pixels de l’image source. La figure suivante présente une grille que l’on cherche à re-échantillonner par une grille .
Ainsi, un pixel de l’image résultat se situera toujours entre 4 pixels de l’image source. On peut ainsi définir deux politiques pour calculer les valeurs des pixels de l’image résultat :
picture resample_picture_nearest(picture image, unsigned int width, unsigned int height);
picture resample_picture_bilinear(picture image, unsigned int width, unsigned int height);
Vous pouvez consulter les pages suivantes pour de plus amples explications sur le re-échantillonnage :
Votre programme principal devra consister en la lecture d’une ou plusieurs images source depuis des fichiers (PGM ou PPM) fournis en arguments du programme. Exemple :
./projet Lenna_gray.pgm Lenna_color.ppm
Votre programme devra alors écrire les fichiers suivants :
Lenna_gray_convert_color.ppm qui contiendra la
conversion de Lenna_gray.pgm en une image couleurLenna_color_convert_gray.pgm qui contiendra la
conversion de Lenna_color.ppm en une image en niveaux de
gris.Lenna_color_red.pgm qui contiendra la composante rouge
de Lenna_color.ppmLenna_color_green.pgm qui contiendra la composante
verte de Lenna_color.ppmLenna_color_blue.pgm qui contiendra la composante bleue
de Lenna_color.ppmLenna_[gray|color]_brighten.p[g|p]m qui contiendra
l’image d’entrée éclaircie d’un facteur
.Lenna_[gray|color]_melted.p[g|p]m qui contiendra
l’image dont on aura fait “fondre”
pixels.Lenna_[gray|color]_inverse.p[g|p]m qui contiendra
l’inversion de l’image source.Lenna_gray_dynamic.pgm qui contiendra la version avec
une dynamique (différence entre les niveaux les plus sombre et les plus
clair) maximisée (aussi appelée dynamique optimale) de
Lenna_gray.pgm.Lenna_color_dynamic.ppm qui ne contiendra pas
directement la dynamique optimale de Lenna_color.ppm car
celle-ci présente déjà une dynamique quasiment optimale. Ce n’est pas le
cas en revanche de chacune de ses composantes (rouge, vert, bleue) prise
séparément. Appliquez une dynamique optimale sur chacune des
composantes, puis re-composez une image couleur à partir de ces
composantes optimisée que vous sauverez dans le fichier
Lenna_color_dynamic.ppm.Lenna_[gray|color]_levels.p[g|p]m qui contiendra une
version de l’image source limitée à 8 niveaux par composante.Lenna_[gray|color]_smaller_nearest.p[g|p]m qui
contiendra l’image source rétrécie d’un facteur
en utilisant la politique du plus proche voisin.Lenna_[gray|color]_smaller_bilinear.p[g|p]m qui
contiendra l’image source rétrécie d’un facteur
en utilisant la politique du plus proche voisin.Lenna_[gray|color]_larger_nearest.p[g|p]m qui
contiendra l’image source agrandie d’un facteur
en utilisant la politique du plus proche voisin.Lenna_[gray|color]_larger_bilinear.p[g|p]m qui
contiendra l’image source agrandie d’un facteur
en utilisant la politique d’interpolation bi-linéaire.Lenna_[gray|color]_difference.p[g|p]m qui contiendra la
différence normalisée entre les deux type d’interpolations utilisées.
Par exemple entre Lenna_[gray|color]_larger_nearest.p[g|p]m
et Lenna_[gray|color]_larger_bilinear.p[g|p]m. Pour
normaliser cette différence, il suffira de maximiser sa dynamique.Lenna_BW.pgm) qui servira de
masque. Si cette image n’est pas de la bonne taille, vous pourrez la
redimensionner à la taille de vos images d’entrées.
Lenna_[gray|color]_product.p[g|p]m qui contiendra le
produit de l’image en entrée avec le masque.Lenna_[gray|color]_mixture.p[g|p]m qui contiendra la
mixture de l’image inversée calculée précédemment et de l’image en
entrée en utilisant le masque.byte b = 192 + 137;
b vaudra
et non pas 329 comme avec le type int.filename.[h|c] pour
décomposer les noms de fichiers à lire et composer les noms des fichiers
à écrire.Si vous avez terminé les questions précédentes ainsi que le programme principal.
Vous remarquerez que vous auriez pu utiliser une LUT pour “éclaircir” l’image comme mentionné dans la Manipulation directe des valeurs des pixels.
Concevez une LUT appliquant le même effet que l’éclaircissement
d’image et appliquez la dans une fonction
picture brighten_picture_lut(picture p, double factor);.
Le filtrage pour une image consiste à convoluer une image avec un “noyau” contenant des coefficients. La valeur d’un pixel de l’image résultat est le résultat de la somme des pixels de l’image source dans un voisinage de taille multipliés par les coefficients du noyau :
On dénotera un noyau de filtrage comme ceci :
MAX_BYTE dans un pixel résultat.Exemples de noyaux avec :
Exemple de noyau avec représentant une gaussienne d’écart type
filtrée avec le
noyau gaussien ci-dessus
ce
qui permet de flouter l’image.
filtrée avec le
noyau
Ce
qui permet d’estimer une (parmi d’autres) dérivée horizontale de
l’image.Les extensions suivantes pour Visual Studio Code vous seront utiles :