[Huawei Developers] Freemind R:
More articles like this, you can visit HUAWEI Developer Forum.

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/025/507/912/5190090000025507912.20200707073946.45247160134259259086178212944890:50510717024609:2800:9AB5343950A9EF5F00134ABDC04BDCF4ED7B3AEC537A8040B95465471442576B.jpg"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/025/507/912/5190090000025507912.20200707073946.45247160134259259086178212944890:50510717024609:2800:9AB5343950A9EF5F00134ABDC04BDCF4ED7B3AEC537A8040B95465471442576B.jpg[/IMG]​

In this article I will tell you about Object Detection with HMS ML Kit first and then we are going to build an Android application which uses HMS ML Kit to detect and track objects in a camera stream. If you haven’t read my last article on detecting objects statically yet, here it is. You can also find introductive information about Artificial Intelligence, Machine Learning and Huawei ML Kit’s capabilities in that article.

The object detection and tracking service can detect and track multiple objects in an image. The detected objects can be located and classified in real time. It is also an ideal choice for filtering out unwanted objects in an image. By the way, Huawei ML Kit provides on device object detection capabilities, hence it is completely free.

Let’s don’t waste our precious time and start doing our sample project step by step!

1. If you haven’t registered as a Huawei Developer yet. Here is the link.

2. Create a Project on AppGalleryConnect. You can follow the steps shown here.

3. In HUAWEI Developer AppGallery Connect, go to Develop > Manage APIs. Make sure ML Kit is activated.

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/025/507/912/5190090000025507912.20200707074604.85262402576614485462670225975480:50510717024609:2800:748BDFB149DF0462AAB8CB92E67B9B024C45C899FE6306E56115965E30BEC476.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/025/507/912/5190090000025507912.20200707074604.85262402576614485462670225975480:50510717024609:2800:748BDFB149DF0462AAB8CB92E67B9B024C45C899FE6306E56115965E30BEC476.png[/IMG]​

4. Integrate ML Kit SDK into your project. Your app level build.gradle will look like this:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
compileSdkVersion 29
buildToolsVersion "29.0.3"

defaultConfig {
applicationId "com.demo.objectdetection"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

kotlinOptions { jvmTarget = "1.8" }

}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

//HMS ML Kit
implementation 'com.huawei.hms:ml-computer-vision:1.0.2.300'
}

and your project-level build.gradle is like this:

buildscript {
ext.kotlin_version = '1.3.72'
repositories {
google()
jcenter()
maven { url 'https://developer.huawei.com/repo/' }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.huawei.agconnect:agcp:1.3.1.300'
}
}

