Workshop de : Jules Fouchy
Une semaine de workshop en binôme pour apprendre à coder des effets sur des images et première introduction à certains concepts de synthèse d'image.
👉 Consignes & Exercices
Liste d'exercices, plus ou moins difficiles et le but est d'en faire le maximum.
- Mini-soutenance optionnelle (exercice de vulgarisation) et mini-rapport à rendre
- Le rendu se fera par groupes de 2
Note
Le nombre d'émoji indique la complexité du filtre, allant de 1 à 6.
Retire le bleu et le rouge de chaque couleur présente sur l'image.

Code
for (glm::vec3 &color : image.pixels())
{
color.b = 0.f;
color.r = 0.f;
}Échange les valeurs de bleu et les valeurs de rouge de l'image grâce à la fonction swap.

Code
for (glm::vec3 &color : image.pixels())
{
std::swap(color.r, color.b);
}Transforme les couleurs en nuances de gris.

Code
for (glm::vec3 &color : image.pixels())
{
float greyLevel = (color.r + color.g + color.b) / 3;
glm::vec3 newcolor(greyLevel);
color = newcolor;
}Inverse les valeurs des couleurs.

Code
for (glm::vec3 &color : image.pixels())
{
color.r = 1.f - color.r;
color.g = 1.f - color.g;
color.b = 1.f - color.b;
}Crée un dégradé de noir à blanc sur une image vide.

Code
for (int x = 0; x < image.width(); x++)
{
for (int y = 0; y < image.height(); y++)
{
float value = static_cast<float>(x) / static_cast<float>(image.width());
image.pixel(x, y) = glm::vec3(value);
}
}Inverse les pixels de l'image.
Crée du bruit aléatoire sur l'image.

Note
Cette fonction nous a permis d'appréhender la notion de random donc nous avons dû lire la documentation pour bien la maitriser et ainsi bruiter l'image. Il ne fallait pas bruiter chaque pixel donc il a fallu définir un nombre de pixel à modifier, ici on a choisi de prendre la moitié des pixels.
Tip
Importer
#include "random.hpp"
Réecrit les pixels de l'image comme s'ils subissaient une rotation à 90° sur une nouvelle image au correct format.

Note
Cette modification d'image semble simple en théorie mais on a tatonné avant d'y parvenir. En effet, au départ nous pensions juste que les x devenaient des y et vice versa mais ce n'était pas le cas, pour la réussir nous avons dû dessiner plusieurs exemples sur papier et cela nous a beaucoup aidées à visualiser les changements à opérer.
Modifie chaque pixel de sorte qu'il prenne le rouge du pixel à sa droite, garde son propre vert, et prenne le bleu du pixel à sa gauche.
Augmente la luminosité de l'image.

Note
Nous avons choisi de multiplier chaque valeur de couleur par 1.5. Une autre méthode utilisable est d'utiliser les puissances grâce à la fonction pow()
Crée d'un disque blanc sur fond noir.

Tip
- Enregistrer les coordonnées du centre de l'image.
- Tracer un cercle d'un rayon r en entrée.
- Consulter chaque pixel de l'image. S'il se trouve dans la zone du cercle, le peindre en blanc.
Créé un cercle blanc sur fond noir

Note
Nous avons répété l'opération de l'exercice 012 en ne peignant en blanc que le périmètre du cercle, dont l'épaisseur est définie par une nouvelle variable en entrée : thickness
Tip
Faire de la fonction createCircle() une fonction réutilisable plusieurs fois sur la même image. Ce sera très important pour notre exercice 013 👇
Crée une succession de cercles formant une rosace.

