一、简介:
Olami Calculator是一款在键盘输入算式的普通计算器的基础上,增加了支持语音控制输入算式输出结果的人工智能计算器。此外还增加了多种动画效果,计算结果提示音功能,多元化主题换肤功能,以及保存计算公式,侧滑栏查看收藏记录等功能。网上也有许多语音计算器,但是打开看,只是添加了按钮提示音等,并不能识别我们对着计算器说的内容,而Olami Calculator可以实现不用手动敲击键盘,只需要把想知道结果的算式对着语音计算器说出来,例如三加四乘五、清空等,然后Olami会根据自己的一套语音识别系统帮我们准确识别出来。真正做到一款语音控制的计算器。

二、界面直观化展示

三、Olami SDK的配置
step 1:创建工程与导入sdk
Olami SDK下载地址:https://github.com/olami-developers/olami-sdk-ios.git
下载下来之后我们可以看到sdk-libs文件夹:,把libOlamiRecognizer.a静态库文件和OlamiRecognize.h对外提供接口文件拖入到我们的工程中。配置工程:

step 2 : 创建Olami应用
点击https://cn.olami.ai/open/website/home/home_show注册创建个人账号并登陆。进入后创建新应用,建好之后进入应用管理可以看到下面界面
点击查看key:

step 3:可直接导入的语音模块
点击配置模块选择你需要的语音模块,目前有天气,二十四点,新闻,听书,数学等50~多个模块,
,供大家选择的还是很多的。也可以点击“进入NLI系统”,再点击导入,可以看到如下界面,这里也是已经有的模块有需要的直接导入:

step 4:自定制语音模块
olami平台会为广大开发者提供一些已经写好了的语法模块,如果提供给大家的模块不能满足当下解析录入语音的需求,那么不要慌,下面就是教大家如何定制属于自己的模块。
首先.登录,进入我的应用(没有应用的话记得创建新应用哦),然后点击“进入NLI系统”。下面是点击之后的界面,可以看到右上角有导入和新增

如果没有所需的模块,那么就需要点击”新增“。我们做的是计算器那就给个名字,输入calculate,提交成功后可以看到我的模块里面有了一个新模块,:


点击calculate后面的进入模块。界面中有例句库,grammar,rule,slot,template模板。

现在做的计算器,那需要olami为我们识别出什么呢
比如:9+8+7 就这个算式而言,我们对着蜜蜜说完,是希望把数字还有符号都给我们识别出来的。

分析:”9”、”+”、”8”、”+”、”7”是我们需要系统帮我们识别并且返回给我们的变量,那就可以在slot设置5个变量,slot有五种类型(这里数字用float、符号用internal),rule是一些临时的中间表达式:[等于|结果是],modifier传递预定义好的信息,不管是slot还是rule都是为grammar服务的,要显示句子要写grammar。
各举一个例子:
grammar:名称:两个数结果等于多少 内容:[<再>][<数字一>][<符号一>][<数字二>][<结果是>|<等于几>]
slot:名称:数字一 类型:float 最长:50 最短:1
rule:名称:结果是 内容:[的]结果[是|等于[多少|几]] (|:或 []:可以省略的)。
要更多的了解点击这里查看OSL 语法描述语言 grammar的简介:https://cn.olami.ai/wiki/?mp=overview&content=quickstart.html。
一切就绪提交成功了之后,就可以测试了,测试无误满足需求,点击“发布”就可以使用啦!

上图:

  • 1.新增grammar:

  • 2.添加语料:写出希望可以识别的一句grammar,测试并提交

  • 3.最后测试无误一定要点发布

  • 4.完成配置
    以上都完成了回到应用管理,我们就可以配置自己搭建的模块了!

  • 5.再测试
    噔噔噔噔~可以使用了,变量都帮我们识别出来了!

四、代码处实现

先来看下OlamiRecognizer.h为我提供了哪些接口

 *返回结果*/
