DEV Community

Cover image for Create RESTAPI of YOLOv5 on Flask deployed on Azure App Service and use in Flutter App
AneeqMalik
AneeqMalik

Posted on

Create RESTAPI of YOLOv5 on Flask deployed on Azure App Service and use in Flutter App

STEPS:

  1. Creating YOLOV5 Flask App
  2. Creating Docker Image
  3. Running Docker Container and Pushing to DockerHub
  4. Deploying Rest API to Azure for Free.
  5. Using API in flutter project to perform detections.

Creating YOLOV5 Flask App

Create a flask app with your ML model with requests defined.

"""
Run a rest API exposing the yolov5s object detection model
"""
import argparse
import io
from PIL import Image

import torch
from flask import Flask, request

app = Flask(__name__)

DETECTION_URL = "/v1/object-detection/yolov5"


@app.route(DETECTION_URL, methods=["POST"])
def predict():
    if not request.method == "POST":
        return

    if request.files.get("image"):
        image_file = request.files["image"]
        image_bytes = image_file.read()
        img = Image.open(io.BytesIO(image_bytes))
        results = model(img, size=640) # reduce size=320 for faster inference
        return results.pandas().xyxy[0].to_json(orient="records")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Flask api exposing yolov5 model")
    parser.add_argument("--port", default=5000, type=int, help="port number")
    parser.add_argument('--model', default='yolov5s', help='model to run, i.e. --model yolov5s')
    args = parser.parse_args()

    model = torch.hub.load('ultralytics/yolov5', args.model)
    app.run(host="0.0.0.0", port=args.port)  # debug=True causes Restarting with stat
Enter fullscreen mode Exit fullscreen mode

you can use a your own model as well with the help of following place the model on the same path as the app.py or flask app file.

model = torch.hub.load('.','custom', path='best.pt',force_reload=True,source='local', pretrained =Flase)

Creating Docker Image:

  1. First create a DockerFile with the following:
# Use an official Python runtime as a parent image
FROM python:3.8-slim-buster

RUN apt-get update
RUN apt-get install ffmpeg libsm6 libxext6  -y

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Install any needed packages specified in requirements.txt
RUN pip install -r requirements.txt

# Make port 5000 available to the world outside this container
EXPOSE 5000

# Run app.py when the container launches
CMD ["python", "app.py", "--port=5000"]

Enter fullscreen mode Exit fullscreen mode

change the app.py if you have different app name

  1. Run the Following Commands to create an Image and push to the docker hub:

Image description

Running Docker Container and Pushing to DockerHub

Run to Ensure it is Working:
Image description
Image description
Login to Docker Hub:
Image description
Create a tag to push to Docker Hub image:
Image description

Deploying Rest API to Azure for Free.

  1. Create a new Web App Service in Azure:
  • Login to your Azure account.
  • Select "Create a resource" and search for "Web App".
  • Select "Web App" from the search results and click "Create".
  • Choose a unique name, subscription, resource group, and app service plan.

Image description

  • Choose "Docker Container" as the Publish option and "Linux" as the Operating System. Image description
  • Choose "Docker Hub" as the Registry and enter the name and version of the image you created in step 1. Image description
  • Click "Create" to create the Web App Service.
  • Wait for Azure to deploy your container to the Web App Service. This may take a few minutes.

Using API in flutter project to perform detections.

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_restapi/ObjectDetectionScreen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: ObjectDetectionScreen(),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

ObjectScreen.dart

import 'dart:io';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:http/http.dart' as http;

class BoundingBox {
  final double xMin;
  final double yMin;
  final double xMax;
  final double yMax;
  final double confidence;
  final String name;

  BoundingBox({
    required this.xMin,
    required this.yMin,
    required this.xMax,
    required this.yMax,
    required this.confidence,
    required this.name,
  });
}

class ObjectDetectionScreen extends StatefulWidget {
  const ObjectDetectionScreen({super.key});

  @override
  State<ObjectDetectionScreen> createState() => _ObjectDetectionScreenState();
}

class _ObjectDetectionScreenState extends State<ObjectDetectionScreen> {
  late List<BoundingBox> _boundingBoxes = [];
  File? _image;
  bool _loading = false;
  final picker = ImagePicker();

  Future<List<BoundingBox>> detectObjects(File image) async {
    final url =
        "https://flask-restapi-yolov5s.azurewebsites.net/v1/object-detection/yolov5";
    final request = await http.MultipartRequest("POST", Uri.parse(url));
    request.files.add(await http.MultipartFile.fromPath("image", image.path));
    final response = await request.send();

    if (response.statusCode == 200) {
      final jsonStr = await response.stream.bytesToString();
      final jsonResponse = json.decode(jsonStr);
      print(jsonResponse);
      return List<BoundingBox>.from(jsonResponse.map((bbox) => BoundingBox(
            xMin: bbox["xmin"],
            yMin: bbox["ymin"],
            xMax: bbox["xmax"],
            yMax: bbox["ymax"],
            confidence: bbox["confidence"],
            name: bbox["name"],
          )));
    } else {
      throw Exception('Failed to detect objects');
    }
  }

