You are currently viewing An image classification app in Android using Keras and Tensorflow |  by Vishnu Sivan |  Coinmonks |  Sep 2022

An image classification app in Android using Keras and Tensorflow | by Vishnu Sivan | Coinmonks | Sep 2022

Image classification is the process of categorizing and labeling images based on specific rules. It can be beneficial for object identification in satellite images, brake light detection, traffic control systems etc.

To create an image classification app for Android using the Tensorflow lite plugin follow the below link.

These apps are useful for performing image classification using the lightweight tflite models. Edge devices require more processing when the model size increases. TFLite android package supports only tflite models. Edge devices may not be a great option for creating/training/running models whose size is large. To overcome the limitation, use the Flask server-based model deployment.

In this session, we will create a Keras model and deploy it on the Flask server. We can use flask APIs to access the model in the android app. Also, we will host the flask server on the Heroku cloud.

In this session, we will create an android application that classifies handwritten digits from the input image. To reduce the efforts for data collection and cleaning, we will be using the MNIST dataset for the application.

We have divided this tutorial into three parts,

  1. Creation and deployment of Keras image classification model
  2. Create an android application for image classification
  3. Flask server deployment on Heroku

In this section, we will create a multilayer perceptron (MLP) model using Keras, which is trained on the MNIST dataset.

Table of Contents

Install the dependencies

It is advised to install the dependencies on a virtual environment instead of installing them globally.

  • Create a virtual environment using the following command
python -m venv venv
  • Switch to the virtual environment in Windows
venvScriptsactivate
  • Install the dependencies using the following command,
pip install tensorflow flask imageio

Prepare the MNIST Dataset

The MNIST dataset is a handwritten digit recognition dataset for digits 0 to 9. It has 70,000 images — 60,000 samples for training, and 10,000 for testing. Every single image is a gray image of size 28×28. Samples from the dataset are shown below.

Import the mnist dataset to the project using the following code,

from tensorflow import keras
from tensorflow.keras import datasets
from tensorflow.keras import models, layers, optimizers
import numpy
batch_size = 128
num_classes = 10
epochs = 20
# the data, split between train and test sets
(x_train, y_train), (x_test, y_test) = datasets.mnist.load_data()
print(x_train.shape) # (60000, 28, 28)
print(y_train.shape) # (60000,)
print(x_test.shape) # (10000, 28, 28)
print(y_test.shape) # (10000,)

Keras module allows us to download certain datasets automatically. We can download the dataset from Keras module using keras.datasets. Import keras.datasets.mnist to the code for performing various operations on the mnist dataset. Use the load_data() function to load the dataset in the project. This function returns a tuple of 2 elements as follows:

  • (x_train, y_train) — Training inputs & labels.
  • (x_test, y_test) — Test inputs & labels.

We can determine the size of the dataset using the shape element of the array. The shape of the training inputs array (x_train) is (60000, 28, 28), which means there are 60,000 samples and the size of each sample image is 28×28.

We will be building an MLP model which accepts a vector and not an array as an input. We require to convert each array into a vector. It can be done using the reshape() function. Use the following code to reshape the input array.

x_train = x_train.reshape(60000, 784) # (60000, 28 * 28)
x_test = x_test.reshape(10000, 784) # (10000, 28 * 28)

The data type of the images is uint8 where each pixel ranges from 0 to 255. Rescaling is useful to reduce the gradients in the backpropagation phase.

x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255

As per the shape of the train and test labels, there’s a single value assigned for each sample. There are 10 classes in the MNIST dataset where each sample must be assigned a binary vector of length 10. The index of the element corresponds to the class label.

For example, if a sample has a class label 2, create a vector of 10 elements where all elements are zeros except for the element at index 2 which is 1,

[0, 0, 1, 0, 0, 0, 0, 0, 0, 0]

To do the conversion, use the keras.utils.to_categorical() function.

y_train = keras.utils.to_categorical(y_train, num_classes) # (60000, 10)
y_test = keras.utils.to_categorical(y_test, num_classes) # (10000, 10)

The complete code for data preparation is given below,

from tensorflow import keras
from tensorflow.keras import datasets
import numpy
batch_size = 128
num_classes = 10
epochs = 20# the data, split between train and test sets
(x_train, y_train), (x_test, y_test) = datasets.mnist.load_data()
print(x_train.shape) # (60000, 28, 28)
print(y_train.shape) # (60000,)
print(x_test.shape) # (10000, 28, 28)
print(y_test.shape) # (10000,)
x_train = x_train.reshape(60000, 784)
x_test = x_test.reshape(10000, 784)
x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255
y_train = keras.utils.to_categorical(y_train, num_classes) # (60000, 10)
y_test = keras.utils.to_categorical(y_test, num_classes) # (10000, 10)

Build the MLP model

Declare the MLP model as a sequential model using the keras.models.Sequential() function.

from tensorflow.keras import models, layers, optimizers
import numpy
...
model = models.Sequential()
model.add(layers.Dense(512, activation='relu', input_shape=(784,)))
model.add(layers.Dropout(0.2))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dropout(0.2))
model.add(layers.Dense(num_classes, activation='softmax'))

The sequential model contains 3 dense and 2 dropout layers. The first dense layer accepts a vector image of shape 784 which contains 512 neurons. The last dense layer uses the softmax function with 10 neurons, which returns the probability of each class.

After building the model, print a summary of the model using the model.summary() function. It lists Layer, Output Shape and Params.

The Param column gives the total number of parameters of the layer, as the product of the number of inputs (784) and the number of neurons in the layer (512) which results in 784 * 512 = 401,408. Some neurons tend to have bias, so additional 512 parameters are added to the total which results in 401,920.

Define the loss function, optimizer, and metrics for training the model using the compile() function. Set loss function as categorical_crossentropyoptimizer as Root Mean Square Propagation (RMSprop) and metric as classification accuracy.

from tensorflow.keras import optimizers
model.compile(loss='categorical_crossentropy',
optimizer=keras.optimizers.RMSprop(),
metrics=['accuracy'])

Train the MLP model

Train the model using the fit() function as given below.

batch_size = 128
epochs = 20
history = model.fit(x_train, y_train,
batch_size=batch_size,
epochs=epochs,
verbose=1,
validation_data=(x_test, y_test))

It accepts the training inputs (x_train), training labels (y_train), batch size (128), and number of epochs (20). The verbose=1 is added to the input to print messages while the model is being trained, and the validation data.

The evaluate() function is used to evaluate the model with respect to the validation data as follows.

score = model.evaluate(x_test, y_test, verbose=0)
print('Test accuracy:', score[1]) # prints 98.41%

Save the Model

Use the model.save() function to save the model for future reference.

model.save("model.h5")

The complete code to prepare the data, build, train, and save the model is given below.

from tensorflow import keras
from tensorflow.keras import datasets
from tensorflow.keras import models, layers, optimizers
import numpy
batch_size = 128
num_classes = 10
epochs = 20
# the data, split between train and test sets
(x_train, y_train), (x_test, y_test) = datasets.mnist.load_data()
print(x_train.shape) # (60000, 28, 28)
print(y_train.shape) # (60000,)
print(x_test.shape) # (10000, 28, 28)
print(y_test.shape) # (10000,)
x_train = x_train.reshape(60000, 784)
x_test = x_test.reshape(10000, 784)
x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255
y_train = keras.utils.to_categorical(y_train, num_classes) # (60000, 10)
y_test = keras.utils.to_categorical(y_test, num_classes) # (10000, 10)model = models.Sequential()
model.add(layers.Dense(512, activation='relu', input_shape=(784,)))
model.add(layers.Dropout(0.2))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dropout(0.2))
model.add(layers.Dense(num_classes, activation='softmax'))
model.summary()
model.compile(loss='categorical_crossentropy',
optimizer=optimizers.RMSprop(),
metrics=['accuracy'])history = model.fit(x_train, y_train,
batch_size=batch_size,
epochs=epochs,
verbose=1,
validation_data=(x_test, y_test))
score = model.evaluate(x_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
model.save("model.h5")
loaded_model = keras.models.load_model('model.h5')
predicted_label = loaded_model.predict_classes(numpy.array([x_test[0, :]]))[0]
print(predicted_label)

NB: If you are running the code from the git repo provided in this tutorial then try to install dependencies from the requirements.txt file using pip install -r requirements.txt command.

Make predictions using the saved model

Load the model using the keras.models.load_model() function and predict the classes using the predict_classes() function.

loaded_model = keras.models.load_model('model.h5')
predicted_label = loaded_model.predict_classes(numpy.array([x_test[0, :]]))[0]
print(predicted_label)

Create a Flask API for image classification

Flask is a lightweight web framework written in Python.

Create a file named server.py and add the following code to it to return “Hello World” text when hitting the server.

app = flask.Flask(__name__)
@app.route('/', methods = ['GET', 'POST'])
def welcome():
return "Hello World"
app.run(host="0.0.0.0", port=5000, debug=True)

The app.route() decorator function associates a URL with a callback function. In this example, the function is named welcome() which returns the “Hello World” text when the user hits the default route (“/”). We set the host set to “0.0.0.0” and port to 5000.

Let’s try to create a route “/predict” to predict the class based on the input image. For which, create a file named server.py and add the following code to it.

import flask
import werkzeug
from tensorflow.keras import models
import numpy
import imageio
import os
app = flask.Flask(__name__)
@app.route('/', methods = ['GET', 'POST'])
def welcome():
return "Hello World"
@app.route('/predict/', methods = ['GET', 'POST'])
def handle_request():
imagefile = flask.request.files['image0']
filename = werkzeug.utils.secure_filename(imagefile.filename)
print("nReceived image File name : " + imagefile.filename)
imagefile.save(filename)

img = imageio.imread(filename, pilmode="L")
if img.shape != (28, 28):
return "Image size mismatch " + str(img.shape) + ". nOnly (28, 28) is acceptable."
img = img.reshape(784)
loaded_model = models.load_model('model.h5')
predicted_label = numpy.argmax(loaded_model.predict(numpy.array([img]))[0], axis=-1)
print(predicted_label)
return str(predicted_label)
app.run(host="0.0.0.0", port=os.environ.get('PORT', 5000), debug=True)

Let’s understand the code,

  • imageio.imread reads an image as grayscale and reshapes it into (28, 28).
  • Load the pre-trained model using models.load_model(‘model.h5’)
  • Use the loaded_model.predict() function to predict the class label using the Keras pre-trained model, and return the classification label as a string.

Start the server by running the following command from the FlaskServer/app folder.

python server.py

NB: If you are running the code from the git repo provided in this tutorial then try to install dependencies from the requirements.txt file using pip install -r requirements.txt command.

Now, we can create a client-side android application that uploads an image to the flask server which we created in the previous step. The app has an option to select the image from the gallery. The selected image is converted into a byte array which is sent to the Flask server. We can handle the HTTP requests and responses using OkHttp.

Table of contents

Create the app

Open android studio and Click on New project button and provide the necessary information.

Design the layout

Define a basic layout with editText, buttons, imageViews and textViews. It contains 3 buttons — Capture Image, Select Image and Predict class and editText — for inputting server URL (IP address and port).

Add the following code to activity_main.xml to define the layout.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="20dp">
<Button
android:id="@+id/btnCapture"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="captureImage"
android:text="Capture Image"
android:textStyle="bold" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Select Image"
android:onClick="selectImage"
android:textStyle="bold"/>
<ImageView
android:id="@+id/imageView"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center"
android:layout_margin="10dp"
app:srcCompat="@android:drawable/ic_menu_report_image">
</ImageView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="No Image Selected."
android:id="@+id/imgName"
android:textAlignment="center"
android:textStyle="bold"
android:textColor="#807E76"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="center"
android:padding="10dp">
<TextView
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="Server URL"
android:textStyle="bold"
android:textColor="#807E76"
android:gravity="center_vertical"/>
<EditText
android:layout_weight="4"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/edtServerURL"
android:text="http://192.168.1.9:5000"
android:singleLine="true"
android:textStyle="bold"/>
</LinearLayout>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="connectServer"
android:text="Predict Class"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
android:id="@+id/responseText"
android:textAlignment="center"
android:textStyle="bold"
android:layout_marginTop="10dp"/>
</LinearLayout>

The resultant layout is as shown below,

Communicate to the flask server

  • Add the following line to the dependencies section of the apps build.gradle and sync your project.
implementation 'com.squareup.okhttp3:okhttp:3.4.1'
  • Add the following code to the AndroidManifest.xml file to request camera, internet and storage permissions in the app.
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
...
android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true">
  • Request permissions on the activity initialization.
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA}, 2);
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
  • Create a method, captureImage in MainActivity.java to capture an image from the camera. Create a URI of the captured image and assign it to the imageView. Create a byteArray of the image and add it to the OkHttp request body.
