Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Java Camera2 View for Android #10081

Merged
merged 30 commits into from Feb 17, 2018
Merged

Conversation

floe
Copy link
Contributor

@floe floe commented Nov 13, 2017

Based on #10050, here's a first alpha implementation of the Camera2 adapter class which should allow zero-copy access to preview image data. Comments very welcome.

Known issues at the moment:

  • tutorial2-mixedprocessing works with this class as a drop-in replacement for JavaCameraView, but quits after ~ 20-30 seconds (no crash, just exit due to internal camera queue error, perhaps some race condition?) - fixed, was a memory leak
  • lots of code overlap with JavaCameraView and Camera2Renderer (e.g. JavaCameraFrame internal class), needs cleanup/refactoring
  • cacPreviewSize(...) currently buggy, selects smaller preview size than feasible
force_builders=Android pack

if (image == null) return;
ByteBuffer plane = image.getPlanes()[0].getBuffer();
Mat yuv = new Mat( h * 3/2, w, CvType.CV_8UC1, plane );
JavaCamera2Frame tempFrame = new JavaCamera2Frame(yuv,w,h);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This says that all three planes should be used for YUV_420_888.

BTW, Please add additional sanity checks (assertions) - for actual Image format, ByteBuffer size, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... OTOH, you're correct (there are 3 planes), but obviously, the planes are contiguous in memory and also mapped in one single chunk, otherwise, the cvtColor call would cause a segfault.

To implement this in a way that references all 3 planes separately would AFAICT require a new implementation of cvtColor that can handle 3 separate Mat objects. Otherwise, you'd need to copy the planes together into a single Mat and lose the advantage of the zero-copy native buffer.

Copy link
Contributor

@mshabunin mshabunin Nov 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have hal:: conversion functions accepting two separate pointers for Y and UV planes (example), so you can try to use them. You can add more overloaded functions if needed. But it is hard to say how it will work taking in account that each plane has its own PixelStride parameter which is not supported by current implementations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to https://developer.android.com/reference/android/graphics/ImageFormat.html#YUV_420_888 the Y plane will always have a stride of 1, and the U/V planes will have equal stride. I may simply assert that U/V stride is also 1 for the moment (seems to be true for all implementations I've seen so far).

Copy link
Contributor

@mshabunin mshabunin Nov 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure U and V have PixelStride=1? Probably it is 2. If your current code worked with COLOR_YUV2RGBA_NV12 conversion by providing only Y pointer, then planes should be located in continuous memory block like this:

YYYY
YYYY
UVUV

Then:

Uptr = Yptr + row_stride * rows
Upixel_stride = 2
Vptr = Uptr + 1
Vpixel_stride = 2

Copy link
Contributor Author

@floe floe Nov 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My mistake, you're right. So it's one separate Y plane, directly followed by one interleaved V/U plane.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But in that case, cvtTwoPlaneYUVtoBGR should probably work?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably it should, but we should check these assumptions (via CV_Assert) before calling any processing code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@floe , the problem with cvtTwoPlaneYUVtoBGR is that it does not get wrapped automatically, because it accepts raw C++ pointers. So you will need to write JNI wrapper by hand or wait_for/propose extended cvtColor(InputArray ...) interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any way to check whether the planes are contiguous in memory before passing them to Mat()? Then the current approach would already be sufficient...

}
}

boolean cacPreviewSize(final int width, final int height) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is typo (in original code too): calcPreviewSize

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aaah. This is supposed to mean "calc". 🤦‍♂️ Will fix that.

@alalek
Copy link
Member

alalek commented Nov 13, 2017

Please update files filtering for old Android versions here

@floe
Copy link
Contributor Author

floe commented Dec 23, 2017

I've updated this PR:

  • fixed memory leak
  • fixed some typos
  • fixed incorrect format handling
  • added lots of asserts

The only thing that remains open IMHO is the handling of the RGB conversion here: https://github.com/opencv/opencv/pull/10081/files#diff-980f99834f5223fab7fde83308fc9930R353

AFAICT the cleanest way would be to store Y and U/V plane as two Mat objects in JavaCamera2Frame and add a wrapper to ImgProc.cvtColor that calls through to cvtTwoPlaneYUVtoBGR. Opinions?

@floe
Copy link
Contributor Author

floe commented Dec 28, 2017

Side question: where are the Java wrappers for cvtColor defined?

@alalek
Copy link
Member

alalek commented Dec 28, 2017

Almost all wrapper for Java/Python bindings are generated automatically from public headers of modules (functions with CV_EXPORTS_W).

BTW, cv::Mat doesn't support multiple planes (you need 2 cv::Mat to store Y and UV planes).

@floe
Copy link
Contributor Author

floe commented Jan 1, 2018

Another side question: I added

force_builders=Android pack

to the initial PR comment, but that still doesn't seem to trigger the Android pack build?

@floe
Copy link
Contributor Author

floe commented Jan 15, 2018

bump sorry for the noise, but I do need the Android pack to work for testing...

@floe
Copy link
Contributor Author

floe commented Jan 15, 2018

Nevermind, seems to work now 👍

@floe
Copy link
Contributor Author

floe commented Jan 29, 2018

I think this should now be ready for merging. I still can't get the "Android pack" build to trigger reliably, though. Any comments welcome /cc @alalek @mshabunin @vpisarev

Copy link
Member

@alalek alalek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done! Works great with Android emulator (x86)!

