web-dev-qa-db-fra.com

Algorithme pour faire pivoter une image de 90 degrés en place? (Pas de mémoire supplémentaire)

Dans une application C intégrée, j'ai une grande image que je voudrais faire pivoter de 90 degrés. Actuellement, j'utilise le bien connu simple algorithme pour ce faire. Cependant, cet algorithme nécessite que je fasse une autre copie de l'image. Je voudrais éviter d'allouer de la mémoire pour une copie, je préfère la faire pivoter en place. Étant donné que l'image n'est pas carrée, cela est délicat. Quelqu'un connaît-il un algorithme approprié?

Modifié pour ajouter des éclaircissements, car les gens demandent:

Je stocke une image au format habituel:

// Images are 16 bpp
struct Image {
    int width;
    int height;
    uint16_t * data;
};

uint16_t getPixel(Image *img, int x, int y)
{
    return img->data[y * img->width + x];
}

J'espère déplacer le contenu du tableau data, puis permuter sur les variables membres width et height. Donc, si je commence avec une image de 9 x 20 pixels, puis la fais pivoter, je me retrouverai avec une image de 20 x 9 pixels. Cela change la foulée de l'image, ce qui complique beaucoup l'algorithme.

38
user9876

Cela pourrait aider: transposition matricielle sur place .

(Vous devrez peut-être également effectuer une mise en miroir après la transposition, comme le mentionne rlbond).

29
Aryabhatta

Si vous lisez l'image de la mémoire dans "le mauvais ordre", c'est essentiellement la même chose que la faire pivoter. Cela peut convenir ou non à tout ce que vous faites, mais voici:

image[y][x] /* assuming this is the original orientation */
image[x][original_width - y] /* rotated 90 degrees ccw */
image[original_height - x][y] /* 90 degrees cw */
image[original_height - y][original_width - x] /* 180 degrees */
22
Matti Virkkunen

Vous ne savez pas quel traitement vous ferez après la rotation, mais vous pouvez le laisser seul et utiliser une autre fonction pour lire le pixel pivoté dans la mémoire d'origine.

uint16_t getPixel90(Image *img, int x, int y) 
{
    return img->data[(img->height - x) * img->width + y];
}

Où le paramètre d'entrée x et y a échangé la dimension de l'original

6
sjchoi

la vraie réponse: non, vous ne pouvez pas sans allouer de mémoire.

ou vous devez utiliser la récursivité, qui échouera avec de grandes images.

mais il existe des méthodes qui nécessitent moins de mémoire que l'image elle-même

par exemple, vous pouvez prendre le point A (x de 0 à la largeur, y de 0 à la hauteur), calculer son nouvel emplacement, B, copier B vers son nouvel emplacement (C) avant de le remplacer par A, etc.

mais, cette méthode nécessiterait de garder une trace des octets qui ont déjà été déplacés. (en utilisant un bitmap d'un bit par pixel dans l'image pivotée)

voir l'article wikipedia, il démontre clairement que cela ne peut pas être fait pour des images non carrées: voici à nouveau le lien: http://en.wikipedia.org/wiki/In-place_matrix_transposition

2

Voici une méthode simple en Java,

    public static void rotateMatrix(int[][] a) {                                                                            
    int m =0;
    for(int i=0; i<a.length; ++i) {
        for(int j=m; j<a[0].length; ++j) {
            int tmp = a[i][j];
            a[i][j] = a[j][i];
            a[j][i] = tmp;
        }
        m++;
    }

    for(int i=0; i<a.length; ++i) {
        int end = a.length-1;
        for(int j=0; j<a[0].length; j++) {
            if(j>=end)
                break;
            int tmp = a[i][j];
            a[i][j] = a[i][end];
            a[i][end] = tmp;
            end--;
        }
    }
}
1
kakaly

Ce problème m'a pris un certain temps, mais si vous avez la bonne approche, c'est très simple.

Notez que cela ne fonctionne que pour une matrice carrée. Un rectangle vous obligera à utiliser l'autre algorithme (transposition et retournement). Si vous souhaitez le faire sur place, vous devrez peut-être redimensionner temporairement le tableau.