public void captureImage(View v) {
if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.CAMERA}, MY_CAMERA_PERMISSION_CODE);
}
else {
Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(cameraIntent, CAMERA_REQUEST);
}
}
  • Create a method, selectImage to browse images from the gallery. Create a URI of the selected image and assigned it to the imageView. Create a byteArray of the image and add it to the OkHttp request body.
public void selectImage(View v) {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, "Select Picture"), SELECT_IMAGES);
}
  • Create a method, getImageUri to get the image path from the Bitmap image data.
public Uri getImageUri(Context inContext, Bitmap inImage) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, "Title", null);
return Uri.parse(path);
}
  • Create a method, getFileName to get the file name from Uri.
public String getFileName(Uri uri) {
String result = null;
if (uri.getScheme().equals("content")) {
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
}
} finally {
cursor.close();
}
}
if (result == null) {
result = uri.getPath();
int cut = result.lastIndexOf('/');
if (cut != -1) {
result = result.substring(cut + 1);
}
}
return result;
}
  • Create a method, postRequest which accepts postUrl and postBody. The method sends the image to the server and receives the response in the app. Set the result on the responseText.
  • The request is created using the Request.Builder class. This class is responsible for mapping the destination URL with the request body. The url() method accepts the URL and the post() method accepts the request body. Finally, the request is built using the build() method.
  • Send the request via the instance of the OkHttpClient using a method called newCall(). There are 2 callback methods based on the state of the call:
  • onFailure(): Called when the request couldn’t be executed due to cancellation, a connection problem, or a timeout.
  • onResponse(): Called when the HTTP response is successfully returned by the remote server.
void postRequest(String postUrl, RequestBody postBody) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(postUrl)
.post(postBody)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
call.cancel();
Log.d("FAIL", e.getMessage());
runOnUiThread(new Runnable() {
@Override
public void run() {
TextView responseText = findViewById(R.id.responseText);
responseText.setText("Failed to Connect to Server. Please Try Again.");
}
});
}
@Override
public void onResponse(Call call, final Response response) throws IOException {
runOnUiThread(new Runnable() {
@Override
public void run() {
TextView responseText = findViewById(R.id.responseText);
try {
String[] res = response.toString().split(",");
if(res[1].trim().equals("code=200"))
responseText.setText("Server's Responsen" + response.body().string());
else
responseText.setText("Oops! Something went wrong. nPlease try again.");
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
});
}
  • Create a method, connectServer to get the image information from the file system and to send requests to the server.
public void connectServer(View v) {
TextView responseText = findViewById(R.id.responseText);
if (imagesSelected == false) { // This means no image is selected and thus nothing to upload.
responseText.setText("No Image Selected to Upload. nSelect Image and Try Again.");
return;
}
responseText.setText("Sending the Files. Please Wait ...");
EditText edtServerURL = findViewById(R.id.edtServerURL);
String postUrl = edtServerURL.getText().toString() + "/predict/";
MultipartBody.Builder multipartBodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM); for (int i = 0; i < selectedImagesPaths.size(); i++) {
byte[] byteArray = null;
try {
InputStream inputStream = getContentResolver().openInputStream(selectedImagesPaths.get(i));
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, len);
}
byteArray = byteBuffer.toByteArray();
}catch(Exception e) {
responseText.setText("Please Make Sure the Selected File is an Image.");
return;
}
multipartBodyBuilder.addFormDataPart("image" + i, "input_img.jpg", RequestBody.create(MediaType.parse("image/*jpg"), byteArray));
}
RequestBody postBodyImage = multipartBodyBuilder.build();// RequestBody postBodyImage = new MultipartBody.Builder()
// .setType(MultipartBody.FORM)
// .addFormDataPart("image", "androidFlask.jpg", RequestBody.create(MediaType.parse("image/*jpg"), byteArray))
// .build();
postRequest(postUrl, postBodyImage);
}
  • The full code of the MainActivity is given below,
