Node.js: Visual Studio Code を使って首振り人形生成アプリを作ろう

 

本記事は、マイクロソフト本社の The Visual Studio Blog の記事を抄訳したものです。 【元記事】 Node.js: From Zero to Bobble with Visual Studio Code 2016/2/16

 

Node.js は、JavaScript を使ってすばやくスケーラブルなアプリを作成できるプラットフォームで、サーバー、Internet of Things デバイス、デスクトップ アプリケーションなどさまざまなものに対応しています。ただ、それだけではありません。

今回ご紹介する首振り人形生成アプリもその1つです!

clip_image001

 

首振り人形生成アプリを作ろう

首振り人形を 1 つ作りたいわけではありません。たくさん作りたいので、試しに Node.js を使って首振り人形生成アプリを作ってみたいと思います。初心者の方にもベテランの Node 開発者の方にもお楽しみいただける内容ですので、どうぞお付き合いください。手順は以下のとおりです。

  1. Node.js の Web フレームワークとして人気の Express を使って画像アップロード用の簡単な Web サービスを構築します。
  2. マイクロソフトの Project Oxford (英語) に公開されている人工知能ベースの API を使用して顔を検出します。
  3. 顔部分を長方形にトリミングし、さまざまな角度に回転させ、元の画像に貼り付けます。
  4. 画像を 1 つの GIF ファイルに統合します。

clip_image003

 

 

位置について、ヨーイ、Node!

まず以下をインストールします。

  • Node.js v5.x (英語): Node.js の安定版の最新リリースで、依存関係を最大限にフラットなディレクトリ構造にインストールする npm v3 パッケージにバンドルされています。
  • Visual Studio Code (英語): マイクロソフトが提供する無料のクロス プラットフォーム対応エディター (Windows、OS X、Linux) で、Node.js の便利なデバッグ機能と IntelliSense 機能を使用できます。
  • Git (英語): アプリを Azure に展開するときに使用します。

 

 

アプリケーションのスキャフォールディング

まず、npm を使用して express-generator パッケージをグローバルにインストールします。

 

 C:\src> npm install express-generator -g
  

次に、以下のコマンドを実行します (もちろんコメント部分は入力する必要はありません)。

 C:\src> express myapp #プロジェクト フォルダーに Express のスキャフォールディングを生成
C:\src> cd myapp #プロジェクト フォルダーに移動
C:\src\myapp> npm install #package.json に含まれる依存関係をインストール
C:\src\myapp> code . #カレント フォルダー内の Visual Studio Code を起動!
  

Visual Studio Code の左側にある [Explore] ウィンドウを開き、生成された内容を確認します。

