Aller au contenu

Corrigé du TD Quart de Tour⚓︎

Algorithme Naïf de rotation d'un quart de tour Droite⚓︎

Choisir une image de taille carrée \(512\times512\)⚓︎

Algorithme Naïf de rotation d'un quart de Tour Droite⚓︎

\((a,b)_{\text{img\_rot}}=(x,y)_{\text{img}}\) avec \( \left\{ \begin{array}{ll} a=n-1-y\\ b=x \end{array} \right. \)

Conclusion: \((n-1-y,x)_{\text{img\_rot}}=(x,y)_{\text{img}}\)

D'après la question précédente, \((x,y)_{\text{img\_rot}}=(a,b)_{\text{img}}\) avec \( \left\{ \begin{array}{ll} x=n-1-b\\ y=a \end{array} \right. \Leftrightarrow \left\{ \begin{array}{ll} b=n-1-x\\ a=y \end{array} \right. \Leftrightarrow \left\{ \begin{array}{ll} a=y\\ b=n-1-x \end{array} \right. \)

Conclusion: \((x,y)_{\text{img\_rot}}=(y,n-1-x)_{\text{img}}\) Le pixel \((x,y)\) de l'image rotationnée droite provient du pixel \((y,n-1-x)\) de l'image initiale

OK, c'est fait

from PIL import Image
img = Image.open("img/bateau.jpg")
largeur, hauteur = img.size
n = largeur
img_rot = Image.new("RGB",(largeur,hauteur))
for x in range(n):
    for y in range(n):
        # 1/4 Tour Droite : Solution 1
        # On mémorise le pixel (x,y) de l'image initiale
        # On place ce pixel à sa nouvelle position après rotation droite
        (r,g,b) = img.getpixel((x,y)) # getter de la composante RGB du pixel (x,y)
        img_rot.putpixel((n-1-y,x),(r,g,b)) ## setter de la composatnte RGB du pixel (x,y)

        ## 1/4 Tour Droite : Solution 2
        ## le pixel (x,y) de l'image rotationnée droite provient du pixel (y,n-1-x) de l'image initiale
        #(r,g,b) = img.getpixel((y,n-1-x)) # getter de la composante RGB du pixel (x,y)
        #img_rot.putpixel((x,y),(r,g,b)) ## setter de la composatnte RGB du pixel (x,y)

img_rot.save("img/bateauRot.jpg") # sauvegarde l'image
img_rot.show() # Affiche l'image

La taille des entrées est la largeur \(n\). Il y a deux boucles Pour imbriquées, chacune de taille \(n\). Les instructions à l'intérieur des deux boucles sont donc répétées \(n\times n = n^2\) fois. L'instruction img.getpixel() a un coût unitaire, de même que l'instruction img.putpixel(), donc la Complexité Temporelle en \(O(n\times n) = O(n^2)\)

Pour chaque image de taille \(n\), il faut créer une nouvelle image dont la taille vaut \(n\), c'est-à-dire avec \(n^2\) pixels dont chacun a une taille constante (à priori 3 octets pour le code RGB). donc la Complexité Spatiale est aussi en \(O(n^2)\)

Algorithme de rotation d'un quart de tour Droite, avec Espace mémoire constant⚓︎

Il suffit de récupérer les valeur des pixels et de les mettre à leur nouvel emplacement :

def echangePixels(image,x0,y0,x1,y1):
    pixel0 = image.getpixel((x0,y0))
    pixel1 = image.getpixel((x1,y1))
    image.putpixel((x0,y0), pixel1)
    image.putpixel((x1,y1), pixel0)
Dans la suite de cette partie, On va adopter une stratégie Diviser Pour Régner. L'image est divisée en quatre quadrants. Chaque cadran est tourné récursivement, puis une permutation circulaire des quadrants est effectuée.

Un2413Un:2->Un:4Un:4->Un:3Un:s->Un:sUn:1->Un:2

Méthode de ¼ de Tour Droite en Diviser Pour Régner

La permutation circulaire est réalisée en enchaînant plusieurs échanges de cadran, dont voici un exemple de stratégie possible:

UnABCDDeuxBACDUn:2->Deux:1Échange A et BQuatreCADBUn:sw->Quatre:sePermutation CirculaireTroisDACBDeux:2->Trois:1Échange B et DTrois:2->Quatre:1Échange C et D

Il suffit de récupérer les valeurs des pixels et de les mettre à leur nouvel emplacement :

def echangeQuadrants(image,x0,y0,x1,y1,n):
    for i in range(n):
        for j in range(n):
            echangePixels(image,x0+i,y0+j,x1+i,y1+j)
Remarque : Une procédure ne retourne pas de résultat, contrairement à une fonction. Cela explique pourquoi on appelle une procédure comme suit:
echangePixels(...)   # BON APPEL D'UNE PROCÉDURE
# et NON PAS comme une fonction:
img = echange(...)   # MAUVAIS APPEL D'UNE PROCÉDURE

On applique la Méthode du ¼ de Tour Droite en Diviser Pour Régner décrite sur la 1ère figure ci-dessus, en faisant attention de ne le faire que si le quadrant est au minimum de taille \(2\times 2\). Dans le cas contraire, le quadrant est composé d'un unique pixel, auquel cas il est inutile de faire quoi que ce soit (c'est-à-dire de retourner un unique pixel). On fait également attention à utiliser une division entière, pour préserver le type des données.

