[PYTHON] Publions l'API de super résolution à l'aide de Google Cloud Platform

introduction

Cet article est [ici](https://aiotadiary.wp.xdomain.jp/2020/03/01/google-cloud-platform%E3%82%92%E4%BD%BF%E3%81%A3% E3% 81% A6% E8% B6% 85% E8% A7% A3% E5% 83% 8F% E5% BA% A6% E5% 8C% 96api% E3% 82% 92% E5% 85% AC% E9% 96% 8B% E3% 81% 97% E3% 81% A6% E3% 81% BF% E3% 82% 8B /) Ceci est une réécriture de l'article de blog pour qiita. Jetez également un œil à celui-ci.   Cette fois, je souhaite publier l'API à l'aide de Google Cloud Platform, communément appelé GCP. J'avais l'habitude d'utiliser GCP il y a quelque temps, donc cette fois, j'aimerais garder cela à l'esprit et avoir l'expérience du déploiement du service moi-même. À propos, l'API est un modèle de super-résolution utilisant l'ESPCN précédemment implémenté. Ce que je voudrais aborder cette fois, c'est une fonction appelée Cloud Run dans GCP.

Inscription auprès de GCP

Pour vous inscrire à GCP, vous pouvez généralement vous référer au guide Google ou à d'autres articles. Il n'y a aucun problème si vous suivez le guide de démarrage. L'article ci-dessous vous sera utile. GCP (GCE) pour commencer à partir de maintenant, utilisez en toute sécurité le cadre libre En gros, inscrivez-vous dans l'ordre suivant.

  1. Connectez-vous à GCP  image.png
  2. Appuyez sur le bouton "S'inscrire pour un essai gratuit" sur l'écran ci-dessus  image.png
  3. Remplissez les informations selon les instructions affichées  image.png Ici, vous entrerez des informations telles qu'une carte de crédit, mais soyez assuré qu'elle ne sera pas retirée sans autorisation à moins que vous ne la définissiez sur un compte payant.

Achèvement de l'inscription

L'inscription est maintenant terminée. Avec le niveau GCP gratuit, vous pouvez créer une instance GCE de f1-micro gratuitement, veuillez donc l'utiliser. En passant, dans l'essai gratuit, vous pouvez utiliser le service d'une valeur de 300 $ pendant 12 mois gratuitement, vous pouvez donc essayer diverses choses.

Utiliser Cloud Run

Cloud Run est un service qui vous permet de déployer des conteneurs Docker et déploie essentiellement le service à l'aide de l'image Docker téléchargée dans Container Registry. Pour le moment, il semble qu'il existe un moyen de créer et de déployer le conteneur déclenché en tirant sur Github, mais cette fois, cela semble un peu difficile, alors cette fois, j'aimerais prendre la forme de télécharger l'image construite localement.

Installez la commande gcloud

Puisque j'utilise mac, l'explication suivante n'est utile pour personne d'autre que mac. Tout d'abord, téléchargez l'archive suivante.

google-cloud-sdk-245.0.0-darwin-x86_64.tar.gz

Exécutez ensuite la commande suivante.

$ ./google-cloud-sdk/install.sh 

Ensuite, initialisez le SDK.

$ gcloud init

Voulez-vous vous connecter après cela? Un message apparaîtra disant cela, alors entrez Y. Après cela, sélectionnez le projet auquel vous connecter, mais soyez assuré qu'il sera sélectionné arbitrairement lorsqu'il n'y a qu'un seul projet. Après cela, vous pourrez utiliser la commande si vous la saisissez correctement. ↓ est la méthode de configuration officielle.

Démarrage rapide pour macOS Démarrage rapide pour Windows Démarrage rapide pour Linux

Créer une image Docker

Ici, j'utiliserai le modèle ESPCN mis en œuvre la dernière fois et utiliserai le code qui double la résolution et le produit en tant qu'API. J'utilise Flask et Gunicorn.

En tant que flux de toute l'opération

  1. Décodez la base64 de l'image d'entrée à partir de la requête json
  2. Entrez l'image décodée dans la fonction de super-résolution
  3. Codez en base64 et répondez

C'est aussi simple que ça. Tout d'abord, créez un référentiel Git pour Espcn-API (Cliquez ici pour le référentiel). La structure du répertoire ressemble à ce qui suit.

ESPCN-API
 ├model
 │ └pre_trained_mode.pth
 ├api.py
 ├requirements.txt
 ├Dockerfile
 └networks.py
test
 ├test.py
 └test.png

Code de test

Commençons par le code de test. Il y a deux choses à vérifier dans le test.

・ Une réponse est renvoyée (status_code est 200) ・ La taille de l'image a été doublée   Nous mettrons en œuvre l'opération pour les confirmer.

import sys
import requests
import json
from io import BytesIO
from PIL import Image
import base64

def image_file_to_base64(file_path):
    with open(file_path, "rb") as image_file:
        data = base64.b64encode(image_file.read())
    return 'data:image/png;base64,' + data.decode('utf-8')

def base64_to_img(img_formdata):
    img_base64 = img_formdata.split(',')[1]
    input_bin = base64.b64decode(img_base64)
    with BytesIO(input_bin) as b:
        img = Image.open(b).copy().convert("RGB")
    return img

if __name__ == "__main__":
    source_image_path = "test.png "
    source_image = Image.open(source_image_path)
    source_width, source_height = source_image.size
    print("source_width :", source_width)
    print("source_height :", source_height)
    host_url = "http://0.0.0.0:8000"
    data = {"srcImage":image_file_to_base64(source_image_path)}
    json_data = json.dumps(data)

    response = requests.post(host_url, json_data, headers={'Content-Type': 'application/json'})

    assert response.status_code == 200, "validation error status code should be 200"

    res_json = response.json()
    
    res_image = base64_to_img(res_json["sresoImage"])
    sreso_width, sreso_height = res_image.size
    print("sreso_width :", sreso_width)
    print("sreso_height :", sreso_height)
    assert sreso_width == source_width * 2 and sreso_height == source_height * 2 , \
        "validation error image size should be 2 times of input image"
    res_image.show()
    print("OK")

Code principal

Ensuite, nous allons implémenter le principal api.py. Tout d'abord, nous avons créé le squelette de l'ensemble de l'implémentation. Le contenu de la fonction n'est pas du tout implémenté.

from flask import Flask, request, jsonify
from networks import Espcn
import os
from io import BytesIO
import base64
from torchvision import transforms
from torch import load
import torch
import json
from PIL import Image

device = "cpu"
net = Espcn(upscale=2)
net.load_state_dict(torch.load(opt.model_path, map_location="cpu"))
net.to(device)
net.eval()

def b64_to_PILImage(b64_string):
    """
    process convert base64 string to PIL Image
    input: b64_string: base64 string : data:image/png;base64,{base64 string}
    output: pil_img: PIL Image
    """
    pass

def PILImage_to_b64(pil_img):
    """
    process convert PIL Image to base64
    input: pil_img: PIL Image
    output: b64_string: base64 string : data:image/png;base64,{base64 string}
    """
    pass

def expand(src_image, model=net, device=device):
    pass

@app.route("/", methods=["POST"])
def superResolution():
    pass

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=os.environ.get("PORT", 8000))

