Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

73 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Workshop - Image Manipulation

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

Attendus

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

Sommaire

Filtres
Effets Customs

Filtres

Note

Le nombre d'émoji indique la complexité du filtre, allant de 1 à 6.

001 - Ne garder que le vert 🥑

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;
}

002 - Échanger les canaux 🥑

É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);
}

003 - Noir & Blanc 🥑

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;
}

004 - Négatif 🥑

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;
}

005 - Dégradé 🥑

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);
    }
}

006 - Miroir 🥑🥑

Inverse les pixels de l'image.



007 - Image bruitée 🥑🥑

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"

008 - Rotation de 90° 🥑🥑

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.

009 - RGB Split 🥑🥑

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.



010 - Luminosité 🥑🥑

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()

011 - Disque 🥑🥑

Crée d'un disque blanc sur fond noir.

Tip

  1. Enregistrer les coordonnées du centre de l'image.
  2. Tracer un cercle d'un rayon r en entrée.
  3. Consulter chaque pixel de l'image. S'il se trouve dans la zone du cercle, le peindre en blanc.

012 - Cercle 🥑🥑

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 👇

013 - Rosace 🥑🥑🥑

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");
}

014 - Mosaïque 🥑🥑

Crée la mosaïque de l'image.

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.

015 - Mosaïque miroir 🥑🥑🥑

Inverse une image sur deux sur le résultat de la mosaïque.

016 - Glitch 🥑🥑🥑

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.

017 - Fractale de Mandelbrot 🥑🥑🥑

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>

018 - Vortex 🥑🥑🥑(🥑)

Pas terminée

019 - Tramage 🥑🥑🥑(🥑)

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>

020 - Normalisation de l'histogramme 🥑🥑🥑(🥑)

Modifie chaque pixel en fonction du pixel le plus lumineux de l'image et le moins lumineux, améliore le contratse d'une image.



021 - Convolutions 🥑🥑🥑🥑

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}};

022 - Netteté, Contours, etc. 🥑

Applique différents kernels pour avoir plusieurs effets.
Dans l'ordre : Emboss, Ouline, Sharpen.

024 - Différence de gaussiennes 🥑🥑

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.

025 - Tri de pixels 🥑🥑🥑🥑

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();
}

026 - Filtre de Kuwahara 🥑🥑🥑🥑🥑

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.

027 - K-means🥑🥑🥑🥑🥑

Créé une image en fontion des k couleurs les plus représentatives de l'image. Ici 2, 3 et 16 couleurs.

🎨 Effets customs

✨ Fun Color

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};
}

✨ Circle Wave

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.

✨ Fringe

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);
        }
    }
}

✨ Pixelate

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++;
}

About

One week workshop on C++ image manipulation

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages