スマホで写真を撮るだけで植物の情報をゲット！！（機械学習初心者に便利なツールも公開）

## 自宅にある植物の育成情報を簡単に知りたい
皆さんのおうちには観葉植物がありますか。私の自宅にはたくさんの観葉植物があるのですが、植物ごとに適した環境や水の量が異なり植物を枯らしてしまうことが多々ありました。そのため、スマホのカメラで植物の写真を撮影して適切な水の量や直射日光は大丈夫かといった情報がすぐに得られたらいいなと思いこのWebアプリを作りました。
また、明日の天気と気温もわかるのでそれを参考に植物を窓際に置くか、日の当たらないところに置くかなどの検討材料にしてもらえたらと思います。

まずは以下の動画を見てください！
**↓使い方を紹介した動画↓**

https://www.youtube.com/shorts/IdIpypDFd50


## 使い方
1.スタートボタンを押す
2.撮影が始まるので枠内に収める
3.撮影が終わると植物の育成方法を教えてくれる

という流れです。実際のアプリのURLがあるので自身のスマホでぜひ使ってみてください～

**↓アプリのリンク↓**

https://plant-judge.netlify.app

## 作成手順
### ①観葉植物データベースで育成情報を取得

Webアプリを作成するにあたって適切な水の量や照度等の情報がどこかにまとまっていないかと探したところ『観葉植物データベース』という植物名を入力するとその植物を育てるにあたって必要な情報を得ることのできるサイトを見つけました。

http://leafdb.com/

Webアプリに生育情報を掲載するにあたって必要になる適切な水の量や、照度については『観葉植物データベース』からExcelファイルにてダウンロードを行うこととしました。今回はメジャーな下記の6種類の植物を対象とします。
* ガジュマル
* コーヒーノキ
* サンスベリア
* パキラ
* ポトス
* モンステラ


ダウンロードしたExcelシートには下記のような情報があり、数字と下の凡例が対応しています。
**Excelシート**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2618860/bf5dacd7-361c-00c2-5a93-eb9044e02028.png)
**凡例**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2618860/a8e1ff37-2de0-91a2-dcd0-131b84edd818.png "凡例")

例えば、ガジュマルだったら越冬温度は4なので中温性で水は3なので普通といった具合で対応しています。
次にこの情報をVue.jsのコードに直書きをしていきます。
</details><details><summary>Vue.js</summary>