Je pense à une structure dans laquelle la fonction d'expansion effectue la super-résolution principale et l'exécute dans la super-résolution qui s'exécute pour la requête. Aussi, en définissant le modèle utilisé pour la super-résolution en dehors de la fonction, je pense qu'il est possible d'éviter la peine de charger le modèle lorsque le worker traite plusieurs requêtes.

Donc, tout d'abord, implémentez deux fonctions ~ to ~ qui sont entrées et sorties.

def b64_to_PILImage(b64_string):
    """
    process convert base64 string to PIL Image
    input: b64_string: base64 string : data:image/png;base64,{base64 string}
    output: pil_img: PIL Image
    """
    b64_split = b64_string.split(",")[1]
    b64_bin = base64.b64decode(b64_split)
    with BytesIO(b64_bin) as b:
        pil_img = Image.open(b).copy().convert('RGB')
    return pil_img

def PILImage_to_b64(pil_img):
    """
    process convert PIL Image to base64
    input: pil_img: PIL Image
    output: b64_string: base64 string : data:image/png;base64,{base64 string}
    """
    with BytesIO() as b:
        pil_img.save(b, format='png')
        b.seek(0)
        img_base64 = base64.b64encode(b.read()).decode('utf-8')
    img_base64 = 'data:image/png;base64,' + img_base64
    return img_base64

