web-dev-qa-db-ja.com

画像を90度回転させるアルゴリズム? (追加メモリなし)

埋め込みCアプリで、90度回転したい大きな画像があります。現在、私はよく知られたシンプルな algorithm を使用してこれを行っています。ただし、このアルゴリズムでは、イメージの別のコピーを作成する必要があります。コピーにメモリを割り当てないようにしたいのですが、その場でローテーションします。画像が正方形ではないため、これはトリッキーです。誰かが適切なアルゴリズムを知っていますか?

人々が求めているので、明確化を追加するために編集されました:

通常の形式で画像を保存します。

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

data配列の内容を移動してから、widthおよびheightメンバー変数を入れ替えたいと思っています。したがって、9x20ピクセルの画像から始めて、それを回転させると、20x9ピクセルの画像になります。これにより画像のストライドが変化し、アルゴリズムが非常に複雑になります。

38
user9876

これは役立ちます: インプレースマトリックス転置

(rlbondが言及するように、転置後にミラーリングを行う必要がある場合もあります)。

29
Aryabhatta

メモリから画像を「間違った順序」で読み取る場合、それは基本的にそれを回転させることと同じです。これはあなたがやっていることに適しているかもしれませんし、そうでないかもしれませんが、ここに行きます:

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

回転後にどのような処理を行うかはわかりませんが、そのままにして、別の関数を使用して、元のメモリから回転したピクセルを読み取ることができます。

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

入力パラメーターxとyが元の次元と入れ替わった場所

6
sjchoi

本当の答え:いいえ、メモリを割り当てないと、uはできません。

または、大きな画像で失敗する再帰を使用する必要があります。

しかし、画像自体よりも少ないメモリを必要とする方法があります

たとえば、ポイントA(xが0から幅、yが0から高さ)を取り、新しい位置Bを計算し、Bを新しい位置(C)にコピーしてからAに置き換えることができます。

ただし、その方法では、すでに移動したバイトを追跡する必要があります。 (回転した画像のピクセルごとに1ビットのビットマップを使用)

ウィキペディアの記事を参照してください。これは、正方形以外の画像ではこれを実行できないことを明確に示しています。ここに再びリンクがあります: http://en.wikipedia.org/wiki/In-place_matrix_transposition

これが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

この問題にはかなり時間がかかりましたが、適切なアプローチがあれば非常に簡単です。

これは正方行列に対してのみ機能することに注意してください。長方形では、他のアルゴリズム(転置と反転)を使用する必要があります。配置したい場合は、一時的に配列のサイズを変更する必要があります。

問題を単純化する

次のマトリックスを考えてみましょう:

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

90度回転し、コーナー(番号1、4、16、13)のみを確認します。視覚化に問題がある場合は、付箋で自分を助けてください。

では、次のことを考えてみましょう。

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

90度回転させて、数値がどのように円形に回転するかに注意してください。2が1になり、3が2になり、4が3になり、1が4になります。

回転コーナー

コーナーを回転させるには、最初のコーナーに関してすべてのコーナーを定義する必要があります。

  • 最初のコーナーは(i, j)
  • 2番目のコーナーは(SIZE - j, i)
  • 3番目のコーナーは(SIZE - i, SIZE - j)
  • 4番目のコーナーは(j, SIZE - i)

配列は0ベースであるため、SIZEも0ベースにする必要があることに注意してください。(つまり、1を減算する必要があります)。

回転角の概念を理解したところで、「回転角」の概念を「回転象限」に拡張します。同じ原理が成り立ちます。

コード

上書きする場合は、番号がないことを確認する必要があります。つまり、同時に4つの数値をローテーションする必要があります。

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

印刷

マトリックスを印刷するには、以下を使用できます。

#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

これは漠然としていて、あなたが探しているものではないかもしれませんが、とにかく投稿します。

画像をピクセルの2D配列と見なす場合、水平または垂直のどちらのフリップを使用するかに応じて、最上位またはネストされた配列の順序を逆にするだけで済みます。

したがって、各ピクセル列(0-> columns/2)をループしてスワップするか(画像全体ではなく、1ピクセルの一時メモリのみが必要です)、または行をループして水平方向に反転します。意味は?そうでない場合は、コードを詳しく説明/記述します。

1
Jeriko