Android + OpenCV : Part 2 — CameraView

In March 2020, the OpenCV updated Android pack is version 3.4.9. You can download and install 3.4.9 or the newest version on OpenCV.org by redoing the steps of Part 1. In Part 2, I will insert the camera view, and listener to capture the image frame. Here is the menu:

1. Install OpenCV Manager
2. UI Code
3. Listener: CvCameraViewListener2
4. Android Permissions
5. CameraView: viewfinder
7. OpenCV Manager: BaseLoaderCallBack
8. Handle Camera Frame
9. First View on the Camera
10. Fix the View
11. Test Again

👔 1. Install OpenCV Manager

If you want to use all the functions of OpenCV, you can not ignore OpenCV Manger. But you have to install it from separate app. If you don't want it installed, please check on Part 4. Now, let’s connect your phone and open the terminal in your project. Next, you need to go to
“OpenCV library directory”\apk.

>dir
Volume in drive D is 512 Gigabyte
Volume Serial Number is BE4E-1582


Directory of D:\Android\OpenCV-Android-Lib\OpenCV-android-sdk-349\apk


04/05/2020 12:40 AM <DIR> .
04/05/2020 12:40 AM <DIR> ..
12/19/2019 10:38 AM 7,956,744 OpenCV_3.4.9_Manager_3.49_arm64-v8a.apk
12/19/2019 10:31 AM 11,953,673 OpenCV_3.4.9_Manager_3.49_armeabi-v7a.apk
12/19/2019 10:34 AM 11,883,427 OpenCV_3.4.9_Manager_3.49_armeabi.apk
12/19/2019 10:55 AM 15,910,690 OpenCV_3.4.9_Manager_3.49_mips.apk
12/19/2019 10:51 AM 8,518,751 OpenCV_3.4.9_Manager_3.49_mips64.apk
12/19/2019 10:47 AM 22,178,553 OpenCV_3.4.9_Manager_3.49_x86.apk
12/19/2019 10:43 AM 21,849,970 OpenCV_3.4.9_Manager_3.49_x86_64.apk
12/06/2019 06:53 PM 978 readme.txt
8 File(s) 100,252,786 bytes
2 Dir(s) 425,404,383,232 bytes free


D:\Android\OpenCV-Android-Lib\OpenCV-android-sdk-349\apk>

Next, you need to check CPU type of your phone. Here is mine: arm64-v8a.

>adb shell getprop ro.product.cpu.abi
arm64-v8a

Let’s find connected devices.

>adb devices
List of devices attached
ZY326LVCZQ device

Please install the right type of manager APK.

>adb -s ZY326LVCZQ install OpenCV_3.4.9_Manager_3.49_arm64-v8a.apk
Performing Streamed Install
Success

Check your phone for installation:

😙2. UI Code

At activity_main.xml, please add JavaCamera2View.

<androidx.constraintlayout.widget.ConstraintLayout 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.JavaCamera2View
android:id="@+id/cameraView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:show_fps="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
... />

</androidx.constraintlayout.widget.ConstraintLayout>
  • id: cameraView
  • show_fps: true

👂 3. Listener: CvCameraViewListener2

At MainActivity.kt, let’s add the new listener.

class MainActivity : AppCompatActivity(), CameraBridgeViewBase.CvCameraViewListener2 {

Now, Alt+Enter to implement the new member functions.

I select them all.

override fun onCameraViewStarted(width: Int, height: Int) {
TODO("Not yet implemented")
}

override fun onCameraViewStopped() {
TODO("Not yet implemented")
}

override fun onCameraFrame(inputFrame: CameraBridgeViewBase.CvCameraViewFrame?): Mat {
TODO("Not yet implemented")
}

Very clear, OpenCV runs as: start->frame->stop. You just insert the code one by one. Unlike Part 1, I move the checkOpenCV()

private fun checkOpenCV(context: Context) {

if (OpenCVLoader.initDebug()) {
shortMsg(context, OPENCV_SUCCESSFUL)
lgi("OpenCV started...")
} else {
shortMsg(context, OPENCV_FAIL)
lge(OPENCV_FAIL)
}
}

out of companion object.

companion object {

val TAG = "MYLOG " + MainActivity::class.java.simpleName
fun lgd(s: String) = Log.d(TAG, s)
fun lge(s: String) = Log.e(TAG, s)
fun lgi(s: String) = Log.i(TAG, s)

fun shortMsg(context: Context, s: String) =
Toast.makeText(context, s, Toast.LENGTH_SHORT).show()

// messages:
private const val OPENCV_SUCCESSFUL = "OpenCV Loaded Successfully!"
private const val OPENCV_FAIL = "Could not load OpenCV!!!"
private const val OPENCV_PROBLEM = "There's a problem in OpenCV."
private const val PERMISSION_NOT_GRANTED = "Permissions not granted by the user."

}

🔑4. Android Permissions

You need permission to use the camera. At AndroidManisfest.xml,

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.homan.huang.opencvcamerademo">

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Back to MainActivity.kt,

// Permission vars:
private const val REQUEST_CODE_PERMISSIONS = 111
private val REQUIRED_PERMISSIONS = arrayOf(
CAMERA,
WRITE_EXTERNAL_STORAGE,
READ_EXTERNAL_STORAGE,
RECORD_AUDIO,
ACCESS_FINE_LOCATION
)

class MainActivity : AppCompatActivity(), CameraBridgeViewBase.CvCameraViewListener2 {

...

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.clearFlags(FLAG_FORCE_NOT_FULLSCREEN)
window.setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN)
window.addFlags(FLAG_KEEP_SCREEN_ON)
setContentView(R.layout.activity_main)

// Request camera permissions
if (allPermissionsGranted()) {
checkOpenCV(this)
} else {
ActivityCompat.requestPermissions(
this,
REQUIRED_PERMISSIONS,
REQUEST_CODE_PERMISSIONS )
}
...
}