Simplifier le problème

Considérez la matrice suivante:

 1  2  3  4
 5  6  7  8
 9 10 11 12
13 14 15 16

Faites pivoter de 90 degrés et ne regardez que les coins (numéros 1, 4, 16 et 13). Si vous avez des problèmes pour le visualiser, servez-vous d'un post-it.

Maintenant, considérons le suivant:

1 - - 2
- - - -
- - - -
4 - - 3

Faites-le pivoter de 90 degrés et remarquez comment les nombres tournent de manière circulaire: 2 devient 1, 3 devient 2, 4 devient 3, 1 devient 4.

Coins rotatifs

Pour faire pivoter les coins, il est nécessaire de définir tous les coins en fonction du premier coin:

  • Le 1er coin serait (i, j)
  • Le 2ème coin serait (SIZE - j, i)
  • Le 3e coin serait (SIZE - i, SIZE - j)
  • Le 4ème coin serait (j, SIZE - i)

Notez que les tableaux sont basés sur 0, donc SIZE devra également être basé sur 0. (ce qui signifie que vous devrez soustraire 1).

Maintenant que vous avez compris l'idée de coins tournants, nous allons étendre l'idée de "coins tournants" aux "quadrants tournants". Le même principe est valable.

Code

Vous devrez vous assurer qu'aucun numéro n'est écrasé. Cela signifie que vous devrez faire pivoter 4 chiffres à la fois simultanément.

#include <algorithm>
#include <numeric>
#include <vector>

using std::iota;
using std::swap;
using std::vector;

// Rotates 4 numbers.
// e.g: 1, 2, 3, 4 becomes 4, 1, 2, 3
// int& means numbers are passed by reference, not copy.
void rotate4(int &a, int &b, int &c, int &d)
{
   swap(a, b);
   swap(b, c);
   swap(c, d);
}

void rotateMatrix(vector<vector<int>>& m) {
    int n = m.size();

    // NOTE: i and j from 0 to n/2 is a quadrant
    for (int i = 0; i < n/2; i++) {
    // NOTE : here + 1 is added to make it work when n is odd
    for (int j = 0; j < (n + 1)/2; j++) {
        int r_i = (n - 1) - i;
        int r_j = (n - 1) - j;

        rotate4(
             m   [i]   [j],
             m [r_j]   [i],
             m [r_i] [r_j],
             m   [j] [r_i]
        );
    }
    }
}

void fillMatrix(vector<vector<int>>& m) {
    int offset = 0;

    for (auto &i : m) {
        iota(i.begin(), i.end(), offset);
        offset += i.size();
    }
}

// Usage:
const int size = 8;
vector<vector<int>> matrix (size, vector<int>(size));
fillMatrix(matrix);
rotateMatrix(matrix);

Impression

Pour imprimer la matrice, vous pouvez utiliser:

#include <algorithm>
#include <iostream>
#include <iterator>

using std::copy;
using std::cout;
using std::ostream;
using std::ostream_iterator;
using std::vector;

ostream& operator<<(ostream& os, vector<vector<int>>& m) {
    for (auto const &i : m) {
        copy(i.begin(), i.end(), ostream_iterator<int>(os, " "));
        os << "\n";
    }

    return os;
}

// Usage
cout << matrix;
1
arboreal84

Cela peut être trop vague et ne pas être ce que vous recherchez, mais je vais quand même poster.

Si vous considérez une image comme un tableau 2D de pixels, il vous suffit d'inverser l'ordre du tableau de niveau supérieur ou imbriqué, selon que vous souhaitez un retournement horizontal ou vertical.

Vous devez donc parcourir chaque colonne de pixels (0-> colonnes/2) et les échanger (vous n'avez donc besoin que de la mémoire temporaire pour 1 pixel, pas l'image entière), ou parcourir les lignes pour le retournement horizontal. sens? Va élaborer/écrire du code sinon.

1
Jeriko