【Nuxt.js + Firebase】写真を投稿するとAIがハッピー指数を測定してランキング付けするリア充向けのシステムを作ったよ

##作ったもの

タイトルの通り、写真を投稿すると、どれくらいハッピーなのかAIが<B>ハッピー指数</B>を算出します。
![スクリーンショット 2020-09-09 22.48.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291592/1431262c-6fe2-a184-0fa0-b986cd776622.png)
写真は[パクタソ](https://www.pakutaso.com/)さんより

※「ハッピー指数」としましたが、これって「指数」って言ってもいいものだろうか不安になりましたが、頭の悪そうな語感が気に入ったのでこのまま「ハッピー指数」とします。
<br><br>
そして、同時に<B>ハッピー指数のランキング</B>を表示します。

![スクリーンショット 2020-09-09 22.57.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291592/43561bfc-49da-81f1-060e-e5e0292c023b.png)
写真は[パクタソ](https://www.pakutaso.com/)さんより

おお、眩しすぎる。
キラキラする。
まさにリア充向けシステムだ。



という訳で、
あなたが世界でどれくらい幸せなのか、調べてみましょう。


## 公開URL 
ウェブアプリとして公開していますので、是非お試してください。

URL：
http://happy-ranking.tk/

ちなみにこのドメインはこちらで取得しました。
[freenom](https://my.freenom.com/clientarea.php)

##環境
フレームワーク　：Nuxt.js
CSSフレームワーク：[BULMA](https://bulma.io/)
開発プラットフォーム：Firebase



##技術面
###表情解析
アップロードした写真を、[face-api.js](https://github.com/justadudewhohacks/face-api.js/) というTensorFlow.jsで学習済みの顔認識の機械学習モデルのライブラリに投げて、表情からハッピー度を取得します。
ハッピー度は0から1の間で、大きいほどハッピー度が高いです。

```
faceapi.resizeResults(detections, displaySize)[0].expressions.happy =
0.999991774559021
```
ハッピー度はこんな感じで取得されます。
小数点が１５桁くらいあるので、1000倍して見やすくし、ハッピー指数ということにしました。

（ご参考）自分の過去記事
[【忙しい現代人のために】表情で扇風機を操作するシステムを作ったよ](https://qiita.com/tatsuya1970/items/e106514e5049f30dc83b)

###写真およびデータ保存
- FirebaseのCloud FireStorageというストレージに写真を保存
 ![storage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291592/b52a8eda-3dda-a726-7677-d358df609027.png)

ここでハマったことを別記事に書きました。
[【Firebase + Nuxt.js】FirebaseStorageへの画像アップロードでハマったところ](https://qiita.com/tatsuya1970/items/bdceb10071547c68f711)

- あとで呼び出すために、写真を保存した場所のURLを、FirebaseのFirestoreに保存 (下記画像の picURLフィールド）
![firestore.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291592/22840b8f-0735-46e5-d94f-4549ba64c7eb.png)

##ハマったところ

#### リストの中でのイメージの表示方法

こちらを見て解決しました。ありがとうございました。
[【Vue.js】imgタグのsrc要素は指定の仕方によって読み込み方が違う](https://qiita.com/coppieee/items/4260bd0af246aebf5557)

たった、これだけなんですけど、凄くハマりました。
```
<img :src="data.picurl"/>
```

#### 非同期処理のコードの書き方

非同期処理が苦手です。
<B>then(() =>{</B> とか、Promiseとか async とか await　とか・・・
毎回、頭が混乱してしまいます。
<br><br>
じっくりと勉強しなければ。


<br>
#### face-api.js のファイルのままデプロイすると怒られた

```
[BABEL] Note: The code generator has deoptimised the styling of /pages/face-api.js as it exceeds the max of 500KB.
```
500KB以上はダメらしいので、
https://github.com/justadudewhohacks/face-api.js/
に書いてある通り、npmでインストールしました。
``
npm i face-api.js
``
<br>
#### face-api.jsの機械学習モデルの場所はstaticフォルダへ
同じディレクトリに保存してはいけません。
デプロイされません。

<br><br><br>
ハマったところは、その他、たくさんあったけど、書ききれないです。

<br><br><br>


##やりたかったけど出来なかったこと
・送信ボタンを付けたかった
（今のは、添付ファイルを選んだ瞬間に送信される）
・画像をリサイズして保存したかった
・ハッピー指数がでた瞬間に順位が出るようにしたかった




##コード
```vue:index.vue

<template>
  <div class="container">
    <div>
      <br><br>
      <h1 class="title">
        ハッピー・ランキング
      </h1>
      写真を投稿するとハッピー指数を判定して、ランキングします。<br>
      ハッピー指数は１０００点が最高得点です。<br><br>
      ※投稿写真は作者が管理しているクラウドサーバーに保存されますので、ご注意ください。
      <br><hr><br><br>
      <client-only placeholder="Loading...">
        <Pic />
      </client-only>
    </div>
  </div>
</template>

<script defer src="face-api.js"></script>
<script defer src="scripts.js"></script>

<script>
import Pic from '~/components/Pic.vue'

export default {
  components: {
    Pic
  }
}
</script>

<style>
.container {
  margin: 0 auto;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  background-color: pink;
}

.title {
  font-family:
    'Quicksand',
    'Source Sans Pro',
    -apple-system,
    BlinkMacSystemFont,
    'Segoe UI',
    Roboto,
    'Helvetica Neue',
    Arial,
    sans-serif;
  display: block;
  font-weight: bold; 
  font-size: 30px;
  color: #35495e;
  letter-spacing: 1px;
}

#happyScore{
  font-size: 30px;
  font-weight: bold; 
}

.list{
padding-top: 50px;
}

</style>

```

```vue:index.vue
<template>
    <div>
        投稿者名：<input v-model="name" placeholder="投稿者名">           
        <br><br>
        <input @change="post" type="file"  data-label="画像の添付">
        <br>
        <img id="attachedFile" width=350 v-show="uploadedImage" :src="uploadedImage"  />
    
        <div id="happyScore"></div>
        <br><hr>

　　    <div class="list">
            <h1 class="title">
                ハッピー指数　トップ１００
            </h1>
            <br>
            <ul v-for="(data, index) in allData" :key="data.id" class="menu-list" >
                <li>
                順位：　{{index + 1}}  位<br>
                ハッピー指数 ：  {{data.happyScore}} 点 <br>
                投稿者名：{{data.name}}　<br>
                <img width=350 :src="data.picurl"/>
                </li>
                <br><br>
            </ul>
　　    </div>
        <br>
    </div>
</template>

<script>
    import firebase from "firebase/app";
    import "firebase/firestore";
    import 'firebase/storage';
    import * as faceapi from 'face-api.js';
    import uuid from 'uuid';

    export default {
        components: {},
    
        data(){
            return{
                db: {},
                allData: [],
                name: '',
                fileName: '',
                picurl: '',
                uploadedImage: '',
                happyScore: '',
                realhappyScore: '',
                testId: ''
            }
        },

        methods: {
            //初期化、設定 
            //各人の数値を入れること
            init: () => {
                const config = {
                    apiKey: "",
                    authDomain: "",
                    databaseURL: "",
                    projectId: "",
                    storageBucket: "gs://xxxxxx-99999.appspot.com",
                    messagingSenderId: "",
                    appId: "",
                    measurementId: ""
                };

                // Initialize Firebase
                firebase.initializeApp(config);       
            },
        
            post(pic){
                const file = pic.target.files[0];
                if(!file.type.match('image.*')) {
                    alert("画像ファイルでお願いします");
                return;
                }
            
                //イメージファイル描画
                let reader = new FileReader();  
                reader.onload = (pic) => {
                    this.uploadedImage = pic.target.result;
                };
                let imagefiles = pic.target.files || pic.dataTransfer.files;
                reader.readAsDataURL(imagefiles[0]);
                let attachedFile = document.getElementById('attachedFile');           
                const testId = firebase.firestore().collection('pics').doc().id; //ユニークなIDを生成
                const docRef = firebase.firestore().collection('pics').doc(testId);
                const fileName = uuid(); //ファイル名は他と被らないように uuid ライブラリを使って動的に生成
                const storageRef = firebase.storage().ref(fileName);          
        
                // 画像をStorageにアップロード
                storageRef.put(file).then(() => {   
                    let debug_document =  document.getElementById("happyScore");
                    debug_document.innerHTML = "しばらくお待ちください";

                    // アップロードした画像のURLを取得 
                    const picurl = firebase.storage().ref(fileName).getDownloadURL().then((url) => {    
                        const happyScoreGet = getFaceData( document.getElementById('attachedFile')).then((happyScore) => {
                            let debug_document =  document.getElementById("happyScore");
                            let realhappyScore = happyScore;
                            happyScore = Math.floor(happyScore * 1000); //1000倍
                            debug_document.innerHTML = "ハッピー指数: "+String(happyScore)+"点";
                            //firestoreにデータを保存
                            const setScore = docRef.set({
                                name: this.name,
                                happyScore: happyScore,
                                realhappyScore: realhappyScore ,
                                fileName: fileName ,
                                picurl: url
                            });
                    //ランキング作成へ
                    this.get();
                    });

                }).catch((error) => {
                    console.log(error)
                })
            })
        },

        //データ取得
        get: function(){
            this.allData = [];
            //スコアの降順に１００個取得    
            firebase.firestore().collection('pics').orderBy('realhappyScore', 'desc').limit(100).get().then(snapshot => {
                snapshot.forEach(doc => {              
                    this.allData.push(doc.data());
                })
            });    
        }
        },  
        mounted(){
        //ページ読み込み時に実行される
        this.init();
        },
    }

    //表情取得
    async function getFaceData(img) { 
        await faceapi.nets.tinyFaceDetector.load("/models") ;//モデル読み込み
        await faceapi.nets.faceLandmark68Net.load("/models") ;//モデル読み込み
        await faceapi.nets.faceRecognitionNet.load("/models") ;//モデル読み込み
        await faceapi.nets.faceExpressionNet.load("/models") ;//モデル読み込み

        const detectionsWithLandmarks = await faceapi.detectAllFaces(img,new faceapi.TinyFaceDetectorOptions()).withFaceLandmarks();
        if (detectionsWithLandmarks.length == 0){
            alert('人間じゃないよ');
        return(0)
        }else{
            const displaySize = { width: attachedFile.width, height: attachedFile.height }
            //１つの顔だけなのでfaceapi.detectAllFacesではなくて detectSingleFaceでよいはずが、本件はdetectAllFacesを使った。
            const detections = await faceapi.detectAllFaces(attachedFile , new faceapi.TinyFaceDetectorOptions()).withFaceLandmarks().withFaceExpressions()
            const resizedDetections = faceapi.resizeResults(detections, displaySize)
            return(resizedDetections[0].expressions.happy); //ハッピー指数を返す
        }
    }

</script> 
```


<br><br><br><br><br>
<br><br><br><br><br>
<br><br><br><br><br>



## 番外編　（内輪ネタ）

現在、一緒にProtoOut Studioで学んでる受講生が今までQiitaで取り上げた人物で試してみました。

[Juri Tawara](https://qiita.com/twtjudy1128)さん
代表作：[ジェイソン・ステイサムで妄想するのが日課になっていたので、いっそBOTにしてみた。](https://qiita.com/twtjudy1128/items/88f3e8f09c449f49456c)
![ステイサム.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291592/d906152f-23f6-3cc0-42e0-699a77bf10ed.png)
<br>

[UhRhythm](https://qiita.com/UhRhythm)さん
[【Vue.js】さ迷うハロオタがお誕生日カレンダーを作った](https://qiita.com/UhRhythm/items/d80a4a8d6ceeb82f29c1)
![スクリーンショット 2020-09-09 22.44.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291592/28db33cc-8a34-935a-4ebe-b8bece912b19.png)