Проблема при получении изображения в службе GCP App Engine, в которой запущена служба Flask - PullRequest
0 голосов
/ 29 августа 2018

Я разрабатываю приложение, позволяющее пользователю сделать фотографию и отправить ее в модель Keras для прогнозирования. Эта модель уже развернута в Google App Engine Service со скриптом Python, который использует Flask для получения через POST-запрос изображения и вызова модели для прогнозирования. Вот код Python:

import numpy as np
import flask
import io
import logging
import tensorflow as tf
from keras.preprocessing.image import img_to_array
from keras.applications import imagenet_utils
from keras.models import load_model
from PIL import Image


# initialize our Flask application and the Keras model
app = flask.Flask(__name__)
app.config['PROPAGATE_EXCEPTIONS'] = True
model = None

def recortar(image):
    # Function that centers and crop image. Please, asume that it works properly. Return is a numpy array.
    return image

@app.route("/predict", methods=["POST"])
def predict():
    model = load_model('modelo_1.h5')
    graph = tf.get_default_graph()
    data = {"success": False}

    if flask.request.method == "POST":
        if flask.request.files.get("image"):
            # read the image in PIL format
            image = flask.request.files["image"].read()
            image = Image.open(io.BytesIO(image))

            image = recortar(image)
            app.logger.info('Tamaño: '+str(image.size))
            image = img_to_array(image)
            image = np.expand_dims(image, axis=0)

            with graph.as_default():
                preds = model.predict(image)

            data['predictions'] = str(np.squeeze(preds).tolist())

            data["success"] = True  
            return flask.jsonify(data)
        else:
            return "No se ha obtenido la imagen"
    else:
        return "El HTTP request no era POST"

# if this is the main thread of execution first load the model and
# then start the server
if __name__ == "__main__":
    print(("* Loading Keras model and Flask starting server..."
        "please wait until server has fully started"))
    app.debug = True
    app.run()

Отправка изображения с помощью curl работает отлично: как и ожидалось, я получаю ответ JSON с сервера, содержащего прогноз. Вот команда CURL и ответ сервера:

>> curl -X POST -F image=@nevus.jpg 'https://example.com/predict'
{"predictions":"[0.7404708862304688, 0.25952914357185364]","success":true}

Затем я пытаюсь повторить тот же процесс, но через приложение для Android, но в ответ я получаю ошибку 500. При проверке журналов отчетов об ошибках Stackdriver я вижу следующую трассировку стека: AttributeError:

'NoneType' object has no attribute 'size'
at predict (/home/vmagent/app/main.py:73)
at dispatch_request (/env/lib/python3.6/site-packages/flask/app.py:1799)
at full_dispatch_request (/env/lib/python3.6/site-packages/flask/app.py:1813)
at reraise (/env/lib/python3.6/site-packages/flask/_compat.py:35)
at handle_user_exception (/env/lib/python3.6/site-packages/flask/app.py:1718)
at full_dispatch_request (/env/lib/python3.6/site-packages/flask/app.py:1815)
at wsgi_app (/env/lib/python3.6/site-packages/flask/app.py:2292)
at reraise (/env/lib/python3.6/site-packages/flask/_compat.py:35)
at handle_exception (/env/lib/python3.6/site-packages/flask/app.py:1741)
at wsgi_app (/env/lib/python3.6/site-packages/flask/app.py:2295)
at __call__ (/env/lib/python3.6/site-packages/flask/app.py:2309)
at handle_request (/env/lib/python3.6/site-packages/gunicorn/workers/sync.py:176)
at handle (/env/lib/python3.6/site-packages/gunicorn/workers/sync.py:135)

Эта ошибка относится к объекту изображения, поэтому я предполагаю, что, поскольку код работал должным образом, ошибка должна быть связана с тем, как я отправляю изображение через HTTP-запрос. Напомним, что изображение получается, когда пользователь нажимает кнопку, потому что эта кнопка отправляет намерение сделать фотографию. Когда фотография сделана, пользователь может нажать кнопку «Отправить», код которой я опубликую ниже. Обратите внимание, что Ориентированная Битмап соответствует фотографии, снятой в растровом формате.

btn_enviarfoto.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "Botón \"enviar\" pulsado. Codificando imagen.");
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
                orientedBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
                byte[] byteArray = stream.toByteArray();
                orientedBitmap.recycle();
                uploadToServer(byteArray);
            }
        });

uploadToServer просто вызывает метод выполнения класса AsynchTask, как показано ниже:

private void uploadToServer(byte[] data) {
        Bitmap bitmapOrg = BitmapFactory.decodeByteArray(data, 0, data.length);
        Log.d(TAG, "Imagen codificada. Enviando al servidor.");
        ObtenerPrediccionTask task = new ObtenerPrediccionTask();
        task.execute(bitmapOrg);    
    }

