8

I've spent days (well, nights) trying to work this out. So many examples online are for different versions of Android Studio, different versions of Android, different versions of OpenCV and I can't get any of them to the final 'working' stage.

This example (based on a youtube tutorial, I got to the point where I needed permissions. That's fine, I added that in and a check for them, and it pops up asking the user for camera permissions. But the screen stays blank. I've put in logcat debug, all the right methods seem to be getting called. Would appreciate any assistance.

Code:

Manifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.mytestopencvapp" >

    <uses-permission android:name="android.permission.CAMERA"/>


    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >
        <activity android:name=".MainActivity" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

MainActivity.java

package com.example.mytestopencvapp;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceView;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.JavaCamera2View;
import org.opencv.android.JavaCameraView;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.imgproc.Imgproc;

public class MainActivity extends AppCompatActivity implements CameraBridgeViewBase.CvCameraViewListener2 {

    private static String TAG = "MainActivity";

    JavaCameraView javaCameraView;
    Mat mRGBA, mRGBAT;

    private final int PERMISSIONS_READ_CAMERA=1;

    BaseLoaderCallback baseLoaderCallback = new BaseLoaderCallback(MainActivity.this) {
        @Override
        public void onManagerConnected(int status) {
            Log.d(TAG, "callbacksuccess");
            switch (status)
            {
                case BaseLoaderCallback.SUCCESS:
                {
                    Log.d(TAG, "case success");
                    javaCameraView.enableView();
                    break;
                }
                default:
                {
                    Log.d(TAG, "case default");
                    super.onManagerConnected(status);
                    break;
                }

            }

        }
    };

    static
    {
        if (OpenCVLoader.initDebug())
        {
            Log.d(TAG, "OpenCV is intialised");
        }
        else
        {
            Log.d(TAG, "OpenCV is not initialised");
        }
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate");
        setContentView(R.layout.activity_main);
        javaCameraView = (JavaCameraView)findViewById(R.id.my_camera_view);
        javaCameraView.setVisibility(SurfaceView.VISIBLE);
        javaCameraView.setCvCameraViewListener(MainActivity.this);

// Here, thisActivity is the current activity
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {

            // Permission is not granted
            // Should we show an explanation?
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.CAMERA)) {
                // Show an explanation to the user *asynchronously* -- don't block
                // this thread waiting for the user's response! After the user
                // sees the explanation, try again to request the permission.
            } else {
                // No explanation needed, we can request the permission.
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.CAMERA},
                PERMISSIONS_READ_CAMERA);

                // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
                // app-defined int constant. The callback method gets the
                // result of the request.
            }
        } else {
            Log.d(TAG, "PERMISSIOns granted");
            // Permission has already been granted
        }


    }

    @Override
    public void onCameraViewStarted(int width, int height) {
        Log.d(TAG, "onCameraViewStarted");
        mRGBA = new Mat(height, width, CvType.CV_8UC4);
    }

    @Override
    public void onCameraViewStopped() {
        Log.d(TAG, "onCameraViewStopped");
        mRGBA.release();
    }

    @Override
    public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame)
    {
        Log.d(TAG, "onCameraFrame");
/*        mRGBA = inputFrame.rgba();
        mRGBAT = mRGBA.t();
        Core.flip(mRGBA.t(), mRGBAT, 1);
        Imgproc.resize(mRGBAT, mRGBAT, mRGBA.size());
        return mRGBAT;*/

        mRGBA = inputFrame.rgba();
        Core.transpose(mRGBA, mRGBAT);
        Imgproc.resize(mRGBAT, mRGBAT, mRGBAT.size(),0,0,0);
        Core.flip(mRGBA.t(), mRGBA, 1);
        return mRGBA;
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
        if (javaCameraView != null)
        {
            javaCameraView.disableView();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause");
        if (javaCameraView != null)
        {
            javaCameraView.disableView();
        }

    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume");
        if (OpenCVLoader.initDebug())
        {
            Log.d(TAG, "OpenCV is intialised again");
            baseLoaderCallback.onManagerConnected((BaseLoaderCallback.SUCCESS));
        }
        else
        {
            Log.d(TAG, "OpenCV is not working");
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION, this, baseLoaderCallback);
        }
    }
}

And my res layout activity_main.xml file:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <org.opencv.android.JavaCameraView
        android:id="@+id/my_camera_view"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        />


</RelativeLayout>

As far as I can tell, I've linked them all correctly, I can confirm OpenCV initialises, permissions are checked and granted, but then...the JavaCameraView is just black.

Mark Mayo
  • 12,230
  • 12
  • 54
  • 85

3 Answers3

12

You need to tell the CameraView that the camera permission was granted. You can do so by calling the setCameraPermissionGranted() function. This function call should go into the 'permission granted' block in your onCreate method as shown below:

if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
        != PackageManager.PERMISSION_GRANTED) {
    // More code here ...
} else {
    Log.d(TAG, "Permissions granted");
    javaCameraView.setCameraPermissionGranted();
}

In addition you propably want call this function in onRequestPermissionsResult() for the case where the permission is not granted already. The onRequestPermissionsResult() function is located in your Activity class. It is called when the user granted or denied on of the permission requests the app made. This could look as follows:

@Override
public void onRequestPermissionsResult(
        int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

    // Ensure that this result is for the camera permission request
    if (requestCode == PERMISSIONS_READ_CAMERA) {
        // Check if the request was granted or denied
        if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // The request was granted -> tell the camera view
            javaCameraView.setCameraPermissionGranted();
        } else {
            // The request was denied -> tell the user and exit the application
            Toast.makeText(this, "Camera permission required.",
                    Toast.LENGTH_LONG).show();
            this.finish();
        }
    } else {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

For more information about the permission system on Android have a look at the following resources:

  • Request App Permissions This site describes the process of requesting permissions with all necessary steps.
  • Permissions Overview This site has information about uses-feature in the manifest file. You probably want to add the following line to your manifest file: <uses-feature android:name="android.hardware.camera" android:required="true" /> to prevent the installation on devices without camera.

Now you'll see, that the onCameraFrame callback function is actually invoked. This will lead to a NullPointerException because mRGBAT is not initialized. To just see the camera image you can return inputFrame.rgba() directly in this function. This will at least show the camera image. All further steps are normal image processing to rotate / mirror the image.

Jannik
  • 1,583
  • 1
  • 14
  • 22
  • I think I got the gist of it, but unsure exactly when to call onRequestPermissionsResult. Presumably from inside the if statement where granted is not true, but not entirely sure what to pass from my original code. For the request code, sure, but the permissions and grant Results? – Mark Mayo Apr 26 '20 at 05:29
  • What I did was put the setCameraPermissionGranted() in the else code as above, and then as you said, it did a nullpointedexception, so sorted the onCameraFrame to use the inputframe. Boom, we have a working camera! You're an absolute champ. But if you could clear up how and where to use onRequestPermissionsResult above, so I can tidy that up in my code, I'd be really grateful (already am though!) and will accept the answer. – Mark Mayo Apr 26 '20 at 05:35
  • 1
    `onRequestPermissionsResult()` is located in your Activity class. It is called by the system whenever the user grants or denies a permission request you made. Basically there are two code paths in your app that end up with a granted camera permission. Either the permission was granted already, then the `setCameraPermissionGranted()` function is called directly in `onCreate`. For the case where the permission is not available at the app start time, you request it from the user. In this case the result is not immediately available and `onRequestPermissionResult` is called. – Jannik Apr 26 '20 at 08:34
  • Gah, sweet Jebus I have been looking for an entire day for this simple solution that none of the samples in OpenCV's own code gives. Thanks! – Merkidemis Jul 25 '22 at 20:26
1

I had the same problem in android 10. After a lot of trial and errors, I found that the following method must be implemented in the main activity:

@Override
protected List<? extends CameraBridgeViewBase> getCameraViewList() {
        return Collections.singletonList(mOpenCvCameraView);
    }
hosseinkhosravi
  • 406
  • 6
  • 5
1

i had this problam only when i was in release mode apk the solution was to add permission for Manifest.permission.READ_MEDIA_VIDEO in the Activity. hope it help someone after i was 2 days on it

roy.d
  • 1,030
  • 2
  • 16
  • 33