"Interrupted while createCameraPreviewSession", e);
}
finally {
//mCameraOpenCloseLock.release();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is about this line?

What purpose of this semaphore?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was already part of the original JavaCameraView, so I've kept it. However, it's quite possible that this is a kludge from the Camera1 API that's no longer needed for Camera2. I'll see if I can just get rid of it (I'd assume yes ;-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.
Looks like locks are no longer needed (frames are processed in "sync" with callback with Camera2 API).

@floe
Copy link
Contributor Author

floe commented Feb 13, 2018

Removed the old semaphore and ran another test; seems to work just as well. Ready for merging IMHO.

@floe
Copy link
Contributor Author

floe commented Feb 14, 2018

Hm. The Linux OpenCL build failed, looks like a transient build error?

@alalek
Copy link
Member

alalek commented Feb 14, 2018

Build failure is not related to your patch.

I run some tests with JavaCamera2View interface via OpenCV "tutorial2" sample and observing problem with application pause/resume events processing (pressing Android 'Home' button). Does it work for you (pause/restore handling)?

@floe
Copy link
Contributor Author

floe commented Feb 15, 2018

I'll have to test that again. What would be the expected behaviour with the old JavaCameraView?

@alalek
Copy link
Member

alalek commented Feb 15, 2018

expected behaviour

Application activity pause/resume handling should work with app crash/closing

@alalek
Copy link
Member

alalek commented Feb 15, 2018

@floe I pushed several updates to this patch. Please take a look.

Copy link
Member

@alalek alalek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@floe Thank you for the contribution!

@alalek alalek merged commit 6a8f57e into opencv:master Feb 17, 2018
@floe
Copy link
Contributor Author

floe commented Feb 17, 2018

Thanks for your help!

@PhilLab
Copy link
Contributor

PhilLab commented Mar 8, 2018

@floe Awesome, can't wait to try it. Do you know if the current implementation respects the camera orientation? #9269 and #8866

@floe
Copy link
Contributor Author

floe commented Mar 24, 2018

@PhilLab sorry for the late reply, have in fact not tried this yet. But since the code is based on CameraBridgeViewBase too, I suspect it will have the same issues.

@mshabunin mshabunin mentioned this pull request May 10, 2018
@ValkA
Copy link

ValkA commented Jan 4, 2019

I have an Android device that only works with ImageReader of ImageFormat.YV12.
The output is 3 planes as following:

Mat y_mat = new Mat(h, w, CvType.CV_8UC1, image.getPlanes()[0].getBuffer());
Mat u_mat = new Mat(h/2, w/2, CvType.CV_8UC1, image.getPlanes()[1].getBuffer());
Mat v_mat = new Mat(h/2, w/2, CvType.CV_8UC1, image.getPlanes()[2].getBuffer());

Do you have an idea how to convert all these into a full sized RGBA without copying/merging matrices?

@ValkA
Copy link

ValkA commented Jan 4, 2019

Going with @floe assumption that native image buffer is contiguous I'm getting desired results with:

Mat yuv420 = new Mat(h+h/2, w, CvType.CV_8UC1, image.getPlanes()[0].getBuffer());
Imgproc.cvtColor(yuv420, rgba, Imgproc.COLOR_YUV2RGBA_YV12, 4);

@ValkA
Copy link

ValkA commented Jan 4, 2019

Ok inspired by JavaCameraView, this one listener handles both image formats :)
Emulator also.

ImageReader.OnImageAvailableListener listener = new ImageReader.OnImageAvailableListener {
        final Mat rgba;
        int w,h;

        public OnImageAvailableListener(int w, int h){
            this.w = w;
            this.h = h;
            this.rgba = new Mat(h,w,CvType.CV_8UC4);
        }

        @Override
        public void onImageAvailable(ImageReader imageReader) {
            Image image = imageReader.acquireLatestImage();
            if(image == null) return;

            Mat yuv420 = new Mat(h+h/2, w, CvType.CV_8UC1, image.getPlanes()[0].getBuffer());
            switch (imageReader.getImageFormat()){
                case ImageFormat.YV12:
                    Imgproc.cvtColor(yuv420, rgba, Imgproc.COLOR_YUV2RGBA_YV12, 4);
                    break;
                case ImageFormat.YUV_420_888:
                    Imgproc.cvtColor(yuv420, rgba, Imgproc.COLOR_YUV2BGRA_NV12, 4);
                    break;
            }

            //Do your thing here...

            Utils.matToBitmap(rgba, bitmap);
            image.close();
            cameraView.setImageBitmap(bitmap);
        }
    }

@ansman
Copy link

ansman commented Feb 20, 2019

@ValkA Your code only uses the Y plane which would lead to a gray scale picture.

@ValkA
Copy link

ValkA commented Feb 20, 2019

It takes also UV in the extra h/2 rows

@ansman
Copy link

ansman commented Feb 20, 2019

Ah, I see. It does seem to be continuous but that is probably a dangerous assumption that can lead to strange issues down the line.

@ValkA
Copy link

ValkA commented Feb 20, 2019

Yeah, also when lines are padded it has issues

@ansman
Copy link

ansman commented May 7, 2019

@ValkA, @floe, @alalek I've done some experiments and it seems that if you use a different size for your image reader than your surface (or perhaps it's just different from the standard 1920x1080) you don't actually get NV21 because of padding between the rows.

Furthermore it seems that V always comes before U in memory so just taking the second plane as an uv buffer seems incorrect (if anything it should take the third plane and treat it as vu)

@alalek
Copy link
Member

alalek commented May 7, 2019

@ansman Could you please check this patch on your device: #14505 ?

@ansman
Copy link

ansman commented May 9, 2019

@alalek I added a review for the PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants