转载

有趣的认知计算

跟随我们学习如何修改使用 IBM Watson Speech to Text 服务的 speech-to-text-nodejs 示例应用程序。您将创建一个游戏,其中的主要角色 Melvin 将根据您的语音命令而走进一座房子、销毁它并进入下一关。您还将使用 Alchemy API Relation Extraction 服务 从 Speech to Text 服务创建的文本中提取关系。了解这种认知计算的力量之后,就可以尽情地玩这个游戏了!

有趣的认知计算

点击查看大图

关闭 [x]

有趣的认知计算

构建这个应用程序所需的准备工作

学习更多知识。开发更多项目。联络更多同行。

全新的 developerWorks Premium 会员计划支持会员全权访问强大的开发工具和资源,包括 500 篇通过 Safari Books Online 提供的针对应用程序开发人员的顶级技术文章,参加重要开发人员活动可享大幅折扣,最新的 O'Reilly 大会的视频回放,等等。观看 Govind Baliga 的这个揭秘视频 ,他揭示了 developerWorks Premium 会员能获得的好处。立即注册。

  • 一个 Bluemix 帐户(您可 在这里申请一个免费试用帐户 。或者您是否知道 developerWorks Premium 提供了 12 个月的 Bluemix 订阅和在 Bluemix 上开发云应用程序的 240 美元额度?)
  • Cloud Foundry CLI 工具
  • 对 JavaScript 拥有基本的了解
  • 下载并安装 Node.js 框架和 Node Package Manager

运行应用程序

获取代码

第 1 步:创建语音到文本转换 Bluemix 应用程序

首先,我们下载并修改 Speech to Text 示例应用程序。然后,我们需要设置我们的 Bluemix 实例并为它配备访问 Watson Speech to Text 服务和 AlchemyAPI 服务的能力。

  1. 克隆或下载 Speech to Text 示例应用程序:
    • 使用下面这个命令克隆 Git 存储库:

      $ git clone https://github.com/watson-developer-cloud/speech-to-text-nodejs.git

    • 从 speech-to-text-nodejs 存储库下载 master.zip 文件 ,将它解压到您选择的一个目录中。
  2. 转到项目目录,编辑 manifest.yml 文件,使用清单 1清单 1中所示的代码更新它。

    清单 1. 编辑 manifest.yml 文件

    ---     declared-services:       alchemy-api-service-free:         label: alchemy_api         plan: Free       speech-to-text-service-standard:         label: speech_to_text         plan: standard     applications:     - name: speech-to-text-game       path: .       command: npm start       memory: 512M       services:       - speech-to-text-service-standard       - alchemy-api-service-free       env:         NODE_ENV: production
  3. 使用 cf

    命令行工具连接到 Bluemix:

    $ cf api https://api.ng.bluemix.net

    $ cf login -u &lt;<em>your_user_ID</em>&gt;

  4. 在 Bluemix 中创建 Speech to Text 服务。

    $ cf create-service speech_to_text standard speech-to-text-service-standard

  5. 在 Bluemix 中创建 Alchemy API 服务。

    $ cf create-service alchemy_api free alchemy-api-service-free

  6. 动态推送您的 Bluemix 应用程序: $ cf push

第 2 步:为应用程序设置您的凭据

