Azure.CognitiveServicesを使った顔検出アプリをweb上でも公開したいということでNode.jsでやってみようと思います。もちろん他の言語でもやりますよ。まずC#でつくる。Webでも使えるように他の言語(JavaScript/TypeScriptが主ですが)でもやってみたいと考えているところです。
Contents
今回は顔検出アプリをつくる準備編
ということで今回はNode.jsでやってみます。前回はしょぼいサイトをNode.jsで作りましたが、今回は顔検出アプリをつくる前に基本的なことをやってみます。実はNode.jsは前回の記事がはじめてです。生温かく見守ってください。
Expressをつかってみる
ファイルをアップロードしたい。これをやる方法がなかなか見つからなかったのでExpressを使います。ExpressはそのNode.jsでの開発をスピードアップするためのフレームワークです。
動作確認はこちらからお願いします。
ではさっそくインストールしてみましょう。実際にweb上に公開する前にローカル環境でうまく動くことを確認してからにしましょう。
まずは以下を実行して必要なものをインストールします。
1 2 3 4 |
npm install ejs npm install express npm install multer npm install jimp |
必要なファイルの準備
それからindex.ejsを用意します。これは前回と同じなのですが、念のため示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <title><%-page_title%></title> <link rel="stylesheet" href="./style.css" media="all"> </head> <body> <div id = "container"> <div id="header"> <h1><a href="./"><%-site_title%></a></h1> </div> <div id="main"> <h2><%-page_title%></h2> <%-main_content%> </div> <div id="sidebar"> <h2>その他の記事</h2> <ul> <%-sidebar_link%> </ul> </div> <br clear="all"> </div> </body> </html> |
あとstyle.cssも用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
body { font-size: 14pt; color: #333; } #container { width: 1000px; margin:0px auto 0px auto; padding: 0px; min-height: 600px; } #header h1 { font-size: 48pt; background-color: darkcyan; color: #eee; text-align: center; margin:0px; padding: 30px; } #header a{ color: #eee; text-decoration:none; } #header a:hover { color: #fff; text-decoration:underline; } h2 { color: #000000; } #main { float:left; width:60%; padding: 0px 30px 0 30px; } #sidebar { float:right; width:30%; padding: 0px 10px 0 10px; } #sidebar h2{ text-align: center; } #sidebar li{ margin: 0px 0px 10px 0px; list-style: none; } #sidebar a:hover { color: #f00; font-weight:bold; } .red { color: #f00; } @media screen and (max-width: 1000px) { #container { width: 100%; } #main { padding: 0px; } #sidebar { padding: 0px; } } @media screen and (max-width: 600px) { #main { width: 100%; padding: 0px; float: none; } #sidebar { width: 100%; float: none; } } |
app.jsの作成
それではapp.jsを作成します。
まず必要なものをrequireします。
1 2 3 4 5 6 7 |
const fs = require('fs'); const ejs = require('ejs'); const Jimp = require("jimp"); const qs = require('querystring'); const express = require('express'); const multer = require('multer'); |
つぎにexpressを変数expressに代入することで初期化しています。それから必要なindex.ejsとstyle.cssを読み出します。
1 2 3 |
const app = express(); const index = fs.readFileSync('./index.ejs','utf8'); const style = fs.readFileSync('./style.css','utf8'); |
ポート番号は65002とします。
1 2 |
const port = 65002; const baseUrl = 'http://localhost:' + port; |
レンダリングのときに必要なオブジェクトを初期化します。site_titleとsidebar_linkは全部のページで同じなのでここで値をセットしておきます。getSidebarLinkString関数はサイドバーに他のページへのリンクを表示させるために必要な文字列を返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const obj = { site_title: 'Node.jsのテスト', page_title: '', main_content : '', sidebar_link : getSidebarLinkString(), }; function getSidebarLinkString(){ let array = []; array.push('<li><a href="./">トップページへ</a></li>\n'); array.push('<li><a href="./form-test">フォームによる送信の実験</a></li>\n'); array.push('<li><a href="./face-test">顔認識の実験</a></li>\n'); array.push('<li><a href="./404">存在しないページ</a></li>\n'); return array.join('\n'); } |
またこれはCSSファイルを表示させるための関数です。
1 2 3 4 5 6 |
function GetResponseStyleSeet(req, res, obj){ let content = ejs.render(style); res.writeHead(200, {'Content-Type':'text/css'}); res.write(content); res.end(); } |
そしてmain関数を実行します。main関数は以下のとおりです。/、/index.html、/form-testなどのページにアクセスがあったらコンテンツを表示させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
main(); function main(){ // GetResponseIndex関数は後述。以下、同じ app.get('/', (req, res) => GetResponseIndex(req, res, obj)); app.get('/index.html', (req, res) => GetResponseIndex(req, res, obj)); app.get('/style.css', (req, res) => GetResponseStyleSeet(req, res, obj)); app.get('/form-test', (req, res) => GetResponseFormTest(req, res, obj)); app.post('/form-post', (req, res) => GetResponseFormPost(req, res, obj)); app.post('/file-upload', multer({dest: './'}).single('file'), (req, res) => { GetResponseFileUpload(req, res, obj); }); app.get('/face-test', (req, res) => GetResponseFaceTest(req, res, obj)); app.post('/face-result', multer({dest: './'}).single('file'), (req, res) => { GetResponseFaceResult(req, res, obj); }); app.use((req, res) => GetResponse404(req, res, obj)); var server = app.listen(port, function() { console.log("listening at port %s", server.address().port); }); } |
GetResponseIndex関数はトップページにアクセスされたときにコンテンツを表示させます。エックスサーバーでNode.jsを動かしてみるとやっていることは同じです。で囲むと改行をそのまま書けるし、
${~}
の中には、変数や計算式を入れることができるので便利です。
現在時刻を調べて表示するとともに、時刻によって「おはようございます」「こんにちは」「こんばんは」と挨拶を変えます。また時刻によってその日、または翌日の運勢を占います。FortuneTelling関数はエックスサーバーでNode.jsを動かしてみるを参照してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
function GetResponseIndex(req, res, obj){ obj.page_title = 'トップページ'; let date = new Date(); let howr = date.getHours(); let minute = date.getMinutes(); let second = date.getSeconds(); let aisatsu = ''; if(howr < 4) aisatsu = 'こんばんは。'; else if(howr < 12) aisatsu = 'おはようございます。'; else if(howr < 18) aisatsu = 'こんにちは。'; else aisatsu = 'こんばんは。'; let ganbarou = '今日も一日頑張ろう!'; if(howr > 18) ganbarou = 'おつかれさま。明日も頑張ろう。'; let todayOrTomorrow; if(howr < 18) todayOrTomorrow = '今日'; else todayOrTomorrow = '明日'; let str = ` <p>ここはトップページです。</p> <p>Node.jsのテストのために作成したサイトです。</p> <p>${aisatsu} 現在時刻は <strong>${howr} 時 ${minute} 分 ${second} 秒</strong> です。${ganbarou}</p> <p>${todayOrTomorrow}のあなたの運勢は <strong class = "red">${FortuneTelling()}</strong> です。 ${todayOrTomorrow}の運勢が気に入らないときは F5 キーを押してください。</p> `; obj.main_content = str; let content = ejs.render(index, obj); res.writeHead(200, {'Content-Type':'text/html'}); res.write(content); res.end(); } |
フォームによる送信
次にフォームによる送信の実験をおこなうことができるページを作成しました。
/form-testにアクセスするとフォームが表示され、テキストボックスになにかを入力してボタンをおすと入力結果が表示されます。GetResponseFormTest関数は以下のようになっています。テキストボックスとファイルをアップロードするためのコントロールが表示されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
function GetResponseFormTest(req, res, obj){ obj.page_title = 'フォームの実験'; let url_parts = new URL(req.url, baseUrl); let str = ` <p>ここは${url_parts.pathname}です。</p> <form action="form-post" method="post"> <label for="name">名前</label> <input type="text" id="name" name="name"> <input type="submit" value="送信する"> </form> <p>画像をアップロードしてください</p> <form action="file-upload" method="POST" enctype="multipart/form-data"> <input type="file" name="file"> <input type="submit" value="アップロード"> </form> `; obj.main_content = str; let content = ejs.render(index, obj); res.writeHead(200, {'Content-Type':'text/html'}); res.write(content); res.end(); } |
「送信する」がクリックされるとform-postへPostされ、アップロードがクリックされるとfile-uploadへPostされます。
form-postへPostされたときの処理を示します。Postされたときはページのタイトルは「フォーム送信の結果」となり、テキストボックスに入力された文字列が表示されます。またPostしないで/form-postへ直接アクセスしたときはそのページは存在しないと表示されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
function GetResponseFormPost(req, res, obj){ if(req.method == 'POST'){ let body = ''; req.on('data', (data) => body += data); req.on('end', async() => { let post_data = qs.parse(body); let msg = `「${post_data.name}」と入力しましたね。`; obj.page_title = 'フォーム送信の結果'; obj.main_content = msg; var content = ejs.render(index, obj); res.writeHead(200, { 'Content-Type': 'text/html' }); res.write(content); res.end(); }); } else{ obj.main_content = 'POST送信失敗'; var content = ejs.render(index, obj); res.writeHead(200, { 'Content-Type': 'text/html' }); res.write(content); res.end(); } } |
ファイルをアップロードする
次にfile-uploadへPostされたときの処理を示します。アップロードされたファイルが画像ファイルの場合、dataUrlが生成されて、これによって画像が表示されます。画像ではないものをアップロードした場合は例外が発生し、「例外が発生しました」と表示されます。ます。またdataUrlが生成されたらアップロードされたファイルはサーバーから削除されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
async function GetResponseFileUpload(req, res, obj){ obj.page_title = '画像ファイルアップロードの結果'; if(req.file == null){ obj.main_content = 'ファイルが選択されていません。'; let content = ejs.render(index, obj); res.writeHead(200, {'Content-Type':'text/html'}); res.write(content); res.end(); return; } let filePath = req.file.path; // 画像ファイルではないパスをGetDataUrl関数に渡すと例外となり空文字列が返される let dataUrl = await GetDataUrl(filePath); if(dataUrl != "") obj.main_content = `<p><img src = "${dataUrl}" style="max-width: 100%;"></p>\n`; else obj.main_content = `<p>画像ファイルではないものがアップロードされました。</p>\n`; let content = ejs.render(index, obj); res.writeHead(200, {'Content-Type':'text/html'}); res.write(content); res.end(); fs.unlink(filePath, function(err){}); } |
ファイルパスからdataUrlを取得する
ファイルパスからdataUrlを取得する関数を示します。image.getBase64が実行されたときにその値を取得したいのですが、非同期処理にしないとうまくいきません。
1 2 3 4 5 6 7 8 9 10 11 12 |
async function GetDataUrl(filePath){ let dataUrl = ''; await Jimp.read(filePath).then(function (image) { // ここでdataUrlにBase64された文字列を取得している image.getBase64(Jimp.MIME_PNG, (err, src) => dataUrl = src); }).catch(function (err) { console.error(err); return ''; }); return dataUrl; } |
存在しないページにアクセスしたときの処理
存在しないページにアクセスした場合はGetResponse404関数が呼び出され、存在しないページにアクセスした旨とそのurlが表示されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
function GetResponse404(req, res, obj){ obj.page_title = '存在しないページ 404'; let url_parts = new URL(req.url, baseUrl); let str = ` <p>ここは${url_parts.pathname}です。</p> <p>このページは存在しません。</p> <p><a href="./">トップページに戻る</a></p> `; obj.main_content = str; let content = ejs.render(index, obj); res.writeHead(200, {'Content-Type':'text/html'}); res.write(content); res.end(); } |
あとは適当なディレクトリにアップロードして、公開したいUrlに相当するディレクトリに.htaccessを設置します。
.htaccess
1 2 3 4 |
<IfModule mod_rewrite.c> RewriteEngine On RewriteRule ^(.*)$ http://localhost:65002/$1 [P,L] </IfModule> |
そしてSSH接続して以下のコマンドを入力します。
1 2 3 |
node app.js & # 上を実行したらCtrl + Cを押す。そのあと disown |
次回は顔認識をする機能を追加します。