LoginSignup
19
13

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-09-09

作ったもの

タイトルの通り、写真を投稿すると、どれくらいハッピーなのかAIがハッピー指数を算出します。
スクリーンショット 2020-09-09 22.48.59.png
写真はパクタソさんより

※「ハッピー指数」としましたが、これって「指数」って言ってもいいものだろうか不安になりましたが、頭の悪そうな語感が気に入ったのでこのまま「ハッピー指数」とします。



そして、同時にハッピー指数のランキングを表示します。

スクリーンショット 2020-09-09 22.57.11.png
写真はパクタソさんより

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

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

公開URL

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

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

ちなみにこのドメインはこちらで取得しました。
freenom

環境

フレームワーク :Nuxt.js
CSSフレームワーク:BULMA
開発プラットフォーム:Firebase

技術面

表情解析

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

faceapi.resizeResults(detections, displaySize)[0].expressions.happy =
0.999991774559021

ハッピー度はこんな感じで取得されます。
小数点が15桁くらいあるので、1000倍して見やすくし、ハッピー指数ということにしました。

(ご参考)自分の過去記事
【忙しい現代人のために】表情で扇風機を操作するシステムを作ったよ

写真およびデータ保存

  • FirebaseのCloud FireStorageというストレージに写真を保存 storage.png

ここでハマったことを別記事に書きました。
【Firebase + Nuxt.js】FirebaseStorageへの画像アップロードでハマったところ

  • あとで呼び出すために、写真を保存した場所のURLを、FirebaseのFirestoreに保存 (下記画像の picURLフィールド) firestore.png

ハマったところ

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

こちらを見て解決しました。ありがとうございました。
【Vue.js】imgタグのsrc要素は指定の仕方によって読み込み方が違う

たった、これだけなんですけど、凄くハマりました。

<img :src="data.picurl"/>

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

非同期処理が苦手です。
then(() =>{ とか、Promiseとか async とか await とか・・・
毎回、頭が混乱してしまいます。



じっくりと勉強しなければ。


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


face-api.jsの機械学習モデルの場所はstaticフォルダへ

同じディレクトリに保存してはいけません。
デプロイされません。





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




やりたかったけど出来なかったこと

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

コード

index.vue

<template>
  <div class="container">
    <div>
      <br><br>
      <h1 class="title">
        ハッピー・ランキング
      </h1>
      写真を投稿するとハッピー指数を判定して、ランキングします。<br>
      ハッピー指数は1000点が最高得点です。<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>

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">
                ハッピー指数 トップ100
            </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 = [];
            //スコアの降順に100個取得    
            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 }
            //1つの顔だけなので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> 


















番外編 (内輪ネタ)

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

Juri Tawaraさん
代表作:ジェイソン・ステイサムで妄想するのが日課になっていたので、いっそBOTにしてみた。
ステイサム.png

UhRhythmさん
【Vue.js】さ迷うハロオタがお誕生日カレンダーを作った
スクリーンショット 2020-09-09 22.44.53.png

19
13
0

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
19
13