...
/**
* Process result from permission request dialog box, has the request
* been granted? If yes, start Camera. Otherwise display a toast
*/
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults: IntArray
) {
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
checkOpenCV(this)
} else {
shortMsg(this, PERMISSION_NOT_GRANTED)
finish()
}
}
}

/**
* Check if all permission specified in the manifest have been granted
*/
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
}

...
}

👀 5. CameraView: viewfinder

Let’s insert CameraView in MainActivity.kt.

// view
private val viewFinder by lazy { findViewById<JavaCameraView>(R.id.cameraView) }

Setup the camera view:

override fun onCreate(savedInstanceState: Bundle?) {
...

viewFinder.visibility = SurfaceView.VISIBLE
viewFinder.setCameraIndex(
CameraCharacteristics.LENS_FACING_FRONT)
viewFinder.setCvCameraViewListener(this)

You need to disable the view in onPause() and onDestroy().

override fun onPause() {
super.onPause()
viewFinder?.let { viewFinder.disableView() }
}

override fun onDestroy() {
super.onDestroy()
viewFinder?.let { viewFinder.disableView() }
}

👔7. OpenCV Manager: BaseLoaderCallBack

Next, we need a callback from OpenCV Manager to handle the OpenCV library. The BaseLoaderCallback helps us to find the library after OpenCVLoader fails to load the library. Please check this image.

Let’s insert the callback.

// openCV callback
lateinit var cvBaseLoaderCallback: BaseLoaderCallback

override fun onCreate(savedInstanceState: Bundle?) {
...

cvBaseLoaderCallback = object : BaseLoaderCallback(this) {
override fun onManagerConnected(status: Int) {

when (status) {
SUCCESS -> {
lgi(OPENCV_SUCCESSFUL)
shortMsg(this@MainActivity, OPENCV_SUCCESSFUL)
viewFinder.enableView()
}

else -> super.onManagerConnected(status)
}
}
}

}

You need to resume the callback at onResume().

override fun onResume() {
super.onResume()
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION, this, cvBaseLoaderCallback)
}

📷 8. Handle Camera Frame

The OpenCV stores the image in Mat type.

// image storage
lateinit var imageMat: Mat

Let’s insert the code in the listener’s member.

override fun onCameraViewStarted(width: Int, height: Int) {
imageMat = Mat(width, height, CvType.CV_8UC4)
}

override fun onCameraViewStopped() {
imageMat.release()
}

override fun onCameraFrame(inputFrame: CameraBridgeViewBase.CvCameraViewFrame?): Mat {
imageMat = inputFrame!!.rgba()
return imageMat
}

😎9. First View on the Camera

Let’s run.

Not so good, everything is looking sideways. How can we fix it?

🔨10. Fix the View

If you rotate the image in frame function, the app will run very slow. Thanks to Mike Heavers, we can fix the view in the module. For many years, I still don’t understand why OpenCV SDK doesn’t fix the problem. Please check his article and give him a clap. The fix is: openCV-portait-camera-android.java

You need to copy the code and replace the deliverAndDrawFrame() in CameraBridgeViewBase.

😍11. Test Again

Let’s run again.

This is the correct CameraView: an ugly toy robot. The capture rate is 17 fps.

Computer Science BS from SFSU. I studied and worked on Android system since 2017. If you are interesting in my past works, please go to my LinkedIn.