Un dentiste (!?) A créé une application de jugement de quantité de mousse de langue avec flacon + keras

introduction

Avec keras + flask, j'ai créé une application Web (nom de l'application: Tanpic) qui juge le degré de saleté de l'image de la langue.

On dit que la raison de son développement est que la saleté à la surface de la langue appelée mousse de langue est liée à une mauvaise odeur (* 1), et j'ai pensé que ce serait intéressant s'il y avait une application qui pourrait visualiser les mauvaises odeurs avec un smartphone. est.

<a href = "https://px.a8.net/svt/ejp?a8mat=35RZ3H+G23XY2+3L4M+BW8O2&a8ejpredirect=https%3A%2F%2Fwww.udemy.com%2Fcourse%2Ftensorflow-advanced % 2F "rel =" nofollow "> [Développement d'application Image Judgment AI / Partie 1] Introduction au développement d'application Image Judgment AI avec TensorFlow / Python / Flask <img border =" 0 "width =" 1 "height = "1" src = "https://www13.a8.net/0.gif?a8mat=35RZ3H+G23XY2+3L4M+BW8O2" alt = ""> a été très utile. Les explications sont faciles à comprendre et vous pouvez poser des questions, donc je pense que c'est un matériel pédagogique recommandé pour ceux qui font une demande de classification d'images pour la première fois.

Au fait, je suis actuellement en 5e année à la Faculté de médecine dentaire et je ne suis pas encore vraiment dentiste. .. ..

Livrables

ファイル名

Lorsque je télécharge une image de ma langue ...

ファイル名

Le degré de saleté sur la langue est évalué en 3 étapes. En passant, cela vous rendra un peu de connaissance sur les mauvaises odeurs. J'espère que vous pouvez ignorer le mauvais comportement et le manque de sens du design. .. ..

Tanpic:https://tanpic-b86b4.firebaseapp.com/

Collecte de données

Dans cette phase, nous avons effectué les opérations suivantes:

  1. Collectez des images avec icrawler
  2. Élimination des images inutiles
  3. Coupe
  4. Étiqueter en 3 étapes selon le degré de saleté sur la langue

Les données d'image ont été collectées à l'aide d'icrowler. Veuillez vous référer au document pour plus de détails.

Référence: documentation icrawler

Après avoir éliminé les images inutiles, seule la langue est coupée manuellement une par une. Après cela, le degré de saleté sur la langue a été classé en trois.

Degré de saleté 1 (nom de l'étiquette: tongue0) スクリーンショット 2019-11-01 19.11.49.png

Degré de saleté 2 (nom de l'étiquette: tongue1) スクリーンショット 2019-11-01 19.14.19.png

Degré de saleté 3 (nom de l'étiquette: tongue2) ファイル名

Le traitement des données était long et laborieux et assez difficile

Apprentissage

J'ai utilisé le GPU google colabratoly pour apprendre.

Après avoir gonflé et entraîné les images collectées, enregistrez le modèle sous le nom tongue_cnn_aug.h5. J'omettrai les détails.

Test Accuracy

image.png

C'était 66,66%.

Coopération avec flacon

Voici un bref résumé de ce que nous faisons.

  1. Charger le modèle
  2. Convertissez l'image en 3 couleurs avec RVB
  3. Redimensionner l'image à 50 * 50
  4. Convertir l'image en Numpy Array
  5. Passez l'image au modèle et prédisez
  6. Renvoyez la valeur d'étiquette prédite avec la fonction argmax
  7. Transmettez la valeur de l'étiquette au fichier html avec la fonction render_template

predict_web.py



import os
from flask import Flask, flash, request, redirect, url_for, render_template
from werkzeug.utils import secure_filename

from keras.models import load_model
import numpy as np
from PIL import Image
import tensorflow as tf


class_1 = "C'est 1."
class_content_1 = "Il n'y a presque pas de mousse de langue dessus, et il semble être dans un assez bel état."
classes_solution_1 = "Faisons de notre mieux en matière de soins bucco-dentaires comme avant. En guise de mise en garde, un nettoyage excessif de la langue endommage non seulement la langue, mais augmente également la quantité de mousse de langue attachée, alors assurez-vous de le faire doucement une fois par jour."

class_2 = "C'est 2."
class_content_2 = "Vous n'avez pas à vous soucier de la saleté sur votre langue."
classes_solution_2 = "Langue sale(Mousse de langue)Peut causer de mauvaises odeurs, donc si cela vous inquiète, c'est une bonne idée de nettoyer doucement votre langue une fois par jour. De plus, l'odeur buccale a tendance à être plus forte le soir lorsque la production de salive est faible. Assurez-vous de vous gargariser et de vous réhydrater pour prendre des mesures."

class_3 = "C'est 3."
class_content_3 = "Il peut y avoir de la saleté sur votre langue. La mousse de langue (saleté sur la langue) peut également provoquer de mauvaises odeurs, il est donc conseillé de nettoyer votre langue en plus de votre brossage habituel."
classes_solution_3 = "Pour nettoyer la mousse de la langue, utilisez de la gaze ou une brosse à langue et frottez doucement la surface de la langue de l'arrière vers l'avant. Ne le faites qu'une fois par jour."

classes = [class_1,class_2,class_3]
classes_content = [class_content_1, class_content_2, class_content_3]
classes_solution = [classes_solution_1, classes_solution_2, classes_solution_3]


num_classes = len(classes)
image_size = 50

UPLOAD_FOLDER = './uploads'
ALLOWED_EXTENSIOS = set(['png', 'jpg', 'gif','heic'])

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER



model = load_model('./tongue_cnn_aug.h5')
graph = tf.get_default_graph()



def allowed_file(filename):
    #si.が含まれたる かつ si拡張子前の.Séparé par, vrai si l'extension est inférieure ou inférieure
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIOS

# @app.route('/', methods=['GET', 'POST'])
@app.route('/')
def upload_file():
        return render_template('test.html')



@app.route('/predict',methods=['GET', 'POST'])
def predict_file():
    global graph
    with graph.as_default():
        if request.method == 'POST':

            file = request.files['file']  #ajouter à
            if file and allowed_file(file.filename):
                filename = secure_filename(file.filename)
                file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
                filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)

                image = Image.open(filepath)
                image = image.convert('RGB')
                image = image.resize((image_size, image_size))
                data = np.asarray(image)
                X = []
                X.append(data)
                X = np.array(X)

                result = model.predict([X])[0]
                predicted = result.argmax()

                return render_template('predict.html', result = classes[predicted], result_content = classes_content[predicted], result_solution = classes_solution[predicted], result_title="Résultats d'analyse",img_name=file.filename)


from flask import send_from_directory

@app.route('/uploads/<filename>')
def uploaded_file(filename):
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)

predict.html


<!DOCTYPE! html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>Tanpic</title>
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <link rel="stylesheet" href="/static/css/style.css">
        <link rel="shortcut icon" href="/static/favicon.ico">

        <!--Chargement CSS Bootstrap-->
        <link rel="stylesheet" href="/static/css/bootstrap.min.css">

        <!--manifest.Chargement de json-->
        <!--<link rel="manifest" href="./manifest.json">-->
    </head>

    <body>
        <!--navi bar-->
        <nav class="navbar navbar-expand-lg navbar-light bg-light fixed-top">
          <a class="navbar-brand" href="#">Tanpic</a>

          <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarTogglerDemo03" aria-controls="navbarTogglerDemo03" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
          </button>


          <div class="collapse navbar-collapse" id="navbarTogglerDemo03">
            <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
              <li class="nav-item active">
                <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">Link</a>
              </li>
              <li class="nav-item">
                <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
              </li>
            </ul>
          </div>
        </nav>

        <!--header area-->
        <header class = "bg-primary text-center">
            <div class = "bg-mask">
                <div class="container">
                    <h1>Tanpic</h1>
                    <h2>Mettez une photo de votre langue</h2>
                    <ul>
                        {% for entry in entries %}
                        <li>{{entry.title}} / {{entry.text}}</li>
                        {% endfor %}
                    </ul>
                </div>
            </div>
        </header>


        <!--sorting Grid section-->
        <section class = "sorting">
        <div class = "container text-center">
            {%if img_name%}
                <img src="uploads/{{img_name}}" style="width:300px;height:300px;">

            {%endif%}


        </div>
        </section>
<!-- prediction session-->
        <div class = "container">
            <p>
                {%if result_title%}
                    {{result_title}}
                {%else%}
                {%endif%}

            </p>
            <p class="lead">
                <span>
                  {% if result %}
Il y a 3 niveaux de saleté sur votre langue{{ result }}
                  {% else %}
                  {% endif %}
                </span>
                <br>
                <br>
                <span>
                    {%if result_content%}
                    {{result_content}}
                    {%else%}
                    {%endif%}
                </span>
                <br>
                <br>
                <span>
                    {%if result_solution%}
                    {{result_solution}}
                    {%else%}
                    {%endif%}
                </span>
            </p>
        <a href="/">Revenir</a>
        </div>

        <!-- Footer -->
<footer class="page-footer font-small cyan darken-3">

  <!-- Footer Elements -->
  <div class="container">

    <!-- Grid row-->
    <div class="row">

      <!-- Grid column -->
      <div class="col-md-12 py-5">
        <div class="mb-5 flex-center">

          <!-- Facebook -->
          <a class="fb-ic">
            <i class="fab fa-facebook-f fa-lg white-text mr-md-5 mr-3 fa-2x"> </i>
          </a>
          <!-- Twitter -->
          <a class="tw-ic">
            <i class="fab fa-twitter fa-lg white-text mr-md-5 mr-3 fa-2x"> </i>
          </a>
          <!-- Google +-->
          <a class="gplus-ic">
            <i class="fab fa-google-plus-g fa-lg white-text mr-md-5 mr-3 fa-2x"> </i>
          </a>
          <!--Linkedin -->
          <a class="li-ic">
            <i class="fab fa-linkedin-in fa-lg white-text mr-md-5 mr-3 fa-2x"> </i>
          </a>
          <!--Instagram-->
          <a class="ins-ic">
            <i class="fab fa-instagram fa-lg white-text mr-md-5 mr-3 fa-2x"> </i>
          </a>
          <!--Pinterest-->
          <a class="pin-ic">
            <i class="fab fa-pinterest fa-lg white-text fa-2x"> </i>
          </a>
        </div>
      </div>
      <!-- Grid column -->

    </div>
    <!-- Grid row-->

  </div>
  <!-- Footer Elements -->

  <!-- Copyright -->
  <div class="footer-copyright text-center py-3">© 2018 Copyright:
    <a href="https://mdbootstrap.com/education/bootstrap/"> MDBootstrap.com</a>
  </div>
  <!-- Copyright -->

</footer>

        <script type="text/javascript" src="/static/js/jquery-3.3.1.min.js"></script>

        <!--js script-->
        <script type="text/javascript" src="/static/js/text.js"></script>

        <!--Chargement Bootstrap JS-->
        <script type="text/javascript" src="/static/js/bootstrap.min.js"></script>

        <!-- Firebase App (the core Firebase SDK) is always required and must be listed first -->
        <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-app.js"></script>
        <!-- Add Firebase products that you want to use -->
        <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-firestore.js"></script>
        <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-storage.js"></script>


    </body>


</html>

Fonctionne avec Firebase Cloud Storage

J'ai décidé d'utiliser Firebase Cloud Storage pour collecter les images téléchargées.

Article de référence: [Implémentation d'une application de téléchargement d'images sur Firebase Cloud Storage](https://kapi-travel.com/programing/firebase-cloud-storage%E3%81%B8%E3%81%AE%E7%94% BB% E5% 83% 8F% E3% 82% A2% E3% 83% 83% E3% 83% 97% E3% 83% AD% E3% 83% BC% E3% 83% 89% E3% 82% A2% E3% 83% 97% E3% 83% AA% E3% 82% 92% E5% AE% 9F% E8% A3% 85 /)

text.js



// Your web app's Firebase configuration
  var firebaseConfig = {
    apiKey: "",
    authDomain: "",
    databaseURL: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
    measurementId: ""
  };
  // Initialize Firebase
  firebase.initializeApp(firebaseConfig);
  const storage = firebase.storage();
  var file_name;
  var blob;
  
  $('#f1').on('submit',function(e){

    //Traitement lorsqu'une image est soumise sans être téléchargée
    if($('#myfile').val()===""){
      $('#error').text("Veuillez sélectionner un fichier");
      e.preventDefault();
    }

    //Processus de téléchargement sur Cloud Storage
    var uploadRef = storage.ref().child(file_name);
    uploadRef.put(blob).then(snapshot => {
      console.log(snapshot.state);
    });
    //réinitialisation de la valeur
    file_name = '';
    blob = '';

  });

Lorsque j'ai vérifié l'écran de la console, l'image a été enregistrée fermement. C'était pratique car très simple à mettre en œuvre.

Déployer sur heroku

Je l'ai déployé sur heroku en me référant à l'article suivant.

Référence: Qiita Flask Tutorial + Deploy to heroku

Après vous être connecté à heroku, suivez les étapes ci-dessous pour vous engager.

$ git add .
$ git commit
$ git push heroku master

Je suis tombé sur une erreur R14 en raison d'une mémoire insuffisante lors de git push heroku master. Avec le plan gratuit, la limite supérieure de mémoire est de 512 Mo, et il semble qu'une erreur R14 s'est produite car elle a dépassé cette valeur. La solution était d'utiliser un modèle plus petit et de maintenir l'utilisation de la mémoire en dessous de la limite supérieure.

Référence: Que faire lorsqu'une erreur R14 / R15 se produit dans Qiita Heroku

Code source

github:https://github.com/salmon0511/TongueCoatingClassificationApp

Impressions

C'était amusant de le mettre en œuvre en se référant à divers articles afin de concrétiser l'idée. De plus, j'ai été impressionné quand j'ai pu surmonter de nombreuses erreurs et déployer.

Cependant, je ne me souciais pas de la lisibilité (pour être exact, je ne sais toujours pas quel code est hautement lisible), alors j'aimerais essayer d'écrire du code facile à lire à partir de maintenant.

référence

Recommended Posts

Un dentiste (!?) A créé une application de jugement de quantité de mousse de langue avec flacon + keras
Créez une application Web simple avec Flask
Déployer une application Web créée avec Streamlit sur Heroku
Comment déployer une application Web créée avec Flask sur Heroku
Création d'un livre de lecture lié à PostgreSQL avec Flask
(Échec) Déployer une application Web créée avec Flask avec heroku
Créer un serveur Flask avec Docker
Exécutez l'application avec Flask + Heroku
Création d'une nouvelle application corona à Kyoto avec le framework Web de Python Dash