Note
Nous avons fait appel à M_PI pour calculer l'angle de chaque cercle puis les fonctions cos(angle) et sin(angle) pour en stocker les position x et y. Notre fonction prend trois variable en entrée : l'image, le rayon r commun à tous les cercles, et le nombre n de cercles (sur l'exemple, 6 cercles sont tracés).
void rosace(sil::Image disque, int r, int n)
{
int x0 = disque.width() / 2;
int y0 = disque.height() / 2;
for (int i = 0; i < n; i++)
{
float angle = 2 * M_PI * i / n;
int x = x0 + r * cos(angle);
int y = y0 + r * sin(angle);
disque = createCircle(disque, x, y, 130, 3);
}
disque.save("output/rosace.png");
}Note
Pour créer cette mosaïque, il fallait parcourir tous les pixels puis séparer l'image finale en fonction du nombre choisi. Au départ, c'était assez facile mais la difficulté a augmenté au fur et à mesure des boucles car il fallait raisonner à la fois en global et en spécifique pour chaque image. On s'est perdues plusieurs fois mais en posant à plat nos idées, on a pu avoir plus de recul et réussir à produire le résultat final.
Inverse une image sur deux sur le résultat de la mosaïque.

Ajoute du glitch sur l'image de manière random.

Note
Pour cet effet, la fonction random_int(); a été utilisée plusieurs fois pour produire un glitch le plus aléatoire possible : à la fois pour le nombre de glitch, pour les coordonnées des pixels qu'on modifie mais aussi pour la taille et la hauteur de chaque glitch.
Génère une fractale de Mandelbrot, un concept mathématique découvert par Gaston Julia et Pierre Fatou.

Note
Pour générer une fractale, nous nous servons de 2 nombres complexes : c et z. c réfère à la position d'un pixel) et z à notre point de départ que l'on itère en z = z * z + c jusqu'à ce que std::abs(z) > 2. Nous pouvons ensuite préciser la fractale avec de nombreux paramètres.
Exemple de paramètres :
const int max = 25;
const int width = disque.width();
const int height = disque.height();
const float scale = 1.5;
const float zoom = 0.5;
const float moveX = 0.0;
const float moveY = -0.3;
float white = 0.1;Tip
Importer
#include <complex>
Pas terminée
Génère un tramage de la photo.

Note
Nous avons tramé cette photo grâce à une matrice Bayer de niveau 1 (4x4). La Bayer ne détecte que la luminosité des pixels, ce qui rendra l'image tramée bien plus lumineuse que son original.
Matrice Bayer 4x4
const int bayer_n = 4;
float bayer_matrix_4x4[][bayer_n] = {
{-0.5, 0, -0.375, 0.125},
{0.25, -0.25, 0.375, -0.125},
{-0.3125, 0.1875, -0.4375, 0.0625},
{0.4375, -0.0625, 0.3125, -0.1875},
};Tip
Importer
#include <glm/gtx/matrix_transform_2d.hpp>
Modifie chaque pixel en fonction du pixel le plus lumineux de l'image et le moins lumineux, améliore le contratse d'une image.
Transforme l'image en lui appliquant une convolution, c'est-à-dire une opération qui va ajouter un effet sur l'image en fonction d'un kernel donné.

Note
La difficulté de l'exercice a résidé dans la compréhension du fonctionnement du kernel car il y avait de nombreuses erreurs (dépassement de l'image, pixels en double etc.) donc il a fallu se replonger dans les matrices. Finalement, après avoir fait en sorte que cela marche pour 1 matrice de 3 par 3, il y a eu de nombreuses itérations pour adapter le code à toutes les tailles.
Kernel en 3x3
float kernelBlur[3][3] = {
{1.f / 9.f, 1.f / 9.f, 1.f / 9.f},
{1.f / 9.f, 1.f / 9.f, 1.f / 9.f},
{1.f / 9.f, 1.f / 9.f, 1.f / 9.f}};Applique différents kernels pour avoir plusieurs effets.
Dans l'ordre : Emboss, Ouline, Sharpen.

Soustrait deux images auxquelles on a appliqué deux filtres gaussiens différents pour mettre en avant la différence d'échelle. Le résultat a ensuite été passé en noir et blanc.