J'ai eu du mal à utiliser BytesIO parce que c'était étonnamment difficile. Viennent ensuite la fonction d'expansion et la fonction principale de super-résolution.

def tensor_to_pil(src_tensor):
    src_tensor = src_tensor.mul(255)
    src_tensor = src_tensor.add_(0.5)
    src_tensor = src_tensor.clamp_(0, 255)
    src_tensor = src_tensor.permute(1, 2, 0)
    src_tensor = src_tensor.to("cpu", torch.uint8).numpy()
    return Image.fromarray(src_tensor)


def expand(src_image, model=net, device=device):
    src_tensor = transforms.ToTensor()(src_image).to(device)
    if src_tensor.dim() == 3:
        src_tensor = src_tensor.unsqueeze(0)
    
    srezo_tensor = model(src_tensor).squeeze()
    srezo_img = tensor_to_pil(srezo_tensor)
    return srezo_img

@app.route("/", methods=["POST"])
def superResolution():
    req_json = json.loads(request.data)
    src_b64 = req_json["srcImage"]

    # main process
    src_img = b64_to_PILImage(src_b64)
    srezo_img = expand(src_img)
    srezo_b64 = PILImage_to_b64(srezo_img)

    results = {"sresoImage":srezo_b64}

    return jsonify(results)


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=os.environ.get("PORT", 8000))

Au début, je voulais l'adapter à deux fonctions, mais la conversion de torch.tensor en PIL Image est devenue étrange lors de l'utilisation de transforms.ToPILImage, j'ai donc décidé de la définir séparément. Fait. Aussi, concernant l'argument au moment du dernier app.run, il semble qu'il soit nécessaire de définir pour lire la variable d'environnement PORT car un autre port est automatiquement attribué à chaque conteneur docker en raison des spécifications de cloud run.

Maintenant, créez ces opérations vérifiées localement en tant qu'image docker.

$ docker build -t gcr.io/[project id]/espcn-api:0 .

Ensuite, téléchargez l'image dans le registre de conteneurs à l'aide de la commande gcloud.

$ gcloud docker -- push gcr.io/[project id]/espcn-api:0

Vous pouvez en fait vérifier l'image dans Container Registry sur GCP. image.png Puis déployez en utilisant l'image téléchargée.

$ gcloud beta run deploy SR-API --image=gcr.io/[project id]/espcn-api:0 

La partie dans laquelle vous entrez SR-API après le déploiement est le nom du service, vous pouvez donc l'ajouter comme vous le souhaitez. De plus, il semble que le composant bêta sera installé lors de sa première exécution. Si vous n'entrez pas (--platform managed) à ce moment, sur la console

 [1] Cloud Run (fully managed)
 [2] Cloud Run for Anthos deployed on Google Cloud
 [3] Cloud Run for Anthos deployed on VMware
 [4] cancel
Please enter your numeric choice: _

Vous serez invité à entrer 1, alors entrez 1. Il sera difficile de fonctionner gratuitement s'il n'est pas entièrement géré. Ensuite, on vous demandera la région.

 [1] asia-east1
 [2] asia-northeast1
 [3] europe-north1
 [4] europe-west1
 [5] europe-west4
 [6] us-central1
 [7] us-east1
 [8] us-east4
 [9] us-west1
 [10] cancel
Please enter your numeric choice: _

Ici, sélectionnons "us- *". Dans Cloud Run, le réseau de liaison descendante en Amérique du Nord est gratuit jusqu'à 1 Go, donc si vous appelez cela depuis Cloud Functions (depuis Cloud Functions, le réseau de liaison descendante est gratuit jusqu'à 5 Go n'importe où), vous pouvez l'utiliser presque gratuitement.

Après cela, il vous sera demandé si vous souhaitez autoriser l'accès non authentifié, mais pour l'instant, le but est de le déplacer, alors définissons-le sur oui. Si vous entrez comme ci-dessus, le service sera déployé et le message suivant sera affiché.

Deploying container to Cloud Run service [espcn-api] in project [studied-brace-261908] region [us-central1]
✓ Deploying new service... Done.                                                                                                                       
  ✓ Creating Revision...                                                                                                                               
  ✓ Routing traffic...                                                                                                                                 
  ✓ Setting IAM Policy...                                                                                                                              
Done.                                                                                                                                                  
Service [espcn-api] revision [espcn-api-00001-guj] has been deployed and is serving 100 percent of traffic at https://espcn-api-~~~-uc.a.run.app

