Android ImageReader YUV 420 888重复数据
我正在尝试使用Camera 2 API将从Android ImageReader YUV 420 888重复数据,android,opencv,Android,Opencv,我正在尝试使用Camera 2 API将从ImageReader接收到的图像转换为OpenCV矩阵,并使用CameraBridgeViewBase在屏幕上显示它,更具体地说是函数deliverAndDrawFrame。读取器的ImageFormat是YUV_420_888,据我所知,它有一个Y平面,每个像素有灰度值,和一个U平面,每个U/V都有,每4个像素有1个。但是,当我尝试显示此图像时,它似乎是重复的图像,并且旋转了90度。下面的代码应该将YUV数据放入OpenCV矩阵中(目前只是灰度,而不
ImageReader
接收到的图像转换为OpenCV矩阵,并使用CameraBridgeViewBase
在屏幕上显示它,更具体地说是函数deliverAndDrawFrame
。读取器的ImageFormat
是YUV_420_888
,据我所知,它有一个Y平面,每个像素有灰度值,和一个U平面,每个U/V都有,每4个像素有1个。但是,当我尝试显示此图像时,它似乎是重复的图像,并且旋转了90度。下面的代码应该将YUV数据放入OpenCV矩阵中(目前只是灰度,而不是rgba):
接收框架图纸的代码:
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
final Image yuvImage = reader.acquireLatestImage();
yuv420888imageToRgbaMat(yuvImage, mRgbaMat);
deliverAndDrawFrame(mFrame);
yuvImage.close();
}
};
这是制作图像阅读器的代码:
mRgbaMat = new Mat(mFrameHeight, mFrameWidth, CvType.CV_8UC4);
mFrame = new CameraFrame(mRgbaMat);
mImageReader = ImageReader.newInstance(mFrameWidth, mFrameHeight, ImageFormat.YUV_420_888, 1);
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
AllocateCache();
这是阵列的初始化:
protected static byte[] mRawRGBAFrameData = new byte[640*480*4], mYdata = new byte[640*480], mUandVData = new byte[640*480 / 2];
注:mFrameWidth
为480,mFrameHeight
为640。一件奇怪的事情是,ImageReader
的高度和宽度以及从中接收到的图像
的尺寸是反向的
以下是带有上述代码的图像:
这是在yuv420888imageToRgbaMat
for(int i=0;i<640*480;i++){
mrawgbaframedata[i]=mYdata[i];
}
我们可以看到数据在Y帧中重复,出于某种原因,这提供了一个真实的好看的图像。对于任何试图将OpenCV与Camera 2 API一起使用的人,我提出了一个解决方案。我发现的第一件事是,ImageReader
提供的ByteBuffer
中有填充,因此如果不考虑,这可能会导致输出失真。我选择做的另一件事是创建我自己的SurfaceView
,并使用Bitmap
而不是使用CameraViewBase
来绘制它,到目前为止,它的效果非常好。OpenCV有一个函数Util.matToBitmap
,它接受BGR矩阵并将其转换为android位图
,因此非常有用。通过将ImageReader
提供的前两个Image.Plane
s中的信息放入格式为YUV 420的OpenCV单通道矩阵,并使用Imgproc.cvtColor
和Imgproc.COLOR\u YUV420p2BGR
获得BGR矩阵。需要知道的重要一点是,图像的Y平面具有完整像素,但第二个UV平面具有交错像素,这些像素映射一到四个Y像素,因此UV平面的总长度是Y平面的一半。看见无论如何,这里有一些代码:
矩阵的初始化
m_BGRMat = new Mat(Constants.VISION_IMAGE_HEIGHT, Constants.VISION_IMAGE_WIDTH, CvType.CV_8UC3);
m_Yuv420FrameMat = new Mat(Constants.VISION_IMAGE_HEIGHT * 3 / 2, Constants.VISION_IMAGE_WIDTH, CvType.CV_8UC1);
每帧:
// Convert image to YUV 420 matrix
ImageUtils.imageToMat(image, m_Yuv420FrameMat, m_RawFrameData, m_RawFrameRowData);
// Convert YUV matrix to BGR matrix
Imgproc.cvtColor(m_Yuv420FrameMat, m_BGRMat, Imgproc.COLOR_YUV420p2BGR);
// Flip width and height then mirror vertically
Core.transpose(m_BGRMat, m_BGRMat);
Core.flip(m_BGRMat, m_BGRMat, 0);
// Draw to Surface View
m_PreviewView.drawImageMat(m_BGRMat);
以下是到YUV 420矩阵的转换:
/**
* Takes an Android {@link Image} in the {@link ImageFormat#YUV_420_888} format and returns an OpenCV {@link Mat}.
*
* @param image {@link Image} in the {@link ImageFormat#YUV_420_888} format
*/
public static void imageToMat(final Image image, final Mat mat, byte[] data, byte[] rowData) {
ByteBuffer buffer;
int rowStride, pixelStride, width = image.getWidth(), height = image.getHeight(), offset = 0;
Image.Plane[] planes = image.getPlanes();
if (data == null || data.length != width * height) data = new byte[width * height * ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8];
if (rowData == null || rowData.length != planes[0].getRowStride()) rowData = new byte[planes[0].getRowStride()];
for (int i = 0; i < planes.length; i++) {
buffer = planes[i].getBuffer();
rowStride = planes[i].getRowStride();
pixelStride = planes[i].getPixelStride();
int
w = (i == 0) ? width : width / 2,
h = (i == 0) ? height : height / 2;
for (int row = 0; row < h; row++) {
int bytesPerPixel = ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8;
if (pixelStride == bytesPerPixel) {
int length = w * bytesPerPixel;
buffer.get(data, offset, length);
// Advance buffer the remainder of the row stride, unless on the last row.
// Otherwise, this will throw an IllegalArgumentException because the buffer
// doesn't include the last padding.
if (h - row != 1)
buffer.position(buffer.position() + rowStride - length);
offset += length;
} else {
// On the last row only read the width of the image minus the pixel stride
// plus one. Otherwise, this will throw a BufferUnderflowException because the
// buffer doesn't include the last padding.
if (h - row == 1)
buffer.get(rowData, 0, width - pixelStride + 1);
else
buffer.get(rowData, 0, rowStride);
for (int col = 0; col < w; col++)
data[offset++] = rowData[col * pixelStride];
}
}
}
mat.put(0, 0, data);
}
这种方法可行,但我认为最好的方法是设置OpenGL渲染上下文并编写某种简单的着色器来显示矩阵。我认为这行Core.flip(m_bgrat,m_bgrat,0)有问题代码>图像看起来颠倒了,所以我将其更改为Core.flip(m_bgrat,m_bgrat,1)
和COLOR\u yuv420 p2bgr
到COLOR\u yuv420 p2rgb
进行jpeg转换。
m_BGRMat = new Mat(Constants.VISION_IMAGE_HEIGHT, Constants.VISION_IMAGE_WIDTH, CvType.CV_8UC3);
m_Yuv420FrameMat = new Mat(Constants.VISION_IMAGE_HEIGHT * 3 / 2, Constants.VISION_IMAGE_WIDTH, CvType.CV_8UC1);
// Convert image to YUV 420 matrix
ImageUtils.imageToMat(image, m_Yuv420FrameMat, m_RawFrameData, m_RawFrameRowData);
// Convert YUV matrix to BGR matrix
Imgproc.cvtColor(m_Yuv420FrameMat, m_BGRMat, Imgproc.COLOR_YUV420p2BGR);
// Flip width and height then mirror vertically
Core.transpose(m_BGRMat, m_BGRMat);
Core.flip(m_BGRMat, m_BGRMat, 0);
// Draw to Surface View
m_PreviewView.drawImageMat(m_BGRMat);
/**
* Takes an Android {@link Image} in the {@link ImageFormat#YUV_420_888} format and returns an OpenCV {@link Mat}.
*
* @param image {@link Image} in the {@link ImageFormat#YUV_420_888} format
*/
public static void imageToMat(final Image image, final Mat mat, byte[] data, byte[] rowData) {
ByteBuffer buffer;
int rowStride, pixelStride, width = image.getWidth(), height = image.getHeight(), offset = 0;
Image.Plane[] planes = image.getPlanes();
if (data == null || data.length != width * height) data = new byte[width * height * ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8];
if (rowData == null || rowData.length != planes[0].getRowStride()) rowData = new byte[planes[0].getRowStride()];
for (int i = 0; i < planes.length; i++) {
buffer = planes[i].getBuffer();
rowStride = planes[i].getRowStride();
pixelStride = planes[i].getPixelStride();
int
w = (i == 0) ? width : width / 2,
h = (i == 0) ? height : height / 2;
for (int row = 0; row < h; row++) {
int bytesPerPixel = ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8;
if (pixelStride == bytesPerPixel) {
int length = w * bytesPerPixel;
buffer.get(data, offset, length);
// Advance buffer the remainder of the row stride, unless on the last row.
// Otherwise, this will throw an IllegalArgumentException because the buffer
// doesn't include the last padding.
if (h - row != 1)
buffer.position(buffer.position() + rowStride - length);
offset += length;
} else {
// On the last row only read the width of the image minus the pixel stride
// plus one. Otherwise, this will throw a BufferUnderflowException because the
// buffer doesn't include the last padding.
if (h - row == 1)
buffer.get(rowData, 0, width - pixelStride + 1);
else
buffer.get(rowData, 0, rowStride);
for (int col = 0; col < w; col++)
data[offset++] = rowData[col * pixelStride];
}
}
}
mat.put(0, 0, data);
}
/**
* Given an {@link Mat} that represents a BGR image, draw it on the surface canvas.
* use the OpenCV helper function {@link Utils#matToBitmap(Mat, Bitmap)} to create a {@link Bitmap}.
*
* @param bgrMat BGR frame {@link Mat}
*/
public void drawImageMat(final Mat bgrMat) {
if (m_HolderReady) {
// Create bitmap from BGR matrix
Utils.matToBitmap(bgrMat, m_Bitmap);
// Obtain the canvas and draw the bitmap on top of it
final SurfaceHolder holder = getHolder();
final Canvas canvas = holder.lockCanvas();
canvas.drawBitmap(m_Bitmap, null, new Rect(0, 0, m_HolderWidth, m_HolderHeight), null);
holder.unlockCanvasAndPost(canvas);
}
}