-(void)onResult:(NSData*)result;
/**取消本次会话*/
-(void)onCancel;/**识别失败*/
-(void)onError:(NSError *)error;/**音量的大小 音频强度范围时0到100*/
-(void)onUpdateVolume:(float) volume;/****开始录音*/
-(void)onBeginningOfSpeech;/***结束录音**/
-(void)onEndOfSpeech;
@endtypedef NS_ENUM(NSInteger, LanguageLocalization) {LANGUAGE_SIMPLIFIED_CHINESE = 0, //简体中文LANGUAGE_TRADITIONA_CHINESE = 1  //繁体中文
};
@interface OlamiRecognizer : NSObject
@property (nonatomic,weak) id<OlamiRecognizerDelegate> delegate;
@property (nonatomic, assign,readonly) BOOL isRecording;//是否正在录音
-(void)start;//开始录音
-(void)stop;//结束录音,开始识别
-(void)cancel;//取消本次回话
/***设置语系的选项,目前只支持一种,简体中文*/
-(void)setLocalization:(LanguageLocalization) location;
/***CUSID;//终端用户标识id,用来区分各个最终用户 例如:手机的IMEI*appKey;//创建应用的appkey*api;//要调用的API类型。现有3种:语义(nli)和分词(seg)和语音(asr)*appSecret;//加密的秘钥,由应用管理自动生成*/
-(void)setAuthorization:(NSString*)appKey api:(NSString*)api appSecret:(NSString*)appSecret cusid:(NSString*)CUSID;
-(void)setVADTimeoutFrontSIL:(unsigned int)value;//设置VAD前端点超时范围 1000~~10000(ms) 默认3000
-(void)setVADTimeoutBackSIL:(unsigned int)value;//设置VAD后端点超时范围  1000~~10000(ms) 默认2000
-(void)setInputType:(int) type;//设置是语音输入还是文字输入 0 为语音 1为文字输入
-(void)setLatitudeAndLongitude:(double) latitude longitude:(double)longit;//设置地理位置,参数为经纬度
-(void)sendText:(NSString*)text;//发送输入的文字

项目中,首先 初始化Olami语音识别对象并设置代理

 /***CUSID;//终端用户标识id,用来区分各个最终用户 例如:手机的IMEI*appKey;//创建应用的appkey*api;//要调用的API类型。现有3种:语义(nli)和分词(seg)和语音(asr)*appSecret;//加密的秘钥,由应用管理自动生成*/
#define AppKey @""//查看自己的
#define AppSecret @""
#define macID @""-(void)setupOLAMI{_olamiRecognizer= [[OlamiRecognizer alloc] init];_olamiRecognizer.delegate = self;//此处为OlamiRecognizerDelegate[_olamiRecognizer setAuthorization:AppKey api:@"asr" appSecret:AppSecret cusid:macID];//设置语言,目前只支持中文[_olamiRecognizer setLocalization:LANGUAGE_SIMPLIFIED_CHINESE];}

设置一个录音键

#pragma mark --录音键
- (IBAction)recordButton:(UIButton *)sender {//设置为语音模式(代理方法:0为语音)[_olamiRecognizer setInputType:0];//开始录音if (_olamiRecognizer.isRecording) {//isRecording = YES 即为录音模式[_olamiRecognizer stop];//代理方法[_recordButton setImage:[UIImage imageNamed:@"话筒4.png"] forState:UIControlStateNormal];}else{[_olamiRecognizer start];//代理方法[_recordButton setImage:[UIImage imageNamed:@"话筒7.png"] forState:UIControlStateNormal];[_recordButton.layer addAnimation:[self shine] forKey:@"shine"];//添加一个动画}
}//发光动画
- (CABasicAnimation *)shine{CABasicAnimation *animation =[CABasicAnimation animationWithKeyPath:@"shine"];animation.fromValue = [NSNumber numberWithFloat:1.0f];animation.toValue = [NSNumber numberWithFloat:0.0f];animation.autoreverses = YES;animation.duration = 0.5;animation.repeatCount = MAXFLOAT;animation.removedOnCompletion = NO;animation.fillMode = kCAFillModeForwards;animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];return animation;
}#pragma mark -- 录音结束(代理方法)
- (void)onEndOfSpeech {[_recordButton setImage:[UIImage imageNamed:@"话筒4.png"] forState:UIControlStateNormal];[_recordButton.layer removeAnimationForKey:@"shine"];
}

