图像实战 - 旋转RGB、YUV图像

在开发相机程序显示相机预览数据时,有时相机的位置是固定的,那我们可能会需要用到图像的旋转进行纠正,以获取我们需要的旋转一定角度后的图像。本文介绍了一些常见的YUV、RGB数据的旋转方法。

效果图:

0度
90度
180度
270度

一、 按像素点旋转图像

假设有以下一张图像:

Pixel1  Pixel2  Pixel3  Pixel4

Pixel5  Pixel6  Pixel7  Pixel8

其图像分辨率是width x height

  1. 在顺时针旋转90度后,其内容将会变成:

Pixel5  Pixel1

Pixel6  Pixel2

Pixel7  Pixel3

Pixel8  Pixel4

分辨率变成了height x width

也就是:

  • 原始数据第 height - 1 行第 0 列的像素点将会变成目标数据第 0 行第 0 列的像素点
  • 原始数据第 height - 1 行第 1 列的像素点将会变成目标数据第 1 行第 0 列的像素点
  • ...
  • 原始数据第 height - 1 行第 width - 1 列的像素点将会变成目标数据第 width - 1 行第 0 列的像素点
  • ...
  • 原始数据第 1 行第 width - 1 列的像素点将会变成目标数据第 width - 1 行第 height - 2 列的像素点
  • 原始数据第 1 行第 width - 2 列的像素点将会变成目标数据第 width - 2 行第 height - 2 列的像素点
  • 原始数据第 1 行第 width - 3 列的像素点将会变成目标数据第 width - 3 行第 height - 2 列的像素点
  • ...
  • 原始数据第 i 行第 j 列的像素点将会变成目标数据第 j 行第 height - 1 - i 列的像素点


  1. 若旋转180度,其内容将会变成:

Pixel8  Pixel7  Pixel6  Pixel5

Pixel4  Pixel3  Pixel2  Pixel1

也就是

  • 原始数据第 0 行第 0 列的像素点将会变成目标数据第 height - 1 行第 width - 1 列的像素点
  • 原始数据第 1 行第 0 列的像素点将会变成目标数据第 height - 2 行第 width - 1 列的像素点
  • ...
  • 原始数据第 height - 1 行第 0 列的像素点将会变成目标数据第 0 行第 width - 1 列的像素点
  • 原始数据第 height - 1 行第 1 列的像素点将会变成目标数据第 0 行第 width - 2 列的像素点
  • ...
  • 原始数据第 i 行第 j 列的像素点将会变成目标数据第 height - 1 - i 行第 width - 1 - j 列的像素点


  1. 若旋转270度,其内容将会变成:

Pixel4  Pixel8

Pixel3  Pixel7

Pixel2  Pixel6

Pixel1  Pixel5

也就是:

  • 原始数据第 0 行第 width - 1 列的像素点将会变成目标数据第 0 行第 0 列的像素点
  • 原始数据第 0 行第 width - 2 列的像素点将会变成目标数据第 1 行第 0 列的像素点
  • ...
  • 原始数据第 0 行第 0 列的像素点将会变成目标数据第 width - 1 行第 0 列的像素点
  • ...
  • 原始数据第 1 行第 width - 1 列的像素点将会变成目标数据第 0 行第 1 列的像素点
  • 原始数据第 1 行第 width - 2 列的像素点将会变成目标数据第 1 行第 1 列的像素点
  • ...
  • 原始数据第 i 行第 j 列的像素点将会变成目标数据第 width - j - 1 行第 i 列的像素点

二、 旋转 BGR24 / RGB24 数据

BGR24 / RGB24 都是以3个byte作为一个像素,因此在旋转时需要将3个byte作为一个整体进行旋转。示例代码如下。

  • 旋转90度:
