如何在原地旋转图像90度?(不使用额外内存的算法)

41
在嵌入式C应用程序中,我有一张大图片,想要将其旋转90度。目前我使用众所周知的简单算法来实现这一点。然而,这个算法需要我再复制一份图像。我想避免为副本分配内存,而是宁愿就地旋转它。由于图像不是正方形,这很棘手。有人知道一个合适的算法吗?
编辑以添加澄清,因为有人在问:
我按照通常的格式存储了一张图片:
// 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数组的内容,然后交换widthheight成员变量。所以如果我有一个9x20像素的图像,然后旋转它,最终得到一个20x9像素的图像。这会改变图像的步幅,从而使算法变得更加复杂。

4
你打算如何旋转一个非正方形的图像而不分配额外的空间?你打算在过程中交换x/y索引吗? - Matti Virkkunen
你能告诉我们一些关于图像存储的具体细节吗? - tur1ng
3
哦,一个扁平数组…嗯,我应该想到这个的。 - Matti Virkkunen
一个有趣的问题。我想,如果图像是单色的每像素1位,那么这可能会给问题增加另一个复杂度级别。 - Craig McQueen
当我处理yuv420p图像帧时,我遇到了这个问题,需要将其旋转90度然后转换为jpeg格式。我真的需要原地旋转它,因为该图像是类似视频流的,大约25 fps,并且需要低延迟。有人能给我一个高效的算法吗? - acrazing
9个回答

31
这可能会有所帮助:原地矩阵转置
(在转置后,您可能还需要进行一些镜像操作,如rlbond所提到的)。

5
请注意,简单的转置并不能满足他的要求 —— 他还需要在水平方向上进行镜像翻转。 - rlbond
@rlbond:这很容易做到。我会编辑答案并提到这一点。谢谢。 - Aryabhatta
是的,那看起来就是我想要的,谢谢。不幸的是,这些算法似乎需要每个像素进行一次乘法和除法运算,在嵌入式CPU上代价太高了... - user9876
不幸的是,这种方法非常慢...我遇到了同样的问题,我选择了辅助内存分配而不是无休止地复制字节。 - DanielHsH

28

如果你按照错误的顺序从内存中读取图像,那么它实际上就等同于旋转它。这可能适用或不适用于你正在做的任何事情,但是接下来就是方法:

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 */

1
这基本上就是我试图表达的,只不过更优雅了 :) - Jeriko
5
因为这让我考虑在将图像传输到屏幕时进行旋转,所以我给了一个+1。此时有一个屏幕缓冲区可供书写,因此可以使用传统的旋转算法。 - user9876
2
我很确定你的 cwccw 被交换了。 - Kuba Spatny

7

不确定旋转后您将进行什么处理,但您可以放置不管并使用另一个函数从原始内存中读取旋转像素。

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

输入参数x和y的维度已经与原始数据交换


如果x大于图像高度,则会得到负的x索引。 - Omry Yadan
没问题:旋转后,getWidth90()应该返回img->height。因此,x应该始终小于img->height。 - user9876
1
这个答案缺少一个-1。应该是:return img->data[(img->height - 1 - x) * img->width + y];(否则在要求读取x = 0 y = 0时会越界)。 - user9876

6
这个问题花费了我相当长的时间,但如果你采用正确的方法,它就非常简单。 请注意,这仅适用于方阵。矩形将需要使用其他算法(转置和翻转)。如果要原地完成,则可能需要暂时调整数组大小。

简化问题

考虑以下矩阵:
 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)
  • 第二个角是(SIZE - j, i)
  • 第三个角是(SIZE - i, SIZE - j)
  • 第四个角是(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
这可能有些模糊,不一定符合您的要求,但我还是会发布一下。
如果您将图像视为像素的2D数组,则只需要反转顶层或嵌套数组的顺序,具体取决于您想要水平或垂直翻转。因此,您可以循环遍历每个像素列(0->columns/2),并交换它们(因此您只需要一个像素的临时内存,而不是整个图片),或通过行进行水平翻转。明白了吗?如果不理解,我可以详细说明 / 写代码。

那很有道理,但不幸的是我需要旋转而不仅仅是翻转。 - user9876
非常有趣的想法,不过需要编程检查列数是否为奇数。 - RanchiRhino

1
真正的答案是:不行,你必须分配一些内存。
或者你可以使用递归,但对于大型图像会失败。
然而,有一些方法所需的内存比图像本身少。
例如,您可以取点A(x从0到宽度,y从0到高度),计算其新位置B,在将B复制到其新位置C之前,将其替换为A等等。
但是,这种方法需要跟踪已经移动的字节。(在旋转图像中使用每个像素一个位的位图)
参见维基百科文章,它清楚地说明了这不能用于非方形图像:这里再次提供链接:http://en.wikipedia.org/wiki/In-place_matrix_transposition

0
这是一个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--;
        }
    }
}

