介紹
華為機器學習(ML Kit)提供手部關鍵點識別服務,可用於手語識別。手部關鍵點識別服務能識別手部21個關鍵點,通過每個手指的方向和手語規則作比較去找手語字母表。
應用場景
手語通常被聽力和口語有障礙的人來使用,是收集手勢包含日常互動中所使用的動作和手勢。
使用ML Kit 可以建立一個智能手語字母表識別器,它可以像一個輔助器一樣將手勢翻譯成單詞或者句子,也可以將單詞或者句子翻譯成手勢。
這里嘗試的是手勢當中的美國手語字母表,是基於關節,手指和手腕的位置進行分類。接下來小編將會嘗試從手勢中收集單詞“HELLO”。
開發步驟
1. 准備
詳細的准備步驟可以參考華為開發者聯盟:
https://developer.huawei.com/consumer/cn/doc/development/HMS-Guides/ml-process-4
這里列舉關鍵的開發步驟。
1.1 啟動ML Kit
在華為開發者AppGallery Connect, 選擇Develop > Manage APIs。確保ML Kit 激活。
1.2 項目級gradle里配置Maven倉地址
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/'}
}
}
1.3 集成SDK后,在文件頭添加配置.
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'
}
1.4 將以下語句添加到AndroidManifest.xml文件中
<meta-data
android:name="com.huawei.hms.ml.DEPENDENCY"
android:value= "handkeypoint"/>
1.5申請攝像頭權限和本地文件讀取權限
<!--Camera permission-->
<uses-permission android:name="android.permission.CAMERA" />
<!--Read permission-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
2. 代碼開發
2.1 創建用於相機預覽的Surface View,創建用於結果的Surface View。
目前我們只在UI中顯示結果,您也可以使用TTS識別擴展和讀取結果。
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()
}
}
2.2創建手部關鍵點分析器
//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)
2.3 開發者創建識別結果處理類“HandKeypointTransactor”,該類MLAnalyzer.MLTransactor
接口,使用此類中的“transactResult”方法獲取檢測結果並實現具體業務。
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
})
}
2.4 創建LensEngine
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();
2.5 運行LensEngine
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)
}
}
2.6 停止分析器,釋放檢測資源
fun stopAnalyzer() {
mAnalyzer.stop()
}
2.7 處理 transactResult() 以檢測字符
您可以使用HandKeypointTransactor類中的transtresult方法來獲取檢測結果並實現特定的服務。檢測結果除了手部各關鍵點的坐標信息外,還包括手掌和每個關鍵點的置信值。手掌和手部關鍵點識別錯誤可以根據置信值過濾掉。在實際應用中,可以根據誤認容忍度靈活設置閾值。
2.7.1 找到手指的方向:
讓我們先假設可能手指的矢量斜率分別在X軸和Y軸上。
private const val X_COORDINATE = 0
private const val Y_COORDINATE = 1
假設我們有手指分別在5個矢量上,任意手指的方向在任意時間可以被分類為上,下,下-上,上-下,不動。
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
}
首先將對應的關鍵點從結果中分離到不同手指的關鍵點數組,像這樣:
var firstFinger = arrayListOf<MLHandKeypoint>()
var middleFinger = arrayListOf<MLHandKeypoint>()
var ringFinger = arrayListOf<MLHandKeypoint>()
var littleFinger = arrayListOf<MLHandKeypoint>()
var thumb = arrayListOf<MLHandKeypoint>()
手指上的每個關鍵點都對應手指的關節,通過計算關節與手指的平均位置值之間的距離就可以計算出斜率。根據附近關鍵點的坐標,查詢該關鍵點的坐標。
例如:
拿字母H的兩個簡單關鍵點來說
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};
用手指坐標的平均值來計算矢量
//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 ) ;
矢量的結果將會是正值或者負值,如果它是正值它會出現X軸的正四方向,如果相反它就是負值。用這個方式對所有字母進行矢量映射,一旦你掌握了所有的矢量我們就可以用它們來進行編程。
用上述矢量方向,我們可以分類矢量,定義第一個為手指方向枚舉
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
獲取每個手指的方向並且儲存在一個數組里。
xDirections[Finger.FIRST_FINGER] = getSlope(firstFinger, X_COORDINATE)
yDirections[Finger.FIRST_FINGER] = getSlope(firstFinger, Y_COORDINATE )
2.7.2 從手指方向找到字符:
現在我們把它當作唯一的單詞“HELLO”,它需要字母H,E,L,O。它們對應的X軸和Y軸的矢量如圖所示。
假設:
-
手的方向總是豎向的。
-
讓手掌和手腕與手機平行,也就是與X軸成90度。
-
姿勢至少保持3秒用來記錄字符。
開始用字符映射矢量來查找字符串
// Alphabet H
if (xDirections[Finger.LITTLE_FINGER] == FingerDirection.VECTOR_DOWN_UP
&& xDirections [Finger.RING_FINGER] == FingerDirection.VECTOR_DOWN_UP
&& xDirections [Finger.MIDDLE_FINGER] == FingerDirection.VECTOR_DOWN
&& xDirections [Finger.FIRST_FINGER] == FingerDirection.VECTOR_DOWN
&& xDirections [Finger.THUMB] == FingerDirection.VECTOR_DOWN)
return "H"
//Alphabet E
if (yDirections[Finger.LITTLE_FINGER] == FingerDirection.VECTOR_UP_DOWN
&& yDirections [Finger.RING_FINGER] == FingerDirection.VECTOR_UP_DOWN
&& yDirections [Finger.MIDDLE_FINGER] == FingerDirection.VECTOR_UP_DOWN
&& yDirections [Finger.FIRST_FINGER] == FingerDirection.VECTOR_UP_DOWN
&& xDirections [Finger.THUMB] == FingerDirection.VECTOR_DOWN)
return "E"
if (yDirections[Finger.LITTLE_FINGER] == FingerDirection.VECTOR_UP_DOWN
&& yDirections [Finger.RING_FINGER] == FingerDirection.VECTOR_UP_DOWN
&& yDirections [Finger.MIDDLE_FINGER] == FingerDirection.VECTOR_UP_DOWN
&& yDirections [Finger.FIRST_FINGER] == FingerDirection.VECTOR_UP
&& yDirections [Finger.THUMB] == FingerDirection.VECTOR_UP)
return "L"
if (xDirections[Finger.LITTLE_FINGER] == FingerDirection.VECTOR_UP
&& xDirections [Finger.RING_FINGER] == FingerDirection.VECTOR_UP
&& yDirections [Finger.THUMB] == FingerDirection.VECTOR_UP)
return "O"
3. 畫面和結果
4. 更多技巧和訣竅
-
當擴展到26個字母時,誤差很更多。為了更精准的掃描需要2-3秒,從2-3秒的時間尋找和計算最有可能的字符,這可以減少字母表的誤差。
-
為了能支持所有方向,在X-Y軸上增加8個或者更多的方向。首先,需要求出手指的度數和對應的手指矢量。
總結
這個嘗試是強力坐標技術,它可以在生成矢量映射后擴展到所有26個字母,方向也可以擴展所有8個方向,所以它會有2685個手指=1040個矢量。為了更好的解決這一問題,我們可以利用手指的一階導數函數來代替矢量從而簡化計算。
我們可以增強其它的去代替創建矢量,可以使用圖像分類和訓練模型,然后使用自定義模型。這個訓練是為了檢查華為ML Kit使用關鍵點處理特性的可行性。
欲了解更多詳情,請參閱:
華為開發者聯盟官網:https://developer.huawei.com/consumer/cn/hms
獲取開發指導文檔:https://developer.huawei.com/consumer/cn/doc/development
參與開發者討論請到Reddit社區:https://www.reddit.com/r/HMSCore/
下載demo和示例代碼請到Github:https://github.com/HMS-Core
解決集成問題請到Stack Overflow:https://stackoverflow.com/questions/tagged/huawei-mobile-services?tab=Newest
原文鏈接:
https://developer.huawei.com/consumer/cn/forum/topic/0204423958265820665?fid=18
作者:timer