华为机器学习(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
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官方论坛
华为机器学习开源仓地址:GitHub、Gitee
解决集成问题请到Stack Overflow
点击关注,第一时间了解HMS Core最新技术~
这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub
只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总
深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal
require"socket"server="irc.rizon.net"port="6667"nick="RubyIRCBot"channel="#0x40"s=TCPSocket.open(server,port)s.print("USERTesting",0)s.print("NICK#{nick}",0)s.print("JOIN#{channel}",0)这个IRC机器人没有连接到IRC服务器,我做错了什么? 最佳答案 失败并显示此消息::irc.shakeababy.net461*USER:Notenoughparame
我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or
我经常将预配置的lambda插入可枚举的方法中,例如“map”、“select”等。但是“注入(inject)”的行为似乎有所不同。例如与mult4=lambda{|item|item*4}然后(5..10).map&mult4给我[20,24,28,32,36,40]但是,如果我制作一个2参数lambda用于像这样的注入(inject),multL=lambda{|product,n|product*n}我想说(5..10).inject(2)&multL因为“inject”有一个可选的单个初始值参数,但这给了我......irb(main):027:0>(5..10).inject
在Rails自动生成的功能测试(test/functional/products_controller_test.rb)中,我看到以下代码:classProductsControllerTest我的问题是:方法调用products()在哪里/如何定义?products(:one)到底是什么意思?看代码,大概意思是“创建一个产品”,但是它是如何工作的呢?注意我是Ruby/Rails的新手,如果这些是微不足道的问题,我深表歉意。 最佳答案 如果您查看test/fixtures文件夹,您会看到一个products.yml文件。这是在您创建
是否有self验证的问题列表。看着那个,我可以确定我知道。我应该复习一下。在学习的过程中,我列了一个这样的list,但它只包含我在某处听说过的项目。我需要一段时间才能找到新的东西。 最佳答案 以下是针对ruby和Rails的一些测试列表。证书名称:RubyonRails谁提供:oDeskIncorporation认证费用:免费网站:https://www.odesk.com/tests/985?pos=0证书名称:RubyonRails提供者:Techgig.com(TimesBusinessSolutionsLimited(T