对于本地开发,您可将您的凭据存储在一个单独的环境文件(一个 .env 文件)中,让它们在存储库之外。然后,您可以灵活地在您的生产环境中使用不同的凭据。所幸,一个名为 node-env-file 的 Node 库执行了大部分工作。下面设置我们的应用程序来使用这个库并将我们的凭据存储在一个 .env 文件中。

  1. 将 node-env-file 库依赖项添加到应用程序中:

    npm install --save node-env-file

  2. 获取 Bluemix 服务的凭据:
    1. 登录到您的 Bluemix 帐户
    2. 从 Bluemix 仪表板中,选择您的新 speech-to-text-game 应用程序。
    3. 展开 Speech to Text 服务和 AlchemyAPI 服务的凭据。 有趣的认知计算 有趣的认知计算
    4. 将凭据复制到一个文本文件中,以便在设置 .env 文件时使用。
  3. 创建 .env 文件并插入凭据,如清单 2清单 2中所示。

    清单 2. .env 文件中的凭据

    USERNAME=[your-speech-to-text-username-goes-here]   PASSWORD=[your-speech-to-text-password-goes-here]   ALCHEMY_API_KEY=[your-alchemy-api-key-goes-here]
  4. 更新 app.js 文件以使用 .env 文件中的凭据,如清单 3清单 3中所示。

    清单 3. 编辑 app.js 以使用 .env 文件

    var express      = require('express');   var app          = express();   var vcapServices = require('vcap_services');   var extend       = require('util')._extend;   var watson       = require('watson-developer-cloud');   var env          = require('node-env-file');    // Bootstrap application settings   require('./config/express')(app);    // Load .env environmental variables   env(__dirname + '/.env', { raise: false });    var alchemyApiCredentials = vcapServices.getCredentials('alchemy_api');    // For local development, replace username and password   var speechToTextConfig = extend({     version: 'v1',     url: 'https://stream.watsonplatform.net/speech-to-text/api',     username: process.env.USERNAME || '<username>',     password: process.env.PASSWORD || '<password>'   }, vcapServices.getCredentials('speech_to_text'));    var alchemyConfig = extend({     api_key: process.env.ALCHEMY_API_KEY || '<api_key>'   }, vcapServices.getCredentials('alchemy_api'));    var authService = watson.authorization(speechToTextConfig);    var alchemyLanguage = watson.alchemy_language(alchemyConfig);

    请注意,我们将 config 对象重命名为 speechToTextConfig

  5. 更新 app.js 中的 express API 端点 /api/token (它公开一个端点以允许 JavaScript 应用程序请求一个临时 Speech To Text 令牌),以便使用更新的名称,如清单 4清单 4

    中所示。

    清单 4. 编辑 app.js 以使用 express API

    // Get token using your speech-to-text credentials   app.post('/api/token', function(req, res, next) {     authService.getToken({url: speechToTextConfig.url}, function(err, token) {       if (err)         next(err);       else         res.send(token);     });   });

    您还需要创建一个 API 接口来代为处理从 JavaScript 应用程序到 Alchemy API Relations API 的 Alchemy API 请求,这样一来凭据就不可见,进而更加安全。

  6. 更新 app.js 文件,公开一个新端点,如清单 5清单 5中所示。

    清单 5. 编辑 app.js 文件以创建新端点

    // User our secret API key to proxy request to the alchemy-keywords API   app.post('/api/alchemy-relations', function(req, res, next) {     alchemyLanguage.relations(req.body, function(err, response) {       if (err) {         next(err);       } else {         res.send(response);       }     });   });

第 3 步:与 Alchemy API 代理通信