Note
Pour cet algorithme, nous avons beaucoup tatonné car nous avions récupéré la formule de la vidéo et le résultat était loin de celui attendu. Après avoir réécrit le code de différentes façons, nous avons finalement joué avec ce paramètre avant de réaliser que le souci venait de là, le code était bon et après l'avoir passée en noir et blanc on a enfin eu un résultat similaire.
Tri chaque colonne de l'image pour créer une image en fonction de la luminosité des pixels, du plus au moins lumineux.

Note
Cet algorithme a nécessité l'utilisation de tableaux pour pouvoir trier les pixels par la suite : on utilise des tableaux de tableaux ce qui était un peu tricky pour accéder aux bonnes valeurs au bon moment. Nous avons bloqué un petit moment sur eux car nous avions oublié de vider le tableau entre chaque étape, ce qui faisait que chaque itération du tableau contenait tous les pixels de la ligne en plus des lignes précédentes : les opérations étaient longues et le résultat était donc le même qu'en entrée. En ajoutant l'étape de vidage, tout fonctionnait comme nous le souhaitions !
Gestion et vidage du tableau
for (int x = 0; x < image.width(); x++)
{
for (int y = 0; y < image.height(); y++)
{
pixels.push_back(image.pixel(x, y));
}
sortedPixels.push_back(pixels);
pixels.clear();
}Ajoute un filtre peinture à l'huile à l'image en récupérant les 3 pixels autour de chaque pixel, en les divisant en 4 zones et en prenant la moyenne de la zone avec la variance la plus faible.

Note
Nous avons eu beaucoup de difficultés avec ce filtre, en effet nous avons tout supprimé et tout recommencé plusieurs fois chacune de notre côté. On se perdait souvent dans les boucles car pour chaque pixel, il fallait prendre les 9 autour et dans ces 9, les séparer en deux 5x5 pour pouvoir les parcourir et faire les calculs. De plus, nous avons quelques difficultés en maths donc au départ nous avions fait la moyenne et non la variance, on avait emmelé nos pinceaux. On avait également oublié de réinitialiser les valeurs ce qui faussait tous nos résultats mais nous avons fini par trouver cette erreur et déterminer où il fallait le faire car il y avait plusieurs boucles donc sa place était capitale.
Créé une image en fontion des k couleurs les plus représentatives de l'image. Ici 2, 3 et 16 couleurs.

Prend une image, la simplifie avec un système de seuil et la décline en 3 couleurs (r, g, b) en style Pop Art.

Traitement en fonction du seuil de rouge
if (image.pixel(x, y).r <= seuil)
{
result.pixel(x, y) = {0, 0, 0.9};
result.pixel(x + image.width(), y) = {0.9, 0, 0};
result.pixel(x + image.width()*2, y) = {0, 0.9, 0};
}Génère une série de vagues à partir d'un cercle et de ses arctangentes.

Note
À gauche, on divise le PI utilisé pour chaque angle par 6. À droite, on ne les divise pas.
Alterne les colonnes de pixel pour les décaler vers le haut ou vers le bas en alternance. Pourrait être aussi à l'horizontal, vers la gauche et vers la droite.

Code
int width = image.width();
int height = image.height();
sil::Image glitched{width, height};
for (int x = 0; x < width; x++)
{
int offset = random_int(-20, 20);
for (int y = 0; y < height; y++)
{
if (x + offset < width && x + offset >= 0)
{
glitched.pixel(x, y) = image.pixel(x + offset, y);
}
else
{
glitched.pixel(x, y) = image.pixel(x, y);
}
}
}Pixelise l'image sélectionnée au degré souhaité.

Note
Nous réalisons une moyenne des pixels pour rendre leurs couleurs de plus en plus approximatives.
Exemple d'une boucle :
for (int y = 0; y < sizeH; y++)
{
int coordX = x + (i * sizeW);
int coordY = y + (j * sizeH);
moyenneR += image.pixel(coordX, coordY).r;
moyenneG += image.pixel(coordX, coordY).g;
moyenneB += image.pixel(coordX, coordY).b;
nbPixel++;
}