{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Hand keypoint detection is the process of finding fingertips, knuckles and wrists in an image. Hand keypoint detection and hand gesture recognition is still a challenging problem in computer vision domain. It is really a tough work to build your own model for hand keypoint detection as it is hard to collect a large enough hand dataset and it requires expertise in this domain.
Hand keypoint detection can be used in variety of scenarios. For example, it can be used during artistic creation, users can convert the detected hand keypoints into a 2D model, and synchronize the model to a character’s model to produce a vivid 2D animation. You can create a puppet animation game using the above idea. Another example may be creating a rock paper scissors game. Or if you take it further, you can create a sign language to text conversion application. As you see varieties to possible usage scenarios are abundant and there is no limit to ideas.
Hand keypoint detection service is a brand-new feature that is added to Huawei Machine Learning Kit family. It has recently been released and it is making developers and computer vision geeks really excited! It detects 21 points of a hand and can detect up to ten hands in an image. It can detect hands in a static image or in a camera stream. Currently, it does not support scenarios where your hand is blocked by more than 50% or you wear gloves. You don’t need an internet connection as this is a device side capability and what is more: It is completely free!
It wouldn’t be a nice practice only to read related documents and forget about it after a few days. So I created a simple demo application that counts fingers and tells us the number we show by hand. I strongly advise you to develop your hand keypoint detection application beside me. I developed the application in Android Studio in Kotlin. Now, I am going to explain you how to build this application step by step. Don’t hesitate to ask questions in the comments if you face any issues.
1.Firstly, let’s create our project on Android Studio. I named my project as HandKeyPointDetectionDemo. I am sure you can find better names for your application. We can create our project by selecting Empty Activity option and then follow the steps described in this post to create and sign our project in App Gallery Connect.
2. In HUAWEI Developer AppGallery Connect, go to Develop > Manage APIs. Make sure ML Kit is activated.
3. Now we have integrated Huawei Mobile Services (HMS) into our project. Now let’s follow the documentation on developer.huawei.com and find the packages to add to our project. In the website click Developer / HMS Core/ AI / ML Kit. Here you will find introductory information to services, references, SDKs to download and others. Under ML Kit tab follow Android / Getting Started / Integrating HMS Core SDK / Adding Build Dependencies / Integrating the Hand Keypoint Detection SDK. We can follow the guide here to add hand detection capability to our project. We have also one meta-data tag to be added into our AndroidManifest.xml file. After the integration your app-level build.gradle file will look like this.
Code:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.huawei.agconnect'
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "com.demo.handkeypointdetection"
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
//AppGalleryConnect Core
implementation 'com.huawei.agconnect:agconnect-core:1.3.1.300'
// Import the base SDK.
implementation 'com.huawei.hms:ml-computer-vision-handkeypoint:2.0.2.300'
// Import the hand keypoint detection model package.
implementation 'com.huawei.hms:ml-computer-vision-handkeypoint-model:2.0.2.300'
}
Our project-level build.gradle file:
Code:
buildscript {
ext.kotlin_version = "1.4.0"
repositories {
google()
jcenter()
maven {url 'https://developer.huawei.com/repo/'}
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.1"
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
}
And don’t forget to add related meta-data tags into your AndroidManifest.xml.
Code:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.demo.handkeypointdetection">
<uses-permission android:name="android.permission.CAMERA" />
<application
...
<meta-data
android:name="com.huawei.hms.ml.DEPENDENCY"
android:value= "handkeypoint"/>
</application>
</manifest>
4. I created a class named HandKeyPointDetector. This class will be called from our activity or fragment. Its init method has two parameters context and a viewgroup. We will add our views on rootLayout.
Code:
fun init(context: Context, rootLayout: ViewGroup) {
mContext = context
mRootLayout = rootLayout
addSurfaceViews()
}
5. We are going to detect hand key points in a camera stream, so we create a surfaceView for camera preview and another surfaceView to draw somethings. The surfaceView that is going to be used as overlay should be transparent. Then, we add our views to our rootLayout passed as a parameter from our activity. Lastly we add SurfaceHolder.Callback to our surfaceHolder to know when it is ready.
Code:
private fun addSurfaceViews() {
val surfaceViewCamera = SurfaceView(mContext).also {
it.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)
mSurfaceHolderCamera = it.holder
}
val surfaceViewOverlay = SurfaceView(mContext).also {
it.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)
mSurfaceHolderOverlay = it.holder
mSurfaceHolderOverlay.setFormat(PixelFormat.TRANSPARENT)
mHandKeyPointTransactor.setOverlay(mSurfaceHolderOverlay)
}
mRootLayout.addView(surfaceViewCamera)
mRootLayout.addView(surfaceViewOverlay)
mSurfaceHolderCamera.addCallback(surfaceHolderCallback)
}
6. Inside our surfaceHolderCallback we override three methods: surfaceCreated, surfacehanged and surfaceDestroyed.
Code:
private val surfaceHolderCallback = object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
createAnalyzer()
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
prepareLensEngine(width, height)
mLensEngine.run(holder)
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
mLensEngine.release()
}
}
7. createAnalyzer method creates MLKeyPointAnalyzer with settings. If you want you can use default settings also. Scene type can be keypoint and rectangle around hands or we can use TYPE_ALL for both. Max hand results can be up to MLHandKeypointAnalyzerSetting.MAX_HANDS_NUM which is 10 currently. As we will count fingers of 2 hands, I set it to 2.
Code:
private fun createAnalyzer() {
val settings = MLHandKeypointAnalyzerSetting.Factory()
.setSceneType(MLHandKeypointAnalyzerSetting.TYPE_ALL)
.setMaxHandResults(2)
.create()
mAnalyzer = MLHandKeypointAnalyzerFactory.getInstance().getHandKeypointAnalyzer(settings)
mAnalyzer.setTransactor(mHandKeyPointTransactor)
}
8. LensEngine is responsible for handling camera frames for us. All we need to do is to prepare it with right dimensions according to orientation, choose the camera we want to work with, apply fps an so on.
Code:
private fun prepareLensEngine(width: Int, height: Int) {
val dimen1: Int
val dimen2: Int
if (mContext.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
dimen1 = width
dimen2 = height
} else {
dimen1 = height
dimen2 = width
}
mLensEngine = LensEngine.Creator(mContext, mAnalyzer)
.setLensType(LensEngine.BACK_LENS)
.applyDisplayDimension(dimen1, dimen2)
.applyFps(5F)
.enableAutomaticFocus(true)
.create()
}
9. When you no longer need the analyzer stop it and release resources.
Code:
fun stopAnalyzer() {
mAnalyzer.stop()
}
10. As you can see in step-7 we used mHandKeyPointTransactor. It is a custom class that we created named HandKeyPointTransactor, which inherits MLAnalyzer.MLTransactor<MLHandKeypoints>. It has two overriden methods inside. transactResult and destroy. Detected results will fall inside transactResult method and then we will try to find the number.
Code:
override fun transactResult(result: MLAnalyzer.Result<MLHandKeypoints>?) {
if (result == null)
return
val canvas = mOverlay?.lockCanvas() ?: return
//Clear canvas.
canvas.drawColor(0, PorterDuff.Mode.CLEAR)
//Find the number shown by our hands.
val numberString = analyzeHandsAndGetNumber(result)
//Find the middle of the canvas
val centerX = canvas.width / 2F
val centerY = canvas.height / 2F
//Draw a text that writes the number we found.
canvas.drawText(numberString, centerX, centerY, Paint().also {
it.style = Paint.Style.FILL
it.textSize = 100F
it.color = Color.GREEN
})
mOverlay?.unlockCanvasAndPost(canvas)
}
11. We will check hand by hand and then finger by finger to find the fingers that are up to find the number.
Code:
private fun analyzeHandsAndGetNumber(result: MLAnalyzer.Result<MLHandKeypoints>): String {
val hands = ArrayList<Hand>()
var number = 0
for (key in result.analyseList.keyIterator()) {
hands.add(Hand())
for (value in result.analyseList.valueIterator()) {
number += hands.last().createHand(value.handKeypoints).getNumber()
}
}
return number.toString()
}
For more information, you can visit https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0202369245767250343&fid=0101187876626530001
Related
More information like this, you can visit HUAWEI Developer Forum
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Image Kit Vision Service of HMS (Huawei Mobile Services) offers us very stylish filters to build a photo editor app. In this article, we will design a nice filtering screen using Vision Service. Moreover, it will be very easy to develop and it will allow you to make elegant beauty apps.
The Image Vision service provides 24 color filters for image optimization. It renders the images you provide and returns them as filtered bitmap objects.
Requirements :
Huawei Phone (It doesn’t support non-Huawei Phones)
EMUI 8.1 or later (Min Android SDK Version 26)
Restrictions :
When using the filter function of Image Vision, make sure that the size of the image to be parsed is not greater than 15 MB, the image resolution is not greater than 8000 x 8000, and the aspect ratio is between 1:3 and 3:1. If the image resolution is greater than 8000 x 8000 after the image is decompressed by adjusting the compression settings or the aspect ratio is not between 1:3 and 3:1, a result code indicating parameter error will be returned. In addition, a larger image size can lead to longer parsing and response time as well as higher memory and CPU usage and power consumption.
Let’s start to build a nice filtering screen. First of all, please follow these steps to create a regular app on App Gallery.
Then we need to add dependencies to the app level gradle file. (implementation ‘com.huawei.hms:image-vision:1.0.2.301’)
Don’t forget to add agconnect plugin. (apply plugin: ‘com.huawei.agconnect’)
Code:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'com.huawei.agconnect'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.huawei.hmsimagekitdemo"
minSdkVersion 26
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'
}
}
lintOptions {
abortOnError false
}
}
repositories {
flatDir {
dirs 'libs'
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.aar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.huawei.hms:image-vision:1.0.2.301'
}
Add maven repo url and agconnect dependency to the project level gradle file.
Code:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.3.72"
repositories {
google()
jcenter()
maven { url 'http://developer.huawei.com/repo/' }
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.huawei.agconnect:agcp:1.3.1.300'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
maven { url 'http://developer.huawei.com/repo/' }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
After added dependencies, we need to create an ImageVision instance to perform related operations such as obtaining the filter effect. From now on, you can initialize the service.
Code:
private fun initFilter() {
coroutineScope.launch { // CoroutineScope is used for the async calls
// Create an ImageVision instance and initialize the service.
imageVisionAPI = ImageVision.getInstance(baseContext)
imageVisionAPI.setVisionCallBack(object : VisionCallBack {
override fun onSuccess(successCode: Int) {
val initCode = imageVisionAPI.init(baseContext, authJson)
// initCode must be 0 if the initialization is successful.
if (initCode == 0)
Log.d(TAG, "getImageVisionAPI rendered image successfully")
}
override fun onFailure(errorCode: Int) {
Log.e(TAG, "getImageVisionAPI failure, errorCode = $errorCode")
Toast.makeText([email protected], "initFailed", Toast.LENGTH_SHORT).show()
}
})
}
}
Our app is allowed to use the Image Vision service only after the successful verification. So we should provide an authJson object with app credentials. The value of initCode must be 0, indicating that the initialization is successful.
Code:
private fun startFilter(filterType: String, intensity: String, compress: String) {
coroutineScope.launch { // CoroutineScope is used for the async calls
val jsonObject = JSONObject()
val taskJson = JSONObject()
try {
taskJson.put("intensity", intensity) //Filter strength. Generally, set this parameter to 1.
taskJson.put("filterType", filterType) // 24 different filterType code
taskJson.put("compressRate", compress) // Compression ratio.
jsonObject.put("requestId", "1")
jsonObject.put("taskJson", taskJson)
jsonObject.put("authJson", authJson) // App can use the service only after it is successfully authenticated.
coroutineScope.launch {
var deferred: Deferred<ImageVisionResult?> = async(Dispatchers.IO) {
imageVisionAPI?.getColorFilter(jsonObject, bitmapFromGallery)
// Obtain the rendering result from visionResult
}
visionResult = deferred.await() // wait till obtain ImageVisionResult object
val image = visionResult?.image
if (image == null)
Log.e(TAG, "FilterException: Couldn't render the image. Check the restrictions while rendering an image by Image Vision Service")
channel.send(image)
// Sending image bitmap with an async channel to make it receive with another channel
}
} catch (e: JSONException) {
Log.e(TAG, "JSONException: " + e.message)
}
}
}
Select an image from the Gallery. Call Init filter method and then start filtering images one by one which are located in recyclerView.
Code:
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
super.onActivityResult(requestCode, resultCode, intent)
if (resultCode == RESULT_OK) {
when (requestCode) {
PICK_REQUEST ->
if (intent != null) {
coroutineScope.launch {
var deferred: Deferred<Uri?> =
async(Dispatchers.IO) {
intent.data
}
imageUri = deferred.await()
imgView.setImageURI(imageUri)
bitmapFromGallery = (imgView.getDrawable() as BitmapDrawable).bitmap
initFilter()
startFilterForSubImages()
}
}
}
}
}
In our scenario, a user clicks a filter to render the selected image we provide and the Image Vision Result object returns the filtered bitmap. So we need to implement onSelected method of the interface to our activity which gets the FilterItem object of the clicked item from the adapter.
Code:
// Initialize and start a filter operation when a filter item is selected
override fun onSelected(item: BaseInterface) {
if (!channelIsFetching)
{
if (bitmapFromGallery == null)
Toast.makeText(baseContext, getString(R.string.select_photo_from_gallery), Toast.LENGTH_SHORT).show()
else
{
var filterItem = item as FilterItem
initFilter() // initialize the vision service
startFilter(filterItem.filterId, "1", "1") // intensity and compress are 1
coroutineScope.launch {
withContext(Dispatchers.Main)
{
imgView.setImageBitmap(channel.receive()) // receive the filtered bitmap result from another channel
stopFilter() // stop the vision service
}
}
}
}
else
Toast.makeText(baseContext, getString(R.string.wait_to_complete_filtering), Toast.LENGTH_SHORT).show()
}
FilterType codes of 24 different filters as follows:
When the user opens the gallery and selects an image from a directory, we will produce 24 different filtered versions of the image. I used async coroutine channels to render images with first in first out manner. So we can obtain the filter images one by one. Using Image Vision Service with Kotlin Coroutines is so fast and performance-friendly.
To turn off hardware acceleration, configure the AndroidManifest.xml file as follows:
Code:
<application
android:allowBackup="true"
android:hardwareAccelerated="false"
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=".ui.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
If you do not need to use filters any longer, call the imageVisionAPI.stop() API to stop the Image Vision service. If the returned stopCode is 0, the service is successfully stopped.
Code:
private fun stopFilter() {
if(imageVisionAPI != null)
imageVisionAPI.stop() // Stop the service if you don't need anymore
}
We have designed an elegant filtering screen quite easily. Preparing a filter page will no longer take your time. You will be able to develop quickly without having to know OpenGL. You should try Image Kit Vision Service as soon as possible.
And the result :
For more information about HMS Image Kit Vision Service please refer to :
HMS Image Kit Vision Service Documentation
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Hi all,
In the era of powerful mobile devices we store thousands of photos, have video calls, shop, manage our bank accounts and perform many other tasks in the palm of our hands. We can also manage to take photos and videos or have video calls with our cameras integrated on our mobile phones. But, it would be inefficient to only use these cameras we carry by ourselves all day to take raw photos and videos. They can perform more, much more.
Face detection services are used by many applications in different industries. It is mainly used for security and entertainment purposes. For example, it can be used by a taxi app to identify its taxi’s customer, it can be used by a smart home app to identify guests’ faces and announce to the host who the person ringing the bell at the door is, it can be used to draw moustache on faces in an entertainment app or it can be used to detect if a drivers eyes are open or not and warn our driver if his/her eyes closed.
In contrary to how many different areas face detection can be used and how important tasks this service performs, it is really easy to implement a face detection app with the help of Huawei ML Kit. As it’s a device side capability that works on all Android devices with ARM architecture, it is completely free, faster and more secure than other services. The face detection service can detect the shapes and features of your user’s face, including their facial expression, age, gender, and wearing.
With face detection service you can detect up to 855 face contour points to locate face coordinates including face contour, eyebrows, eyes, nose, mouth, and ears, and identify the pitch, yaw, and roll angles of a face. You can detect seven facial features including the possibility of opening the left eye, possibility of opening the right eye, possibility of wearing glasses, gender possibility, possibility of wearing a hat, possibility of wearing a beard, and age. In addition to there, you can also detect facial expressions, namely, smiling, neutral, anger, disgust, fear, sadness, and surprise.
Let’s start to build our demo application step by step from scratch!
1.Firstly, let’s create our project on Android Studio. We can create a project selecting Empty Activity option and then follow the steps described in this post to create and sign our project in App Gallery Connect. You can follow this guide.
2. Secondly, In HUAWEI Developer AppGallery Connect, go to Develop > Manage APIs. Make sure ML Kit is activated.
3. Now we have integrated Huawei Mobile Services (HMS) into our project. Now let’s follow the documentation on developer.huawei.com and find the packages to add to our project. In the website click Developer > HMS Core > AI > ML Kit. Here you will find introductory information to services, references, SDKs to download and others. Under ML Kit tab follow Android > Getting Started > Integrating HMS Core SDK > Adding Build Dependencies > Integrating the Face Detection SDK. We can follow the guide here to add face detection capability to our project. To later go round and learn more about this service I added all of the three packages shown here. You can only choose the base SDK or select packages according to your needs. After the integration your app-level build.gradle file will look like this.
Code:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.huawei.agconnect'
android {
compileSdkVersion 29
buildToolsVersion "30.0.1"
defaultConfig {
applicationId "com.demo.faceapp"
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'
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.appcompat:appcompat:1.2.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'
// Import the base SDK.
implementation 'com.huawei.hms:ml-computer-vision-face:2.0.1.300'
// Import the contour and key point detection model package.
implementation 'com.huawei.hms:ml-computer-vision-face-shape-point-model:2.0.1.300'
// Import the facial expression detection model package.
implementation 'com.huawei.hms:ml-computer-vision-face-emotion-model:2.0.1.300'
// Import the facial feature detection model package.
implementation 'com.huawei.hms:ml-computer-vision-face-feature-model:2.0.1.300'
}
And your project-level build.gradle file will look like this.
Code:
buildscript {
ext.kotlin_version = "1.3.72"
repositories {
google()
jcenter()
maven { url 'https://developer.huawei.com/repo/' }
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.1"
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
}
Don’t forget to add the following meta-data tags in your AndroidManifest.xml. This is for automatic update of the machine learning model.
Code:
<?xml version="1.0" encoding="utf-8"?>
<manifest ...
<application ...
</application>
<meta-data
android:name="com.huawei.hms.ml.DEPENDENCY"
android:value= "face"/>
</manifest>
4. Now we can select detecting faces on a static image or on a camera stream. Let’s choose detecting faces on a camera stream for this example. Firstly, let’s create our analyzer. Its type is MLFaceAnalyzer. It is responsible for analyzing the faces detected. Here is the sample implementation. We can also use our MLFaceAnalyzer with default settings to make it simple.
Code:
private lateinit var mAnalyzer: MLFaceAnalyzer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mAnalyzer = createAnalyzer()
}
private fun createAnalyzer(): MLFaceAnalyzer {
val settings = MLFaceAnalyzerSetting.Factory()
.allowTracing()
.setFeatureType(MLFaceAnalyzerSetting.TYPE_FEATURES)
.setShapeType(MLFaceAnalyzerSetting.TYPE_SHAPES)
.setMinFaceProportion(.5F)
.setKeyPointType(MLFaceAnalyzerSetting.TYPE_KEYPOINTS)
.create()
return MLAnalyzerFactory.getInstance().getFaceAnalyzer(settings)
}
5. Create a simple layout. Use two surfaceViews, one above the other, for camera frames and for our overlay. Because, later we will draw some shapes on our overlay view. Here is a sample layout.
Code:
<?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/surfaceViewCamera"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<SurfaceView
android:id="@+id/surfaceViewOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
6. We should prepare our views. We need two surfaceHolders in our application. We are going to make surfaceHolderOverlay transparent, because we want to see our camera frames. Later we are going to add a callback to our surfaceHolderCamera to know when it’s created, changed and destroyed. Let’s create them.
Code:
private lateinit var mAnalyzer: MLFaceAnalyzer
private var surfaceHolderCamera: SurfaceHolder? = null
private var surfaceHolderOverlay: SurfaceHolder? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mAnalyzer = createAnalyzer()
prepareViews()
}
private fun prepareViews() {
surfaceHolderCamera = surfaceViewCamera.holder
surfaceHolderOverlay = surfaceViewOverlay.holder
surfaceHolderOverlay?.setFormat(PixelFormat.TRANSPARENT)
surfaceHolderCamera?.addCallback(surfaceHolderCallback)
}
private val surfaceHolderCallback = object : SurfaceHolder.Callback {
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
}
override fun surfaceCreated(holder: SurfaceHolder?) {
}
}
7. Now we can create our LensEngine. It is a magic class that handles camera frames for us. You can set different settings to your LensEngine. Here is how you can create it simply. As you can see in the example, the order of width and height passed to LensEngine changes according to orientation. We can create our LensEngine inside surfaceChanged method of surfaceHolderCallback and release it inside surfaceDestroyed. Here is an example of creating and running LensEngine. LensEngine needs a surfaceHolder or a surfaceTexture to run on.
Code:
private lateinit var mAnalyzer: MLFaceAnalyzer
private lateinit var mLensEngine: LensEngine
private var surfaceHolderCamera: SurfaceHolder? = null
private var surfaceHolderOverlay: SurfaceHolder? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mAnalyzer = createAnalyzer()
prepareViews()
}
private fun prepareViews() {
surfaceHolderCamera = surfaceViewCamera.holder
surfaceHolderOverlay = surfaceViewOverlay.holder
surfaceHolderOverlay?.setFormat(PixelFormat.TRANSPARENT)
surfaceHolderCamera?.addCallback(surfaceHolderCallback)
}
private val surfaceHolderCallback = object : SurfaceHolder.Callback {
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
mLensEngine = createLensEngine(width, height)
mLensEngine.run(holder)
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
mLensEngine.release()
}
override fun surfaceCreated(holder: SurfaceHolder?) {
}
}
private fun createLensEngine(width: Int, height: Int): LensEngine {
val lensEngineCreator = LensEngine.Creator(this, mAnalyzer)
.applyFps(20F)
.setLensType(LensEngine.FRONT_LENS)
.enableAutomaticFocus(true)
return if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
lensEngineCreator.let {
it.applyDisplayDimension(height, width)
it.create()
}
} else {
lensEngineCreator.let {
it.applyDisplayDimension(width, height)
it.create()
}
}
}
8. We also need somewhere to receive detected results and interact with them. For this purpose we create our FaceAnalyzerTransactor class, you can name it as you wish. It should implement MLAnalyzer.MLTransactor<MLFace> interface. We are going to set an overlay which is of type SurfaceHolder, get our canvas from this overlay and draw some shapes on the canvas. We have the required data about the detected face inside transactResult method. Here is the sample implementation of the whole of our FaceAnalyzerTransactor class.
Code:
class FaceAnalyzerTransactor : MLAnalyzer.MLTransactor<MLFace> {
private var mOverlay: SurfaceHolder? = null
fun setOverlay(surfaceHolder: SurfaceHolder) {
mOverlay = surfaceHolder
}
override fun transactResult(result: MLAnalyzer.Result<MLFace>?) {
draw(result?.analyseList)
}
private fun draw(faces: SparseArray<MLFace>?) {
val canvas = mOverlay?.lockCanvas()
if (canvas != null && faces != null) {
//Clear the canvas
canvas.drawColor(0, PorterDuff.Mode.CLEAR)
for (face in faces.valueIterator()) {
//Draw all 855 points of the face. If Front Lens is selected, change x points side.
for (point in face.allPoints) {
val x = mOverlay?.surfaceFrame?.right?.minus(point.x)
if (x != null) {
Paint().also {
it.color = Color.YELLOW
it.style = Paint.Style.FILL
it.strokeWidth = 16F
canvas.drawPoint(x, point.y, it)
}
}
}
//Prepare a string to show if the user smiles or not and draw a text on the canvas.
val smilingString = if (face.emotions.smilingProbability > 0.5) "SMILING" else "NOT SMILING"
Paint().also {
it.color = Color.RED
it.textSize = 60F
it.textAlign = Paint.Align.CENTER
canvas.drawText(smilingString, face.border.exactCenterX(), face.border.exactCenterY(), it)
}
}
mOverlay?.unlockCanvasAndPost(canvas)
}
}
override fun destroy() {
}
}
9. Create a FaceAnalyzerTransactor instance in MainActivity and use it as shown below.
Code:
private lateinit var mAnalyzer: MLFaceAnalyzer
private lateinit var mLensEngine: LensEngine
private lateinit var mFaceAnalyzerTransactor: FaceAnalyzerTransactor
private var surfaceHolderCamera: SurfaceHolder? = null
private var surfaceHolderOverlay: SurfaceHolder? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
init()
}
private fun init() {
mAnalyzer = createAnalyzer()
mFaceAnalyzerTransactor = FaceAnalyzerTransactor()
mAnalyzer.setTransactor(mFaceAnalyzerTransactor)
prepareViews()
}
Also, don’t forget to set the overlay of our transactor. We can do this inside surfaceChanged method like this.
Code:
private val surfaceHolderCallback = object : SurfaceHolder.Callback {
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
mLensEngine = createLensEngine(width, height)
surfaceHolderOverlay?.let { mFaceAnalyzerTransactor.setOverlay(it) }
mLensEngine.run(holder)
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
mLensEngine.release()
}
override fun surfaceCreated(holder: SurfaceHolder?) {
}
}
10. We are almost done! Don’t forget to ask for permissions from our users. We need CAMERA and WRITE_EXTERNAL_STORAGE permissions. WRITE_EXTERNAL_STORAGE permission is for automatically updating the machine learning model. Add these permissions to your AndroidManifest.xml and ask from user to grant them at runtime. Here is a simple example.
Code:
private val requiredPermissions = arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (hasPermissions(requiredPermissions))
init()
else
ActivityCompat.requestPermissions(this, requiredPermissions, 0)
}
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 == 0 && grantResults.isNotEmpty() && hasPermissions(requiredPermissions))
init()
}
11. Well done! We have finished all the steps and created our project. Now we can test it. Here are some examples.
12. We have created a simple FaceApp which detects faces, face features and emotions. You can produce countless number of types of face detection apps, it is up to your imagination. ML Kit empowers your apps with the power of AI. If you have any questions, please ask through the link below. You can also find this project on Github.
Related Links
Thanks to Oğuzhan Demirci for this article.
Original post: https://medium.com/huawei-developers/build-a-face-detection-app-with-huawei-ml-kit-32caec06484
Nice and useful article
Does face detection depend on specific hardware devices?
Image classification uses the transfer learning algorithm to perform minute-level learning training on hundreds of images in specific fields (such as vehicles and animals) based on the base classification model with good generalization capabilities, and can automatically generate a model for image classification. The generated model can automatically identify the category to which the image belongs. This is an auto generated model. What if we want to create our image classification model?
In Huawei ML Kit it is possible. The AI Create function in HiAI Foundation provides the transfer learning capability of image classification. With in-depth machine learning and model training, AI Create can help users accurately identify images. In this article we will create own image classification model and we will develop an Android application with using this model. Let’s start.
First of all we need some requirement for creating our model;
You need a Huawei account for create custom model. For more detail click here.
You will need HMS Toolkit. In Android Studio plugins find HMS Toolkit and add plugin into your Android Studio.
You will need Python in our computer. Install Python 3.7.5 version. Mindspore is not used in other versions.
And the last requirements is the model. You will need to find the dataset. You can use any dataset you want. I will use flower dataset. You can find my dataset in here.
Model Creation
Create a new project in Android Studio. Then click HMS on top of the Android Studio screen. Then open Coding Assistant.
1- In the Coding Assistant screen, go to AI and then click AI Create. Set the following parameters, then click Confirm.
Operation type : Select New Model
Model Deployment Location : Select Deployment Cloud.
After click confirm a browser will be opened to log into your Huawei account. After log into your account a window will opened as below.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
2- Drag or add the image classification folders to the Please select train image folder area then set Output model file path and train parameters. If you have extensive experience in deep learning development, you can modify the parameter settings to improve the accuracy of the image recognition model. After preparation click Create Model to start training and generate an image classification model.
3- Then it will start training. You can follow the process on log screen:
4- After training successfully completed you will see the screen like below:
In this screen you can see the train result, train parameter and train dataset information of your model. You can give some test data for testing your model accuracy if you want. Here is the sample test results:
5- After confirming that the training model is available, you can choose to generate a demo project.
Generate Demo: HMS Toolkit automatically generates a demo project, which automatically integrates the trained model. You can directly run and build the demo project to generate an APK file, and run the file on the simulator or real device to check the image classification performance.
Using Model Without Generated Demo Project
If you want to use the model in your project you can follow the steps:
1- In your project create an Assests file:
2- Then navigate to the folder path you chose in step 1 in Model Creation. Find your model the extension will be in the form of “.ms” . Then copy your model into Assets file. After we need one more file. Create a txt file containing your model tags. Then copy that file into Assets folder also.
3- Download and add the CustomModelHelper.kt file into your project. You can find repository in here:
https://github.com/iebayirli/AICreateCustomModel
Don’t forget the change the package name of CustomModelHelper class. After the ML Kit SDK is added, its errors will be fixed.
4- After completing the add steps, we need to add maven to the project level build.gradle file to get the ML Kit SDKs. Your gradle file should be like this:
Code:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.3.72"
repositories {
google()
jcenter()
maven { url "https://developer.huawei.com/repo/" }
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
maven { url "https://developer.huawei.com/repo/" }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
5- Next, we are adding ML Kit SDKs into our app level build.gradle. And don’t forget the add aaptOption. Your app level build.gradle file should be like this:
Code:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "com.iebayirli.aicreatecustommodel"
minSdkVersion 26
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
kotlinOptions{
jvmTarget= "1.8"
}
aaptOptions {
noCompress "ms"
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'com.huawei.hms:ml-computer-model-executor:2.0.3.301'
implementation 'mindspore:mindspore-lite:0.0.7.000'
def activity_version = "1.2.0-alpha04"
// Kotlin
implementation "androidx.activity:activity-ktx:$activity_version"
}
6- Let’s create the layout first:
Code:
<?xml version="1.0" encoding="utf-8"?>
<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">
<androidx.constraintlayout.widget.Guideline
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/guideline1"
android:orientation="horizontal"
app:layout_constraintGuide_percent=".65"/>
<ImageView
android:id="@+id/ivImage"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintDimensionRatio="3:4"
android:layout_margin="16dp"
android:scaleType="fitXY"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="@+id/guideline1"/>
<TextView
android:id="@+id/tvResult"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="16dp"
android:autoSizeTextType="uniform"
android:background="@android:color/white"
android:autoSizeMinTextSize="12sp"
android:autoSizeMaxTextSize="36sp"
android:autoSizeStepGranularity="2sp"
android:gravity="center_horizontal|center_vertical"
app:layout_constraintTop_toTopOf="@+id/guideline1"
app:layout_constraintBottom_toTopOf="@+id/btnRunModel"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<Button
android:id="@+id/btnRunModel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Pick Image and Run"
android:textAllCaps="false"
android:background="#ffd9b3"
android:layout_marginBottom="8dp"
app:layout_constraintWidth_percent=".75"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
7- Then lets create const values in our activity. We are creating four values. First value is for permission. Other values are relevant to our model. Your code should look like this:
Code:
companion object {
const val readExternalPermission = android.Manifest.permission.READ_EXTERNAL_STORAGE
const val modelName = "flowers"
const val modelFullName = "flowers" + ".ms"
const val labelName = "labels.txt"
}
8- Then we create the CustomModelHelper example. We indicate the information of our model and where we want to download the model:
Code:
private val customModelHelper by lazy {
CustomModelHelper(
this,
modelName,
modelFullName,
labelName,
LoadModelFrom.ASSETS_PATH
)
}
9- After, we are creating two ActivityResultLauncher instances for gallery permission and image picking with using Activity Result API:
Code:
private val galleryPermission =
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
if (!it)
finish()
}
private val getContent =
registerForActivityResult(ActivityResultContracts.GetContent()) {
val inputBitmap = MediaStore.Images.Media.getBitmap(
contentResolver,
it
)
ivImage.setImageBitmap(inputBitmap)
customModelHelper.exec(inputBitmap, onSuccess = { str ->
tvResult.text = str
})
}
In getContent instance. We are converting selected uri to bitmap and calling the CustomModelHelper exec() method. If the process successfully finish we update textView.
10- After creating instances the only thing we need to is launching ActivityResultLauncher instances into onCreate():
Code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
galleryPermission.launch(readExternalPermission)
btnRunModel.setOnClickListener {
getContent.launch(
"image/*"
)
}
}
11- Let’s bring them all the pieces together. Here is our MainActivity:
Code:
package com.iebayirli.aicreatecustommodel
import android.os.Bundle
import android.provider.MediaStore
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private val customModelHelper by lazy {
CustomModelHelper(
this,
modelName,
modelFullName,
labelName,
LoadModelFrom.ASSETS_PATH
)
}
private val galleryPermission =
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
if (!it)
finish()
}
private val getContent =
registerForActivityResult(ActivityResultContracts.GetContent()) {
val inputBitmap = MediaStore.Images.Media.getBitmap(
contentResolver,
it
)
ivImage.setImageBitmap(inputBitmap)
customModelHelper.exec(inputBitmap, onSuccess = { str ->
tvResult.text = str
})
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
galleryPermission.launch(readExternalPermission)
btnRunModel.setOnClickListener {
getContent.launch(
"image/*"
)
}
}
companion object {
const val readExternalPermission = android.Manifest.permission.READ_EXTERNAL_STORAGE
const val modelName = "flowers"
const val modelFullName = "flowers" + ".ms"
const val labelName = "labels.txt"
}
}
Summary
In summary, we learned how to create a custom image classification model. We used HMS Toolkit for model training. After model training and creation we learned how to use our model in our application. If you want more information about Huawei ML Kit you find in here.
Here is the output:
https://github.com/iebayirli/AICreateCustomModel
Minimum sdk version for this
Introduction:
ML Kit provides a hand keypoint detection capability, which can be used to detect sign language. Handle Key point detection currently provides 21 points in the hand to detect the signs. Here we will use the direction of each finger and compare that with ASL rules to find the alphabet.
Application scenarios:
Sign language can used by deaf and speech impaired people. Sign language is a collection of hand gestures involving motions and signs which are used in daily interaction.
Using ML kit you can create an intelligent signed alphabet recognizer can work as an aiding agent to translate the signs to words (and also sentences) and vice versa.
What I am trying here is American Sign Language (ASL) alphabets from hand gesture. The classification is based on the position of the joints, fingers and wrists. I have attempted to gather alphabets for the word “HELLO” from hand gestures.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Development Practice
1. Preparations
You can find detailed information about the preparations you need to make on the HUAWEI Developers-Development Process URL https://developer.huawei.com/consumer/en/doc/development/HMS-Guides/ml-process-4
Here, we will just look at the most important procedures.
Step 1 Enable ML Kit
In HUAWEI Developer AppGallery Connect, choose Develop > Manage APIs. Make sure ML Kit is activated
Step 2 Configure the Maven Repository Address in the Project-Level build.gradle File
Code:
buildscript {
repositories {
...
maven {url 'https://developer.huawei.com/repo/'}
}
}
dependencies {
...
classpath 'com.huawei.agconnect:agcp:1.3.1.301'
}
allprojects {
repositories {
...
maven {url 'https://developer.huawei.com/repo/'}
}
}
Step 3 Add SDK Dependencies to the App-Level build.gradle File
Code:
apply plugin: 'com.android.application'
apply plugin: 'com.huawei.agconnect'
dependencies{
// Import the base SDK.
implementation 'com.huawei.hms:ml-computer-vision-handkeypoint:2.0.2.300'
// Import the hand keypoint detection model package.
implementation 'com.huawei.hms:ml-computer-vision-handkeypoint-model:2.0.2.300'
}
Step 4 Add related meta-data tags into your AndroidManifest.xml.
Code:
<meta-data
android:name="com.huawei.hms.ml.DEPENDENCY"
android:value= "handkeypoint"/>
Step 5 Apply for Camera Permission and Local File Reading Permission
Code:
<!--Camera permission-->
<uses-permission android:name="android.permission.CAMERA" />
<!--Read permission-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
2. Coding Steps:
Step 1 Create SurfaceView for Camera Preview and also create surfaceView for the result.
At present we are showing theresult only in the UI, but you can extend and read the result using the TTS recognition.
Code:
mSurfaceHolderCamera.addCallback(surfaceHolderCallback)
private val surfaceHolderCallback = object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
createAnalyzer()
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
prepareLensEngine(width, height)
mLensEngine.run(holder)
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
mLensEngine.release()
}
}
Step 2 Create a Hand Keypoint Analyzer
Code:
//Creates MLKeyPointAnalyzer with MLHandKeypointAnalyzerSetting.
val settings = MLHandKeypointAnalyzerSetting.Factory()
.setSceneType(MLHandKeypointAnalyzerSetting.TYPE_ALL)
.setMaxHandResults(2)
.create()
// Set the maximum number of hand regions that can be detected within an image. A maximum of 10 hand regions can be detected by default
mAnalyzer = MLHandKeypointAnalyzerFactory.getInstance().getHandKeypointAnalyzer(settings)
mAnalyzer.setTransactor(mHandKeyPointTransactor)
Step 3 Create the HandKeypointTransactor class for processing detection results
Below class implements the MLAnalyzer.MLTransactor<T> API and uses the transactResult method in this class to obtain the detection results and implement specific services.
Code:
class HandKeyPointTransactor(surfaceHolder: SurfaceHolder? = null): MLAnalyzer.MLTransactor<MLHandKeypoints> {
override fun transactResult(result: MLAnalyzer.Result<MLHandKeypoints>?) {
var foundCharacter = findTheCharacterResult(result)
if (foundCharacter.isNotEmpty() && !foundCharacter.equals(lastCharacter)) {
lastCharacter = foundCharacter
displayText.append(lastCharacter)
}
canvas.drawText(displayText.toString(), paddingleft, paddingRight, Paint().also {
it.style = Paint.Style.FILL
it.color = Color.YELLOW
})
}
Step 4 Create an Instance of the Lens Engine
Code:
LensEngine lensEngine = new LensEngine.Creator(getApplicationContext(), analyzer)
setLensType(LensEngine.BACK_LENS)
applyDisplayDimension(width, height) // adjust width and height depending on the orientation
applyFps(5f)
enableAutomaticFocus(true)
create();
Step 5 Run the LensEngine
Code:
private val surfaceHolderCallback = object : SurfaceHolder.Callback {
// run the LensEngine in surfaceChanged()
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
createLensEngine(width, height)
mLensEngine.run(holder)
}
}
Step 6 Need the analyzer stop it and release resources
Code:
fun stopAnalyzer() {
mAnalyzer.stop()
}
Step 7. Process the transactResult() to detect character
You can use the transactResult method in HandKeypointTransactor class to obtain the detection results and implement specific services. In addition to the coordinate information of each hand keypoint, the detection result includes a confidence value of the palm and of each keypoint. The palm and hand keypoints that are incorrectly recognized can be filtered out based on the confidence values. In actual scenarios, a threshold can be flexibly set based on tolerance of misrecognition.
Step 7.1 Find the direction of the finger:
Let us start consider possible vector slope for finger is in two axis x-axis and y-axis
Code:
private const val X_COORDINATE = 0
private const val Y_COORDINATE = 1
We have Fingers possible in five vectros. The direction of any finger at any time can be categorised as UP, DOWN, DOWN_UP, UP_DOWN, NEUTRAL.
Code:
enum class FingerDirection {
VECTOR_UP, VECTOR_DOWN, VECTOR_UP_DOWN, VECTOR_DOWN_UP, VECTOR_UNDEFINED
}
enum class Finger {
THUMB, FIRST_FINGER, MIDDLE_FINGER, RING_FINGER, LITTLE_FINGER
}
First separate the corresponding keyupoints from the result to different fingers's key point array like this
Code:
var firstFinger = arrayListOf<MLHandKeypoint>()
var middleFinger = arrayListOf<MLHandKeypoint>()
var ringFinger = arrayListOf<MLHandKeypoint>()
var littleFinger = arrayListOf<MLHandKeypoint>()
var thumb = arrayListOf<MLHandKeypoint>()
Each keypoint in the finger corresponds to the joint in the finger. By finding the distance of the joints from from the average position value of the finger, you can find the slope. Check the x and y co-ordinates in the key point with respect to the x and y co-ordinates of the nearby keypoint.
For example :
Take two samples keypoints for H letter
Code:
int[] datapointSampleH1 = {623, 497, 377, 312, 348, 234, 162, 90, 377, 204, 126, 54, 383, 306, 413, 491, 455, 348, 419, 521 };
int [] datapointSampleH2 = {595, 463, 374, 343, 368, 223, 147, 78, 381, 217, 110, 40, 412, 311, 444, 526, 450, 406, 488, 532};
You can calculate the vector using average of the coordinates of the finger
Code:
//For ForeFinger - 623, 497, 377, 312
double avgFingerPosition = (datapoints[0].getX()+datapoints[1].getX()+datapoints[2].getX()+datapoints[3].getX())/4;
// find the average and subract it from the value of x
double diff = datapointSampleH1 [position] .getX() - avgFingerPosition ;
//vector either positive or negative representing the direction
int vector = (int)((diff *100)/avgFingerPosition ) ;
Resulting vector will be of negative or positive value, if it is positive it is in the direction of x-axis positive quad and if it negative it is viceversa. Using this approrach do the vector mapping for all alphabets. Once you have all the vectors we can use those for your programming.
Using above vector direction we can categorise into the vectors we defined first as FingerDirection enum .
Code:
private fun getSlope(keyPoints: MutableList<MLHandKeypoint>, coordinate: Int): FingerDirection {
when (coordinate) {
X_COORDINATE -> {
if (keyPoints[0].pointX > keyPoints[3].pointX && keyPoints[0].pointX > keyPoints[2].pointX)
return FingerDirection.VECTOR_DOWN
if (keyPoints[0].pointX > keyPoints[1].pointX && keyPoints[3].pointX > keyPoints[2].pointX)
return FingerDirection.VECTOR_DOWN_UP
if (keyPoints[0].pointX < keyPoints[1].pointX && keyPoints[3].pointX < keyPoints[2].pointX)
return FingerDirection.VECTOR_UP_DOWN
if (keyPoints[0].pointX < keyPoints[3].pointX && keyPoints[0].pointX < keyPoints[2].pointX)
return FingerDirection.VECTOR_UP
}
Y_COORDINATE -> {
if (keyPoints[0].pointY > keyPoints[1].pointY && keyPoints[2].pointY > keyPoints[1].pointY && keyPoints[3].pointY > keyPoints[2].pointY)
return FingerDirection.VECTOR_UP_DOWN
if (keyPoints[0].pointY > keyPoints[3].pointY && keyPoints[0].pointY > keyPoints[2].pointY)
return FingerDirection.VECTOR_UP
if (keyPoints[0].pointY < keyPoints[1].pointY && keyPoints[3].pointY < keyPoints[2].pointY)
return FingerDirection.VECTOR_DOWN_UP
if (keyPoints[0].pointY < keyPoints[3].pointY && keyPoints[0].pointY < keyPoints[2].pointY)
return FingerDirection.VECTOR_DOWN
}
}
return FingerDirection.VECTOR_UNDEFINED
Get the directions of each finger and store in an array
Code:
xDirections[Finger.FIRST_FINGER] = getSlope(firstFinger, X_COORDINATE)
yDirections[Finger.FIRST_FINGER] = getSlope(firstFinger, Y_COORDINATE )
More details, you can visit https://forums.developer.huawei.com/forumPortal/en/topic/0204403586406920123
Hi everyone!
Today I will be briefing through how to implement a 3D Scene to display objects and play sounds in your Andorid Kotlin projects.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
All we need is Android Studio with version 3.5 or higher and a smartphone running Android 4.4 or later. The kits we need requires these specifications at minimum:
For Scene Kit alone:
JDK version: 1.7 or later
minSdkVersion: 19 or later
targetSdkVersion: 19 or later
compileSdkVersion: 19 or later
Gradle version: 3.5 or later
For Audio Kit alone:
JDK version: 1.8.211 or later
minSdkVersion: 21
targetSdkVersion: 29
compileSdkVersion: 29
Gradle version: 4.6 or later
That brings us to use Audio Kit’s minimum requirements as Scene Kit requirements are lower. So we should keep those in our minds while configuring our project. Let’s begin with implementing Scene Kit.
First of all, our aim with this Scene Kit implementation is to achieve a view of 3D object that we can interact with like this:
We will also add multiple objects and be able to cycle through. In order to use Scene Kit in your project, start by adding these implementations to build.gradle files.
Code:
buildscript {
repositories {
...
maven { url 'https://developer.huawei.com/repo/' }
}
...
}
allprojects {
repositories {
...
maven { url 'https://developer.huawei.com/repo/' }
}
}
Code:
dependencies {
...
implementation 'com.huawei.scenekit:full-sdk:5.1.0.300'
}
Note that in the project I have used viewBinding feature of Kotlin in order to skip boilerplate view initialization codes. If you want to use viewBinding, you should add this little code in your app-level build.gradle.
Code:
android {
...
buildFeatures {
viewBinding true
}
...
}
After syncing gradle files, we are ready to use Scene Kit in our project. Keep in mind that our purpose is solely to display 3D objects that user can interact with. But Scene Kit has much more deeper abilities that is provided for us. If you are actually looking for something different or want to discover all abilities, follow the link. Else, let’s continue with a custom scene view.
Scene Kit - HMS Core - HUAWEI Developer
Simple purpose of this custom view is just to load automatically our first object into the view when it is done initializing. Of course you can skip this part if you don’t need this purpose. Bear in mind that you should use default SceneView and load manually instead. You can still find the code for loading objects in this code snippet.
Code:
import android.content.Context
import android.util.AttributeSet
import android.view.SurfaceHolder
import com.huawei.hms.scene.sdk.SceneView
class CustomSceneView : SceneView {
constructor(context: Context?) : super(context)
constructor(
context: Context?,
attributeSet: AttributeSet?
) : super(context, attributeSet)
override fun surfaceCreated(holder: SurfaceHolder) {
super.surfaceCreated(holder)
loadScene("car1/scene.gltf")
loadSpecularEnvTexture("car1/specularEnvTexture.dds")
loadDiffuseEnvTexture("car1/diffuseEnvTexture.dds")
}
}
Well we cannot display anything actually before adding our object files in our projects. You will need to obtain object files elsewhere as object models I have, are not my creation. You can find public objects with ‘gltf object’ queries in search engines. Once you have your object, head to your project file and create ‘assets’ file under ‘../src/main/’ and place your object file here. In my case:
In the surfaceCreated method, loadScene(), loadSpecularEnvTexture() and loadDiffuseEnvTexture() methods are used to load our object. Once the view surface is created, our first object will be loaded into it. Now head to the xml for your 3D objects to display, in this guide case, activity_main.xml. In activity_main.xml, create the view we just created. I have also added simple arrows to navigate between models.
Code:
<?xml version="1.0" encoding="utf-8"?>
<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">
<com.example.sceneaudiodemo.CustomSceneView
android:id="@+id/csv_main"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ImageView
android:id="@+id/iv_rightArrow"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="12dp"
android:src="@drawable/ic_arrow"
android:tint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_leftArrow"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="12dp"
android:rotation="180"
android:src="@drawable/ic_arrow"
android:tint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Now we are all set for our object to be displayed once our app is launched. Let’s add a few other objects and navigate between. In MainActivity:
Code:
private lateinit var binding: ActivityMainBinding
private var selectedId = 0
private val modelSceneList = arrayListOf(
"car1/scene.gltf",
"car2/scene.gltf",
"car3/scene.gltf"
)
private val modelSpecularList = arrayListOf(
"car1/specularEnvTexture.dds",
"car2/specularEnvTexture.dds",
"car3/specularEnvTexture.dds"
)
private val modelDiffList = arrayListOf(
"car1/diffuseEnvTexture.dds",
"car2/diffuseEnvTexture.dds",
"car3/diffuseEnvTexture.dds"
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.ivRightArrow.setOnClickListener {
if (modelSceneList.size == 0) [email protected]
selectedId = (selectedId + 1) % modelSceneList.size // To keep our id in the range of our model list
loadImage()
}
binding.ivLeftArrow.setOnClickListener {
if (modelSceneList.size == 0) [email protected]
if (selectedId == 0) selectedId = modelSceneList.size - 1 // To keep our id in the range of our model list
else selectedId -= 1
loadImage()
}
}
private fun loadImage() {
binding.csvMain.loadScene(modelSceneList[selectedId])
binding.csvMain.loadSpecularEnvTexture(modelSpecularList[selectedId])
binding.csvMain.loadDiffuseEnvTexture(modelDiffList[selectedId])
}
In onCreate(), we are making a simple next/previous logic to change our objects. And we are storing our objects’ file paths as strings in separate lists that we created hardcoded. You may want to tinker to make it dynamic but I wanted to keep it simple for the guide. We used ‘selectedId’ to keep track of current object being displayed.
And that’s all for SceneView implementation for 3D object views!
Now no time to waste, let’s continue with adding Audio Kit.
Head back to the app-level build.gradle and add Audio Kit implementation.
Code:
dependencies {
...
implementation 'com.huawei.hms:audiokit-player:1.1.0.300'
...
}
As we already added necessary repository while implementing Scene Kit, we won’t need to make any changes in the project-level build.gradle. So let’s go back and complete Audio Kit.
I added a simple play button to activity_main.xml.
Code:
<Button
android:id="@+id/btn_playSound"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Play"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
I will use this button to play sound for current object. Afterwards, all we need to do is make these changes in our MainActivity.
Code:
private var mHwAudioManager: HwAudioManager? = null
private var mHwAudioPlayerManager: HwAudioPlayerManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
...
initPlayer(this)
binding.btnPlaySound.setOnClickListener {
mHwAudioPlayerManager?.play(selectedId) // Requires playlist to play, selectedId works for index to play.
}
...
}
private fun initPlayer(context: Context) {
val hwAudioPlayerConfig = HwAudioPlayerConfig(context)
HwAudioManagerFactory.createHwAudioManager(hwAudioPlayerConfig,
object : HwAudioConfigCallBack {
override fun onSuccess(hwAudioManager: HwAudioManager?) {
try {
mHwAudioManager = hwAudioManager
mHwAudioPlayerManager = hwAudioManager?.playerManager
mHwAudioPlayerManager?.playList(getPlaylist(), 0, 0)
} catch (ex: Exception) {
ex.printStackTrace()
}
}
override fun onError(p0: Int) {
Log.e("init:onError: ","$p0")
}
})
}
fun getPlaylist(): List<HwAudioPlayItem>? {
val playItemList: MutableList<HwAudioPlayItem> = ArrayList()
val audioPlayItem1 = HwAudioPlayItem()
val sound = Uri.parse("android.resource://yourpackagename/raw/soundfilename").toString() // soundfilename should not include file extension.
audioPlayItem1.audioId = "1000"
audioPlayItem1.singer = "Taoge"
audioPlayItem1.onlinePath =
"https://lfmusicservice.hwcloudtest.cn:18084/HMS/audio/Taoge-chengshilvren.mp3"
audioPlayItem1.setOnline(1)
audioPlayItem1.audioTitle = "chengshilvren"
playItemList.add(audioPlayItem1)
val audioPlayItem2 = HwAudioPlayItem()
audioPlayItem2.audioId = "1001"
audioPlayItem2.singer = "Taoge"
audioPlayItem2.onlinePath =
"https://lfmusicservice.hwcloudtest.cn:18084/HMS/audio/Taoge-dayu.mp3"
audioPlayItem2.setOnline(1)
audioPlayItem2.audioTitle = "dayu"
playItemList.add(audioPlayItem2)
val audioPlayItem3 = HwAudioPlayItem()
audioPlayItem3.audioId = "1002"
audioPlayItem3.singer = "Taoge"
audioPlayItem3.onlinePath =
"https://lfmusicservice.hwcloudtest.cn:18084/HMS/audio/Taoge-wangge.mp3"
audioPlayItem3.setOnline(1)
audioPlayItem3.audioTitle = "wangge"
playItemList.add(audioPlayItem3)
return playItemList
}
After making the changes, we will be able to play sounds in our projects. I had to add online sounds available, if you want to use sounds added in your project, then you should use ‘sound’ variable I have given example of, and change ‘audioPlayItem.setOnline(1)’ to ‘audioPlayItem.setOnline(0)’. Also change ‘audioPlayItem.onlinePath’ to ‘audioPlayItem.filePath’. Then you should be able to play imported sound files too. By the way, that’s all for Audio Kit as well! We didn’t need to implement any play/pause or seekbar features as we just want to hear the sound and get done with it.
So we completed our guide for how to implement a Scene Kit 3D Scene View and Audio Kit to play sounds in our projects in Kotlin. If you have any questions or suggestions, feel free to contact me. Thanks for reading this far and I hope it was useful for you!
References
Scene Kit - HMS Core - HUAWEI Developer
Audio Kit - Audio Development Component - HUAWEI Developer
Can we install in non-huawei devices will it support?
sujith.e said:
Can we install in non-huawei devices will it support?
Click to expand...
Click to collapse
For Scene Kit compability our options are as these:
As for the Audio Kit, only Huawei devices are supported, referenced from: https://developer.huawei.com/consum.../HMSCore-Guides/introduction-0000001050749665