Finalement, un message indiquant où et où l'URL a été déployée apparaîtra, donc lorsque j'ai essayé de faire une demande en utilisant test.py ici, j'ai pu confirmer que la réponse a été renvoyée correctement.

Une chose est restée bloquée à ce moment-là, mais lorsque j'ai fait la demande pour la première fois, aucune réponse n'a été retournée. Donc, si vous consultez le journal Cloud Run,

Memory limit of 244M exceeded with 272M used. Consider increasing the memory limit, see https://cloud.google.com/run/docs/configuring/memory-limits

J'ai reçu un message indiquant qu'il n'y avait pas assez de mémoire. Il semble que la mémoire à utiliser soit décidée dans le cloud, et la taille de la mémoire qui peut être spécifiée est de 128 Mo ~ 2 Go, et 256 Mo sont alloués par défaut. Puisque cette erreur s'est produite avec une image de 512 x 512, il semble qu'elle puisse être résolue en allouant 512 Mo etc., mais si vous allouez trop de mémoire, il semble que le quota libre sera bientôt dépassé. Au fait, si vous souhaitez modifier la mémoire allouée à l'application, utilisez la commande suivante.

$ gcloud beta run services update [service name] --memory 512M

À la fin

Nous avons créé une API et l'avons publiée sur Cloud Run. Dans l'état actuel, cette API est accessible de n'importe où et n'est pas compatible avec la sécurité ou le portefeuille.Par conséquent, après avoir limité l'accès à Cloud Run depuis GCP, utilisez Cloud Functions pour ce faire. Je voudrais passer à une structure qui envoie des demandes à. Je voudrais aborder un jour le déploiement continu.

Recommended Posts

Publions l'API de super résolution à l'aide de Google Cloud Platform
J'ai essayé d'utiliser l'API Google Cloud Vision
[GoogleCloudPlatform] Utiliser l'API Google Cloud avec la bibliothèque cliente d'API
Comment utiliser l'API Google Cloud Translation
Continuer à relever les défis de Cyma en utilisant le service OCR de Google Cloud Platform
Procédure de transcription vocale à l'aide de Python et de l'API Google Cloud Speech
Consultez les prévisions météo sur M5Stack + Google Cloud Platform
Essayez d'utiliser l'API Twitter
Essayez d'utiliser l'API Twitter
Essayez de juger des photos de plats à l'aide de l'API Google Cloud Vision
Essayez d'utiliser l'API PeeringDB 2.0
J'ai essayé l'API Google Cloud Vision pour la première fois
L'histoire de la création d'une base de données à l'aide de l'API Google Analytics
Collectons automatiquement les informations de l'entreprise (données XBRL) à l'aide de l'API EDINET (4/10)
Diffusez la reconnaissance vocale à l'aide de l'API gRPC Google Cloud Speech avec python3 sur Mac!
Jusqu'à ce que vous essayiez l'API Google Cloud Vision (détection d'images dangereuses)
Imprimez un PDF à l'aide de Google Cloud Print. (GoogleAPI)
[Python] Accédez à l'API Google Translation
Affichons la carte en utilisant Basemap
J'ai essayé d'utiliser l'API checkio
Affichez le résultat de l'analyse vidéo à l'aide de l'API Cloud Video Intelligence de Colaboratory.
Lors de l'introduction de l'API Google Cloud Vision sur les rails, j'ai suivi la documentation.
API Google Cloud Speech et Amazon Transcribe
Exemple d'API Google Cloud Vision pour python
Essayez d'utiliser l'API Wunderlist en Python
Reconnaissance vocale en streaming avec l'API Google Cloud Speech
Essayez d'utiliser l'API Kraken avec Python
Essayez d'utiliser Python avec Google Cloud Functions
Utiliser l'API Google Cloud Vision de Python
Créer une application à l'aide de l'API Spotify
Obtenez des vacances avec l'API Google Agenda
Collection d'images à l'aide de l'API Google Custom Search
Jouez avec Dajare en utilisant l'API COTOHA
Créer une feuille de calcul Google à l'aide de l'API Python / Google Data
Enregistrez des événements personnalisés à l'aide de l'API Shotgun
J'ai essayé d'utiliser l'API BigQuery Storage
Convertir l'API cURL en script Python (à l'aide du stockage d'objets IBM Cloud)