package com.vsoft.androidflaskdemo.app;
public class MainActivity extends AppCompatActivity {
final int SELECT_IMAGES = 1;
final int CAMERA_REQUEST = 2;
final int MY_CAMERA_PERMISSION_CODE = 100;
ArrayList<Uri> selectedImagesPaths; // Paths of the image(s) selected by the user.
boolean imagesSelected = false; // Whether the user selected at least an image or not.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.INTERNET}, 2);
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
setContentView(R.layout.activity_main);
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Toast.makeText(getApplicationContext(), "Access to Storage Permission Granted.", Toast.LENGTH_SHORT).show();
} else {
// Toast.makeText(getApplicationContext(), "Access to Storage Permission Denied.", Toast.LENGTH_SHORT).show();
}
return;
}
case 2: {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Toast.makeText(this, "Camera Permission Granted.", Toast.LENGTH_LONG).show();
}
else {
Toast.makeText(this, "camera permission denied", Toast.LENGTH_LONG).show();
}
return;
}
}
}
public void connectServer(View v) {
TextView responseText = findViewById(R.id.responseText);
if (imagesSelected == false) { // This means no image is selected and thus nothing to upload.
responseText.setText("No Image Selected to Upload. nSelect Image and Try Again.");
return;
}
responseText.setText("Sending the Files. Please Wait ...");
EditText edtServerURL = findViewById(R.id.edtServerURL);
String postUrl = edtServerURL.getText().toString() + "/predict/";
MultipartBody.Builder multipartBodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
for (int i = 0; i < selectedImagesPaths.size(); i++) {
byte[] byteArray = null;
try {
InputStream inputStream = getContentResolver().openInputStream(selectedImagesPaths.get(i));
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, len);
}
byteArray = byteBuffer.toByteArray();}catch(Exception e) {
responseText.setText("Please Make Sure the Selected File is an Image.");
return;
}
multipartBodyBuilder.addFormDataPart("image" + i, "input_img.jpg", RequestBody.create(MediaType.parse("image/*jpg"), byteArray));
}
RequestBody postBodyImage = multipartBodyBuilder.build();
// RequestBody postBodyImage = new MultipartBody.Builder()
// .setType(MultipartBody.FORM)
// .addFormDataPart("image", "androidFlask.jpg", RequestBody.create(MediaType.parse("image/*jpg"), byteArray))
// .build();postRequest(postUrl, postBodyImage);
}
void postRequest(String postUrl, RequestBody postBody) {OkHttpClient client = new OkHttpClient();Request request = new Request.Builder()
.url(postUrl)
.post(postBody)
.build();client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
call.cancel();
Log.d("FAIL", e.getMessage());runOnUiThread(new Runnable() {
@Override
public void run() {
TextView responseText = findViewById(R.id.responseText);
responseText.setText("Failed to Connect to Server. Please Try Again.");
}
});
}
@Override
public void onResponse(Call call, final Response response) throws IOException {
runOnUiThread(new Runnable() {
@Override
public void run() {
TextView responseText = findViewById(R.id.responseText);
try {
String[] res = response.toString().split(",");
if(res[1].trim().equals("code=200"))
responseText.setText("Server's Responsen" + response.body().string());
else
responseText.setText("Oops! Something went wrong. nPlease try again.");
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
});
}
public void captureImage(View v) {
if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.CAMERA}, MY_CAMERA_PERMISSION_CODE);
}
else {
Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(cameraIntent, CAMERA_REQUEST);
}
}
public void selectImage(View v) {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, "Select Picture"), SELECT_IMAGES);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
try {
if (requestCode == SELECT_IMAGES && resultCode == RESULT_OK && data != null) {
selectedImagesPaths = new ArrayList<>();
TextView imgName = findViewById(R.id.imgName);
ImageView imgView = findViewById(R.id.imageView);
if (data.getData() != null) {
Uri uri = data.getData();
Log.d("ImageDetails", "URI : " + uri);
selectedImagesPaths.add(uri);
imagesSelected = true;
imgName.setText(getFileName(selectedImagesPaths.get(0)));
imgView.setImageURI(selectedImagesPaths.get(0));
}
} else if (requestCode == CAMERA_REQUEST && resultCode == RESULT_OK && data != null) {
selectedImagesPaths = new ArrayList<>();
TextView imgName = findViewById(R.id.imgName);
ImageView imgView = findViewById(R.id.imageView);
if (data.getExtras().get("data") != null) {
Bitmap photo = (Bitmap) data.getExtras().get("data");
Uri uri = getImageUri(getApplicationContext(), photo);
Log.d("ImageDetails", "URI : " + uri);
selectedImagesPaths.add(uri);
imagesSelected = true;
imgName.setText(getFileName(selectedImagesPaths.get(0)));
imgView.setImageURI(selectedImagesPaths.get(0));
}
} else {
Toast.makeText(this, "You haven't Picked any Image.", Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
Toast.makeText(this, "Something Went Wrong.", Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}
public Uri getImageUri(Context inContext, Bitmap inImage) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, "Title", null);
return Uri.parse(path);
}
public String getFileName(Uri uri) {
String result = null;
if (uri.getScheme().equals("content")) {
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
}
} finally {
cursor.close();
}
}
if (result == null) {
result = uri.getPath();
int cut = result.lastIndexOf('/');
if (cut != -1) {
result = result.substring(cut + 1);
}
}
return result;
}
}