void rotateRgb24Degree90(char *rgb24, char *rotatedRgb24, int width, int height) {
    int lineDataSize = width * 3;
    int rotatedRgb24Index = 0;
    int finalLineStartIndex = (height - 1) * lineDataSize;
    for (int w = 0; w < lineDataSize; w += 3) {
        int bgr24StartIndex = finalLineStartIndex + w;
        int offset = 0;
        for (int h = 0; h < height; h++) {
            rotatedRgb24[rotatedRgb24Index++] = rgb24[bgr24StartIndex - offset];
            rotatedRgb24[rotatedRgb24Index++] = rgb24[bgr24StartIndex - offset + 1];
            rotatedRgb24[rotatedRgb24Index++] = rgb24[bgr24StartIndex - offset + 2];
            offset += lineDataSize;
        }
    }
}
  • 旋转180度
void rotateRgb24Degree180(char *rgb24, char *rotatedRgb24, int width, int height) {
    int lineDataSize = width * 3;
    int rotatedRgb24Index = 0;
    int rgb24StartIndex = lineDataSize * height - 3;
    for (int h = height - 1; h >= 0; h--) {
        for (int w = lineDataSize - 3; w >= 0; w -= 3) {
            rotatedRgb24[rotatedRgb24Index++] = rgb24[rgb24StartIndex];
            rotatedRgb24[rotatedRgb24Index++] = rgb24[rgb24StartIndex + 1];
            rotatedRgb24[rotatedRgb24Index++] = rgb24[rgb24StartIndex + 2];
            rgb24StartIndex -= 3;
        }
    }
}
  • 旋转270度
void rotateRgb24Degree270(char *rgb24, char *rotatedRgb24, int width, int height) {
    int lineDataSize = width * 3;
    int rotatedRgb24Index = 0;
    int finalColumnStartIndex = lineDataSize;
    for (int w = 0; w < lineDataSize; w += 3) {
        finalColumnStartIndex -= 3;
        int offset = 0;
        for (int h = 0; h < height; h++) {
            int originalOffset = finalColumnStartIndex + offset;
            rotatedRgb24[rotatedRgb24Index++] = rgb24[originalOffset];
            rotatedRgb24[rotatedRgb24Index++] = rgb24[originalOffset + 1];
            rotatedRgb24[rotatedRgb24Index++] = rgb24[originalOffset + 2];
            offset += lineDataSize;
        }
    }
}

三、旋转NV21、NV12数据

对于NV21或NV12数据,其排列是width * height个Y连续存储,接下来是 height / 2 行的UV数据,每一行的UV数据是 width / 2 个U和 width / 2 个V交叉存储(NV21是VU VU VU VU....,NV12是UV UV UV UV ....),Y数据的大小刚好就是像素数,可以直接进行旋转,对于U和V数据,需要考虑下标的跳跃情况(由于NV21和NV12的U和V只是刚好位置相反,因此旋转NV21的代码也同样适用于旋转NV12)。示例代码如下。

  • 旋转90
void rotateYuv420spDegree90(char *yuv420sp, char *rotateYuv420sp, int width, int height) {
    int yFinalLineStartIndex = (height - 1) * width;
    int rotatedYIndex = 0;
    //rotate y
    for (int w = 0; w < width; w++) {
        int yStartIndex = yFinalLineStartIndex + w;
        int offset = 0;
        for (int h = 0; h < height; h++) {
            *(rotateYuv420sp + rotatedYIndex++) = *(yuv420sp + yStartIndex - offset);
            offset += width;
        }
    }
    //rotate uv
    int uvFinalLineStartIndex = width * height * 3 / 2 - width;
    int rotatedVIndex = width * height;
    int rotatedUIndex = width * height + 1;
    for (int w = 0; w < width; w += 2) {
        int uvStartIndex = uvFinalLineStartIndex + w;
        int offset = 0;
        for (int h = 0; h < height; h += 2) {
            int originalOffset = uvStartIndex - offset;
            *(rotateYuv420sp + rotatedVIndex) = *(yuv420sp + originalOffset);
            *(rotateYuv420sp + rotatedUIndex) = *(yuv420sp + originalOffset + 1);
            offset += width;
            rotatedVIndex += 2;
            rotatedUIndex += 2;
        }
    }
}
  • 旋转180
