[PYTHON] J'ai essayé de créer une API de reconnaissance d'image simple avec Fast API et Tensorflow

introduction

J'utilise généralement beaucoup Flask, mais ma connaissance a dit: "Fast API is good!", J'ai donc décidé de créer une simple API de reconnaissance d'image. Cependant, je n'ai pas vu beaucoup d'articles japonais sur FastAPI et ML, j'ai donc décidé de créer cet article au lieu d'un mémo!

Dans cet article, après avoir préparé l'environnement de développement, nous donnerons une brève explication du serveur API et du front-end.

Tout le code utilisé cette fois-ci est publié sur Github. ** (La structure des dossiers de l'implémentation ci-dessous est décrite sur l'hypothèse de Github. Le téléchargement de l'exemple de modèle est également décrit dans README.md.) **

Qu'est-ce que FastAPI?

C'est l'un des frameworks Python comme Flask.

Pour un aperçu simple et un résumé de son utilisation, veuillez vous référer à l'article suivant. (Merci beaucoup pour votre aide dans cet article aussi!)

https://qiita.com/bee2/items/75d9c0d7ba20e7a4a0e9

Pour ceux qui veulent en savoir plus, nous vous recommandons les tutoriels officiels Fast API!

https://fastapi.tiangolo.com/tutorial/

À propos de la reconnaissance d'image

Je n'ai pas eu le temps cette fois, donc je vais le construire en utilisant le modèle de tensorflow.keras!

Plus précisément, nous utiliserons ResNet50 appris par imagenet tel quel et en déduire à laquelle des 1000 classes appartient l'image d'entrée.

(Le modèle que je voulais vraiment utiliser n'était pas à temps car j'apprenais à être acclamé en ce moment ...)

https://www.tensorflow.org/versions/r1.15/api_docs/python/tf/keras?hl=ja

Environnement de développement

Mac OS X Mojave Python3.7.1(Anaconda)

Environnement

Installez la bibliothèque Python requise.

$pip install tensorflow==1.15
$pip install fastapi
$pip install uvicorn

Puisqu'il existe les conditions suivantes, installez également les bibliothèques nécessaires. --Rendu index.html

$pip install Jinja
$pip install aiofiles
$pip install python-multipart
$pip install opencv-python

Serveur API

La mise en œuvre du serveur API est la suivante.

# -*- coding: utf-8 -*-
import io
from typing import List

import cv2
import numpy as np
import tensorflow as tf
from tensorflow.keras.applications.resnet50 import decode_predictions
from fastapi import FastAPI, Request, File, UploadFile
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

#Préparation du modèle de reconnaissance d'image
global model, graph
graph = tf.get_default_graph()
model = tf.keras.models.load_model("./static/model/resnet_imagenet.h5")

#Préparation de Fast API
app = FastAPI()

# static/js/post.index js.Obligatoire pour appeler à partir de HTML
app.mount("/static", StaticFiles(directory="static"), name="static")

#Index stocké sous des modèles.Requis pour rendre le HTML
templates = Jinja2Templates(directory="templates")


def read_image(bin_data, size=(224, 224)):
    """Charger l'image

    Arguments:
        bin_data {bytes} --Données binaires d'image

    Keyword Arguments:
        size {tuple} --Taille d'image que vous souhaitez redimensionner(default: {(224, 224)})

    Returns:
        numpy.array --image
    """
    file_bytes = np.asarray(bytearray(bin_data.read()), dtype=np.uint8)
    img = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = cv2.resize(img, size)
    return img


@app.post("/api/image_recognition")
async def image_recognition(files: List[UploadFile] = File(...)):
    """API de reconnaissance d'image

    Keyword Arguments:
        files {List[UploadFile]} --Informations sur le fichier téléchargé(default: {File(...)})

    Returns:
        dict --Résultat d'inférence
    """
    bin_data = io.BytesIO(files[0].file.read())
    img = read_image(bin_data)
    with graph.as_default():
        pred = model.predict(np.expand_dims(img, axis=0))
        result_label = decode_predictions(pred, top=1)[0][0][1]
        return {"response": result_label}


@app.get("/")
async def index(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

Recevoir des données de la réception

@app.post("/api/image_recognition")
async def image_recognition(files: List[UploadFile] = File(...)):
    """API de reconnaissance d'image

    Keyword Arguments:
        files {List[UploadFile]} --Informations sur le fichier téléchargé(default: {File(...)})

    Returns:
        dict --Résultat d'inférence
    """
    bin_data = io.BytesIO(files[0].file.read())
    img = read_image(bin_data)
    with graph.as_default():
        pred = model.predict(np.expand_dims(img, axis=0))
        result_label = decode_predictions(pred, top=1)[0][0][1]
        return {"response": result_label}

Cette fois, nous utilisons le fichier de téléchargement Fast API pour obtenir l'image POSTée.

bin_data = io.BytesIO(files[0].file.read())

Étant donné qu'un seul fichier est POSTÉ, il est défini en tant que fichiers [0], et comme il est passé au format BASE64 depuis le recto, il a été converti en un tableau d'octets du côté API.

Convertir les données en image

def read_image(bin_data, size=(224, 224)):
    """Charger l'image

    Arguments:
        bin_data {bytes} --Données binaires d'image

    Keyword Arguments:
        size {tuple} --Taille d'image que vous souhaitez redimensionner(default: {(224, 224)})

    Returns:
        numpy.array --image
    """
    file_bytes = np.asarray(bytearray(bin_data.read()), dtype=np.uint8)
    img = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = cv2.resize(img, size)
    return img

Avec l'aide d'opencv, il convertit un tableau d'octets en une image uint8. À ce stade, comme le format par défaut d'opencv est BGR, je l'ai converti en RVB et l'ai redimensionné.

Déduire

global model, graph
graph = tf.get_default_graph()
model = tf.keras.models.load_model("./static/model/resnet_imagenet.h5")

...

with graph.as_default():
        pred = model.predict(np.expand_dims(img, axis=0))
        result_label = decode_predictions(pred, top=1)[0][0][1]

J'ai créé resnet_imagenet.h5 à l'avance et l'ai lu en haut du fichier. Le processus d'inférence lui-même est déduit avec la fonction prédire en fixant le contexte de ce thread au graphe TensorFlow défini globalement avec with graph.as_default ().

Puisque nous utilisons ResNet50 de tf.keras cette fois, nous utilisons decode_predictions pour convertir le résultat de prédire en une étiquette pour obtenir le résultat de l'inférence.

Je pense que d'autres modèles et modèles personnalisés peuvent être utilisés comme cette implémentation en enregistrant le fichier .h5 quelque part dans le répertoire du projet et en le chargeant_model.

Montage frontal

Je l'ai utilisé comme référence. (Je vous remercie!)

https://qiita.com/katsunory/items/9bf9ee49ee5c08bf2b3d

<html>
<head>
    <meta http-qeuiv="Content-Type" content="text/html; charset=utf-8">
    <title>Test de reconnaissance d'image Fastapi</title>
    <script src="//code.jquery.com/jquery-2.2.3.min.js"></script>
    <script src="/static/js/post.js"></script>
</head>

<body>

<!--Bouton de sélection de fichier-->
<div style="width: 500px">
  <form enctype="multipart/form-data" method="post">
    <input type="file" name="userfile" accept="image/*">
  </form>
</div>

<!--Zone d'affichage de l'image-->
<canvas id="canvas" width="0" height="0"></canvas>

<!--Télécharger le bouton de démarrage-->
<button class="btn btn-primary" id="post">Publier</button>
<br>
<h2 id="result"></h2>
</body>
</html>

//Redimensionner l'image et l'afficher en HTML
$(function () {
  var file = null;
  var blob = null;
  const RESIZED_WIDTH = 300;
  const RESIZED_HEIGHT = 300;

  $("input[type=file]").change(function () {
    file = $(this).prop("files")[0];

    //Contrôle de fichier
    if (file.type != "image/jpeg" && file.type != "image/png") {
      file = null;
      blob = null;
      return;
    }

    var result = document.getElementById("result");
    result.innerHTML = "";

    //Redimensionner l'image
    var image = new Image();
    var reader = new FileReader();
    reader.onload = function (e) {
      image.onload = function () {
        var width, height;

        //Redimensionner pour s'adapter au plus long
        if (image.width > image.height) {
          var ratio = image.height / image.width;
          width = RESIZED_WIDTH;
          height = RESIZED_WIDTH * ratio;
        } else {
          var ratio = image.width / image.height;
          width = RESIZED_HEIGHT * ratio;
          height = RESIZED_HEIGHT;
        }

        var canvas = $("#canvas").attr("width", width).attr("height", height);
        var ctx = canvas[0].getContext("2d");
        ctx.clearRect(0, 0, width, height);
        ctx.drawImage(
          image,
          0,
          0,
          image.width,
          image.height,
          0,
          0,
          width,
          height
        );

        //Obtenez des données d'image base64 à partir du canevas et créez un objet blob pour POST
        var base64 = canvas.get(0).toDataURL("image/jpeg");
        var barr, bin, i, len;
        bin = atob(base64.split("base64,")[1]);
        len = bin.length;
        barr = new Uint8Array(len);
        i = 0;
        while (i < len) {
          barr[i] = bin.charCodeAt(i);
          i++;
        }
        blob = new Blob([barr], { type: "image/jpeg" });
        console.log(blob);
      };
      image.src = e.target.result;
    };
    reader.readAsDataURL(file);
  });

  //Lorsque le bouton de démarrage du téléchargement est cliqué
  $("#post").click(function () {
    if (!file || !blob) {
      return;
    }

    var name,
      fd = new FormData();
    fd.append("files", blob);

    //POST à l'API
    $.ajax({
      url: "/api/image_recognition",
      type: "POST",
      dataType: "json",
      data: fd,
      processData: false,
      contentType: false,
    })
      .done(function (data, textStatus, jqXHR) {
          //Si la communication réussit, afficher le résultat
        var response = JSON.stringify(data);
        var response = JSON.parse(response);
        console.log(response);
        var result = document.getElementById("result");
        result.innerHTML = "Cette image...「" + response["response"] + "Yanke";
      })
      .fail(function (jqXHR, textStatus, errorThrown) {
          //Émet un message d'erreur si la communication échoue
        var result = document.getElementById("result");
        result.innerHTML = "La communication avec le serveur a échoué...";
      });
  });
});

Le POST est effectué sur l'API de reconnaissance d'image à l'aide d'ajax et le résultat est affiché.

Contrôle de fonctionnement

En conséquence, cela fonctionne comme ça! demo.png

(Je voulais rendre la réception un peu plus à la mode ...)

en conclusion

J'ai créé une API de reconnaissance d'image pour étudier Fast API. Je ne pense pas que la mise en œuvre que j'ai faite cette fois-ci soit la meilleure pratique, mais je suis content d'avoir pu faire quelque chose qui fonctionne.

Je ne sais pas quel framework utiliser pour travailler à l'avenir, mais je pensais que FastAPI est relativement facile à utiliser et que je devrais passer de Flask.

** Enfin et surtout, merci à tous ceux qui nous ont aidés! ** **

Recommended Posts

J'ai essayé de créer une API de reconnaissance d'image simple avec Fast API et Tensorflow
J'ai créé une API Web
J'ai essayé la reconnaissance d'image simple avec Jupyter
Rubyist a essayé de créer une API simple avec Python + bouteille + MySQL
J'ai essayé de faire un processus d'exécution périodique avec Selenium et Python
J'ai créé un jeu ○ ✕ avec TensorFlow
J'ai essayé de créer un LINE BOT "Sakurai-san" avec API Gateway + Lambda
J'ai essayé d'implémenter Grad-CAM avec keras et tensorflow
J'ai essayé de faire un processus périodique avec CentOS7, Selenium, Python et Chrome
J'ai créé une API de recherche de château avec Elasticsearch + Sudachi + Go + echo
J'ai essayé de créer une interface graphique à trois yeux côte à côte avec Python et Tkinter
J'ai créé un éditeur de texte simple en utilisant PyQt
J'ai essayé de faire quelque chose comme un chatbot avec le modèle Seq2Seq de TensorFlow
[5e] J'ai essayé de créer un certain outil de type Authenticator avec python
J'ai essayé d'implémenter Autoencoder avec TensorFlow
[2nd] J'ai essayé de créer un certain outil de type Authenticator avec python
J'ai essayé de créer une application de notification de publication à 2 canaux avec Python
J'ai essayé de créer des taureaux et des vaches avec un programme shell
J'ai essayé de créer une application todo en utilisant une bouteille avec python
[4th] J'ai essayé de créer un certain outil de type Authenticator avec python
[1er] J'ai essayé de créer un certain outil de type Authenticator avec python
J'ai essayé de faire une étrange citation pour Jojo avec LSTM
J'ai essayé de créer une fonction de similitude d'image avec Python + OpenCV
J'ai essayé de créer un mécanisme de contrôle exclusif avec Go
J'ai essayé de faire un signal avec Raspeye 4 (édition Python)
J'ai essayé de faire la reconnaissance de caractères manuscrits de Kana Partie 2/3 Création et apprentissage de données
J'ai essayé de créer un service de raccourcissement d'url sans serveur avec AWS CDK
J'ai essayé la reconnaissance d'image de CIFAR-10 avec Keras-Learning-
J'ai essayé la reconnaissance d'image de CIFAR-10 avec la reconnaissance d'image Keras-
Je veux faire un jeu avec Python
Quand j'ai essayé de créer un VPC avec AWS CDK mais que je n'ai pas pu le faire
J'ai essayé d'extraire le dessin au trait de l'image avec Deep Learning
J'ai essayé d'implémenter et d'apprendre DCGAN avec PyTorch
Essayez de créer un jeu simple avec Python 3 et iPhone
J'ai essayé de lire et d'enregistrer automatiquement avec VOICEROID2
J'ai essayé de découvrir notre obscurité avec l'API Chatwork
J'ai essayé un RPA simple pour me connecter avec du sélénium
J'ai essayé de créer une application OCR avec PySimpleGUI
J'ai essayé de trouver la classe alternative avec tensorflow
J'ai essayé la reconnaissance d'image de "Moon and Suppon" avec Pytorch (en utilisant torchvision.datasets.ImageFolder qui correspond à from_from_directry de keras)
J'ai essayé de faire une simulation de séparation de source sonore en temps réel avec l'apprentissage automatique Python
[Mac] Je souhaite créer un serveur HTTP simple qui exécute CGI avec Python
Traitement d'image avec Python (j'ai essayé de le binariser en art mosaïque 0 et 1)
J'ai essayé de faire de l'art créatif avec l'IA! J'ai programmé une nouveauté! (Article: Réseau Adversaire Créatif)
J'ai essayé d'implémenter une ligne moyenne mobile de volume avec Quantx
J'ai essayé de trouver l'entropie de l'image avec python
J'ai essayé de créer diverses "données factices" avec Python faker
J'ai essayé de créer automatiquement un rapport avec la chaîne de Markov
Je veux créer un éditeur de blog avec l'administrateur de django
Je veux faire une macro de clic avec pyautogui (désir)
J'ai essayé la gestion du suivi avec l'API Twitter et Python (facile)