识别音量

#pragma mark--NLU delegate
- (void)onUpdateVolume:(float)volume {if (_olamiRecognizer.isRecording) {_waveView.present = volume/100;}
}waveview: 根据sin函数 y=Asin(ωx+φ)+b//e.g.:1.CGContextRef context = UIGraphicsGetCurrentContext();CGMutablePathRef path = CGPathCreateMutable();CGContextSetLineWidth(context, 3);CGContextSetLineCap(context, kCGLineCapRound);CGContextSetAllowsAntialiasing(context, true);CGContextSetRGBStrokeColor(context, 124 / 255.0, 145 / 255.0, 155 / 255.0, 1.0);  CGContextBeginPath(context);float y= (1 - _present) * rect.size.height;CGPathMoveToPoint(path, NULL, -10, y);for(float x=0;x<=rect.size.width;x++){y=  sin( 3*x/rect.size.width * M_PI + moveX/rect.size.width *M_PI ) *maxA + _currentLinePointY;CGPathAddLineToPoint(path, nil, x, y);}CGContextAddPath(context, path);CGContextDrawPath(context, kCGPathStroke);CGPathRelease(path);

界面差不多就这些,主要是看返回来的result
调用代理这个方法-(void)onResult:(NSData*)result; 其语义分析后的结果以一个json字符串的形式回调过来,对这个字符串进行解析,就可以获得想要的变量。

