在安卓上旋转一个YUV字节数组

25
我想要旋转从预览回调接收到的YUV帧图像,目前我找到了这篇文章,其中包含一种算法来旋转帧预览,但是它会破坏预览图像 camera pixels rotated 另一种旋转图像的方法是将YUV图像创建为jpg格式,创建位图,旋转位图并获取位图的字节数组,但我真正需要的是YUV(NV21)格式。
FYI。我之所以问这个问题,是因为我有一个支持旋转的相机应用程序,但帧预览仅在横向模式下返回。

1
我也遇到过这个问题,并最终采用你提到的解决方案,使用Bitmap来旋转图像并使用JPEG格式。实际上,根据文档所述,设置显示方向不会影响PreviewCallback接收到的缓冲区:这不会影响通过onPreviewFrame(byte[], Camera)传递的字节数组顺序、JPEG图片或录制的视频。 - El Bert
你好。你解决了这个问题吗?非常感谢。 - Paul
3个回答

21

以下方法可以将 YUV420 字节数组旋转90度。

private byte[] rotateYUV420Degree90(byte[] data, int imageWidth, int imageHeight) 
{
    byte [] yuv = new byte[imageWidth*imageHeight*3/2];
    // Rotate the Y luma
    int i = 0;
    for(int x = 0;x < imageWidth;x++)
    {
        for(int y = imageHeight-1;y >= 0;y--)                               
        {
            yuv[i] = data[y*imageWidth+x];
            i++;
        }
    }
    // Rotate the U and V color components 
    i = imageWidth*imageHeight*3/2-1;
    for(int x = imageWidth-1;x > 0;x=x-2)
    {
        for(int y = 0;y < imageHeight/2;y++)                                
        {
            yuv[i] = data[(imageWidth*imageHeight)+(y*imageWidth)+x];
            i--;
            yuv[i] = data[(imageWidth*imageHeight)+(y*imageWidth)+(x-1)];
            i--;
        }
    }
    return yuv;
}

(请注意,这只有在宽度和高度是4的因数时才有效)


谢谢,这确实对我有帮助! 但是我有一个小问题。 当我将这段代码片段应用到我的数据字节上时,我的图像会旋转到(-90)度。 我想要旋转到+90度,或者简单地说,需要向相反的方向旋转。 你能帮我解决一下吗?谢谢! - Usama
@Usama:你找到逆时针旋转的解决方案了吗?我也卡在这里了。谢谢。 - Mihai
是的,我调用同一个函数3次,将返回数据作为参数传递给下一次调用,并交换宽度和高度参数。 - Usama
图像顺时针旋转90度,但颜色不匹配。我怀疑我收到的缓冲区(来自外部库)是否为YUV格式。如果我将接收到的缓冲区直接转储到文件中,并使用ffplay播放,则可以正常播放。 ffplay -v info -f rawvideo -video_size 320x240 yuv.bin - toyvenu

10

以下是转弯的不同选项(90度、180度、270度):

public static byte[] rotateYUV420Degree90(byte[] data, int imageWidth, int imageHeight) {
    byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
    // Rotate the Y luma
    int i = 0;
    for (int x = 0; x < imageWidth; x++) {
        for (int y = imageHeight - 1; y >= 0; y--) {
            yuv[i] = data[y * imageWidth + x];
            i++;
        }
    }
    // Rotate the U and V color components
    i = imageWidth * imageHeight * 3 / 2 - 1;
    for (int x = imageWidth - 1; x > 0; x = x - 2) {
        for (int y = 0; y < imageHeight / 2; y++) {
            yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + x];
            i--;
            yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth)
                    + (x - 1)];
            i--;
        }
    }
    return yuv;
}

private static byte[] rotateYUV420Degree180(byte[] data, int imageWidth, int imageHeight) {
    byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
    int i = 0;
    int count = 0;
    for (i = imageWidth * imageHeight - 1; i >= 0; i--) {
        yuv[count] = data[i];
        count++;
    }
    i = imageWidth * imageHeight * 3 / 2 - 1;
    for (i = imageWidth * imageHeight * 3 / 2 - 1; i >= imageWidth
            * imageHeight; i -= 2) {
        yuv[count++] = data[i - 1];
        yuv[count++] = data[i];
    }
    return yuv;
}

public static byte[] rotateYUV420Degree270(byte[] data, int imageWidth,
                                     int imageHeight) {
    byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
    int nWidth = 0, nHeight = 0;
    int wh = 0;
    int uvHeight = 0;
    if (imageWidth != nWidth || imageHeight != nHeight) {
        nWidth = imageWidth;
        nHeight = imageHeight;
        wh = imageWidth * imageHeight;
        uvHeight = imageHeight >> 1;// uvHeight = height / 2
    }
    // ??Y
    int k = 0;
    for (int i = 0; i < imageWidth; i++) {
        int nPos = 0;
        for (int j = 0; j < imageHeight; j++) {
            yuv[k] = data[nPos + i];
            k++;
            nPos += imageWidth;
        }
    }
    for (int i = 0; i < imageWidth; i += 2) {
        int nPos = wh;
        for (int j = 0; j < uvHeight; j++) {
            yuv[k] = data[nPos + i];
            yuv[k + 1] = data[nPos + i + 1];
            k += 2;
            nPos += imageWidth;
        }
    }
    return rotateYUV420Degree180(yuv, imageWidth, imageHeight);
}

3
将图像旋转270度的方法是对图像应用镜像效果。正确的解决方案应该是:return rotateYuv420Degree180(rotateYuv420Degree90(data, imageWidth, imageHeight), imageWidth, imageHeight);无需以其他方式旋转90度,与原始的rotateYuv420Degree90方法相同。 - Francisco Durdin Garcia
1
请注意,这些方法非常耗费资源,可能会对性能产生显著影响,特别是当您像Francisco建议的那样调用两个方法时。对于拍照/单帧来说可能还好,但是“实时处理”(例如在流媒体中)将不可能,特别是在较慢/旧的设备上。这些方法也可以使用纯/本地C/C++(NDK)或RenderScript代码编写,效率提高几倍。 - snachmsm

1
这是我完成它的方法。
这段代码在其他地方设置。
    Camera.Size size
    Rect rectangle = new Rect();
    rectangle.bottom = size.height;
    rectangle.top = 0;
    rectangle.left = 0;
    rectangle.right = size.width;

这是执行工作的方法。
    private Bitmap rotateBitmap(YuvImage yuvImage, int orientation, Rect rectangle)
    {
    ByteArrayOutputStream os = new ByteArrayOutputStream();
    yuvImage.compressToJpeg(rectangle, 100, os);

    Matrix matrix = new Matrix();
    matrix.postRotate(orientation);
    byte[] bytes = os.toByteArray();
    Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    return Bitmap.createBitmap(bitmap, 0 , 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
    }

将YUVImage压缩成JPEG,以便位图可以处理它。旋转位图,然后导出它。为了将其恢复为所需的JPEG格式,我使用了这行代码。
image.compress(Bitmap.CompressFormat.JPEG, 50, outputStream);

这可能会导致伪影,并且速度会很慢,因为存在额外的JPEG编解码步骤。除了测试之外,不建议用于其他任何用途。 - Sami Kuhmonen
太慢了,请参考上面Sami的评论。 - Guilherme Campos Hazan
它很容易实现和理解,比其他答案更容易部署。它比绝对最优解慢一点,但对于可能只旋转一次的图像来说,这是完全可以接受的。 - Steven

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