void rotateYuv420spDegree180(char *yuv420sp, char *rotateYuv420sp, int width, int height) {
    int yIndex = width * height - 1;
    int rotatedYIndex = 0;
    //rotate y
    for (int h = height - 1; h >= 0; h--) {
        for (int w = width - 1; w >= 0; w--) {
            *(rotateYuv420sp + rotatedYIndex++) = *(yuv420sp + yIndex);
            yIndex--;
        }
    }
    int uvIndex = width * height * 3 / 2 - 2;
    int rotatedVIndex = width * height;
    int rotatedUIndex = width * height + 1;
    //rotate uv
    for (int h = height - 1; h >= 0; h -= 2) {
        for (int w = width - 1; w >= 0; w -= 2) {
            *(rotateYuv420sp + rotatedVIndex) = *(yuv420sp + uvIndex);
            *(rotateYuv420sp + rotatedUIndex) = *(yuv420sp + uvIndex + 1);
            uvIndex -= 2;
            rotatedVIndex += 2;
            rotatedUIndex += 2;
        }
    }
}
  • 旋转270
void rotateYuv420spDegree270(char *yuv420sp, char *rotateYuv420sp, int width, int height) {
    int rotatedYIndex = 0;
    int yFinalColumnStartIndex = width;
    //rotate y
    for (int w = 0; w < width; w++) {
        int offset = 0;
        for (int h = 0; h < height; h++) {
            *(rotateYuv420sp + rotatedYIndex++) = *(yuv420sp + yFinalColumnStartIndex + offset);
            offset += width;
        }
        yFinalColumnStartIndex--;
    }
    //rotate uv
    int uvFinalColumnStartIndex = width * height + width;
    int rotatedVIndex = width * height;
    int rotatedUIndex = width * height + 1;
    for (int w = 0; w < width; w += 2) {
        uvFinalColumnStartIndex -= 2;
        int offset = 0;
        for (int h = 0; h < height; h += 2) {
            int originalOffset = uvFinalColumnStartIndex + offset;
            *(rotateYuv420sp + rotatedVIndex) = *(yuv420sp + originalOffset);
            *(rotateYuv420sp + rotatedUIndex) = *(yuv420sp + originalOffset + 1);
            offset += width;
            rotatedVIndex += 2;
            rotatedUIndex += 2;
        }
    }
}

四、旋转I420、YV12数据

对于I420、YV12数据,其排列是width * height个Y连续存储,接下来是连续的U和V或连续的V和U(I420是UUUUUUUU....VVVVVVVV....,YV12是VVVVVVVV....UUUUUUUU....),Y数据的大小刚好就是像素数,可以直接进行旋转;因为U和V的宽高都只有Y的宽高的一半,所以宽高的循环数各只有Y的一半(由于I420和YV12的U和V只是刚好位置相反,因此旋转I420的代码也同样适用于旋转YV12)。示例代码如下。

  • 旋转90
void rotateYuv420pDegree90(char *yuv420p, char *rotateYuv420p, int width, int height) {
    int halfWidth = width / 2;
    int yFinalLineStartIndex = (height - 1) * width;
    int rotatedYIndex = 0;
    //rotate y
    for (int w = 0; w < width; w++) {
        int yStartIndex = yFinalLineStartIndex + w;
        int offset = 0;
        for (int h = 0; h < height; h++) {
            rotateYuv420p[rotatedYIndex++] = yuv420p[yStartIndex - offset];
            offset += width;
        }
    }
    //rotate uv
    int uFinalLineStartIndex = width * height * 5 / 4 - halfWidth;
    int vFinalLineStartIndex = width * height * 3 / 2 - halfWidth;
    int rotatedUIndex = width * height;
    int rotatedVIndex = width * height * 5 / 4;
    for (int w = 0; w < width; w += 2) {
        int uStartIndex = uFinalLineStartIndex + w / 2;
        int vStartIndex = vFinalLineStartIndex + w / 2;
        int offset = 0;
        for (int h = 0; h < height; h += 2) {
            rotateYuv420p[rotatedUIndex++] = yuv420p[uStartIndex - offset];
            rotateYuv420p[rotatedVIndex++] = yuv420p[vStartIndex - offset];
            offset += halfWidth;
        }
    }
}
  • 旋转180
