我正在编写一个带摄像头的应用程序,将其转换为rgb,以便进行一些处理。
它适用于使用NV21 Yuv格式的旧相机实现。 我遇到的问题是使用新的Yuv格式YUV_420_888。在新的Camera2 Api中,图像不再正确转换为RGB,后者发送YUV_420_888 yuv格式而不是NV21(YUV_420_SP)格式。
有人可以告诉我应该如何将YUV_420_888转换为RGB?
由于
答案 0 :(得分:8)
在我的方法中,我使用OpenCV Mat和脚本 https://gist.github.com/camdenfullmer/dfd83dfb0973663a7974
首先,使用上面链接中的代码将YUV_420_888图像转换为Mat。
* mImage是我在ImageReader.OnImageAvailableListener中获得的Image对象
Mat mYuvMat = imageToMat(mImage);
public static Mat imageToMat(Image image) {
ByteBuffer buffer;
int rowStride;
int pixelStride;
int width = image.getWidth();
int height = image.getHeight();
int offset = 0;
Image.Plane[] planes = image.getPlanes();
byte[] data = new byte[image.getWidth() * image.getHeight() * ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8];
byte[] 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;
int 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);
if (h - row != 1) {
buffer.position(buffer.position() + rowStride - length);
}
offset += length;
} else {
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 mat = new Mat(height + height / 2, width, CvType.CV_8UC1);
mat.put(0, 0, data);
return mat;
}
我们有1通道YUV垫。为BGR定义新的Mat(不是RGB)图像:
Mat bgrMat = new Mat(mImage.getHeight(), mImage.getWidth(),CvType.CV_8UC4);
我刚刚开始学习OpenCV,所以这并不是必须是4通道Mat,而是3通道,但它对我有用。 现在我使用转换颜色方法将我的yuv Mat更改为bgr Mat。
Imgproc.cvtColor(mYuvMat, bgrMat, Imgproc.COLOR_YUV2BGR_I420);
现在我们可以进行所有图像处理,例如查找轮廓,颜色,圆圈等。要在屏幕上打印图像,我们需要将其转换为位图:
Mat rgbaMatOut = new Mat();
Imgproc.cvtColor(bgrMat, rgbaMatOut, Imgproc.COLOR_BGR2RGBA, 0);
final Bitmap bitmap = Bitmap.createBitmap(bgrMat.cols(), bgrMat.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(rgbaMatOut, bitmap);
我将所有图像处理都放在单独的线程中,以便设置我的ImageView我需要在UI线程上执行此操作。
runOnUiThread(new Runnable() {
@Override
public void run() {
if(bitmap != null) {
mImageView.setImageBitmap(bitmap);
}
}
});
答案 1 :(得分:1)
答案 2 :(得分:1)
Camera2 YUV_420_888到Java中的RGB Mat(opencv)
@Override
public void onImageAvailable(ImageReader reader){
Image image = null;
try {
image = reader.acquireLatestImage();
if (image != null) {
byte[] nv21;
ByteBuffer yBuffer = mImage.getPlanes()[0].getBuffer();
ByteBuffer uBuffer = mImage.getPlanes()[1].getBuffer();
ByteBuffer vBuffer = mImage.getPlanes()[2].getBuffer();
int ySize = yBuffer.remaining();
int uSize = uBuffer.remaining();
int vSize = vBuffer.remaining();
nv21 = new byte[ySize + uSize + vSize];
//U and V are swapped
yBuffer.get(nv21, 0, ySize);
vBuffer.get(nv21, ySize, vSize);
uBuffer.get(nv21, ySize + vSize, uSize);
Mat mRGB = getYUV2Mat(nv21);
}
} catch (Exception e) {
Log.w(TAG, e.getMessage());
}finally{
image.close();// don't forget to close
}
}
public Mat getYUV2Mat(byte[] data) {
Mat mYuv = new Mat(image.getHeight() + image.getHeight() / 2, image.getWidth(), CV_8UC1);
mYuv.put(0, 0, data);
Mat mRGB = new Mat();
cvtColor(mYuv, mRGB, Imgproc.COLOR_YUV2RGB_NV21, 3);
return mRGB;
}
答案 3 :(得分:0)
比上面提到的&#34; imageToMat&#34;快了大约10倍 - 上面的代码就是这段代码:
Image image = reader.acquireLatestImage();
...
Mat yuv = new Mat(image.getHeight() + image.getHeight() / 2, image.getWidth(), CvType.CV_8UC1);
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
final byte[] data = new byte[buffer.limit()];
buffer.get(data);
yuv.put(0, 0, data);
...
image.close();
答案 4 :(得分:0)
所以我遇到了完全相同的问题,我有一个代码从OnPreviewFrame()获取旧的YUV_420_SP格式byte []数据并将其转换为RGB。
他们关键的是“老”&#39; byte []中的数据为YYYYYY ... CrCbCrCbCrCb,以及&#39; new&#39;来自Camera2 API的数据被分成3个平面:0 = Y,1 = Cb,2 = Cr。,从那里你可以获得每个字节[] s。因此,您需要做的就是将新数据重新排序为与“旧”数据相匹配的单个数组。格式,您可以传递给现有的toRGB()函数:
Image.Plane[] planes = image.getPlanes(); // in YUV220_888 format
int acc = 0, i;
ByteBuffer[] buff = new ByteBuffer[planes.length];
for (i = 0; i < planes.length; i++) {
buff[i] = planes[i].getBuffer();
acc += buff[i].capacity();
}
byte[] data = new byte[acc],
tmpCb = new byte[buff[1].capacity()] , tmpCr = new byte[buff[2].capacity()];
buff[0].get(data, 0, buff[0].capacity()); // Y
acc = buff[0].capacity();
buff[2].get(tmpCr, 0, buff[2].capacity()); // Cr
buff[1].get(tmpCb, 0, buff[1].capacity()); // Cb
for (i=0; i<tmpCb.length; i++) {
data[acc] = tmpCr[i];
data[acc + 1] = tmpCb[i];
acc++;
}
..现在data []的格式与旧的YUV_420_SP一样。
(希望它可以帮助某人,尽管多年过去了。)
答案 5 :(得分:0)
使用Shyam Kumar的答案不适合我的手机,但DanielWięcek的答案正确。我对其进行调试,找到planes [i] .getRowStride()为1216,planes [i] .getPixelStride()为2。图像宽度和高度均为1200。
因为我的声誉是3,所以我无法发表评论,只能发表答案。
答案 6 :(得分:0)
由于您尚未将此问题标记为java
或ndk
。我的回复实现是从ndk
或c++
的角度来看的:
我知道您想将AIMAGE_FORMAT_YUV_420_888
转换为RGB
b位。相反,如果在调用AIMAGE_FORMAT_RGB_888
时要求AIMAGE_FORMAT,则可以直接获得AImageReader_new()
格式。
/*
* Callback when image is available for processing
*/
static void imageCallback(void* context, AImageReader* reader)
{
AImage *image = nullptr;
auto status = AImageReader_acquireNextImage(reader, &image);
// Check status here ...
// Try to process data without blocking the callback
std::thread processor([=](){
uint8_t *data = nullptr;
int len = 0;
AImage_getPlaneData(image, 0, &data, &len);
// Process data here
// ...
AImage_delete(image);
});
processor.detach();
}
/*
* Create RGB reader
*/
AImageReader* createRGBReader()
{
AImageReader* reader = nullptr;
media_status_t status = AImageReader_new(640, 480, AIMAGE_FORMAT_RGB_888,
4, &reader);
//if (status != AMEDIA_OK)
// Handle errors here
AImageReader_ImageListener listener{
.context = nullptr,
.onImageAvailable = imageCallback,
};
AImageReader_setImageListener(reader, &listener);
return reader;
}
/*
* Create the surface for this reader
*/
ANativeWindow* createSurface(AImageReader* reader)
{
ANativeWindow *nativeWindow;
AImageReader_getWindow(reader, &nativeWindow);
return nativeWindow;
}
...
createSession()
{
...
createSurface(createRGBReader());
...
}