zl程序教程

您现在的位置是:首页 >  其它

当前栏目

FFmpeg之yuv旋转(十九)

FFMPEG 旋转 十九 YUV
2023-09-14 09:09:53 时间

一、 按像素点旋转图像

假设有以下一张图像:

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 列的像素点


2. 若旋转**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 列的像素点


3. 若旋转**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++) {
            rotatedRgb24[rotatedRgb24Index++] = rgb24[finalColumnStartIndex + offset];
            rotatedRgb24[rotatedRgb24Index++] = rgb24[finalColumnStartIndex + offset + 1];
            rotatedRgb24[rotatedRgb24Index++] = rgb24[finalColumnStartIndex + offset + 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 rotateNv21Degree90(char *nv21, char *rotatedNv21, 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++) {
            rotatedNv21[rotatedYIndex++] = nv21[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) {
            rotatedNv21[rotatedVIndex] = nv21[uvStartIndex - offset];
            rotatedNv21[rotatedUIndex] = nv21[uvStartIndex - offset + 1];
            offset += width;
            rotatedVIndex += 2;
            rotatedUIndex += 2;
        }
    }
}
  • 旋转180度
void rotateNv21Degree180(char *nv21, char *rotatedNv21, 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--) {
            rotatedNv21[rotatedYIndex++] = nv21[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) {
            rotatedNv21[rotatedVIndex] = nv21[uvIndex];
            rotatedNv21[rotatedUIndex] = nv21[uvIndex + 1];
            uvIndex -= 2;
            rotatedVIndex += 2;
            rotatedUIndex += 2;
        }
    }
}
  • 旋转270度
void rotateNv21Degree270(char *nv21, char *rotatedNv21, 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++) {
            rotatedNv21[rotatedYIndex++] = nv21[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) {
            rotatedNv21[rotatedVIndex] = nv21[uvFinalColumnStartIndex + offset];
            rotatedNv21[rotatedUIndex] = nv21[uvFinalColumnStartIndex + offset + 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 rotateI420Degree90(char *i420, char *rotatedI420, 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++) {
            rotatedI420[rotatedYIndex++] = i420[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) {
            rotatedI420[rotatedUIndex++] = i420[uStartIndex - offset];
            rotatedI420[rotatedVIndex++] = i420[vStartIndex - offset];
            offset += halfWidth;
        }
    }
}
  • 旋转180度
void rotateI420Degree180(char *i420, char *rotatedI420, 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--) {
            rotatedI420[rotatedYIndex++] = i420[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) {
            rotatedI420[rotatedUIndex++] = i420[uIndex--];
            rotatedI420[rotatedVIndex++] = i420[vIndex--];
        }
    }
}
  • 旋转270度
void rotateI420Degree270(char *i420, char *rotatedI420, 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++) {
            rotatedI420[rotatedYIndex++] = i420[yFinalColumnStartIndex + offset];
            offset += width;
        }
        yFinalColumnStartIndex--;
    }
    //rotate uv
    int uFinalColumnStartIndex = width * height + width;
    int vFinalColumnStartIndex = width * height * 5 / 4 + width;
    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) {
            rotatedI420[rotatedUIndex++] = i420[uFinalColumnStartIndex + offset];
            rotatedI420[rotatedVIndex++] = i420[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
             */
            //y5
            rotatedYuyv[rotatedYuyvIndex] = yuyv[yuyvStartIndex - offset + lineDataSize];
            //u3
            rotatedYuyv[rotatedYuyvIndex + 1] = yuyv[yuyvStartIndex - offset + lineDataSize + 1];
            //y1
            rotatedYuyv[rotatedYuyvIndex + 2] = yuyv[yuyvStartIndex - offset];
            //v3
            rotatedYuyv[rotatedYuyvIndex + 3] = yuyv[yuyvStartIndex - offset + lineDataSize + 3];

            //y6
            rotatedYuyv[rotatedYuyvIndex + rotatedLineDataSize] = yuyv[yuyvStartIndex + lineDataSize - offset + 2];
            //u1
            rotatedYuyv[rotatedYuyvIndex + rotatedLineDataSize + 1] = yuyv[yuyvStartIndex - offset + 1];
            //y2
            rotatedYuyv[rotatedYuyvIndex + rotatedLineDataSize + 2] = yuyv[yuyvStartIndex - offset + 2];
            //v1
            rotatedYuyv[rotatedYuyvIndex + rotatedLineDataSize + 3] = yuyv[yuyvStartIndex - offset + 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
             */

            //y4
            rotatedYuyv[rotatedYuyvIndex] = yuyv[finalColumnStartIndex + offset + 2];
            //u2
            rotatedYuyv[rotatedYuyvIndex + 1] = yuyv[finalColumnStartIndex + offset + 1];
            //y8
            rotatedYuyv[rotatedYuyvIndex + 2] = yuyv[finalColumnStartIndex + offset + lineDataSize +
                                                     2];
            //v2
            rotatedYuyv[rotatedYuyvIndex + 3] = yuyv[finalColumnStartIndex + offset + 3];

            //y3
            rotatedYuyv[rotatedYuyvIndex + rotatedLineDataSize] = yuyv[finalColumnStartIndex + offset];
            //u4
            rotatedYuyv[rotatedYuyvIndex + rotatedLineDataSize + 1] = yuyv[finalColumnStartIndex + lineDataSize + offset + 1];
            //y7
            rotatedYuyv[rotatedYuyvIndex + rotatedLineDataSize + 2] = yuyv[finalColumnStartIndex + lineDataSize + offset];
            //v4
            rotatedYuyv[rotatedYuyvIndex + rotatedLineDataSize + 3] = yuyv[finalColumnStartIndex + lineDataSize + offset + 3];

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