И, наконец, самое важное, это код класса ObtenerPrediccionTask:

public class ObtenerPrediccionTask extends AsyncTask<Bitmap, Void, String> {

    @Override
    protected String doInBackground(Bitmap... imagen) {
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        HttpURLConnection connection = null;
        DataOutputStream outputStream = null;
        String probabilidad_melanoma = "";
        JsonReader jsonReader = null;


        try {
            for (int i = 0; i < imagen.length; i++) {
                Bitmap imagen2 = imagen[i];
                imagen2.compress(Bitmap.CompressFormat.JPEG, 90, bao);
                byte[] ba = bao.toByteArray();
                InputStream fileInputStream = new ByteArrayInputStream(ba);


                URL url = new URL("https://example.com/predict"); // not the real URL

                String lineEnd = "\r\n";
                String twoHyphens = "--";
                String boundary = "xxxxxxxx";
                String str = twoHyphens + boundary + lineEnd;


                connection = (HttpURLConnection) url.openConnection();

                // Allow Inputs & Outputs
                connection.setDoInput(true);
                connection.setDoOutput(true);
                connection.setUseCaches(false);

                // Enable POST method
                connection.setRequestMethod("POST");

                connection.setRequestProperty("Connection", "Keep-Alive");
                connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);

                outputStream = new DataOutputStream(connection.getOutputStream());
                outputStream.writeBytes(twoHyphens + boundary + lineEnd);
                outputStream.writeBytes("Content-Disposition: form-data; name=\"" +
                        "image" + "\";filename=\"" +
                        "foto.jpg" + "\"" + lineEnd);
                outputStream.writeBytes(lineEnd);

                int bytesAvailable = fileInputStream.available();
                int bufferSize = Math.min(bytesAvailable, 1024);
                byte[] buffer = new byte[bufferSize];

                // Read file
                int bytesRead = fileInputStream.read(buffer, 0, bufferSize);

                while (bytesRead > 0) {
                    outputStream.write(buffer, 0, bufferSize);
                    bytesAvailable = fileInputStream.available();
                    bufferSize = Math.min(bytesAvailable, 1024);
                    bytesRead = fileInputStream.read(buffer, 0, bufferSize);
                }

                outputStream.writeBytes(lineEnd);
                outputStream.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);

                // Responses from the server (code and message)
                int responseCode = connection.getResponseCode();
                connection.getResponseMessage();

                fileInputStream.close();
                outputStream.flush();
                outputStream.close();

                if (responseCode == HttpURLConnection.HTTP_OK) {
                    InputStream responseStream = new
                            BufferedInputStream(connection.getInputStream());

                    BufferedReader responseStreamReader =
                            new BufferedReader(new InputStreamReader(responseStream));

                    String line = "";
                    StringBuilder stringBuilder = new StringBuilder();

                    while ((line = responseStreamReader.readLine()) != null) {
                        stringBuilder.append(line).append("\n");
                    }
                    responseStreamReader.close();

                    String response = stringBuilder.toString();
                    Log.d(TAG, "Imagen recibida por el servidor y pasada al modelo. Esta es la respuesta: " + response);

                    jsonReader = new JsonReader(new StringReader(response));
                    probabilidad_melanoma = readJson(jsonReader);
                } else {
                    Log.d(TAG, Integer.toString(responseCode));
                }
            }
            return probabilidad_melanoma;
        } catch (MalformedURLException malformedURLException) {
            Log.e(TAG, malformedURLException.toString());
            return null;
        } catch (IOException io) {
            Log.e(TAG, io.toString());
            return null;
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }

    protected void onPostExecute(String probabilidad_melanoma) {
        if (probabilidad_melanoma != null) {
            Log.d(TAG, "Probabilidad melanoma: " + probabilidad_melanoma);
        } else {
            Log.w(TAG, "La respuesta ha sido nula");
        }
    }
}

Функция readJson также работает должным образом, так что не беспокойтесь об этом.

Этот последний фрагмент кода является результатом тщательного поиска в SO способа правильной отправки изображения, но, поскольку пока ничего не помогло, у меня закончились идеи. В чем проблема с моим кодом?

1 Ответ

0 голосов
/ 04 декабря 2018

Функция отслеживания сбоя указывает, что в этой строке image равно None:

        app.logger.info('Tamaño: '+str(image.size))

Что означает, что recortar() возвращает None, несмотря на ваши комментарии:

# Function that centers and crop image. Please, asume that it works properly. Return is a numpy array.

Так что ваше the error must be in the way I send the image through the HTTP request предположение может быть неверным. Прежде чем тратить время на это, я бы сначала добавил проверки, чтобы убедиться, что recortar() работает правильно.

...