4

I am trying to capture images from camera preview and do some drawing on it. The problem is, I have only about 3-4 fps of drawing, and half of the frame processing time is receiving and decoding NV21 image from camera preview and converting to bitmap. I have a code to do this task, which I found on another stack question. It does not seem to be fast, but I do not know how to optimize it. It takes about 100-150 ms on Samsung Note 3, image size 1920x1080. How can I make it work faster?

Code :

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;
    }
}

The final format of image has to be bitmap, so I could then do processing on it. I've tried to set Camera.Parameters.setPreviewFormat to RGB_565, but could not assign camera params to camera, I've read also that NV21 is the only available format. I am not sure about that, whether it is possible to find solution in these format changes.

Thank you in advance.

Dainius Šaltenis
  • 1,644
  • 16
  • 29
  • The fastest way is by using RenderScript intrinsic, but the question is why you need so many bitmaps per second? Drawing these bitmaps or storing them will become the next bottleneck on your flow, at 10 FPS or less – Alex Cohn Mar 06 '16 at 12:54
  • Thank you. My task is to draw rectangles around texts fields on camera preview, so while the camera moves, I want to keep those boundaries around texts fields moving as well and to find and draw rectangles around newly found text fields (so tracking will not work here). It's first time I do something like this, like drawing on camera preview real time, so maybe you do have any suggestions or observations, maybe I do something really wrong here? – Dainius Šaltenis Mar 06 '16 at 13:07
  • 1
    Consider rendering your rectangles via OpenGL; you cannot display the SurfaceTexture to render the camera preview because your rectangles will be displayed with some delay (the recognition and tracking algorithms). Better use a shader to render the YUV preview frames manually, perfectly in sync with your rectangles. Maybe your recognition and tracking algorithms may also be tuned to rely on YUV data instead of RGB? – Alex Cohn Mar 06 '16 at 14:09
  • "you cannot display the SurfaceTexture to render the camera preview" - I am not. I am just receiving image from this function in onPreviewFrame function, doing processing and drawing lines on canvas (finding their coordinates by processing) on other SurfaceView, like this `canvas.drawLine(...` (if I understood your statement corectly). Actualy, the idea to tune algorythms to support YUV images is quite good, i think, but I am still interested in optimising this convertion, because this looks terrible. Thank you and sorry for taking too long to reply, I was busy. – Dainius Šaltenis Mar 06 '16 at 20:04
  • 1
    Check this [example](https://android.googlesource.com/platform/frameworks/rs/+/master/java/tests/LivePreview) on GoogleSource: there is some image processing and YUV to RGB conversion to display it, using RenderScript intrinsics. – Alex Cohn Mar 06 '16 at 20:14
  • Thank you, Alex Cohn, RenderScript instrinsics really helped me, I rewrote my code and posted it in the answer. – Dainius Šaltenis Mar 07 '16 at 19:31

3 Answers3

7

Thank you, Alex Cohn, for helping me to do make this conversion faster. I implemented your suggested methods (RenderScript intrinsics). This code, made with RenderScript intrinsics, converts YUV image to bitmap about ~5 times faster. Previous code took 100-150 ms. on Samsung Note 3, this takes 15-30 or so. If someone needs to do the same task, here is the code:

These will be used:

private RenderScript rs;
private ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic;
private Type.Builder yuvType, rgbaType;
private Allocation in, out;

In on create function I initialize..:

rs = RenderScript.create(this);
yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));

And the whole onPreviewFrame looks like this (here I receive and convert the image):

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);
Dainius Šaltenis
  • 1,644
  • 16
  • 29
  • 1
    Thanks, from basic method ( stackoverflow.com/questions/9192982/displaying-yuv-image-in-android#9330203 ) ~1seconds to process 1080p image to ~0.08 seconds with your method :) – e-info128 Jan 03 '17 at 01:00
  • there are lines that should be called only once, also checking for null in preview looks really ugly https://stackoverflow.com/a/43551798/4548520 answer is much better – user25 Aug 14 '18 at 22:38
6

You can get even more speed (using JellyBean 4.3, API18 or higher): Camera preview mode must be NV21 !

On "onPreviewFrame()" do only:

aIn.copyFrom(data);

yuvToRgbIntrinsic.forEach(aOut);

aOut.copyTo(bmpout);  // and of course, show the bitmap or whatever

Do not create any objects here.

All other stuff (creating rs, yuvToRgbIntrinsic, allocations, bitmap) do in the onCreate() method before starting the camera preview.

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);

On Nexus 7 (2013, JellyBean 4.3) a full HD (1920x1080) camera preview conversion takes about 0.007 s (YES, 7 ms).

Matti81
  • 216
  • 3
  • 3
  • AMRAY also said that lubyuv is the fastest, but I tried and your answer is still the fastest - https://stackoverflow.com/questions/35826709/yuv-nv21-image-converting-to-bitmap#comment90686973_43729112 – user25 Aug 15 '18 at 23:14
1

OpenCV-JNI to construct Mat from Nv21 data for 4160x3120 sized image seems 2x faster (38msec) compared to renderscript (68msec-excluding initialization time). If we need to resize down constructed bitmap, OpenCV-JNI seems better approach as we would be using full size only for Y data. CbCr data would be resized to downsized Your data at the time of OpenCV mat construction only.

A still better way is passing NV21 byte array, and int pixel array to Jni. Array copy may not be needed at Jni side. Then, use open source libyuv library (https://chromium.googlesource.com/libyuv/libyuv/) to convert NV21 to ARGB. In Java, we use passed pixel array to construct bitmap. In Jni, conversion from NV21 to ARGB takes only ~4ms for 4160x3120 sized byte array on arm64 platform.

AMRAY
  • 21
  • 3
  • byte[] NV21 to OpenCV Mat and then Mat to Bitmap is NOT faster than https://stackoverflow.com/a/43551798/4548520 so RenderScript is faster a little bit – user25 Aug 14 '18 at 22:40
  • p.s. author's answer is slower than Matti81's https://stackoverflow.com/a/43551798/4548520 – user25 Aug 14 '18 at 22:42
  • mb you didn't try Matti81's answer – user25 Aug 14 '18 at 22:42
  • you also wrong about lubyuv, it's slower 1-3 ms than renderscript https://s8.postimg.cc/uvl85emx1/on_Preview.png https://s8.postimg.cc/5csvsdg7p/lubyuv_jni.png `method renderScript 14 method lubyuv 14 method renderScript 12 method lubyuv 12 method renderScript 12 method lubyuv 15 method renderScript 12 method lubyuv 13 method renderScript 11 method lubyuv 14 method renderScript 12 method lubyuv 12` – user25 Aug 15 '18 at 23:03
  • also mb you forgot convert ARGB byte[] to Bitmap and thought that it is the fastest one? we use Matti81's answer to convert NV21 byte array to Bitmap and not just to ARGB byte array – user25 Aug 15 '18 at 23:09
  • also I checked bitmap and it's just green background – user25 Aug 15 '18 at 23:20