手語也能機器翻譯了!機器學習手勢識別功能瞭解一下

HMSCore發表於2021-08-16

華為機器學習(ML Kit)提供手部關鍵點識別服務,可用於手語識別。手部關鍵點識別服務能識別手部21個關鍵點,通過每個手指的方向和手語規則作比較去找手語字母表。

應用場景

手語通常被聽力和口語有障礙的人來使用,是收集手勢包含日常互動中所使用的動作和手勢。使用ML Kit 可以建立一個智慧手語字母表識別器,它可以像一個輔助器一樣將手勢翻譯成單詞或者句子,也可以將單詞或者句子翻譯成手勢。這裡嘗試的是手勢當中的美國手語字母表,是基於關節,手指和手腕的位置進行分類。接下來小編將會嘗試從手勢中收集單詞“HELLO”。

開發步驟

1. 開發準備

詳細的準備步驟可以參考華為開發者聯盟,這裡列舉關鍵的開發步驟。

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.更多技巧和訣竅

1. 當擴充套件到26個字母時,誤差很更多。為了更精準的掃描需要2-3秒,從2-3秒的時間尋找和計算最有可能的字元,這可以減少字母表的誤差。

2. 為了能支援所有方向,在X-Y軸上增加8個或者更多的方向。首先,需要求出手指的度數和對應的手指向量。

總結

這個嘗試是強力座標技術,它可以在生成向量對映後擴充套件到所有26個字母,方向也可以擴充套件所有8個方向,所以它會有26*8*5個手指=1040個向量。為了更好的解決這一問題,我們可以利用手指的一階導數函式來代替向量從而簡化計算。

我們可以增強其它的去代替建立向量,可以使用影像分類和訓練模型,然後使用自定義模型。這個訓練是為了檢查華為ML Kit使用關鍵點處理特性的可行性。

瞭解更多相關內容>>

訪問華為機器學習服務官網
獲取華為機器學習服務開發指導文件
華為HMS Core官方論壇
華為機器學習開源倉地址:GitHubGitee
解決整合問題請到Stack Overflow

點選關注,第一時間瞭解HMS Core最新技術~

相關文章