allprojects {
repositories {
google()
jcenter()
maven { url 'https://developer.huawei.com/repo/' }
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}

5. Create the layout first. There will be two surfaceViews. The first surfaceView is to display our camera stream, the second surfaceView is to draw our canvas. We will draw rectangles around detected objects and write their respective types on our canvas and show this canvas on our second surfaceView. Here is the sample:

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

<SurfaceView
android:id="@+id/surface_view_camera"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<SurfaceView
android:id="@+id/surface_view_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

6. By the way, make sure you set your activity style as “Theme.AppCompat.Light.NoActionBar” or similar in res/styles.xml to hide the action bar.

7.1. We have two important classes that help us detect objects in HMS ML Kit. MLObjectAnalyzer and LensEngine. MLObjectAnalyzer detects object information (MLObject) in an image. We can also customize it using MLObjectAnalyzerSetting. Here is our createAnalyzer method:

private fun createAnalyzer(): MLObjectAnalyzer {
val analyzerSetting = MLObjectAnalyzerSetting.Factory()
.setAnalyzerType(MLObjectAnalyzerSetting.TYPE_VIDEO)
.allowMultiResults()
.allowClassification()
.create()

return MLAnalyzerFactory.getInstance().getLocalObjectAnalyzer(analyzerSetting)
}

7.2. Other important class that we are using today is LensEngine. LensEngine is responsible for camera initialization, frame obtaining, and logic control functions. Here is our createLensEngine method:

private fun createLensEngine(orientation: Int): LensEngine {
val lensEngineCreator = LensEngine.Creator(applicationContext, mAnalyzer)
.setLensType(LensEngine.BACK_LENS)
.applyFps(10F)
.enableAutomaticFocus(true)

return when(orientation) {
Configuration.ORIENTATION_PORTRAIT ->
lensEngineCreator.applyDisplayDimension(getDisplayMetrics().heightPixels, getDisplayMetrics().widthPixels).create()
else ->
lensEngineCreator.applyDisplayDimension(getDisplayMetrics().widthPixels, getDisplayMetrics().heightPixels).create()
}
}

8. Well, LensEngine handles camera frames, MLObjectAnalyzer detects MLObjects in those frames. Now we need to create our ObjectAnalyzerTranscator class which implements MLAnalyzer.MLTransactor interface. The detected MLObjects are going to be dropped in transactResult method of this class. I will share our ObjectAnalyzerTransactor class here with an additional draw method for drawing rectangles and some text around detected objects.

package com.demo.objectdetection

import android.graphics.Color
import android.graphics.Paint
import android.graphics.PorterDuff
import android.util.Log
import android.util.SparseArray
import android.view.SurfaceHolder
import androidx.core.util.forEach
import androidx.core.util.isNotEmpty
import androidx.core.util.valueIterator
import com.huawei.hms.mlsdk.common.MLAnalyzer
import com.huawei.hms.mlsdk.objects.MLObject

class ObjectAnalyzerTransactor : MLAnalyzer.MLTransactor<MLObject> {

companion object {
private const val TAG = "ML_ObAnalyzerTransactor"
}

private var mSurfaceHolderOverlay: SurfaceHolder? = null

fun setSurfaceHolderOverlay(surfaceHolder: SurfaceHolder) {
mSurfaceHolderOverlay = surfaceHolder
}

override fun transactResult(results: MLAnalyzer.Result<MLObject>?) {
val items = results?.analyseList

items?.forEach { key, value ->
Log.d(TAG, "transactResult -> " +
"Border: ${value.border} " + //Rectangle around this object
"Type Possibility: ${value.typePossibility} " + //Possibility between 0-1
"Tracing Identity: ${value.tracingIdentity} " + //Tracing number of this object
"Type Identity: ${value.typeIdentity}") //Furniture, Plant, Food etc.
}

items?.also {
draw(it)
}

}

private fun draw(items: SparseArray<MLObject>) {

val canvas = mSurfaceHolderOverlay?.lockCanvas()

if (canvas != null) {

//Clear canvas first
canvas.drawColor(0, PorterDuff.Mode.CLEAR)

for (item in items.valueIterator()) {
val type = getItemType(item)

//Draw a rectangle around detected object.
val rectangle = item.border
Paint().also {
it.color = Color.YELLOW
it.style = Paint.Style.STROKE
it.strokeWidth = 8F
canvas.drawRect(rectangle, it)
}

//Draw text on the upper left corner of the detected object, writing its type.
Paint().also {
it.color = Color.BLACK
it.style = Paint.Style.FILL
it.textSize = 24F
canvas.drawText(type, (rectangle.left).toFloat(), (rectangle.top).toFloat(), it)
}
}
}

mSurfaceHolderOverlay?.unlockCanvasAndPost(canvas)
}

private fun getItemType(item: MLObject) = when(item.typeIdentity) {
MLObject.TYPE_OTHER -> "Other"
MLObject.TYPE_FACE -> "Face"
MLObject.TYPE_FOOD -> "Food"
MLObject.TYPE_FURNITURE -> "Furniture"
MLObject.TYPE_PLACE -> "Place"
MLObject.TYPE_PLANT -> "Plant"
MLObject.TYPE_GOODS -> "Goods"
else -> "No match"
}

override fun destroy() {
Log.d(TAG, "destroy")
}
}

9. Our lensEngine needs a surfaceHolder to run on. Therefore will start it when our surfaceHolder is ready. Here is our callback:

private val surfaceHolderCallback = object : SurfaceHolder.Callback {

override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
mLensEngine.close()
init()
mLensEngine.run(holder)
}

override fun surfaceDestroyed(holder: SurfaceHolder?) {
mLensEngine.release()
}

override fun surfaceCreated(holder: SurfaceHolder?) {
mLensEngine.run(holder)
}

}

10. We require CAMERA and WRITE_EXTERNAL_STORAGE permissions. Make sure you add them to your AndroidManifest.xml file and ask user at runtime. For the sake of simplicity we do it as shown below:

class MainActivity : AppCompatActivity() {

companion object {
private const val PERMISSION_REQUEST_CODE = 8
private val requiredPermissions = arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
}

override fun onCreate(savedInstanceState: Bundle?) {

if (hasPermissions(requiredPermissions))
init()
else
ActivityCompat.requestPermissions(this, requiredPermissions, PERMISSION_REQUEST_CODE)
}

private fun hasPermissions(permissions: Array<String>) = permissions.all {
ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)

if (requestCode == PERMISSION_REQUEST_CODE && hasPermissions(requiredPermissions))
init()
}

}

11. Let’s bring them all the pieces together. Here is our MainActivity.