void rotateYuv420pDegree180(char *yuv420p, char *rotateYuv420p, int width, int height) {
    int yIndex = width * height - 1;
    int rotatedYIndex = 0;
    //rotate y
    for (int h = height - 1; h >= 0; h--) {
        for (int w = width - 1; w >= 0; w--) {
            rotateYuv420p[rotatedYIndex++] = yuv420p[yIndex];
            yIndex--;
        }
    }
    int uIndex = width * height * 5 / 4 - 1;
    int vIndex = width * height * 3 / 2 - 1;
    int rotatedUIndex = width * height;
    int rotatedVIndex = width * height * 5 / 4;
    //rotate uv
    for (int h = height - 1; h >= 0; h -= 2) {
        for (int w = width - 1; w >= 0; w -= 2) {
            rotateYuv420p[rotatedUIndex++] = yuv420p[uIndex--];
            rotateYuv420p[rotatedVIndex++] = yuv420p[vIndex--];
        }
    }
}
  • 旋转270
void rotateYuv420pDegree270(char *yuv420p, char *rotateYuv420p, int width, int height) {
    int halfWidth = width / 2;
    int yFinalColumnStartIndex = width;
    int rotatedYIndex = 0;
    //rotate y
    for (int w = 0; w < width; w++) {
        int offset = 0;
        for (int h = 0; h < height; h++) {
            rotateYuv420p[rotatedYIndex++] = yuv420p[yFinalColumnStartIndex + offset];
            offset += width;
        }
        yFinalColumnStartIndex--;
    }
    //rotate uv
    int uFinalColumnStartIndex = width * height + halfWidth;
    int vFinalColumnStartIndex = width * height * 5 / 4 + halfWidth;
    int rotatedUIndex = width * height;
    int rotatedVIndex = width * height * 5 / 4;
    for (int w = 0; w < width; w += 2) {
        uFinalColumnStartIndex--;
        vFinalColumnStartIndex--;
        int offset = 0;
        for (int h = 0; h < height; h += 2) {
            rotateYuv420p[rotatedUIndex++] =
                    yuv420p[uFinalColumnStartIndex + offset];
            rotateYuv420p[rotatedVIndex++] =
                    yuv420p[vFinalColumnStartIndex + offset];
            offset += halfWidth;
        }
    }
}

五、 旋转YUYV数据

由于YUYV的排列方式是(YUYV YUYV YUYV ....),其共用关系是每2个横向相邻的Y会使用同一组U和V,因此,在旋转180度时,YUV的共用关系可以不被打破,只是更改每4个byte中的2个Y的顺序;但是在旋转90度或270度时,由于原来横向的Y将被修改为纵向,YUV的共用关系也将被打破。示例代码如下。

  • 旋转90