clip_image005

  • package.json: このプロジェクトで必要なサードパーティの依存関係のリストや、その他の有用な構成情報が含まれています。npm install を実行すると、このファイルの内容に従って各依存関係が自動的にダウンロードおよびインストールされます。
  • node_modules: npm install がすべての依存関係を保存する場所です。
  • bin/www: アプリケーションのエントリ ポイントです (www には .js という拡張子がありませんが、JavaScript ファイルです)。関連するポートを作成する前に、require('../app.js')app.js のコードを呼び出す点に注目してください。
  • app.js: アプリケーション構成用の定型コードです。
  • views/* : クライアント側 UI として表示されるテンプレートです。ここでは Jade テンプレート エンジンを使用します (app.js を参照)。お好みに応じて他のエンジンを使用してもかまいません。
  • routes/* : 各種要求の処理方法を定義します。既定では、生成されたテンプレートは / および /users への GET 要求を処理します。

新しくスキャフォールディングされたアプリが動作するかどうかを確認します。コマンド ラインで node ./bin/www を実行し、ブラウザーで localhost:3000 にアクセスしてアプリが実行されていることを確認します。

clip_image007

コマンド ラインで Ctrl キーを押しながら C キーを押してサーバーを停止し、以下のように他の npm パッケージをインストールします。

 

 C:\src\myapp> npm install --save gifencoder jimp multer png-file-stream project-oxford

 

npm に慣れていない方向けに説明すると、--save パラメーターによって package.json の “dependencies” セクションにもこれらのパッケージのエントリが作成されます。このため、ソースコードを他の開発者に提供する際に node_modules 内のパッケージをすべて含めなくても (コードをリポジトリにアップロードする場合など)、npm install を実行するとすべての依存関係を簡単にダウンロードできます。

また、依存関係は package.json に直接追加することもできます。このとき、Visual Studio Code の IntelliSense とオートコンプリートを使用して、使用可能なパッケージや最新バージョンを入力できます。その後、npm install を再度実行すると、ダウンロードされます。

以下に、npm の便利なヒントを 2 つ紹介します。

  • 依存関係に関するドキュメントを表示するには、npm doc <``パッケージ名`` > を実行します。
  • --save パラメーターの代わりに --save-dev パラメーターを使用すると、依存関係は “dev-dependencies” というセクションに書き込まれます。これは、テスト フレームワークやタスク実行ツールなどの開発ツールのように、展開したアプリケーションに同梱されないパッケージ用のリストです。

 

エディターの設定 : デバッガーの起動と IntelliSense

表示する画面の切り替えを最小限に抑えるために、エディターからアプリケーションを直接起動できるように Visual Studio Code を構成します。F5 キーを押し、Node.js デバッグ エンジンを選択すると、 .vscode/launch.json 構成ファイルが自動的に生成されます。再び F5 キーを押すと、デバッガーがアタッチされた状態でアプリケーションが起動します。routes/index.js にブレークポイントを設定し、ブラウザーで localhost:3000 を再度読み込むと、コール スタックや変数などの検証にデバッグ用ウィンドウを使用できるようになります。

clip_image009

Visual Studio Code では、Node.js や多数の主要なパッケージのコード補完機能や IntelliSense 機能を使用できます。

clip_image011

JavaScript は動的型付き言語であるため、IntelliSense は型定義ファイル (.d.ts) (英語) に大きく依存しています。このファイルは通常 npm パッケージに含まれています。また、コミュニティでも型定義ファイルが提供されており、tsd (TypeScript 定義) パッケージを使用してインストールすることができます。

 

 C:\src> npm install tsd –g          #tsd をグローバル ツールとしてインストール
C:\src> cd myapp
C:\src\myapp> tsd install express   #IntelliSense を Express 向けにインストール

 

 

 

 

ファイルをアップロードする

アプリケーションのスキャフォールディングには簡単な Express Web サービスも含まれています。それでは、ファイルのアップロードを許可するように設定しましょう。まず、views/index.jade の内容を以下のテンプレートに置き換えて、基本的な参照およびアップロード用 UI を追加します。

 

 <!-- views/index.jade -->   extends layout   block content
  div(style='width:400px')
    h1= title
    p= location
    p Upload a picture of a head to get started!   
    form(action='/'       method='post' enctype="multipart/form-data")
      input(type="file" name="userPhoto")
      input(type="submit" value="Upload Image" name="submit")

    img(src= image)
  

このとき、Visual Studio Code でアプリを再起動してからブラウザーで localhost:3000 を再び読み込むと、ファイル アップロード用の UI が表示されます。しかし、POST 要求の処理に対応していないため、404 エラーが返されます。この処理を行うために、デバッガーを停止して以下のコードを routes/index.js に追加し、req.file.path のファイルを処理するようにします。これは、出力された首振り人形を最終的にページに表示するための処理です。

 

 // 必要な依存関係
var fs = require('fs');
var oxford = require('project-oxford');
var Jimp = require('jimp');
var pngFileStream = require('png-file-stream');
var GIFEncoder = require('gifencoder');

// POST リクエストのハンドル
router.post('/', function (req, res, next) {
     var imgSrc = req.file ? req.file.path : '';
     Promise.resolve(imgSrc)
         .then(function detectFace(image) {
             console.log("TODO: detect face using Oxford API.");
         })
         .then(function generateBobblePermutations (response) {
             console.log("TODO: generate multiple images with head rotated.");
         })
         .then(function generateGif (dimensions) {
             console.log('TODO: generate GIF')
             return imgSrc;
         }).then(function displayGif(gifLocation) {
             res.render('index', { title: 'Done!', image: gifLocation })
         });
});

 

JavaScript は非同期的であるため、アプリケーションの処理の流れを把握しづらいことがあります。そこで、Promises の新しい EcmaScript 6 機能を使用することにより、detectFacegenerateBobblePermutationsgenerateGif をシーケンシャルに実行すると同時に、コードの可読性を維持します。

アプリを実行すると、コンソールには 3 つの TODO が表示されますが、アップロードされたファイルを特定の場所に保存する処理が作成されていません。この処理を行うために、app.js の中の app.use/ および /users のルートを定義する部分 (25 行目付近) の直前に以下のコードを追加します。

 

 // Public/images に存在するファイルをブラウザーで表示できるように公開 
app.use('/public/images', express.static('public/images'));

// multer ミドルウェアをしようして 写真を public/images にアップロード
var multer = require('multer');
app.use(multer({dest: './public/images'}).single('userPhoto'));

 

アプリケーションを再起動すると、アップロードした写真が表示されます。この写真は public/images に保存されています。

 

 

 

顔を検出する

顔を検出するというと複雑な処理のようですが、このような人工知能は Project Oxford (英語) の Face API から簡単に利用できます。routes/index.js で、Promise チェーンに含まれる detectFace 関数を以下のように変更します。

 

 function detectFace(image) {
     var client = new oxford.Client(process.env.OXFORD_API);
     return client.face.detect({path: image});
}

 

Project Oxford クライアントを使用するには、OXFORD_API 環境変数から取得した API キーが必要になります。こちらのページから無料でサインアップし、無料の Face API のキーを要求します。このキーはサブスクリプション ページに表示されます ([Show] をクリックするとキーを表示できます)。

clip_image013

次に、OXFORD_API 環境変数を [Primary Key] の値に設定します。コードでは、process.env.OXFORD_API プロパティによってこの値を取得できます (注: 変数を設定した後、取得されるようにするには Visual Studio Code の再起動が必要な場合があります)。この方法を使用すれば、リポジトリで公開する可能性のあるコードにセキュリティ キーを貼り付けるよりも安全です。次に、generateBobblePermutations 関数の中の console.log 呼び出しにブレークポイントを設定し、アプリケーションを実行して、response[0].faceRectangle を確認して顔検出が正常に動作したことを検証します (ここでも、API キーが存在しないというエラーが発生する場合は Visual Studio Code を再起動します)。

clip_image015

Project Oxford の API は非常に便利なので、他の API や設定可能なオプションもぜひ試してみてください。たとえば、client.face.detect に以下のオプションを渡すと、Project Oxford が写真の人物の年齢と性別を推定します (このテクノロジは https://how-old.net/ (英語) で使用されているものと同じです)。

 {
     path: image,
     analyzesAge: true,
     analyzesGender: true
}

 

 

 

 

さまざまな構成で顔の トリミング、回転、貼り付けを行う

首振り人形のアニメーション GIF を生成するために、検出された顔の部分をさまざまな角度に回転させて、3 つの画像を作成します。この処理を行うために、routes/index.jsgenerateBobblePermutations 関数を以下のコードに変更します。

 

 function generateBobblePermutations(response) {
     var promises = [];
     var degrees = [10, 0, -10];

      for (var i = 0; i < degrees.length; i++) {
 var outputName = req.file.path + '-' + i + '.png';
             promises.push(cropHeadAndPasteRotated(req.file.path,
                 response[0].faceRectangle, degrees[i], outputName))
         }
 return Promise.all(promises);
}

 

メインの処理は以下の cropHeadAndPasteRotated 関数で実行します。これを routes/index.js に追加します。

 

 function cropHeadAndPasteRotated(inputFile, faceRectangle, degrees, outputName) {
     return new Promise (function (resolve, reject) {
         Jimp.read(inputFile).then(function (image) {
             // 顔検出では検出される部分が狭いため,
             // 面積を適度に広くして対応
             var height = faceRectangle['height'];
             var top = faceRectangle['top'] - height * 0.5;
             height *= 1.6;
             var left = faceRectangle['left'];
             var width = faceRectangle['width'];
 
              // 顔の部分をトリミングしてわずかに拡大し、回転させて元の画像に張り付ける
             image.crop(left, top, width, height)
             .scale(1.05)
             .rotate(degrees, function(err, rotated) {
                 Jimp.read(inputFile).then(function (original) {
                     original.composite(rotated, left-0.1*width, top-0.05*height)
                     .write(outputName, function () {
                         resolve([original.bitmap.width, original.bitmap.height]);
                     });
                 });
             });
         });
     });
}

 

この関数ではアップロードされた画像を読み込み、faceRectangle の境界を拡大して、顔の一部だけではなく顔全体を取得します。次に、この部分をトリミングしてわずかに拡大し、回転させて元の画像に貼り付けます。

ここでは処理を非同期的に行っているため、cropHeadAndPasteRotated で作成された画像をすぐに使用することはできません。このため、resolve を呼び出して、ファイルが正常に書き込まれたことをアプリケーションに通知します。

アプリケーションを実行すると、元の画像の他に 3 つの PNG ファイルが public/images に作成されます。これらの画像をe Visual Studio Code でクリックすると、エディターから直接表示できます。

 

 

GIF を生成する

ここからは最終段階となります。gifencoder と png-file-stream ライブラリ (事前に npm と共にインストール済み) を使用して、先ほど生成した画像を 1 つの GIF に統合します。routes/index.js の generateGif 関数を以下のように変更します。

 function generateGif(dimensions) {
     return new Promise(function (resolve, reject) {
         var encoder = new GIFEncoder(dimensions[0][0], dimensions[0][1]);
         pngFileStream(req.file.path + '-?.png')
             .pipe(encoder.createWriteStream({ repeat: 0, delay: 500 }))
             .pipe(fs.createWriteStream(req.file.path + '.gif'))
             .on('finish', function () {
                 resolve(req.file.path + '.gif');
             });
     })
}

保存してからアプリケーションを実行し、首振り人形を生成してみましょう。

clip_image017

 

 

クラウドで公開する

このアプリをローカルで実行するだけでなく、Git Deploy を使用して Azure に展開してみましょう。

リポジトリを初期化するために、Visual Studio Code の左側にある [Git] ウィンドウを開いて、[Initialize git repository] を選択します。

clip_image019 clip_image021

数千件も変更がありますが、これでは多すぎるので件数を減らしましょう。[File]、[New File] の順に選択して (Ctrl+N) プロジェクトのルートにファイルを作成し、以下のコードを貼り付けて、.gitignore という名前で保存します。

 

 # .gitignore

node_modules
public/images

 

これですっきりしました。node_modules に含まれているパッケージをすべてリポジトリに追加する必要はありません。また、テスト用にアップロードして生成した画像は明らかに不要です。[Git] ウィンドウでは、ローカル リポジトリにコミットするファイルが少なくなったことを確認できます。メッセージを入力してチェックマークをクリックします。

clip_image023

次に、https://try.azurewebsites.net にアクセスして、1 時間限定で使用できる無料評価版 Web サイトを作成します。

  • アプリの種類として [Web App] を選択し、[Next] をクリックします。
  • テンプレートとして [Empty Site] を選択し、[Create] をクリックします。
  • ログイン プロバイダーを選択します。しばらくすると新しいサイトが作成され、その後 1 時間だけ使用できます。

clip_image025

Web アプリの Git URL を取得し (上図を参照)、以下のコマンドを使用してコードをリモート リポジトリにプッシュします。<git url> の部分はポータルからコピーした URL に置き換えてください。

 C:\src\myapp> git push --set-upstream  <git url>  master

コマンド ウィンドウには展開のステータスが表示されます。ここで、プロジェクトのファイルをコピーするほかに、package.json の内容に従って必要な依存関係をインストールするために自動的に npm install が実行されることに注目してください。既にご説明したとおり、すべての依存関係を package.json のリストに追加することで、node_modules の内容が含まれない状態でソース コードを受け取った場合でも、必要なパッケージをすべて簡単に復元することができます。Azure への展開もまったく同様に行われます。このため、node_modules の内容をリポジトリにアップロードする必要はありません。

これで新しいサイトにアクセスすることはできますが、OXFORD_API 環境変数がまだ設定されていないため、首振り人形を生成することはできません。Azure の 1 時間限定の評価版サイトでは環境変数の設定が許可されていないため、routes/index.js を直接編集してプライマリ キーを入力します。編集を開始するには、[Edit with Visual Studio Online ‘Monaco’] リンクをクリックします。

clip_image027

Visual Studio Code とよく似たエディター インターフェイスが起動します。[Explore] ウィンドウを開き、routes/index.js に移動して、プライマリ キーを引用符で囲んで貼り付けます。この変更は自動的に保存されます。

clip_image029

ブラウザーで [Work with your web app at …] の後の URL をクリックすると、首振り人形が生成されます (これで首振り人形生成アプリを世界中に公開できますが、ここまでの作業時間を考えると、公開されるのは 35 分ほどでしょうか)。

 

 

次のステップ

今回作成した首振り人形生成アプリはただのデモで、運用環境で利用できる品質ではありません。アプリを改良するには、以下のような方法が考えられます。

  • リファクタリングしてコードの一部を別のモジュールに分離する。
  • さまざまな形式の画像や、複数人の顔を含む画像や顔が含まれない画像に対応する。問題が発生したら Visual Studio Code を使用してデバッグし、さらに改良してみましょう。
  • ローカルに画像を保存する代わりに Azure Storage API や MongoDB を使用する。
  • npmjs.com の他の有用なパッケージを試したり、自分のパッケージを公開したりする。

また、以下の関連資料もぜひご覧ください。

  • Visual Studio Code (英語) : 新しい軽量なクロス プラットフォーム対応エディターで、Node.js の強力なデバッグ機能と IntelliSense 機能を使用できます。
  • Node.js Tools for Visual Studio (NTVS) : Visual Studio では不十分に感じる方は、無料で提供されるオープン ソースの拡張機能の NTVS を使用すると、Visual Studio を本格的な Node.js IDE として使用できます。
  • Microsoft/nodejs-guidelines (英語) : Node.js を活用するための各種ヒントやコツ、マイクロソフトで進めているその他の Node.js 関連の製品やサービスへのリンクです。

 

 

 

ご質問、ご意見、ご要望をお寄せください

マイクロソフトでは、皆様からのフィードバックをお待ちしております。このページ下部のコメント欄または私の Twitter アカウントまでお寄せください。また、ブログ記事「うるう日に試してみたい 10 のこと (英語)」も併せてお読みください。このうちの 1 つは既に達成済みかもしれません。

 

clip_image030

Sara Itani (@mousetraps) (Node.js Tools 担当ソフトウェア エンジニア) Sara Itani は、Visual Studio および VS Code で優れた Node.js エクスペリエンスを提供するために取り組んでいます。当初は Node.js の有用性に懐疑的でしたが、その多様な可能性に気付いてからは、Visual Studio の機能を Node.js コミュニティを通じて積極的に世界中に広めています。今では彼女自身も JavaScript のエキスパートがどんどん増えることを願っています。 :-)