Yuv (NV21) преобразование изображения в растровое изображение
Я пытаюсь захватить изображения из предварительного просмотра камеры и сделать некоторые рисунки на нем. Проблема в том, что у меня всего около 3-4 кадров в секунду при рисовании, и половина времени обработки кадров - это получение и декодирование изображения NV21 из предварительного просмотра камеры и преобразование в растровое изображение. У меня есть код для выполнения этой задачи, который я нашел в другом вопросе стека. Это не кажется быстрым, но я не знаю, как его оптимизировать. На Samsung Note 3 это занимает около 100-150 мс, размер изображения 1920x1080. Как я могу заставить его работать быстрее?
Код:
public Bitmap curFrameImage(byte[] data, Camera camera)
{
Camera.Parameters parameters = camera.getParameters();
int imageFormat = parameters.getPreviewFormat();
if (imageFormat == ImageFormat.NV21)
{
YuvImage img = new YuvImage(data, ImageFormat.NV21, prevSizeW, prevSizeH, null);
ByteArrayOutputStream out = new ByteArrayOutputStream();
img.compressToJpeg(new android.graphics.Rect(0, 0, img.getWidth(), img.getHeight()), 50, out);
byte[] imageBytes = out.toByteArray();
return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
}
else
{
Log.i(TAG, "Preview image not NV21");
return null;
}
}
Окончательный формат изображения должен быть растровым, чтобы я мог затем обработать его. Я попытался установить Camera.Parameters.setPreviewFormat в RGB_565, но не смог назначить параметры камеры камере, я также читал, что NV21 является единственным доступным форматом. Я не уверен в том, можно ли найти решение в этих изменениях формата.
Заранее спасибо.
3 ответа
Спасибо, Алекс Кон, за помощь в ускорении конверсии. Я реализовал предложенные вами методы (встроенные функции RenderScript). Этот код, созданный с использованием встроенных средств RenderScript, преобразует YUV-изображение в растровое изображение примерно в 5 раз быстрее. Предыдущий код занимал 100-150 мс. на Samsung Note 3 это занимает 15-30 или около того. Если кому-то нужно выполнить ту же задачу, вот код:
Они будут использованы:
private RenderScript rs;
private ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic;
private Type.Builder yuvType, rgbaType;
private Allocation in, out;
В функции создания я инициализирую..:
rs = RenderScript.create(this);
yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));
И весь onPreviewFrame выглядит следующим образом (здесь я получаю и конвертирую изображение):
if (yuvType == null)
{
yuvType = new Type.Builder(rs, Element.U8(rs)).setX(dataLength);
in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);
rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(prevSizeW).setY(prevSizeH);
out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);
}
in.copyFrom(data);
yuvToRgbIntrinsic.setInput(in);
yuvToRgbIntrinsic.forEach(out);
Bitmap bmpout = Bitmap.createBitmap(prevSizeW, prevSizeH, Bitmap.Config.ARGB_8888);
out.copyTo(bmpout);
Вы можете получить еще большую скорость (используя JellyBean 4.3, API18 или выше): режим предварительного просмотра камеры должен быть NV21!
На "onPreviewFrame()" делаем только:
aIn.copyFrom(data);
yuvToRgbIntrinsic.forEach(aOut);
aOut.copyTo(bmpout); // and of course, show the bitmap or whatever
Не создавайте здесь никаких объектов.
Все остальные вещи (создание rs, yuvToRgbIntrinsic, выделения, растровое изображение) выполняются в методе onCreate() перед запуском предварительного просмотра камеры.
rs = RenderScript.create(this);
yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));
// you don´t need Type.Builder objects , with cameraPreviewWidth and cameraPreviewHeight do:
int yuvDatalength = cameraPreviewWidth*cameraPreviewHeight*3/2; // this is 12 bit per pixel
aIn = Allocation.createSized(rs, Element.U8(rs), yuvDatalength);
// of course you will need the Bitmap
bmpout = Bitmap.createBitmap(cameraPreviewWidth, cameraPreviewHeight, Bitmap.Config.ARGB_8888);
// create output allocation from bitmap
aOut = Allocation.createFromBitmap(rs,bmpout); // this simple !
// set the script´s in-allocation, this has to be done only once
yuvToRgbIntrinsic.setInput(aIn);
На Nexus 7 (2013, JellyBean 4.3) преобразование предварительного просмотра камеры в формате Full HD (1920x1080) занимает около 0,007 с (ДА, 7 мс).
OpenCV-JNI для построения Mat из данных Nv21 для изображения размером 4160x3120 кажется в 2 раза быстрее (38 мсек) по сравнению с отрисовкой (68 мсек без учета времени инициализации). Если нам нужно изменить размер созданного растрового изображения, OpenCV-JNI кажется более подходящим подходом, поскольку мы будем использовать полный размер только для данных Y. Размер данных CbCr будет уменьшен до размеров Ваших данных только во время создания мата OpenCV.
Еще лучший способ - передать байтовый массив NV21 и пиксельный массив int в Jni. Копия массива может не понадобиться на стороне Jni. Затем используйте библиотеку libyuv с открытым исходным кодом ( https://chromium.googlesource.com/libyuv/libyuv/) для преобразования NV21 в ARGB. В Java мы используем переданный пиксельный массив для создания растрового изображения. В Jni преобразование из NV21 в ARGB занимает всего ~4 мс для байтового массива размером 4160x3120 на платформе arm64.