void rotateYuyvDegree90(char *yuyv, char *rotatedYuyv, int width, int height) {
    int lineDataSize = width * 2;
    int rotatedLineDataSize = height * 2;
    int rotatedYuyvIndex = 0;
    int finalLineStartIndex = (height - 2) * lineDataSize;
    for (int w = 0; w < lineDataSize; w += 4) {
        int yuyvStartIndex = finalLineStartIndex + w;
        int offset = 0;
        for (int h = 0; h < height; h += 2) {
            /**
             * y1 u1 y2 v2   y3 u2 y4 v2
             *                              ->    旋转后的画面脑补下
             * y5 u3 y6 v3   y7 u4 y8 v4
             */
            int originalOffset = yuyvStartIndex - offset;
            int originalNextLineOffset = yuyvStartIndex - offset + lineDataSize;
            int targetNextLineOffset = rotatedYuyvIndex + rotatedLineDataSize;
            //y5
            rotatedYuyv[rotatedYuyvIndex] = yuyv[originalNextLineOffset];
            //u3
            rotatedYuyv[rotatedYuyvIndex + 1] = yuyv[originalNextLineOffset + 1];
            //y1
            rotatedYuyv[rotatedYuyvIndex + 2] = yuyv[originalOffset];
            //v3
            rotatedYuyv[rotatedYuyvIndex + 3] = yuyv[originalNextLineOffset + 3];

            //y6
            rotatedYuyv[targetNextLineOffset] = yuyv[originalNextLineOffset + 2];
            //u1
            rotatedYuyv[targetNextLineOffset + 1] = yuyv[originalOffset + 1];
            //y2
            rotatedYuyv[targetNextLineOffset + 2] = yuyv[originalOffset + 2];
            //v2
            rotatedYuyv[targetNextLineOffset + 3] = yuyv[originalOffset + 3];

            rotatedYuyvIndex += 4;
            offset += lineDataSize * 2;
        }
        rotatedYuyvIndex += rotatedLineDataSize;
    }
}
  • 旋转180
void rotateYuyvDegree180(char *yuyv, char *rotatedYuyv, int width, int height) {
    int lineDataSize = width * 2;
    int yuyvIndex = lineDataSize * height - 4;
    int rotatedIndex = 0;
    //rotate
    for (int h = height - 1; h >= 0; h--) {
        for (int w = lineDataSize - 4; w >= 0; w -= 4) {
            rotatedYuyv[rotatedIndex++] = yuyv[yuyvIndex + 2];
            rotatedYuyv[rotatedIndex++] = yuyv[yuyvIndex + 1];
            rotatedYuyv[rotatedIndex++] = yuyv[yuyvIndex];
            rotatedYuyv[rotatedIndex++] = yuyv[yuyvIndex + 3];
            yuyvIndex -= 4;
        }
    }
}
  • 旋转270
void rotateYuyvDegree270(char *yuyv, char *rotatedYuyv, int width, int height) {
    int lineDataSize = width * 2;
    int rotatedLineDataSize = height * 2;
    int rotatedYuyvIndex = 0;
    int finalColumnStartIndex = lineDataSize - 4;
    for (int w = 0; w < lineDataSize; w += 4) {
        int offset = 0;
        for (int h = 0; h < height; h += 2) {
            /**
             * y1 u1 y2 v1   y3 u2 y4 v2
             *                              ->    旋转后的画面脑补下
             * y5 u3 y6 v3   y7 u4 y8 v4
             */

            int originalOffset = finalColumnStartIndex + offset;
            int originalNextLineOffset = finalColumnStartIndex + offset + lineDataSize;
            int targetNextLineOffset = rotatedYuyvIndex + rotatedLineDataSize;
            //y4
            rotatedYuyv[rotatedYuyvIndex] = yuyv[originalOffset + 2];
            //u2
            rotatedYuyv[rotatedYuyvIndex + 1] = yuyv[originalOffset + 1];
            //y8
            rotatedYuyv[rotatedYuyvIndex + 2] = yuyv[originalNextLineOffset + 2];
            //v2
            rotatedYuyv[rotatedYuyvIndex + 3] = yuyv[originalOffset + 3];

            //y3
            rotatedYuyv[targetNextLineOffset] = yuyv[finalColumnStartIndex + offset];
            //u4
            rotatedYuyv[targetNextLineOffset + 1] = yuyv[originalNextLineOffset + 1];
            //y7
            rotatedYuyv[targetNextLineOffset + 2] = yuyv[originalNextLineOffset];
            //v4
            rotatedYuyv[targetNextLineOffset + 3] = yuyv[originalNextLineOffset + 3];

            rotatedYuyvIndex += 4;
            offset += lineDataSize * 2;
        }
        finalColumnStartIndex -= 4;
        rotatedYuyvIndex += rotatedLineDataSize;
    }
}

你可能感兴趣的:(图像实战 - 旋转RGB、YUV图像)