-2
这类似于2D矩阵的旋转。以下是我的算法,它可以将2D矩阵旋转90度,也适用于M X N。取给定矩阵的转置,然后交换第一列和最后一列、第二列和倒数第二列等等。您也可以使用行而不是列进行操作。
import java.io.*;
import java.util.*;

public class MatrixRotationTest
{
public static void main(String arg[])throws Exception
{
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    System.out.println("Enter the matrix rows:");
    int r = Integer.parseInt(br.readLine());
    System.out.println("Enter the matrix columns:");
    int c = Integer.parseInt(br.readLine());
    int[][] matrix = new int[r*c][r*c];
    for(int i=0;i<r;i++)
    {
        System.out.println("Enter row "+(i+1));
        for(int j=0;j<c;j++)
        {
            matrix[i][j] = Integer.parseInt(br.readLine());
        }
    }
    matrix = reverseMatrixColumns(transformMatrix(matrix),r,c);
    System.out.println("Rotated Matrix");
    for(int i=0;i<c;i++)
    {
        for(int j=0;j<r;j++)
        {
            System.out.print(matrix[i][j]+" ");
        }
        System.out.println();
    }
}

    //Transform the given matrix
public static int[][] transformMatrix(int[][] matrix)throws Exception
{
    for(int i=0;i<matrix.length;i++)
    {
        for(int j=i;j<matrix[0].length;j++)
        {
            int temp = matrix[i][j];
            matrix[i][j] = matrix [j][i];
            matrix[j][i] = temp;
        }
    }
}

    //Swap columns
public static int[][] reverseMatrixColumns(int[][] matrix,int r,int c)
{
    int i=0,j=r-1;
    while(i!=r/2)
    {
        for(int l=0;l<c;l++)
        {
            int temp = matrix[l][i];
            matrix[l][i] = matrix[l][j];
            matrix[l][j] = temp;
        }
        i++;
        j--;
    }
    return matrix;
}
}

只有在您分配的图像大于所需大小时,此方法才有效。例如,如果我有一个1920x1080的图像,您基本上建议我分配一个1920x1920的缓冲区,并执行众所周知的“原地旋转正方形图像”算法之一。这可能比拥有两个1920x1080缓冲区更好,但仍不是我想要的。 - user9876

-4

这是我在C语言中尝试进行矩阵90度旋转的两步解决方案。
首先原地转置矩阵,然后交换列。

#define ROWS        5
#define COLS        5

void print_matrix_b(int B[][COLS], int rows, int cols) 
{
    for (int i = 0; i <= rows; i++) {
        for (int j = 0; j <=cols; j++) {
            printf("%d ", B[i][j]);
        }
        printf("\n");
    }
}

void swap_columns(int B[][COLS], int l, int r, int rows)
{
    int tmp;
    for (int i = 0; i <= rows; i++) {
        tmp = B[i][l];
        B[i][l] = B[i][r];
        B[i][r] = tmp;
    }
}


void matrix_2d_rotation(int B[][COLS], int rows, int cols)
{
    int tmp;
    // Transpose the matrix first
    for (int i = 0; i <= rows; i++) {
        for (int j = i; j <=cols; j++) {
            tmp = B[i][j];
            B[i][j] = B[j][i];
            B[j][i] = tmp;
        }
    }
    // Swap the first and last col and continue until
    // the middle.
    for (int i = 0; i < (cols / 2); i++)
        swap_columns(B, i, cols - i, rows);
}



int _tmain(int argc, _TCHAR* argv[])
{
    int B[ROWS][COLS] = { 
                  {1, 2, 3, 4, 5}, 
                      {6, 7, 8, 9, 10},
                          {11, 12, 13, 14, 15},
                          {16, 17, 18, 19, 20},
                          {21, 22, 23, 24, 25}
                        };

    matrix_2d_rotation(B, ROWS - 1, COLS - 1);

    print_matrix_b(B, ROWS - 1, COLS -1);
    return 0;
}

如果矩阵不是方阵,那就行不通了。方阵情况比较简单,这也是为什么问题会问非方形图像的原因 :-) - user9876

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接