```javascript:育成情報の入力
    loop: function(classifier) {
      console.log('loop　function');
      // 推論を実行し、エラーがあればerrに、結果をresultsに格納して、
      // 推論が完了次第 { } の中身を実行します
      classifier.classify(async (err, results) => {
        
          // 育成情報を手入力 
          let index_temperature = ['耐寒性。越冬温度の目安は0℃以下。一般の住宅ならば特に保温の必要がない。戸外越冬可能。',
                             '半耐寒性。越冬温度の目安は0℃以上。植物体が凍結しなければ越冬可能なので一般の住宅ならば特に保温の必要がない。暖地では無加温温室、軒下などで越冬する。',
                             '低温性。越冬温度の目安は5℃程度。一般の住宅ならば特に保温・加温の必要がない。',
                             '中温性。越冬温度の目安は10℃程度。寒さに弱く、気密・断熱性能の低い住宅では保温して越冬させる。',
                             '高温性。越冬温度の目安は15℃以上。極めて寒さに弱く、一般に加温して越冬させる。水遣りを控えれば多少耐寒性が高まる。'];
          let index_water = ['長期間の乾燥に耐える。水切れによる障害をほとんど受けない。過湿により障害を受けやすい。生育期には十分潅水する(土が乾いたら潅水)。サボテン・多肉植物など。',
                            '乾燥に耐える。水切れによる障害を受けにくい。過湿により障害を受けやすい。生育期には十分潅水する(土が乾いたら潅水)。葉や茎、根が多肉質な植物、着生植物など。',
                            '普通。一般的な観葉植物の潅水。土が乾いたら潅水する。',
                            '多湿を好む。水切れによる障害を受けやすい。土が乾ききる前に潅水する。シダ植物など。',
                            '非常に多湿を好む。水切れによる深刻な障害を受ける。用土が常に湿っている状態を保つ。水生植物や湿生植物など。'];
          let index_light = ['強い日陰に耐える。',
                            '半日陰を好む。夏の戸外では葉焼けを起こすので半日陰または遮光する(30～50％)。室内では壁側・人工照明による育成が可能。',
                            'やや明るい光を好む夏の戸外では葉焼けを起こすので遮光する(10～30％)。室内では部屋の中心部、レースのカーテン越しの窓辺いに置く。',
                            '明るい光を好む。夏の戸外では葉焼けを起こす可能性があるのでやや遮光する(～10％)。室内では出来るだけ明るい所に置く。',
                            '強い光を好む。戸外では直射日光、室内では出来るだけ明るい所に置く。'];
        
          // ガジュマル,コーヒーノキ,サンスベリア...の順に情報が格納されている。
          let num = 0;
          // 温度
          let t = [4,3,5,3,3,3];
          // 水
          let w = [3,3,3,3,4,3];
          // 光
          let l = [4,4,4,5,3,3];
          // コメント
          let temp_comment = ['成木の幹を挿し木して芽吹かせたもの、根元が膨らんだ盆栽風に仕立てたものなどが流通する。',
                              '言わずと知れた「コーヒー豆」の原料となる有用植物。実生苗から大鉢仕立てまで非常に多く流通する。結実には高温での越冬が必要。',
                              '黄覆輪斑。一般に「サンセベリア」として出回るのは本品種。',
                              '掌状複葉。日照を好むが耐陰性もある。実生小苗から大型の鉢物まで非常に多く流通する。',
                              '園芸品種多数。',
                              'サトイモ科。熱帯アメリカに約３０種。'];
          // console.log(results[0].label)
          // 植物名によって処理を変える
          if (results[0].label == "ガジュマル") {
            num = 0;
          } else if (results[0].label == "コーヒーノキ") {
            num = 1;
          } else if (results[0].label == "サンスベリア") {
            num = 2;
          } else if (results[0].label == "パキラ") {
            num = 3;
          } else if (results[0].label == "ポトス") {
            num = 4;
          } else if (results[0].label == "モンステラ") {
            num = 5;
          }
        // this.myPlant.shift();
        console.log(index_temperature[t[num]])
        this.myPlant.push({name: results[0].label,
                           temperature: index_temperature[t[num]-1],
                           water: index_water[w[num]-1],
                           light: index_light[l[num]-1],
                           comment: temp_comment[num],
                          });
        this.recogButton = '撮影完了';
      });
    },
```
</details>