def tourneQuadrants(image,x0,y0,n):
    if n>=2 :
        m = n // 2
        tourneQuadrants(image,x0,y0,m)              # Tourne A
        tourneQuadrants(image,x0+m,y0,m)            # Tourne B
        tourneQuadrants(image,x0,y0+m,m)            # Tourne C
        tourneQuadrants(image,x0+m,y0+m,m)          # Tourne D
        echangeQuadrants(image,x0,y0,x0+m,y0,m)     # Echange A et B  --> B passe en (x0,y0), A en (x0+m,y0)
        echangeQuadrants(image,x0,y0,x0+m,y0+m,m)   # Echange B et D  --> D passe en (x0,y0), B en (x0+m,y0+m)
        echangeQuadrants(image,x0,y0,x0,y0+m,m)     # Echange D et C  --> C passe en (x0,y0), D en (x0,y0+m)

Il suffit de lancer la procédure tourneImage(image), et de lancer les instructions finales dans le programme principal:

def tourneImage(image):
    n, p = image.size
    assert n == p
    tourneQuadrants(image,0,0,n)
# Programme Principal
img = Image.open("img/bateau.jpg")
tourneImage(img)
img.show() # Affiche l'image
img.save("img/bateauRotDivPourRegner.jpg")

La taille des entrées, est la largeur (/hauteur) de l'image, que nous noterons \(n\). Il s'agit d'étudier la complexité temporelle de la procédure tourneQuadrants(image,x0,y0,n) La relation de Récurrence vérifiée par la Complexité Temporelle est:

\(T(n) = 4T \left( \dfrac n2 \right)+O(n^2)\) avec \(T(1)=0\) car une image de largeur \(1\) ne subit aucun traitement.

En effet, le \(O(n^2)\) provient de la permutation circulaire, nécessaire après le traitement récursif: C'est la Complexité Temporelle de chaque procédure echangeQuadrants(image,x0,y0,x1,y1,n), car deux bloucles Pour imbriquées, chacune de taille \(n\), donc complexité temporelle en \(O(n\times n)=O(n^2)\)

def echangeQuadrants(image,x0,y0,x1,y1,n):
    for i in range(n):
    for j in range(n):
        echangePixels(image,x0+i,y0+j,x1+i,y1+j)

On sait que \(n\) est une puissance de \(2\), Notons \(n=2^p\), donc \(n^2=(2^p)^2=2^{2p}\)

\(T(n) = O(n^2) + 4T \left( \dfrac n2 \right)\) \(T(n) = O(n^2) + 4\times \left[ O\left( \left( \dfrac n2 \right)^2 \right)+ 4\times T\left( \dfrac{n}{2^2} \right) \right]\) \(T(n) = O(n^2) + 4\times O\left( \dfrac {n^2}{2^2} \right)+ 4^2\times T\left( \dfrac{n}{2^2} \right)\) \(T(n) = O(n^2) + O( n^2 )+ 4^2\times \left[ O\left( \left( \dfrac {n}{4} \right)^2 \right)+ 4\times T\left( \dfrac{n}{2^3} \right) \right]\) \(T(n) = O(n^2) + O( n^2 ) + 4^2\times O\left( \dfrac {n^2}{4^2} \right)+ 4^3\times T\left( \dfrac{n}{2^3} \right)= etc...\) \(T(n) = O(n^2) + O( n^2 ) + O( n^2 ) + 4^3\times O\left( \dfrac{n^2}{(2^3)^2} \right) +...+ 4^{p-1}\times T\left( \dfrac{n}{2^{p-1}} \right)\) \(T(n) = O(n^2) + O( n^2 ) + O( n^2 ) + 4^3\times O\left( \dfrac{n^2}{4^3} \right) +...+ 4^{p-1}\times O\left( \dfrac{n^2}{2^{2p-2}} \right) +4^p\times T\left( \dfrac {n}{2^p} \right)\) De plus : \(n=2^p\) donc \(\dfrac {n}{2^p}=1\), donc \(T(n) = O(n^2) + O( n^2 ) + O( n^2 ) + O( n^2 ) +...+ 4^{p-1}\times O\left( \dfrac{n^2}{4^{p-1}} \right) +4^p\times T(1)\) \(T(n) = O(n^2) + O( n^2 ) + O( n^2 ) + O( n^2 ) +...+ O( n^2 ) + 4^p\times 0\) car \(T(1)=0\) \(T(n) = p\times O(n^2)\) \(T(n) = O(p\times n^2)\)

De plus, \(n=2^p \Leftrightarrow p=log_2n\) donc \(T(n) = O(log_2n\times n^2) = O(n^2\times log_2n)\) donc la Complexité Temporelle est en \(O(n^2 log_2 n)\)

Les pixels sont toujours échangés deux à deux, donc cet algorithme est en espace mémoire constant, i.e. en \(O(1)\)