LoginSignup
7
3

More than 1 year has passed since last update.

私の王子さまはどこにいるの?めめに似てる人を教えて~!!(TeachaBleMachineを使った機械学習モデルを作ってみました)

Posted at

こんにちは。
今回はコロナ自粛期間中にドハマりした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の応答設定→詳細設定→応答メッセージをオフに設定!
よし、解決した!

LINEに写真送って試してみる!(エラー③)

写真を送ってみたが、なんの反応もない。
きっとNode.jsのコードか何かがおかしいんだろうな~
(全然まだ理解できていないし、気づきが遅いかもだけどこれが私の実力なので、潔く認めることにした。)

現在

まだまだコードが読めない私はLINEBotにつなぐだけで精一杯だったけど、
もう少し頑張りたいので、コード分析中です。

それでもいつもはLINEBotにさえも繋げられてもないので、今回は少し進んだ気がしてます。
少しずつ、前進したいと思います!!

最後まで読んでいただきありがとうございました。

7
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3