现在您已有一个 API 端点来代为处理 Alchemy Relations API 请求,下面更新我们的应用程序来使用它们。

  1. 更新 src/utils.js 文件来创建新端点,如清单 6清单 6中所示。

    清单 6. 创建新端点

    exports.createAlchemyProxy = function() {     return {       getAlchemyRelations: function(text, callback) {         var url = '/api/alchemy-relations';         var relationsRequest = new XMLHttpRequest();          relationsRequest.open('POST', url, true);         relationsRequest.setRequestHeader('csrf-token', $('meta[name="ct"]').attr('content'));         relationsRequest.setRequestHeader('Content-type', 'application/json');          relationsRequest.onreadystatechange = function() {           if (relationsRequest.readyState !== 4) {             return;           }            if (relationsRequest.status === 200) {             var resp = JSON.parse(relationsRequest.responseText);             callback(null, resp);           } else {             var error = 'Cannot reach server';             if (relationsRequest.responseText) {               try {                 error = JSON.parse(relationsRequest.responseText);               } catch (e) {                 error = relationsRequest.responseText;               }             }              callback(error);           }         };          relationsRequest.send(JSON.stringify({ text: text }));       }     };   };
  2. 更新 src/utils.js 文件以使用我们刚创建的 Alchemy API 代理对象,如清单 7清单 7中所示。

    清单 7. 使用 Alchemy API 代理

    var utils = require('./utils');   var alchemyProxy = utils.createAlchemyProxy();    alchemyProxy.getAlchemyRelations(     "Move Marvin to the left side of the screen.",     function(err, json) {       // Do something with relation results     }   );
  3. 更新 src/index.js 内的视图上下文对象,以包含一个用于使用 Alchemy 代理的 hook。
    1. $(document).ready 内初始化该代理,如清单 8清单 8中所示。

      清单 8. 初始化代理

      $(document).ready(function() {     var tokenGenerator = utils.createTokenGenerator();     var alchemyProxy = utils.createAlchemyProxy();      ...
    2. viewContext 添加一个对 alchemyProxy 的引用,如清单 9清单 9中所示。

      清单 9. 添加 viewContext 引用

      tokenGenerator.getSpeechToTextToken(function(err, token) {     ...      var viewContext = {       currentModel: 'en-US_BroadbandModel',       models: models,       token: token,       alchemyProxy: alchemyProxy,       bufferSize: BUFFERSIZE     };   });

第 4 步:创建游戏

要创建游戏,我们将利用前人的成果,使用 Phaser 游戏引擎来让我们的工作更容易。

观看: 应用程序实际运行和您将在创建这个简单游戏时添加的游戏交互的快速演示

Phaser 框架的使用实质上非常简单,而且我们的语音到文本转换游戏可以仅包含这些函数:

  • preload 用于加载资源,比如游戏中使用的图像和子画面。
  • create 用于设置游戏
  • update 用于检测按钮按下和碰撞
  1. 下载 Phaser 游戏引擎的最新版本 并将它保存到 public/js/phaser.min.js 目录。
  2. 通过将它添加到 views/index.ejs 文件底部来将它包含在我们应用程序的网页中,如清单 10清单 10中所示。

    清单 10. 添加 Phaser

    <script src="https://code.jquery.com/jquery-1.11.3.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>  <!-- Place js/phaser.min.js here at the end of the document just before we load our app code --> <script src="js/phaser.min.js"></script> <script src="js/index.js"></script>
  3. 在 src/views/game.js 中创建一个新文件,该文件将包含核心游戏代码。
  4. 创建一个函数来加载和设置游戏,如清单 11清单 11中所示。

    清单 11. 核心游戏代码,包含 create 函数

    /* global Phaser */    var game;    function initGame(ctx) {     var updateLength = 1;     var updateCount = 0;     var command = '';     var speechButtonRate = 1000;     var nextSpeechButtonPressTime = 0;     var isFairyNextToHouse = false;     var toggleRecordSpeechCommandButton;     var player;     var houses;     var house;     var rightHousePosition = 720;     var leftHousePosition = 0;     var currentHousePosition = 1;     var cursors;     var level = 1;     var levelString;     var levelText;      game = new Phaser.Game(         750,          300,          Phaser.AUTO,          'example-game',          { preload: preload, create: create, update: update, render: render }     );      function preload() {       var fairyData = [           '....F...........F...',           '...F1F.........F1F..',           '..F111F.......F111F..',           '.F11111F.333.F1111F..',           '.F11111534333511111F',           'F111113323333331111F',           'F111133333333333111F',           'F111137773337773111F',           '.F1113858777858311F.',           '.F1113858888858311F..',           '.F111378888888731F..',           '..F11133333333311F..',           '...FF....65556......',           '.......6.757.6......',           '.......5.666.5......',           '.......5.777.5......',           '........6..7........',           '........7..7........'       ];        var houseData = [           '.....6.....',           '....676....',           '...67676...',           '..6767676..',           '.676767676.',           '67676767676',           '55BBB555555',           '55BBB55EE55',           '552BB55EE55',           '55BBB556655',           '55BBB555555',           '55555555555',           '55555555555'       ];        game.create.texture('fairy', fairyData, 3, 3, 0);       house = game.create.texture('house', houseData, 3, 3, 0);     }      function create() {       game.physics.startSystem(Phaser.Physics.ARCADE);        player = game.add.sprite(100, 100, 'fairy');        game.physics.arcade.enable(player);        player.body.collideWorldBounds = true;       player.body.gravity.y = 500;        houses = game.add.physicsGroup();       house = houses.create(rightHousePosition, 260, 'house');        //  The current level       levelString = 'Level : ';       levelText = game.add.text(10, 10, levelString + level, { font: '34px Arial', fill: '#fff' });        houses.setAll('body.immovable', true);        cursors = game.input.keyboard.createCursorKeys();       toggleRecordSpeechCommandButton = game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);     }      function update() {      }      function render() {      }      return game;   }    exports.initGame = initGame;

    在包含 game = new Phaser.Game(750, 300, Phaser.AUTO, 'example-game', { preload: preload, create: create, update: update, render: render }); 的行中,记下字符串 'example-game' 。此名称是将作为游戏的容器的 HTML 元素 ID 的名称。

  5. 再次更新 views/index.ejs 文件,并将以下元素添加到 HTML 中:

    &lt;div id=&quot;example-game&quot;&gt;&lt;/div&gt;

  6. 更新 src/views/index.js 文件以使用 initGame 函数初始化该游戏,该函数已从 src/views/game.js 中导出,如清单 12清单 12中所示。

    清单 12. 初始化游戏

    var initSessionPermissions = require('./sessionpermissions').initSessionPermissions;   var initAnimatePanel = require('./animatepanel').initAnimatePanel;   var initShowTab = require('./showtab').initShowTab;   var initDragDrop = require('./dragdrop').initDragDrop;   var initPlaySample = require('./playsample').initPlaySample;   var initRecordButton = require('./recordbutton').initRecordButton;   var initFileUpload = require('./fileupload').initFileUpload;   var initDisplayMetadata = require('./displaymetadata').initDisplayMetadata;   var initGame = require('./game').initGame;    exports.initViews = function(ctx) {     console.log('Initializing views...');     initPlaySample(ctx);     initDragDrop(ctx);     initRecordButton(ctx);     initFileUpload(ctx);     initSessionPermissions();     initShowTab();     initAnimatePanel();     initShowTab();     initDisplayMetadata();     initGame(ctx);   };
  7. 现在游戏已设置好,从命令行使用 $ npm build$ npm start 来构建并运行它。
  8. 打开您的浏览器并转到 http://localhost:3000,您应该看到游戏角色和一个在 #example-game 元素内绘制的房子。

第 5 步:让魔法精灵 Melvin 动起来

现在我们需要更新代码,以允许游戏触发和处理语音输入,处理空格按钮按下事件,以及将解析的关系与命令相关联。

  1. 更新 src/views/recordbutton.js,公开一个触发并处理语音的回调,如清单 13清单 13中所示。

    清单 13. 触发并处理语音

    var Microphone = require('../Microphone');   var handleMicrophone = require('../handlemicrophone').handleMicrophone;   var showError = require('./showerror').showError;    exports.initRecordButton = function(ctx) {     var running = false;     var micOptions = {       bufferSize: ctx.buffersize     };     var mic = new Microphone(micOptions);      ctx.toggleRecordSpeechCommand = function() {       var currentModel = localStorage.getItem('currentModel');        if (!running) {         $('#resultsText').val('');   // clear hypotheses from previous runs         console.log('Not running, handleMicrophone()');         handleMicrophone(ctx.token, currentModel, mic, function(err) {           if (err) {             var msg = 'Error: ' + err.message;             console.log(msg);             showError(msg);             running = false;           } else {             console.log('starting mic');             mic.record();             running = true;           }         });       } else {         console.log('Stopping microphone, sending stop action message');         $.publish('hardsocketstop');         mic.stop();         running = false;          ctx.alchemyProxy.getAlchemyRelations(           $('#resultsText').val(),           function(err, response) {             if (!err) {               ctx.relations = response.relations;             }           }         );       }     };   };

    清单 13清单 13 中的代码与之前类似,但按钮事件被删除,并使用 toggleRecordSpeechCommand 属性替换为一个分配给视图上下文的回调。 toggleRecordSpeechCommand 函数触发麦克风并使用之前公开的 alchemyProxy 对象将语音到文本转换结果提交到 Alchemy API。Alchemy API 成功处理关系后,会通过视图上下文的 relations 属性与其他视图共享。

  2. 再次更新 src/views/game.js。在文件的开头,添加清单 14清单 14中描述主体和操作的代码,一个检测文本显示方向的函数,以及一个解析关系来关联命令的方法。

    清单 14. 添加空格按钮特性

    var LEFT_COMMAND = 'left';   var RIGHT_COMMAND = 'right';   var subject = 'melvin';    var relationActions = [     {       action: 'move',       parseGameCommand: parseMoveActionCommand     }   ]   function parseMoveActionCommand(text) {     var directionCommands = [LEFT_COMMAND, RIGHT_COMMAND];      return directionCommands.find(function(directionCommand) {       return text.toLowerCase().split(' ').indexOf(directionCommand) !== -1;     });   }    function parseRelationsForCommand(relations) {     try {       var relationWithSubject = relations.find(function(relation) {         return relation.subject.text.toLowerCase() === subject;       });        if (!relationWithSubject) {         return;       }        var relationAction = relationActions.find(function(relationAction) {         return relationAction.action === relationWithSubject.action.lemmatized.toLowerCase();       });        if (!!relationAction) {         return relationAction.parseGameCommand(relationWithSubject.object.text);       }     } catch (e) {       console.log('An error occurred while parsing speech commands', e);     }   }
  3. 更改游戏中的 update 函数以检测空格按下事件,并在视图上下文中检测到新关系时寻找命令,如清单 15清单 15中所示。

    清单 15. 使用 update 函数

    function update() {     if (updateCount > updateLength) {       command = '';     }      if (ctx.relations) {       console.log(ctx.relations);       command = parseRelationsForCommand(ctx.relations);       updateCount = 0;       ctx.relations = null;     }      if (toggleRecordSpeechCommandButton.isDown) {       if (game.time.time >= nextSpeechButtonPressTime) {         nextSpeechButtonPressTime = game.time.time + speechButtonRate;         console.log('toggling recording');         ctx.toggleRecordSpeechCommand();       }     }      switch (command) {       case LEFT_COMMAND:         updateCount++;         player.body.velocity.x = -250;         break;       case RIGHT_COMMAND:         updateCount++;         player.body.velocity.x = 250;         break;     }   }
  4. 使用这些命令构建并运行应用程序:

    $ npm build

    $ npm start

  5. 打开您的浏览器并转到 http://localhost:3000。试用新特性。

让 Melvin 造成破坏并进入下一关

最后,让我们更新游戏来允许精灵 Melvin 销毁游戏中的房子并进入下一关。

  1. 更新 src/views/game.js 以生成一个函数来解析销毁操作命令,并将该操作添加到 relationActions 数组中,如清单 16清单 16中所示。

    清单 16. 使用 relationActions 数组

    function parseDestroyActionCommand(text) {     if (isFairyNextToHouse amp;amp; text.toLowerCase().split(' ').indexOf('house') !== -1) {       return DESTROY_HOUSE_COMMAND;     }   }    var relationActions = [     {       action: 'move',       parseGameCommand: parseMoveActionCommand     },     {       action: 'destroy',       parseGameCommand: parseDestroyActionCommand     }   ];
  2. 创建一个碰撞处理函数,该函数将在精灵 Melvin 触碰一座房子时触发一个标记,并修改 update 函数来检测精灵与房子之间的碰撞,然后响应销毁命令,如清单 17清单 17中所示。

    清单 17. 创建碰撞处理函数

    var DESTROY_HOUSE_COMMAND = 'destroy_house';    function collisionHandler() {     isFairyNextToHouse = true;   }    function update() {     game.physics.arcade.collide(player, houses, collisionHandler);      ...      switch (command) {       case LEFT_COMMAND:         updateCount++;         player.body.velocity.x = -250;         break;       case RIGHT_COMMAND:         updateCount++;         player.body.velocity.x = 250;         break;       case DESTROY_HOUSE_COMMAND:         house.kill();         isFairyNextToHouse = false;         level++;         levelText.text = levelString + level;         command = '';          if (currentHousePosition == 1) {           currentHousePosition = 2;           house = houses.create(leftHousePosition, 260, 'house');           houses.setAll('body.immovable', true);         } else {           currentHousePosition = 1;           house = houses.create(rightHousePosition, 260, 'house');           houses.setAll('body.immovable', true);         }          break;     }   }
  3. 使用这些命令构建并运行应用程序:

    $ npm build

    $ npm start

  4. 打开您的浏览器并转到 http://localhost:3000。试用最终成果。

可在 这里 播放最终游戏的演示,可在 这里 查看最终的源代码。

运行应用程序

获取代码

结束语

在本文中,您学习了如何使用 Watson Speech to Text 服务和 Alchemy API 服务来创建一个语音控制游戏。您直接体验到了这些 API 的强大功能和潜力。希望您已受到启发,会试验并创建应用程序来扩展这个游戏。

BLUEMIX SERVICE USED IN THIS TUTORIAL: IBM Watson Speech to Text 此服务将人类语音转换为书面文字。使用它填补口语与书面语之间的空白,包括语音控制嵌入式系统,听译会议和电话会议,听写电子邮件和笔记。

正文到此结束
Loading...