RaspberryPiで撮った写真をCustom Vision Serviceで画像判定してどの猫がいるかLINEに返す

### はじめに
[LINEBotからRaspberryPiで写真を撮ってLINEにおくる！](https://qiita.com/3yaka4/items/ddeaaa746976327f236e#%E6%84%9F%E6%83%B3)で作ったLINEBotにAI機能を追加してどの猫がいるか教えてくれるものを作りました。
### 概要
LINEBotからRaspberryPiを動かして写真を撮り、Gyazoに送って画像判定で何が映っているかと撮影した写真をLINEに送るものです。
<img width="954" alt="ppt.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282291/208ac2ab-7970-5b90-aae5-10dec4efb16c.png">
### 画像判定
画像判定は[Microsoft Custom Vision Service](https://docs.microsoft.com/ja-jp/learn/modules/classify-images-with-custom-vision-service/)を使用。
茶色と白黒とグレーの３匹のねこを学習させ、mixとして色々な猫を追加学習させて、３匹のどの猫に近いかを教えてくれます。
<img width="874" alt="スクリーンショット 2020-05-13 22.28.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282291/7e2f53cb-5dc2-3fa2-d8d0-085158c98ada.png">
3匹以外の猫を判定するとmixが一番高い値で出ます！(catタグは全ての猫に追加)
<img width="1105" alt="スクリーンショット 2020-05-13 22.26.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282291/8cafcdde-b109-980b-62eb-01d1a4a6b0c6.png">

### できたもの
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">どのニャンコがいるか教えてくれるようになった！<br>ニャンコのプライバシーを守るため色でこたえるw<br><a href="https://twitter.com/hashtag/%E3%81%AD%E3%81%93IoT?src=hash&amp;ref_src=twsrc%5Etfw">#ねこIoT</a> <a href="https://twitter.com/hashtag/cognitive?src=hash&amp;ref_src=twsrc%5Etfw">#cognitive</a> <a href="https://twitter.com/hashtag/customvision?src=hash&amp;ref_src=twsrc%5Etfw">#customvision</a> <a href="https://twitter.com/hashtag/linebot?src=hash&amp;ref_src=twsrc%5Etfw">#linebot</a> <a href="https://twitter.com/hashtag/raspberrypi?src=hash&amp;ref_src=twsrc%5Etfw">#raspberrypi</a> <a href="https://t.co/pILH3mwTq8">pic.twitter.com/pILH3mwTq8</a></p>&mdash; 3yaka (@3yaka4) <a href="https://twitter.com/3yaka4/status/1260376585309282304?ref_src=twsrc%5Etfw">May 13, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

### 使い方
RaspberryPiを猫がよくいる場所に置きます。
猫が見たいので　LINEBotに「ねこ」と入れます。
（「ねこ」以外のワードには「何が見たいの？」と返してきます。）
写真を撮るのに時間がかかるので、「写真撮ってくるから待っててねー」、とつなぎのワードが入ります。
写真を撮って送るときに [Azure Cognitive Vision ServicesのCustom Vision Service](https://docs.microsoft.com/ja-jp/learn/paths/classify-images-with-vision-services/)を使用してどの猫がいるかを画像を分類して、10秒ほどすると猫の種類と写真が送られてきます。
### 環境
```
MacBook Pro macOS Mojave
 Visual Studio Code 1.44.0
Azure Cognitive Vision Services
 Custom Vision Service
RaspberryPi ３B
 Release: 10
 Codename: buster
 Node.js v12.16.3
 npm v6.14.4
 ngrok
Pi camera
洗濯バサミ
```

### コード
``` node.js
'use strict';
const express = require('express');
const line = require('@line/bot-sdk');
const axios = require('axios');
const PORT = process.env.PORT || 3000;
const Gyazo = require('gyazo-api');
const gyazoclient = new Gyazo('***');
const config = {
  channelSecret: '***',
  channelAccessToken: '***'
};
const app = express();
let whichCat;

app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない)
app.post('/webhook', line.middleware(config), (req, res) => {
  console.log(req.body.events);
  Promise
    .all(req.body.events.map(handleEvent))
    .then((result) => res.json(result));
});
const client = new line.Client(config);
async function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }
  let mes = event.message.text;
  if (event.message.text.match("ねこ")) {

    await client.replyMessage(event.replyToken, {
      type: "text",
      text: '写真を撮ってくるからちょっと待っててね'
    });
    await nyanpi();
    return nyancoPic(event.source.userId);
  }
  else {
    return client.replyMessage(event.replyToken, {
      type: "text", text: '何が見たいのかな？'
    });
  }
}
const nyanpi = async (userId) => {
  const PiCamera = require('pi-camera');
  const myCamera = new PiCamera({
    mode: 'photo',
    output: `${__dirname}/nyan.jpg`,
    width: 640,
    height: 480,
    nopreview: true,
  });
  await myCamera.snap()
  await gyazoclient.upload('./nyan.jpg', {
    title: "my picture",
    desc: "upload from nodejs"
  })
}
//Custom Vision Service
const sendCustomVision = async (imageURL) => {
  const CUSTOM_VISION_API_ENDPOINT_URL = '***';
  // Prediction-Keyの値を Prediction-Key ヘッダーに入れる
  // JSONで送るので Content-type ヘッダーに application/json 指定
  const configCustomVisionAPI = {
    url: CUSTOM_VISION_API_ENDPOINT_URL,
    method: 'post',
    headers: {
      'Content-type': 'application/json',
      'Prediction-Key': '＊＊＊'
    },
    data: {
      url: imageURL
    }
  };
  // axiosの送信設定
  let responseCustomVision;
  try {
    // POSTリクエストで送る
    responseCustomVision = await axios.request(configCustomVisionAPI);
    console.log("post OK");
    // データ送信が成功するとレスポンスが来る
    console.log(responseCustomVision.data);
    // 全てのタグにcatが含まれているので、近似値が高いものがcatタグの時は２番目を返す
    const catTag = responseCustomVision.data;
    if (catTag.predictions[0].tagName === "cat") {
      whichCat = catTag.predictions[1].tagName
    } else {
      whichCat = catTag.predictions[0].tagName;
    }
  } catch (error) {
    console.log("post Error");
    // ダメなときはエラー
    console.error(error);
  }
}
//gyazoから最新の写真URLを取得
const nyancoPic = async (userId) => {
  const response = await gyazoclient.list()
  const gyazoimgUrl = response.data[0].url;
//画像判定に送る
  const sendcstmvison = await sendCustomVision(gyazoimgUrl);
//タグの名前を猫の見た目に置き換え
  if (whichCat === "chairo") {
    whichCat = "ちゃいろ";
  } else if (whichCat === "shirokuro") {
    whichCat = "しろくろ";
  } else {
    whichCat = "グレー";
  }
  return client.pushMessage(userId, [{
    type: 'image',
    originalContentUrl: gyazoimgUrl,
    previewImageUrl: gyazoimgUrl
  },
  {
    type: "text", text: `${whichCat}の猫がいるよ`
  }]);
}
app.listen(PORT);
console.log(`Server running at ${PORT}`);
```


### 参考サイト
[Computer Vision サービスを使用した画像処理 \- Learn \| Microsoft Docs](https://docs.microsoft.com/ja-jp/learn/modules/create-computer-vision-service-to-classify-images/)

### 感想
AIが簡単に実装できた
グレーがシンガプーラという猫種で他のことめちゃくちゃ顔が同じなのでシンガプーラ判定やってみたい。
ngrokなので早く何かにdeployしたい。