### ②TeachableMachineに植物を学習させる
植物の画像をモデルに学習させていくのですが、とりあえず、自宅にあった植物をスマホで撮影して、[TeachableMachine](https://teachablemachine.withgoogle.com/)で学習をさせることにしました。
ですが、学習をさせている途中で同じ種類の植物でも成長具合が異なったり、植木鉢によっても異なってしまうので精度があまり出ないのではと思いました。そのため、ネットから植物の画像を検索してダウンロードし、そのような影響を受けにくいモデルを作成しようと思います。
手動で植物の画像をダウンロードするのは大変なので、Pythonのクローラーを使って全自動でCSVファイルに指定してた植物の画像をダウンロードするようなプログラムを書きました。
下の動画のようにCSVファイルに植物の英語名と日本名を記載し、プログラミングを実行することで植物名ごとにファイルが作成され自動的に画像がダウンロードされます。

※pip install icrawlerにてパッケージのインストールが必要です。

![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2618860/198501b9-122b-3398-1885-77f8055df321.gif "Animation.gif")

<details><summary>画像自動取得（Python）</summary>

```python:plant.py
from icrawler.builtin import BingImageCrawler
import csv

class Crawler:
    def __init__(self,file_name,max_num):
        # ファイル名を指定
        self.file_name = file_name
        # ダウンロードする画像の最大枚数
        self.max_num = max_num

    # テキストファイルから植物名を抽出
    def readText(self):
        rows = []
        with open(self.file_name+'.csv') as f:
            reader = csv.reader(f)
            for row in reader:
                self.download(row[1],row[0]+' '+row[1])

    def download(self,dir,keyword):
        # クローラーの生成
        bing_crawler = BingImageCrawler(
            downloader_threads=4,
            storage={'root_dir': "image/"+dir})

        # キーワード検索による画像収集
        bing_crawler.crawl(
            keyword=keyword,
            max_num=self.max_num)       

# ファイル名とダウンロードする画像の最大枚数を指定
plant = Crawler("search_plant",150)
plant.readText()
```
</details>

次にダウンロードした画像を[TeachableMachine](https://teachablemachine.withgoogle.com/)に学習をさせることで植物を見分けるためのモデルを作成し、ml5にてモデルのロードを行うことでスマホで撮影した画像がなんの植物かを判別することができるようになります。
<details><summary>モデルのロード</summary>

```javascript:モデルのロード
      // Googleのサーバーにアップロードした自作モデルを読み込みにいきます
      this.myPlant.imageModelURL = 'https://teachablemachine.withgoogle.com/models/0BBHaK1RV/';
      console.log('imageModelURL:',this.myPlant.imageModelURL)
      const classifier = ml5.imageClassifier(this.myPlant.imageModelURL + 'model.json', video, () => {
        // 読み込みが完了次第ここが実行されます
        console.log('モデルの読み込みが完了しました');
        this.myPlant.shift();
        this.loop(classifier);
        console.log('loop処理1回目');
```
</details>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2618860/4897cafa-9f8d-82e8-faa3-425bd19ead44.png)

### ③天気予報を取得して明日の温度や光の具合をあらかじめ知っておく
こちらは[前回の記事](https://qiita.com/kosuke0517lab/items/be242454c517b10a176b)に記載をしておりますので参考にしてください。

## おわりに
今回初めて[TeachableMachine](https://teachablemachine.withgoogle.com/)を使用したのですが、とにかく簡単にモデルが作成できるし、高い精度が出るので今後も積極的に活用していこうと思います。最後までご覧いただきありがとうございました～！

## ソースコード
<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="QWQJvzN" data-user="kosuke05" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/kosuke05/pen/QWQJvzN">
  植物認識03.js</a> by kosuke05 (<a href="https://codepen.io/kosuke05">@kosuke05</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
<details><summary>HTML</summary>

```html:HTML
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>植物判定</title>
  </head>
  <body>
    <!-- 全体をVue.js有効にする -->
    <div class="container text-center p-3 mb-2 bg-dark text-white" id="app">
        <!-- タイトル行 -->
      <div class="row my-3">
        <div class="col-sm-6 mx-auto"><h1>植物判定アプリ</h1></div>
      </div>

      <div class="info">
        <p>
          育て方を知りたい植物を<br>カメラに写して数秒待ってください<br>
        </p>
      </div>

    <!--今日を含め3日間の天気を表示 -->
      <div class="col-sm-6 mx-auto" id="wether" >
        <dl v-for="obj01 in object">
          {{ obj01.date }}：{{ obj01.weather }}<br>
          {{ obj01.min_temperature }}℃ ~ {{ obj01.max_temperature }}℃<br><br>
          <img v-bind:src=obj01.image>
      </div>
      
    <!--   植物判定 -->
      <div class="col-sm-6 mx-auto" id= "judge" >
        <div class="input-group-append">
          <button @click="start"　class="btn btn-outline-success" type="button" id="button-addon2">{{ recogButton }}</button>
        </div>
      </div>
      <br>
      <div>
        <video id="webcam" width="160" height="160" muted autoplay playsinline></video>   
      </div>
      <div class="col-sm-6 mx-auto" id="plant-name" >
        <dl v-for="obj02 in myPlant">
          <h5>{{ obj02.name }}</h5><br>
      </div>
      <div class="col-sm-6 mx-auto" id="plant-info" >
        <dl v-for="obj03 in myPlant">
          ＜越冬温度＞<br>
          {{ obj03.temperature }}<br><br>
          ＜水＞<br>
          {{ obj03.water }}<br><br>
          ＜光＞<br>
          {{ obj03.light }}<br><br>
          ＜コメント＞<br>
          {{ obj03.comment }}<br>
      </div>
    </div>
  </body>
</html>
```
</details><details><summary>CSS</summary>

```css:CSS
#plant-info {
  width:100%; 
  text-align:left;
}
video {
  border: 3px solid green;
}
```
</details><details><summary>Vue.js</summary>

```javascript:Vue.js
const app = new Vue({
  el: '#app',
  
  data() {
    return{
      recogButton: 'スタート！',
    // 植物
    // 作成したモデルのURL
      myPlant:[{
        imageModelURL:'',
        name:'',
        temperature:'',
        water:'',
        light:'',
        comment:''
      }],
      // 天気予報のためのオブジェクトを定義
      object:[{
        date:'',
        weather:'',
        min_temperature:'',
        max_temperature:'',
        image:''
      }],
      num:3
    }
  },
  created: async function() {
    this.getWeather();
  },  
  // メインの関数（ここでは定義しているだけでボタンクリックされたら実行）
  // awaitを使うとき（非同期）はasync
  methods: {
    async start() {
      this.recogButton = '撮影準備中…';
      const stream = await navigator.mediaDevices.getUserMedia({
        audio: false,
        video: { width: 160, height: 160, facingMode: 'environment' },
        // フロントカメラ優先 { facingMode: "user" }
        // リアカメラ優先 { facingMode: "environment" }
    });

      // 「id="webcam"」となっているパーツ（videoタグ）を取得
      const video = document.getElementById('webcam');

      // videoにカメラ映像ストリームをセット
      video.srcObject = stream;

      // Googleのサーバーにアップロードした自作モデルを読み込みにいきます
      this.myPlant.imageModelURL = 'https://teachablemachine.withgoogle.com/models/0BBHaK1RV/';
      console.log('imageModelURL:',this.myPlant.imageModelURL)
      const classifier = ml5.imageClassifier(this.myPlant.imageModelURL + 'model.json', video, () => {
        // 読み込みが完了次第ここが実行されます
        console.log('モデルの読み込みが完了しました');
        this.myPlant.shift();
        this.loop(classifier);
        console.log('loop処理1回目');
      });
    },
    loop: function(classifier) {
      console.log('loop　function');
      // 推論を実行し、エラーがあればerrに、結果をresultsに格納して、
      // 推論が完了次第 { } の中身を実行します
      classifier.classify(async (err, results) => {
        
          // 育成情報を手入力 
          let index_temperature = ['耐寒性。越冬温度の目安は0℃以下。一般の住宅ならば特に保温の必要がない。戸外越冬可能。',
                             '半耐寒性。越冬温度の目安は0℃以上。植物体が凍結しなければ越冬可能なので一般の住宅ならば特に保温の必要がない。暖地では無加温温室、軒下などで越冬する。',
                             '低温性。越冬温度の目安は5℃程度。一般の住宅ならば特に保温・加温の必要がない。',
                             '中温性。越冬温度の目安は10℃程度。寒さに弱く、気密・断熱性能の低い住宅では保温して越冬させる。',
                             '高温性。越冬温度の目安は15℃以上。極めて寒さに弱く、一般に加温して越冬させる。水遣りを控えれば多少耐寒性が高まる。'];
          let index_water = ['長期間の乾燥に耐える。水切れによる障害をほとんど受けない。過湿により障害を受けやすい。生育期には十分潅水する(土が乾いたら潅水)。サボテン・多肉植物など。',
                            '乾燥に耐える。水切れによる障害を受けにくい。過湿により障害を受けやすい。生育期には十分潅水する(土が乾いたら潅水)。葉や茎、根が多肉質な植物、着生植物など。',
                            '普通。一般的な観葉植物の潅水。土が乾いたら潅水する。',
                            '多湿を好む。水切れによる障害を受けやすい。土が乾ききる前に潅水する。シダ植物など。',
                            '非常に多湿を好む。水切れによる深刻な障害を受ける。用土が常に湿っている状態を保つ。水生植物や湿生植物など。'];
          let index_light = ['強い日陰に耐える。',
                            '半日陰を好む。夏の戸外では葉焼けを起こすので半日陰または遮光する(30～50％)。室内では壁側・人工照明による育成が可能。',
                            'やや明るい光を好む夏の戸外では葉焼けを起こすので遮光する(10～30％)。室内では部屋の中心部、レースのカーテン越しの窓辺いに置く。',
                            '明るい光を好む。夏の戸外では葉焼けを起こす可能性があるのでやや遮光する(～10％)。室内では出来るだけ明るい所に置く。',
                            '強い光を好む。戸外では直射日光、室内では出来るだけ明るい所に置く。'];
        
          // ガジュマル,コーヒーノキ,サンスベリア...の順に情報が格納されている。
          let num = 0;
          // 温度
          let t = [4,3,5,3,3,3];
          // 水
          let w = [3,3,3,3,4,3];
          // 光
          let l = [4,4,4,5,3,3];
          // コメント
          let temp_comment = ['成木の幹を挿し木して芽吹かせたもの、根元が膨らんだ盆栽風に仕立てたものなどが流通する。',
                              '言わずと知れた「コーヒー豆」の原料となる有用植物。実生苗から大鉢仕立てまで非常に多く流通する。結実には高温での越冬が必要。',
                              '黄覆輪斑。一般に「サンセベリア」として出回るのは本品種。',
                              '掌状複葉。日照を好むが耐陰性もある。実生小苗から大型の鉢物まで非常に多く流通する。',
                              '園芸品種多数。',
                              'サトイモ科。熱帯アメリカに約３０種。'];
          // console.log(results[0].label)
          // 植物名によって処理を変える
          if (results[0].label == "ガジュマル") {
            num = 0;
          } else if (results[0].label == "コーヒーノキ") {
            num = 1;
          } else if (results[0].label == "サンスベリア") {
            num = 2;
          } else if (results[0].label == "パキラ") {
            num = 3;
          } else if (results[0].label == "ポトス") {
            num = 4;
          } else if (results[0].label == "モンステラ") {
            num = 5;
          }
        // this.myPlant.shift();
        console.log(index_temperature[t[num]])
        this.myPlant.push({name: results[0].label,
                           temperature: index_temperature[t[num]-1],
                           water: index_water[w[num]-1],
                           light: index_light[l[num]-1],
                           comment: temp_comment[num],
                          });
        // console.log(this.myPlant.name[0])
        this.recogButton = '撮影完了';
        // 推論終了1秒後に自分の関数を実行（ループになる）
        // setTimeout(this.loop(classifier), 1000);
      });
    },
      
    getWeather: async function()  {
      console.log('getWeatherが呼び出されました');
      let response;
      try {
        // 天気のデータを取得
        response = await axios.get('https://weather.tsukumijima.net/api/forecast/city/130010');
        w_data = response.data.forecasts;
        this.object.shift();
        // 日にち、天気、気温、画像を取得する
        this.object.push({date: w_data[1].date,weather : w_data[1].telop,min_temperature:w_data[1].temperature.min.celsius,max_temperature:w_data[1].temperature.max.celsius,image:w_data[1].image.url});
      } catch (error) {
        console.error(error);
      }
    },
  },
});
```
</details>