こんにちは。
今回はコロナ自粛期間中にドハマりしたSnow Manの目黒蓮くんこと【通称めめ】を
身近に発見したい!というただ、それだけの気持ちで画像認証できるものを作りました。
せっかくなので、Snow Manの誰に似ているのかも試してみてください!
※Snow Manを知らない方はこちらをクリック
まずは実際にSnow Man判定してみてください!
注) URL内のボタンを押した後 結果が出るまでしばらくお待ちください!
TeachableMachineの設定
今回は身近なめめを探したかったので、
身近=会社の人をターゲットに作りました。
(最近仕事が忙しくて友達に会えてない!!!!)
会社で会う人はスーツ姿!!
てことで、スーツ姿のSnow Manの写真をTeachableMachineに設定!!
写真に引用したのはブラザービートでのパケ写真だけど
こんなスーツが似合う方たちいないよね~♡
CodePenのエディタ
See the Pen SnowMan誰に似てるか調べてみよう! by NagaharaHitomi (@nagaharahitomi) on CodePen.
部長とSnow Manファンに試してもらう!(感想あり)
まずは部長!!いざ検証!!!!
(とりあえずSnow Manに似てないことは確かなので王子さま判定でないことを祈る)
セーフ!!!!(笑)
でもこの判定合ってる!
正直私の中で部長はSnow Manでいうとふっかさんだと思っていた(笑)
部長の感想
「くだらないな~」と言いつつ、なんか楽しそうだった(笑)
「俺はSnow Manわからないから吉本新喜劇で作ってほしいわ!」とのこと。
はい、次!!
女の子だったらどう判定されるんだ?と疑問になったので
生粋のSnow Manファンに依頼して試してもらうことに!
うん、大体しょっぴーが出てくるよね。
私もしょっぴー出てきた!
友達の感想
「絶対しょっぴーには似てないと思う!!」
確かに似てないよね~
今回あまり明確なアドバイスは出てきませんでしたが、
二人とも割と楽しんで使ってくれました!
さ、番外編!LINEBotと繋げよう!
注意
Node.jsとLINEBotに繋げるまでは出来ましたが、
まだ正常に動いているとは言えない為、参考にする際は注意ください。
実装するにあたって参考にした記事はこちら
Teachable Machineを使って、ゆでたまごの気持ちをツンデレ風に表現してみた
↑上記記事を見つけて読んだときに、凄く面白くて工夫されていると思ったので
割と早い段階から参考にしてました。
LINEBotと繋げた方がCodePenを開くときに出てくる、
「私はロボットではありません」の確認しなくて済むので今回もLINEBotに繋げるに挑戦!
実装環境
Node.js (version:17.6.0)
npm (version:8.5.1)
ngrok (version:2.3.40)
LINE Messaging API
コード
実際に作成したコードはこちら
'use strict';
const { JSDOM } = require('jsdom');
var dom = new JSDOM('');
global.document = dom.window.document;
global.HTMLVideoElement = dom.window.HTMLVideoElement;
const canvas = require('canvas');
global.fetch = require('node-fetch');
const tmImage = require('@teachablemachine/image');
const express = require('express');
const line = require('@line/bot-sdk');
const fs = require('fs');
const PORT = process.env.PORT || 3001;
const config = {
channelSecret: '**********', //忘れず変更
channelAccessToken: '**********', //忘れず変更
};
// Teachable Machine
let model;
// https://teachablemachine.withgoogle.com/
// ここでエクスポート、クラウドにモデルをアップロードした後に取得できる
const URL = 'https://teachablemachine.withgoogle.com/models/iWfSsrVVh/';
// ########################################
// Teachable Machineを使って画像分類をする部分
// ########################################
// 初期化が時間かかるので、node立ち上げ時に行うようにする
async function initTeachableMachine() {
const modelURL = URL + 'model.json';
const metadataURL = URL + 'metadata.json';
// モデルデータのロード
model = await tmImage.load(modelURL, metadataURL);
// クラスのリストを取得
// const classes = model.getClassLabels();
// console.log(classes);
}
initTeachableMachine();
async function predict(imgPath) {
// canvasに画像をロードする
const image = await canvas.loadImage(imgPath);
// 判定する
const predictions = await model.predict(image);
// 一番近いもの順でソート
predictions.sort((a, b) => {
return b.probability - a.probability;
});
return predictions;
}
// ########################################
// LINEサーバーからのWebhookデータを処理する部分
// ########################################
// LINE SDKを初期化します
const client = new line.Client(config);
// LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます
async function handleEvent(event) {
// 受信したWebhookが「画像以外」であればnullを返すことで無視します
if (event.message.type === 'image') {
console.log("あなたは私の王子様なの?");
// 画像を保存
const downloadPath = './01.png'; //左記の名前で使用中のフォルダ内に送信された画像が保存される
const getContent = await downloadContent(event.message.id, downloadPath);
const result = await predict(downloadPath);
// AIメーカーAPIの結果から、返信するメッセージを組み立てる
let text = '';
let name = '';
name = result[0].className
// 判定結果をテキストに代入
text = name;
// これまでの結果を確認するためにコンソールに表示
console.log(result);
console.log(name);
console.log(text);
// 判定結果に応じた画像を送信
if(name === 'あなたはひーくん似'){
return client.replyMessage(event.replyToken, [{
type: 'text',
text: text
}]);
}else if(name === 'あなたはふっかさん似'){
return client.replyMessage(event.replyToken,[{
type: 'text',
text: text
}]);
}else if(name === 'ラウールに似ているなんて素敵'){
return client.replyMessage(event.replyToken, [{
type: 'text',
text: text
}]);
}else if(name === '声もしょっぴーに似ているのかしら?'){
return client.replyMessage(event.replyToken, [{
type: 'text',
text: text
}]);
}else if(name === 'こーじー!!'){
return client.replyMessage(event.replyToken, [{
type: 'text',
text: text
}]);
}else if(name === 'あなたは阿部ちゃん似'){
return client.replyMessage(event.replyToken, [{
type: 'text',
text: text
}]);
}else if(name === 'ここに居たのね私の王子様'){
return client.replyMessage(event.replyToken, [{
type: 'text',
text: text
}]);
}else if(name === '舘さま~'){
return client.replyMessage(event.replyToken, [{
type: 'text',
text: text
}]);
}else if(name === 'あなたは元気いっぱいさっくん'){
return client.replyMessage(event.replyToken, [{
type: 'text',
text: text
}]);
}
}
// 「テキストメッセージ」であれば、受信したテキストをそのまま返事します
if (event.message.type === 'text') {
return client.replyMessage(event.replyToken, {
type: 'text',
text: event.message.text // ← ここに入れた言葉が実際に返信されます
});
}
}
// ########################################
// LINEで送られた画像を保存する部分
// ########################################
function downloadContent(messageId, downloadPath) {
const data = [];
return client.getMessageContent(messageId)
.then((stream) => new Promise((resolve, reject) => {
const writable = fs.createWriteStream(downloadPath);
stream.on('data', (chunk) => data.push(Buffer.from(chunk)));
stream.pipe(writable);
stream.on('end', () => resolve(Buffer.concat(data)));
stream.on('error', reject);
}));
}
// ########################################
// Expressによるサーバー部分
// ########################################
// expressを初期化します
const app = express();
// HTTP GETによって '/' のパスにアクセスがあったときに 'Hello LINE BOT! (HTTP GET)' と返事します
// これはMessaging APIとは関係のない確認用のものです
app.get('/', (req, res) => res.send('<h1>Hello LINE BOT! (HTTP GET)</h1>'));
// HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします
app.post('/webhook', line.middleware(config), (req, res) => {
// Webhookの中身を確認用にターミナルに表示します
console.log(req.body.events);
// 空っぽの場合、検証ボタンをクリックしたときに飛んできた"接続確認"用
// 削除しても問題ありません
if (req.body.events.length == 0) {
res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します
console.log('検証イベントを受信しました!'); // ターミナルに表示します
return; // これより下は実行されません
}
// あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、
// 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します
Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result));
});
// 最初に決めたポート番号でサーバーをPC内だけに公開します
// (環境によってはローカルネットワーク内にも公開されます)
app.listen(PORT);
console.log(`ポート${PORT}番でExpressサーバーを実行中です…`);
package.jsonの"scripts"の中に"start": "node ファイル名"を入力
{
"name": "snowman",
"version": "1.0.0",
"main": "meme.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node meme.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@line/bot-sdk": "^7.5.0",
"@teachablemachine/image": "^0.8.5",
"@tensorflow/tfjs": "^1.3.1",
"canvas": "^2.9.1",
"express": "^4.17.3",
"jimp": "^0.16.1",
"jsdom": "^19.0.0"
},
"description": ""
}
LineBotに繋げる。(出てきたエラー)
でてきたエラー①
node meme.js
ポート3000番でExpressサーバーを実行中です…
node:events:505
throw er; // Unhandled 'error' event
^
me.js:195:5)
at Module._compile (node:internal/modules/cjs/loader:1097:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1151:10)
at Module.load (node:internal/modules/cjs/loader:975:32) at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
Emitted 'error' event on Server instance at:
at emitErrorNT (node:net:1358:8) at processTicksAndRejections (node:internal/process/task_queues:83:21) {
code: 'EADDRINUSE',
errno: -4091, syscall: 'listen',
address: '::',
port: 3000
}
3000番はどこかで使われているのからエラーが起きるらしい・・・
てことで、コードの15行目を3000番から3001番に変更したら
正常に動いた!!
LINEに文字送って試してみる!(エラー②)
同じメッセージが返ってくる・・・
LINEBotの応答設定→詳細設定→応答メッセージをオフに設定!
よし、解決した!
写真を送ってみたが、なんの反応もない。
きっとNode.jsのコードか何かがおかしいんだろうな~
(全然まだ理解できていないし、気づきが遅いかもだけどこれが私の実力なので、潔く認めることにした。)
現在
まだまだコードが読めない私はLINEBotにつなぐだけで精一杯だったけど、
もう少し頑張りたいので、コード分析中です。
それでもいつもはLINEBotにさえも繋げられてもないので、今回は少し進んだ気がしてます。
少しずつ、前進したいと思います!!
最後まで読んでいただきありがとうございました。