Normalerweise benutze ich Flask häufig, aber mein Bekannter sagte: "Schnelle API ist gut!", Also entschied ich mich für eine einfache Bilderkennungs-API. Da ich jedoch nicht viele japanische Artikel über FastAPI und ML gesehen habe, habe ich beschlossen, diesen Artikel anstelle eines Memos zu erstellen!
In diesem Artikel werden wir nach der Vorbereitung der Entwicklungsumgebung eine kurze Erläuterung des API-Servers und des Frontends geben.
Der gesamte diesmal verwendete Code wird auf Github veröffentlicht. ** (Die Ordnerstruktur der folgenden Implementierung wird unter der Annahme von Github beschrieben. Der Download des Beispielmodells wird auch in README.md beschrieben.) **
Es ist eines der Python-Frameworks wie Flask.
Eine einfache Übersicht und eine Zusammenfassung der Verwendung finden Sie im folgenden Artikel. (Vielen Dank für Ihre Hilfe auch in diesem Artikel!)
https://qiita.com/bee2/items/75d9c0d7ba20e7a4a0e9
Für diejenigen, die mehr wissen möchten, empfehlen wir die offiziellen Fast API-Tutorials!
https://fastapi.tiangolo.com/tutorial/
Ich hatte diesmal keine Zeit, also werde ich es mit dem Modell von tensorflow.keras bauen!
Insbesondere werden wir ResNet50 verwenden, das von imagenet gelernt wurde, und daraus schließen, zu welcher der 1000 Klassen das Eingabebild gehört.
(Das Modell, das ich wirklich verwenden wollte, war nicht rechtzeitig, weil ich gerade Anerkennung lernte ...)
https://www.tensorflow.org/versions/r1.15/api_docs/python/tf/keras?hl=ja
Mac OS X Mojave Python3.7.1(Anaconda)
Installieren Sie die erforderliche Python-Bibliothek.
$pip install tensorflow==1.15
$pip install fastapi
$pip install uvicorn
Da die folgenden Bedingungen erfüllt sind, installieren Sie auch die erforderlichen Bibliotheken. --Render index.html
$pip install Jinja
$pip install aiofiles
$pip install python-multipart
$pip install opencv-python
Die Implementierung des API-Servers ist wie folgt.
# -*- 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
#Vorbereitung des Bilderkennungsmodells
global model, graph
graph = tf.get_default_graph()
model = tf.keras.models.load_model("./static/model/resnet_imagenet.h5")
#Vorbereitung der schnellen API
app = FastAPI()
# static/js/post.Index js.Erforderlich, um von HTML aufzurufen
app.mount("/static", StaticFiles(directory="static"), name="static")
#Index unter Vorlagen gespeichert.Erforderlich, um HTML zu rendern
templates = Jinja2Templates(directory="templates")
def read_image(bin_data, size=(224, 224)):
"""Bild laden
Arguments:
bin_data {bytes} --Bildbinärdaten
Keyword Arguments:
size {tuple} --Bildgröße, deren Größe Sie ändern möchten(default: {(224, 224)})
Returns:
numpy.array --Bild
"""
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(...)):
"""Bilderkennungs-API
Keyword Arguments:
files {List[UploadFile]} --Hochgeladene Dateiinformationen(default: {File(...)})
Returns:
dict --Inferenzergebnis
"""
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})
@app.post("/api/image_recognition")
async def image_recognition(files: List[UploadFile] = File(...)):
"""Bilderkennungs-API
Keyword Arguments:
files {List[UploadFile]} --Hochgeladene Dateiinformationen(default: {File(...)})
Returns:
dict --Inferenzergebnis
"""
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}
Dieses Mal verwenden wir die Fast API Upload File, um das POSTed-Image zu erhalten.
bin_data = io.BytesIO(files[0].file.read())
Da nur eine Datei POSTed ist, wird sie als files [0] festgelegt, und da sie von der Vorderseite im BASE64-Format übergeben wird, wurde sie auf der API-Seite in ein Bytes-Array konvertiert.
def read_image(bin_data, size=(224, 224)):
"""Bild laden
Arguments:
bin_data {bytes} --Bildbinärdaten
Keyword Arguments:
size {tuple} --Bildgröße, deren Größe Sie ändern möchten(default: {(224, 224)})
Returns:
numpy.array --Bild
"""
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
Mit Hilfe von opencv konvertiert es ein Byte-Array in ein uint8-Image. Da das Standardformat von opencv BGR ist, habe ich es zu diesem Zeitpunkt in RGB konvertiert und die Größe geändert.
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]
Ich habe resnet_imagenet.h5 im Voraus erstellt und oben in der Datei gelesen. Der Inferenzprozess selbst wird mit der Vorhersagefunktion abgeleitet, indem der Kontext in diesem Thread auf das TensorFlow-Diagramm festgelegt wird, das global mit graph.as_default () festgelegt wurde.
Da wir dieses Mal ResNet50 von tf.keras verwenden, verwenden wir decode_predictions, um das Ergebnis der Vorhersage in ein Label zu konvertieren und das Inferenzergebnis zu erhalten.
Ich denke, dass andere Modelle und selbst erstellte Modelle wie diese Implementierung verwendet werden können, indem die .h5-Datei irgendwo im Projektverzeichnis gespeichert und_model geladen wird.
Ich habe dies als Referenz verwendet. (Vielen Dank!)
https://qiita.com/katsunory/items/9bf9ee49ee5c08bf2b3d
<html>
<head>
<meta http-qeuiv="Content-Type" content="text/html; charset=utf-8">
<title>Fastapi Bilderkennungstest</title>
<script src="//code.jquery.com/jquery-2.2.3.min.js"></script>
<script src="/static/js/post.js"></script>
</head>
<body>
<!--Dateiauswahlschaltfläche-->
<div style="width: 500px">
<form enctype="multipart/form-data" method="post">
<input type="file" name="userfile" accept="image/*">
</form>
</div>
<!--Bildanzeigebereich-->
<canvas id="canvas" width="0" height="0"></canvas>
<!--Startschaltfläche hochladen-->
<button class="btn btn-primary" id="post">Post</button>
<br>
<h2 id="result"></h2>
</body>
</html>
//Ändern Sie die Größe des Bildes und zeigen Sie es in HTML an
$(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];
//Dateiprüfung
if (file.type != "image/jpeg" && file.type != "image/png") {
file = null;
blob = null;
return;
}
var result = document.getElementById("result");
result.innerHTML = "";
//Bildgröße anpassen
var image = new Image();
var reader = new FileReader();
reader.onload = function (e) {
image.onload = function () {
var width, height;
//Passen Sie die Größe an die längere an
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
);
//Holen Sie sich base64-Bilddaten vom Canvas und erstellen Sie Blob für 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);
});
//Wenn Sie auf die Schaltfläche zum Starten des Uploads klicken
$("#post").click(function () {
if (!file || !blob) {
return;
}
var name,
fd = new FormData();
fd.append("files", blob);
//POST an API
$.ajax({
url: "/api/image_recognition",
type: "POST",
dataType: "json",
data: fd,
processData: false,
contentType: false,
})
.done(function (data, textStatus, jqXHR) {
//Wenn die Kommunikation erfolgreich ist, geben Sie das Ergebnis aus
var response = JSON.stringify(data);
var response = JSON.parse(response);
console.log(response);
var result = document.getElementById("result");
result.innerHTML = "Dieses Bild...「" + response["response"] + "Yanke";
})
.fail(function (jqXHR, textStatus, errorThrown) {
//Gibt eine Fehlermeldung aus, wenn die Kommunikation fehlschlägt
var result = document.getElementById("result");
result.innerHTML = "Die Kommunikation mit dem Server ist fehlgeschlagen...";
});
});
});
POST wird mit Ajax an der Bilderkennungs-API ausgeführt und das Ergebnis wird angezeigt.
Infolgedessen funktioniert es so!
(Ich wollte die Rezeption etwas modischer machen ...)
Ich habe eine Bilderkennungs-API erstellt, um die Fast-API zu studieren. Ich denke nicht, dass die Implementierung, die ich dieses Mal gemacht habe, die beste Vorgehensweise ist, aber ich bin froh, dass ich etwas machen konnte, das funktioniert.
Ich weiß nicht, welches Framework ich in Zukunft für die Arbeit verwenden soll, aber ich dachte, dass FastAPI relativ einfach zu verwenden ist und ich von Flask wechseln sollte.
** Last but not least danke an alle, die uns geholfen haben! ** ** **