相机像素旋转

13

我想对从摄像头获取的像素进行一些图像处理。

问题在于从摄像头获取的像素旋转了90度。

我在onPreviewFrame(byte[] data, Camera camera)方法中获取像素。

我尝试了camera.setDisplayOrientation(90);,它可以以正确的方向显示视频,但是我仍然收到了旋转后的像素,如文档所述:

这不会影响通过Android.Hardware.Camera.IPreviewCallback.OnPreviewFrame(Byte[], Android.Hardware.Camera)传递的字节数组的顺序,JPEG图片或录制的视频。

我还尝试了以下方法:

parameters.setRotation(90);
camera.setParameters(parameters);

但是那并没有起作用。

我正在使用Android 2.2

在这里输入图片描述

上面的图片显示了使用camera.setDisplayOrientation(90);时的SurfaceView。

第二张图片是从data数组中获取的,在onPreviewFrame(byte[] data, Camera camera)中得到。正如您所看到的,data数组被旋转了。


我强烈建议直接使用libyuv。在Android上,它会自动选择一个NEON实现,从而带来巨大的改进。 - Alex Cohn
4个回答

8

虽然我来晚了一点,但这里有一个我写的方法,其他人可能会发现它对旋转YUV420sp(NV21)图像很有用。我在SO上看到的其他方法似乎都没有真正实现它。

public static byte[] rotateNV21(final byte[] yuv,
                                final int width,
                                final int height,
                                final int rotation)
{
  if (rotation == 0) return yuv;
  if (rotation % 90 != 0 || rotation < 0 || rotation > 270) {
    throw new IllegalArgumentException("0 <= rotation < 360, rotation % 90 == 0");
  }

  final byte[]  output    = new byte[yuv.length];
  final int     frameSize = width * height;
  final boolean swap      = rotation % 180 != 0;
  final boolean xflip     = rotation % 270 != 0;
  final boolean yflip     = rotation >= 180;

  for (int j = 0; j < height; j++) {
    for (int i = 0; i < width; i++) {
      final int yIn = j * width + i;
      final int uIn = frameSize + (j >> 1) * width + (i & ~1);
      final int vIn = uIn       + 1;

      final int wOut     = swap  ? height              : width;
      final int hOut     = swap  ? width               : height;
      final int iSwapped = swap  ? j                   : i;
      final int jSwapped = swap  ? i                   : j;
      final int iOut     = xflip ? wOut - iSwapped - 1 : iSwapped;
      final int jOut     = yflip ? hOut - jSwapped - 1 : jSwapped;

      final int yOut = jOut * wOut + iOut;
      final int uOut = frameSize + (jOut >> 1) * wOut + (iOut & ~1);
      final int vOut = uOut + 1;

      output[yOut] = (byte)(0xff & yuv[yIn]);
      output[uOut] = (byte)(0xff & yuv[uIn]);
      output[vOut] = (byte)(0xff & yuv[vIn]);
    }
  }
  return output;
}

注意:此内容似乎来自 https://github.com/SMSSecure/SMSSecure/blob/master/src/org/smssecure/smssecure/util/BitmapUtil.java,该项目标记为GPLv3。如果您使用此内容,寻找GPL侵权的工具可能会标记您的程序。 - laalto
源代码:我编写了它并将其放在那里。 - jake
谢谢,看起来第一个项目是你的项目的分支。无论如何,有GPLv3和寻找GPL违规的工具可以标记使用此片段的代码。(对我来说:我正在审查一段专有代码,这个片段很突出,开始搜索它的来源。现在已经从专有代码空间中删除了它。) - laalto
output[uOut] = (byte)(0xff & yuv[uIn]); -> output[uOut] = (字节)(0xff&yuv [uIn]);ArrayIndexOutOfBoundsException :( -> 数组下标越界异常:( - Volodymyr Kulyk
当将旋转后的字节数组解码为位图时,它返回 null?你能帮我解决一下吗? - Shashwat Gupta

6
也许你可以在侧向的框架上执行图像处理,只有在显示它时才需要考虑旋转(这已经完成了)。如果不行,那么你需要采用传统的方式旋转框架。我在某个地方找到了一些代码(稍作修改),可能会有所帮助:
// load the origial bitmap
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
       R.drawable.android);

int width = bitmap.width();
int height = bitmap.height();

// create a matrix for the manipulation
Matrix matrix = new Matrix();
// rotate the Bitmap 90 degrees (counterclockwise)
matrix.postRotate(90);

// recreate the new Bitmap, swap width and height and apply transform
Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
                  width, height, matrix, true);

这很容易,因为createBitmap方法支持矩阵变换:http://developer.android.com/reference/android/graphics/Bitmap.html#createBitmap(android.graphics.Bitmap, int, int, int, int, android.graphics.Matrix, boolean)


在 onpreviewframe 方法中,位图未能正常工作,返回了 null。 - Hardy

4
如果您收到一个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的因数的情况)

2
不工作!请帮我解决这个问题,出现了ArrayIndexOutOfBoundsException异常。 - therealprashant

2
您可以按照以下方式旋转原始数据:
// Rotate the data for Portait Mode
byte[] rotatedData = new byte[data.length];
for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++)
        rotatedData[x * height + height - y - 1] = data[x + y * width];
}

'width'和'height'是使用以下预设大小设置预览图像的:

Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(width, height);
camera.setParameters(parameters);

您可以相应使用旋转后的数据。

我仅在QR码扫描中使用过此方法,似乎完美运作。不确定它可能如何影响其他应用程序。


只要您收到的预览图像与宽度和高度值匹配,那么就不应该出现越界异常。这是我目前正在使用的确切代码,效果很好。 - Scott
原始数据旋转得很好,但图像质量下降了。在使用您的代码之后,我不能获得之前那样的正确图像质量,您能否解释一些关于旋转部分的细节或给我任何有用的相关链接。我非常感谢您的帮助。 - Daud Arfin
抱歉再次打扰您,如果我想通过角度旋转像素,例如90、180、270度,是否有可能这样做呢? - Daud Arfin
在onPreviewFrame中正确地旋转了YuvImage。 - slott
这只旋转Y(亮度)通道。非常适合QR码和许多其他仅使用灰度的CV应用程序。 - Alex Cohn
显示剩余5条评论

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