Test the app

Connect your device to the machine. Click on the Play button ▶ to run the app.

You can either use the local machine flask server address (if both mobile and the machine are connected to the same network) or the remote address (https://androidflaskdemo.herokuapp.com/) as the server URL.

Heroku is a container based cloud platform as a service (PaaS) which is used for deploying your applications on the web. It received interest in recent years due to its simple setup and budget friendly pricing.

Create a Heroku account if you don’t have one already. Login to your account and create a project by clicking on the Create new app button. You can choose any app name (Eg: androidflaskdemo).

Even though Heroku provides instructions to deploy your app in Heroku, you have to include some necessary configuration files into the project to identify the platform, build tools and all.

The flask app deployment requires three files in your project root folder.

  1. Procfile
  2. requirements.txt
  3. runtime.txt
  • Open the terminal in the project root folder and run the following commands to create Procfile and runtime.txt files.
$ echo web: python server.py runserver 0.0.0.0:$PORT > Procfile
$ echo python-3.7.10 > runtime.txt
  • Create the requirements.txt file using pip freeze from the virtual environment.
$ pip freeze > requirements.txt

Now we can move to the deployment process.

  • Download and install Heroku CLI into your machine.
  • Log in to the Heroku account in the terminal by executing the following command:
$ heroku login
  • Execute the commands that are provided in the deploy tab in the Heroku website.
$ git init
$ heroku git:remote -a androidflaskdemo
$ git add .
$ git commit -am "make it better"
$ git push heroku master

We have successfully launched our Flask server on Heroku. You can use the local flask server or https://androidflaskdemo.herokuapp.com/ links to run the classification in the mobile app.

Thanks for reading this article.

Thanks Gowri M Bhatt for reviewing the content.

If you enjoyed this article, please click on the clap button 👏 and share to help others find it!

The article is also available on Dev.

The full source code for this tutorial can be found here,

New to trading? Try crypto trading bots or copy trading

Leave a Reply