  Future<void> getImage(ImageSource source) async {
    setState(() {
      _loading = true;
    });

    final pickedFile =
        await picker.pickImage(source: source, maxWidth: 340, maxHeight: 340);
    if (pickedFile != null) {
      setState(() {
        _image = File(pickedFile.path);
      });
      final bboxes = await detectObjects(_image!);
      setState(() {
        _boundingBoxes = bboxes;
        _loading = false;
      });
    } else {
      setState(() {
        _loading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('Object Detection Using Flask Rest Api'),
        ),
        body: Center(
          child: SingleChildScrollView(
            child: Column(
              children: [
                _image == null
                    ? const Text('No image selected.')
                    : Stack(
                        children: [
                          Image.file(_image!),
                          ..._boundingBoxes.asMap().entries.map((entry) {
                            final index = entry.key;
                            final bbox = entry.value;
                            final xMin = bbox.xMin;
                            final yMin = bbox.yMin;
                            final xMax = bbox.xMax;
                            final yMax = bbox.yMax;
                            final confidence = bbox.confidence;
                            final name = bbox.name;

                            final left = xMin;
                            final top = yMin;
                            final width = xMax - xMin;
                            final height = yMax - yMin;

                            Color color;
                            if (index % 3 == 0) {
                              color = Colors.green;
                            } else if (index % 3 == 1) {
                              color = Colors.yellow;
                            } else {
                              color = Colors.red;
                            }

                            return Positioned(
                              left: left,
                              top: top,
                              width: width,
                              height: height,
                              child: Container(
                                decoration: BoxDecoration(
                                  border: Border.all(
                                    color: color,
                                    width: 2.0,
                                  ),
                                  borderRadius: BorderRadius.circular(20),
                                ),
                                child: Text(
                                  "$name ${(confidence * 100).toStringAsFixed(0)}%",
                                  textAlign: TextAlign.center,
                                  style: TextStyle(
                                    color: color,
                                    fontSize: 12.0,
                                    fontWeight: FontWeight.bold,
                                    shadows: const [
                                      Shadow(
                                        color: Colors.black,
                                        offset: Offset(1, 1),
                                        blurRadius: 2,
                                      )
                                    ],
                                  ),
                                ),
                              ),
                            );
                          }).toList(),
                        ],
                      ),
                ElevatedButton(
                  onPressed: () => getImage(ImageSource.camera),
                  child: const Text("Take a Picture"),
                ),
                ElevatedButton(
                  onPressed: () => getImage(ImageSource.gallery),
                  child: const Text("Choose from Gallery"),
                ),
                const SizedBox(height: 10),
                _loading
                    ? const CircularProgressIndicator(
                        color: Colors.blue,
                      )
                    : const SizedBox(),
              ],
            ),
          ),
        ));
  }
}

Enter fullscreen mode Exit fullscreen mode

BoundingBox.dart

import 'dart:ui';

import 'package:flutter/material.dart';

class BoxPainter extends CustomPainter {
  final dynamic predictions;

  BoxPainter(this.predictions);

  @override
  void paint(Canvas canvas, Size size) {
    final width = size.width;
    final height = size.height;
    final colors = [
      Colors.red,
      Colors.green,
      Colors.blue,
      Colors.yellow,
      Colors.orange,
      Colors.purple,
      Colors.pink,
    ];

    if (predictions != null) {
      for (var i = 0; i < predictions.length; i++) {
        final prediction = predictions[i];
        final bbox = prediction['bbox'];

        final left = bbox['xmin'].toDouble();
        final top = bbox['ymin'].toDouble();
        final right = bbox['xmax'].toDouble();
        final bottom = bbox['ymax'].toDouble();

        final rect = Rect.fromLTWH(
          left / 640 * width,
          top / 640 * height,
          (right - left) / 640 * width,
          (bottom - top) / 640 * height,
        );

        final paint = Paint()
          ..color = colors[i % colors.length]
          ..style = PaintingStyle.stroke
          ..strokeWidth = 2.0;

        final labelPaint = Paint()
          ..color = colors[i % colors.length]
          ..style = PaintingStyle.fill
          ..strokeWidth = 2.0;

        canvas.drawRect(rect, paint);

        final label = '${prediction['name']} (${prediction['confidence']})';
        final labelOffset = Offset(
          left / 640 * width,
          top / 480 * height - 20,
        );
        canvas.drawRect(
          Rect.fromPoints(
            labelOffset,
            Offset(
              labelOffset.dx + label.length * 8,
              labelOffset.dy + 20,
            ),
          ),
          labelPaint,
        );

        final textStyle = TextStyle(
          color: Colors.white,
          fontSize: 14.0,
        );
        final textSpan = TextSpan(
          text: label,
          style: textStyle,
        );
        final textPainter = TextPainter(
          text: textSpan,
          textDirection: TextDirection.ltr,
        );
        textPainter.layout(
          minWidth: 0,
          maxWidth: size.width,
        );
        textPainter.paint(
          canvas,
          Offset(
            labelOffset.dx + 4,
            labelOffset.dy + 2,
          ),
        );
      }
    }
  }

  @override
  bool shouldRepaint(BoxPainter oldDelegate) => false;
}

Enter fullscreen mode Exit fullscreen mode

Compile and debug the project.

App is Running 🎉🎉🎉

Resource and Links:

https://github.com/AneeqMalik/YoloV5-Rest-Api-Flask

Top comments (0)