#pragma mark --返回结果
- (void)onResult:(NSData *)result {NSError *error;__weak typeof(self) weakSelf = self;if (error) {NSLog(@"error is %@",error.localizedDescription);}else{NSDictionary *json = [NSJSONSerialization JSONObjectWithData:resultoptions:NSJSONReadingMutableContainerserror:&error];NSLog(@"json=%@",json);if ([json[@"status"] isEqualToString:@"ok"]) {NSDictionary *asr = [json[@"data"] objectForKey:@"asr"];//如果asr不为空,说明目前是语音输入if (asr) {[weakSelf processASR:asr];}NSDictionary *nli = [[json[@"data"] objectForKey:@"nli"] objectAtIndex:0];NSDictionary *desc = [nli objectForKey:@"desc_obj"];int status = [[desc objectForKey:@"status"] intValue];if (status != 0) {// 0 说明状态正常,非零为状态不正常NSString *result  = [desc objectForKey:@"result"];dispatch_async(dispatch_get_main_queue(), ^{_resultLabel.text = result;//输出不正常提示_resultLabel.font = [UIFont systemFontOfSize:20];[_resultLabel startAnimation];_showTextView.text = asr[@"result"];AudioServicesPlaySystemSound (soundID);});}else{NSDictionary *semantic = [[nli objectForKey:@"semantic"]objectAtIndex:0];//对slot和算式的处理结果[weakSelf processSemantic:semantic asr:asr];//处理modifierNSArray *modifierArr = [semantic objectForKey:@"modifier"];[weakSelf processModifier:modifierArr result:desc[@"result"]];}}else{_showTextView.text = @"请说出要计算的公式";}}}#pragma mark --处理ASR语音对话节点
- (void)processASR:(NSDictionary*)asrDic {NSString *result  = [asrDic objectForKey:@"result"];if (result.length == 0) { //如果结果为空,则弹出警告框[self showAlert:@"没有接受到语音,请重新输入!"];return;}else{dispatch_async(dispatch_get_main_queue(), ^{NSString *str = [result stringByReplacingOccurrencesOfString:@" " withString:@""];//去掉字符中间的空格NSLog(@"answer result = %@",str);});}
}//处理semantic节点返回的slot
- (void)processSemantic:(NSDictionary*)semanticDic asr:(NSDictionary *)asr {NSMutableArray *sumArr = [NSMutableArray array];for (NSDictionary *dic in semanticDic[@"slots"]) {NSString *nameStr = dic[@"name"];//遍历,然后把slot添加到数组里NSString *textStr = [[sumArr componentsJoinedByString:@","] stringByReplacingOccurrencesOfString:@"," withString:@""];NSLog(@"textstr=%@",textStr);if (![textStr isEqualToString:@""]) {_passString = [self replaceInputStrWithPassStr:textStr];if (asr) {_lastAnswer = _resultLabel.text;//语音记录上一次记录}else{_lastAnswer = @"";}//第一次运算或者不再加if ([_lastAnswer isEqualToString:@"error"]||[_lastAnswer isEqualToString:@""]) {if (asr) {dispatch_async(dispatch_get_main_queue(), ^{_showTextView.text = [[textStr stringByReplacingOccurrencesOfString:@"2√" withString:@"√"] stringByAppendingString:@"="];//计算公式});textStr = [_calcultor calculatingWithString:_passString andAnswerString:@"0"];}else{textStr = [_calcultor calculatingWithString:_passString andAnswerString:@"0"];}//有结果考虑再运算的步骤}else{//有结果再运算的情况UniChar c = [_passString characterAtIndex:0];if (c =='-'|| c == '+'||c == 'x'||c =='/'){dispatch_async(dispatch_get_main_queue(), ^{_showTextView.text = [[_lastAnswer stringByAppendingString:[textStr stringByReplacingOccurrencesOfString:@"2√" withString:@"√"]] stringByAppendingString:@"="];//计算公式});textStr = [_calcultor calculatingWithString:[_lastAnswer stringByAppendingString:_passString] andAnswerString:@"0"];//}//有结果但是不想再运算else{dispatch_async(dispatch_get_main_queue(), ^{_showTextView.text = [[textStr stringByReplacingOccurrencesOfString:@"2√" withString:@"√"] stringByAppendingString:@"="];//计算公式});textStr = [_calcultor calculatingWithString:_passString andAnswerString:@"0"];}}dispatch_async(dispatch_get_main_queue(), ^{AudioServicesPlaySystemSound (soundID1);_resultLabel.font = [UIFont systemFontOfSize:50.0];_resultLabel.text = textStr;});[_resultLabel startAnimation];}
}

后台返回:语音内容是显示在asr字段里,大家可能会有疑问后台怎么识别的我们语音的内容,这是由于我们之前在olami平台创建新应用后导入了一套识别相应内容的grammar,这样olami的语义解析功能会为我们自动识别出想要得到的变量内容。

比如我说:3+6乘九等于几?
对应grammar语法:[<再>][<数字一>]<符号一><数字二><符号二><数字三>[<结果>|<等于>]
返回结果:

json={data =     {asr =         {final = 1;result = "\U4e09\U52a0\U516d\U4e58\U4e5d\U7b49\U4e8e\U51e0";"speech_status" = 0;status = 0;};nli =         ({"desc_obj" =                 {status = 0;};semantic =                 ({app = calculator;customer = 59530feb84aea6f385319c65;input = "\U4e09\U52a0\U516d\U4e58\U4e5d\U7b49\U4e8e\U51e0";modifier =                         ();slots =                         ({name = number3;"num_detail" =                                 {"recommend_value" = 9;type = float;};value = "\U4e5d";},{name = number1;"num_detail" =                                 {"recommend_value" = 3;type = float;};value = "\U4e09";},{name = number2;"num_detail" =                                 {"recommend_value" = 6;type = float;};value = "\U516d";},{name = symbol1;value = "+";},{name = symbol2;value = x;});});type = calculator;});};status = ok;
}

再加三等于几?
对应grammar:[<再>][<数字一>][<符号一>][<数字二>][<结果>|<等于>] 、
后台返回json字段:

json={data =     {asr =         {final = 1;result = "\U518d\U52a0\U4e09\U7b49\U4e8e\U51e0";"speech_status" = 0;status = 0;};nli =         ({"desc_obj" =                 {status = 0;};semantic =                 ({app = calculator;customer = 59530feb84aea6f385319c65;input = "\U518d\U52a0\U4e09\U7b49\U4e8e\U51e0";modifier =                         ();slots =                         ({name = again;value = a;},{name = number2;"num_detail" =                                 {"recommend_value" = 3;type = float;};value = "\U4e09";},{name = symbol1;value = "+";});});type = calculator;});};status = ok;
}

键盘上的收藏是用FMDB实现。

计算过程:涉及到算法,数据结构堆栈问题,大概思路设置优先级,设置两个栈,一个数据栈,一个运算符栈,在运算符栈底添加#方便处理。获取表达式第一个元素如果是数据添加到数据栈中,元素如果是运算符,那么每次都要跟运算符栈定元素比较优先级,如果取得的运算符的优先级大于栈顶元素优先级时,该运算符直接进栈,优先级不大的话,就要取栈顶运算符优先运算,最后碰到#停止。如果有记忆上一轮结果的话,结果需要放到数据栈栈进行下一次处理

NSString *subStr1 = [numberArray    objectAtIndex:0];UniChar c = [subStr1   characterAtIndex:0];//如果是数据则添加到数据栈if ((subStr1.length > 1 && [subStr1 hasPrefix:@"-"])||(c >='0'&&c <= '9')) {//插到数据栈顶[operandStackArray insertObject:subStr1 atIndex:0];//元素每次进栈之后要在表达式数组中移除该元素[numberArray   removeObjectAtIndex:0];}else{//元素是运算符,则每次都要跟运算符栈的栈顶元素比较优先级NSString *topStack2 = [stack2   objectAtIndex:0];//取得运算符栈栈顶元素if ([subStr1   isEqualToString:@"#"] && [topStack2   isEqualToString:@"#"]){//当取得元素和栈顶元素都为#时,说明表达式运算结束,获取运算结果result = [operandStackArray  objectAtIndex:0];break;}//对取得的元素在优先级表中获取优先级NSInteger one = [[_opPriority objectForKey:subStr1]integerValue];//对栈顶元素在优先级表中获取优先级NSInteger two = [[_opPriority objectForKey:topStack2]integerValue];if (one < two) {//取得的运算符的优先级大于栈顶元素优先级时,该运算符直接进栈[stack2 insertObject:subStr1 atIndex:0];[numberArray   removeObjectAtIndex:0];}else{NSString *strX = [operandStackArray  objectAtIndex:0];if ([strX   isEqualToString:@"error"]) {result =@"error";break;}double x1 = [strX  doubleValue];[operandStackArray removeObjectAtIndex:0];strX = [operandStackArray  objectAtIndex:0];if ([strX   isEqualToString:@"error"]) {result =@"error";break;}double x2 = [strX doubleValue];[operandStackArray removeObjectAtIndex:0];[stack2 removeObjectAtIndex:0];//计算结果result = [self calculateNumbersWithOperator:topStack2 betweenDouble:x2 andDoule:x1];if ([result   isEqualToString:@"error"]) {break;}[operandStackArray insertObject:result atIndex:0];}}

利用Olami SDK 实现语音控制计算器(iOS)相关推荐

  1. java利用科大讯飞SDK实现语音转文字的功能

    一,下载SDK,获取.pcm格式的语音文件 利用科大讯飞的SDK实现语音转文字的功能,首先可以登录科大讯飞的官网查看"语音听写Java SDK文档"语音听写 Java SDK 文档 ...

  2. 利用OLAMI在unity游戏中加入中文语音控制(一)

    (欢迎转载.本文源地址:http://blog.csdn.net/speeds3/article/details/76209152) 最近打算尝试一下OLAMI在游戏中应用的可能性,这里做一下记录. ...

  3. 利用手机app语音控制arduino

    最近做了一款语音识别的App,想着利用手机控制arduino实现RGB的调节,开发思路:基于讯飞语音识别做一款能识别我们语音的App,识别出语音之后,对应的文字转UTF-8编码,通过蓝牙串口通讯发送到 ...

  4. IOS 集成讯飞语音唤醒+语音识别,实现语音控制效果

    前言 最近项目上需要实现语音调度,一开始是想用苹果原生speech Framework框架的,但是网上找了很久都没有原生实现语音唤醒功能的栗子,到时有不少百度,讯飞的语音唤醒,语音识别的栗子,不过都是 ...

  5. 使用OLAMI SDK和讯飞语音合成制作一个语音回复的短信小助手

    现代人的生活越来越离不开手机,但我们总会遇到一些时候不方便用手去操作,比如开车,玩游戏的时候.智能语音时代这种情况有了新的解决方案.本文介绍了一个使用OLAMI Android SDK进行语音识别和理 ...

  6. 【阿里云生活物联网架构师专题 ②】esp8266 sdk 直连接入阿里云物联网平台,实现天猫精灵找队友零配网功能和语音控制;

    本系列博客学习由非官方人员 半颗心脏 潜心所力所写,仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 1.esp32接入阿里云物联网平台,实现天猫精灵语音控制: 2.es ...

  7. 【阿里云生活物联网架构师专题 ①】esp32 sdk 直连接入阿里云物联网平台,实现天猫精灵语音控制;

    本系列博客学习由非官方人员 半颗心脏 潜心所力所写,仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 1.esp32接入阿里云物联网平台,实现天猫精灵语音控制: 2.es ...

  8. python控制电脑关机_Python利用智能音箱语音控制电脑开关机

    完成下面这个有趣的实验,你所需要的东西有:1.路由器(能够形成局域网,且电脑已用网线连接) 2.一个智能音箱(本教程使用的是亚马逊 Echo Dot 2) 3.主板支持wake on lan (大部分 ...

  9. 语音控制机器人小车运动科大讯飞SDK

    在前面文章小车实现语音识别的基础之上,对小车实现语音控制运动 修改CMakeLists.txt文件 在末尾加上以下代码: add_executable(sub_word src/sub_word.cp ...

最新文章

  1. java的json解析工具_json文件解析工具类(java)
  2. 电容式传感器位移性能试验报告_圆柱形电容式接近开关可以分3类?
  3. wps临时文件不自动删除_今天才发现,原来C盘这些文件夹可以删除,难怪你的电脑越用越卡...
  4. 2016年1月28日报
  5. Mysql中把varchar类型的字段转化为tinyint类型的字段
  6. 第一门编程语言,我应该学什么?
  7. 莫队(bzoj 2038: [2009国家集训队]小Z的袜子(hose))
  8. [BUG]Git Sever搭建与相关错误处理
  9. ThinkPad SL400 改装Win2003方法以及驱动下载列表
  10. 乌班图Linux于windows系统怎么切换
  11. 10个5G应用优秀案例!工业互联网、智慧城市、智慧医疗等都在这里
  12. 项目:艺龙国内机票实时数据爬虫
  13. uniapp 添加table不显示
  14. 团队管理(一)-会议纪要的高效记录和执行
  15. sqlserver数据库的使用
  16. 基于科大讯飞语音识别demo(离线)
  17. 《程序员面试金典(第6版)》 面试题 08.11. 硬币(动态规划,组合问题,C++)
  18. python期末大作业_上海交通大学python期末大作业题目(姚天昉)
  19. 再见,Pycharm
  20. 多测师肖sir_高级讲师_第2个月第33讲解jenkins

热门文章

  1. 自然语言处理-应用场景-文本分类:基于LSTM模型的情感分析【IMDB电影评论数据集】--(重点技术:自定义分词、文本序列化、输入数据批次化、词向量迁移使用)
  2. 博客园样式(仿简书)
  3. Python单元测试详解
  4. opencv 提取彩色图像轮廓
  5. FreeMarker学习手册
  6. extra argument in call
  7. 前端~css~基准线与行高、行距和半行距/圆角矩形~
  8. Android 手机录制wav格式音频文件实现
  9. 【Nginx 源码学习】Nginx 的缓冲区
  10. 读书笔记-kafka常用操作命令-kafka-topics.sh