package com.demo.objectdetection

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.PixelFormat
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.SurfaceHolder
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.huawei.hms.mlsdk.MLAnalyzerFactory
import com.huawei.hms.mlsdk.common.LensEngine
import com.huawei.hms.mlsdk.objects.MLObjectAnalyzer
import com.huawei.hms.mlsdk.objects.MLObjectAnalyzerSetting
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

companion object {
private const val TAG = "ML_MainActivity"
private const val PERMISSION_REQUEST_CODE = 8
private val requiredPermissions = arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
}

private lateinit var mAnalyzer: MLObjectAnalyzer
private lateinit var mLensEngine: LensEngine

private lateinit var mSurfaceHolderCamera: SurfaceHolder
private lateinit var mSurfaceHolderOverlay: SurfaceHolder

private lateinit var mObjectAnalyzerTransactor: ObjectAnalyzerTransactor

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

if (hasPermissions(requiredPermissions))
init()
else
ActivityCompat.requestPermissions(this, requiredPermissions, PERMISSION_REQUEST_CODE)
}

private fun init() {
mAnalyzer = createAnalyzer()
mLensEngine = createLensEngine(resources.configuration.orientation)

mSurfaceHolderCamera = surface_view_camera.holder
mSurfaceHolderOverlay = surface_view_overlay.holder

mSurfaceHolderOverlay.setFormat(PixelFormat.TRANSPARENT)
mSurfaceHolderCamera.addCallback(surfaceHolderCallback)

mObjectAnalyzerTransactor = ObjectAnalyzerTransactor()
mObjectAnalyzerTransactor.setSurfaceHolderOverlay(mSurfaceHolderOverlay)
mAnalyzer.setTransactor(mObjectAnalyzerTransactor)
}

private fun createAnalyzer(): MLObjectAnalyzer {
val analyzerSetting = MLObjectAnalyzerSetting.Factory()
.setAnalyzerType(MLObjectAnalyzerSetting.TYPE_VIDEO)
.allowMultiResults()
.allowClassification()
.create()

return MLAnalyzerFactory.getInstance().getLocalObjectAnalyzer(analyzerSetting)
}

private fun createLensEngine(orientation: Int): LensEngine {
val lensEngineCreator = LensEngine.Creator(applicationContext, mAnalyzer)
.setLensType(LensEngine.BACK_LENS)
.applyFps(10F)
.enableAutomaticFocus(true)

return when(orientation) {
Configuration.ORIENTATION_PORTRAIT ->
lensEngineCreator.applyDisplayDimension(getDisplayMetrics().heightPixels, getDisplayMetrics().widthPixels).create()
else ->
lensEngineCreator.applyDisplayDimension(getDisplayMetrics().widthPixels, getDisplayMetrics().heightPixels).create()
}
}

private val surfaceHolderCallback = object : SurfaceHolder.Callback {

override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
mLensEngine.close()
init()
mLensEngine.run(holder)
}

override fun surfaceDestroyed(holder: SurfaceHolder?) {
mLensEngine.release()
}

override fun surfaceCreated(holder: SurfaceHolder?) {
mLensEngine.run(holder)
}

}

override fun onDestroy() {
super.onDestroy()

//Release resources
mAnalyzer.stop()
mLensEngine.release()
}

private fun getDisplayMetrics() = DisplayMetrics().let {
(getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.getMetrics(it)
it
}

private fun hasPermissions(permissions: Array<String>) = permissions.all {
ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)

if (requestCode == PERMISSION_REQUEST_CODE && hasPermissions(requiredPermissions))
init()
}

}

12. In summary we used LensEngine to handle camera frames for us. We displayed the frames on our first surfaceView. Then MLObjectAnalyzer analyzed these frames and detected objects came into transactResult of our ObjectAnalyzerTrasactor class. In this method we iterated through all objects detected and drew them on our second surfaceView which we used as an overlay. Here is the output:

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/025/507/912/5190090000025507912.20200707080245.31394022579909869837493479659051:50510717024609:2800:9BE263292269938FF1FD30BF2A6E17304CAEE08C748C867DF8BFA8EDFB9DF882.gif"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/025/507/912/5190090000025507912.20200707080245.31394022579909869837493479659051:50510717024609:2800:9BE263292269938FF1FD30BF2A6E17304CAEE08C748C867DF8BFA8EDFB9DF882.gif[/IMG]

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/025/507/912/5190090000025507912.20200707080259.56291165085958755421240004715765:50510717024609:2800:575B79DEB79155291040ED0DFE556CE91B89A233E99B94E492746C8E121589E1.gif"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/025/507/912/5190090000025507912.20200707080259.56291165085958755421240004715765:50510717024609:2800:575B79DEB79155291040ED0DFE556CE91B89A233E99B94E492746C8E121589E1.gif[/IMG]​