Codelinhtinh commited on
Commit
affa2df
·
0 Parent(s):
Files changed (15) hide show
  1. .dockerignore +4 -0
  2. .gitignore +6 -0
  3. Dockerfile +23 -0
  4. README.md +80 -0
  5. api.py +54 -0
  6. app.py +26 -0
  7. configs.py +19 -0
  8. docker-compose.yml +16 -0
  9. main.ipynb +0 -0
  10. main.py +43 -0
  11. postprocess.py +93 -0
  12. preprocess.py +39 -0
  13. requirements.txt +0 -0
  14. test.ipynb +0 -0
  15. utils.py +17 -0
.dockerignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ venv/
2
+ sample/
3
+ __pycache__/
4
+ .vscode/
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ venv/
2
+ __pycache__/
3
+ .vscode/
4
+ sample/*
5
+
6
+ weight/*.onnx
Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.11.1
3
+
4
+ # Set the working directory in the container
5
+ WORKDIR /app
6
+
7
+ # Install necessary system dependencies for OpenCV
8
+ RUN apt-get update && \
9
+ apt-get install -y libgl1-mesa-glx libglib2.0-0
10
+
11
+ # Copy the current directory contents into the container at /app
12
+ COPY . /app
13
+
14
+ # Install any needed packages specified in requirements.txt
15
+ RUN pip install --no-cache-dir -r requirements.txt
16
+
17
+ # Make port available to the world outside this container
18
+ EXPOSE 8001
19
+
20
+ # Run your FastAPI app
21
+ CMD ["python", "api.py"]
22
+ # CMD ["streamlit", "run", "app.py"]
23
+
README.md ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Project Name
2
+ 🌟Image Object Detection🌟
3
+
4
+ ## Description
5
+ This is a FastAPI-based web service that accepts an image upload and performs object detection on the image using a pre-trained model Yolov5m. It returns the annotated image with the detected objects.
6
+
7
+ ## Table of Contents
8
+ - [Installation](#installation)
9
+ - [Features](#features)
10
+ - [Usage](#usage)
11
+ - [Configuration](#configuration)
12
+ - [Contributing](#contributing)
13
+ - [License](#license)
14
+
15
+ ## Installation
16
+ 1. Clone the repository:
17
+ ```bash
18
+ git clone https://github.com/Codelinhtinh/Exercises-AIO.git
19
+ ```
20
+ 2. Install project dependencies:
21
+ ```bash
22
+ pip install -r requirements.txt
23
+ ```
24
+ ## Features
25
+ ✨This code is a FastAPI application that provides an API endpoint for performing predictions on uploaded images.
26
+
27
+ ✨ It uses a pre-trained model to detect objects in images.
28
+
29
+ ✨ Uploaded images are processed and resized to a specific image size defined in the CFG class.
30
+
31
+ ✨ The final results are visualized on the original image using the visualize function.
32
+
33
+ ✨The processed and visualized image is saved to a temporary file and returned as a response to the API request.
34
+ ## Usage
35
+ 1. Run the FastAPI server in makefile:
36
+ ```bash
37
+ uvicorn api:api --port 8000
38
+ ```
39
+
40
+ 2. Open your browser and navigate to `http://localhost:8000/docs` to check prediction.
41
+ ![Alt Text](sample\api.gif)
42
+
43
+ 3. Start the streamlit app in makefile:
44
+ ```bash
45
+ streamlit run app.py
46
+ ```
47
+ 4. Import image and wait:
48
+ ![Alt Text](sample\Usage.gif)
49
+
50
+ ## Configuration
51
+ You can modify the configuration parameters of the model by updating the CFG class in the configs.py file. The available configuration options are:
52
+
53
+ **image_size**: The size of the input image for prediction.
54
+
55
+ **conf_thres**: The confidence threshold for object detection.
56
+
57
+ **iou_thres**: The IoU (Intersection over Union) threshold for non-maximum suppression.
58
+
59
+ Feel free to adjust these parameters according to your requirements.
60
+
61
+
62
+ ## Contributing
63
+ 🤝 Contributions are welcome! Please follow these guidelines when contributing to the project:
64
+ 1. Fork the repository.
65
+ 2. Create a new branch:
66
+ ```bash
67
+ git checkout -b feature/your-feature
68
+ ```
69
+ 3. Commit your changes:
70
+ ```bash
71
+ git commit -m 'Add your feature'
72
+ ```
73
+ 4. Push to the branch:
74
+ ```bash
75
+ git push origin feature/your-feature
76
+ ```
77
+ 5. Create a pull request.
78
+
79
+ ## License
80
+ 📝 This project is open source
api.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, File, UploadFile, HTTPException
2
+ from PIL import Image
3
+ from io import BytesIO
4
+ import cv2
5
+ import numpy as np
6
+ from main import *
7
+ from utils import load_session
8
+ from preprocess import resize_and_pad
9
+ from fastapi.responses import FileResponse
10
+ import tempfile
11
+ import json
12
+
13
+ api = FastAPI()
14
+
15
+ class CFG:
16
+ image_size = IMAGE_SIZE
17
+ conf_thres = 0.01
18
+ iou_thres = 0.1
19
+
20
+ cfg = CFG()
21
+ session = load_session(PATH_MODEL)
22
+
23
+ @api.get("/")
24
+ def read_root():
25
+ return {"message": "Hello World"}
26
+
27
+ @api.post("/predict/")
28
+ async def predict(file: UploadFile):
29
+ # Read and process the uploaded image
30
+ contents = await file.read()
31
+ image = Image.open(BytesIO(contents))
32
+ image = image.copy()
33
+ # Convert the PIL Image to a NumPy array
34
+ image_cv = np.array(image)
35
+ image_cv_2 = image_cv.copy()
36
+ image, ratio, (padd_left, padd_top) = resize_and_pad(image_cv, new_shape=cfg.image_size)
37
+ img_norm = normalization_input(image)
38
+ pred = infer(session, img_norm)
39
+ pred = postprocess(pred)[0]
40
+ paddings = np.array([padd_left, padd_top, padd_left, padd_top])
41
+ pred[:,:4] = (pred[:,:4] - paddings) / ratio
42
+ image_cv = cv2.cvtColor(image_cv, cv2.COLOR_BGR2RGB)
43
+ image = Image.fromarray(image_cv)
44
+ image_cv_2 =Image.fromarray(image_cv_2)
45
+ image = visualize(image_cv_2, pred)
46
+ # Save the processed and visualized image to a temporary file
47
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as temp_file:
48
+ image.save(temp_file, format="JPEG")
49
+ temp_file_path = temp_file.name
50
+ return FileResponse(temp_file_path, media_type="image/jpeg")
51
+
52
+ if __name__ == "__main__":
53
+ import uvicorn
54
+ uvicorn.run(api, host="0.0.0.0", port=8001)
app.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import requests
3
+
4
+ # Streamlit UI
5
+ st.title("Object Detection with FastAPI and Streamlit")
6
+
7
+ uploaded_file = st.file_uploader("Choose an image...", type="jpg")
8
+
9
+ if uploaded_file is not None:
10
+ # Display the uploaded image
11
+ st.image(uploaded_file, caption="Uploaded Image.", use_column_width=True)
12
+
13
+ # Make a prediction using FastAPI endpoint
14
+ if st.button("Predict"):
15
+ # Define the FastAPI endpoint URL
16
+ api_url = "http://localhost:8001/predict/"
17
+
18
+ # Make a POST request to the FastAPI endpoint
19
+ files = {"file": ("image.jpg", uploaded_file, "image/jpeg")}
20
+ response = requests.post(api_url, files=files)
21
+
22
+ if response.status_code == 200:
23
+ # Display the predicted image
24
+ st.image(response.content, caption="Predicted Image.", use_column_width=True)
25
+ else:
26
+ st.error(f"Error: {response.status_code}. Failed to make a prediction.")
configs.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ IDX2TAGs = {
2
+ 0: "bicycle",
3
+ 1: "bus",
4
+ 2: "car",
5
+ 3: "motorbike",
6
+ 4: "person"
7
+ }
8
+
9
+ IDX2COLORs = {
10
+ 0: "#FF5733",
11
+ 1: "#6E0DD0",
12
+ 2: "#B2B200",
13
+ 3: "#009DFF",
14
+ 4: "#FF33A8"
15
+ }
16
+
17
+ IMAGE_SIZE = (448, 448)
18
+
19
+ PATH_MODEL = "weight/best_m.onnx"
docker-compose.yml ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3'
2
+ services:
3
+ fastapi:
4
+ build:
5
+ context: .
6
+ dockerfile: Dockerfile
7
+ ports:
8
+ - "8001:8001"
9
+ command:
10
+ - "uvicorn"
11
+ - "api:api"
12
+ - "--reload"
13
+ - "--port=8001"
14
+ - "--host=0.0.0.0"
15
+ volumes:
16
+ - .:/app # Replace with the path to your local code
main.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
main.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from utils import *
2
+ from preprocess import *
3
+ from postprocess import *
4
+ from PIL import Image, ImageDraw, ImageFont
5
+ from configs import *
6
+
7
+ def prediction(session, image, cfg):
8
+ image, ratio, (padd_left, padd_top) = resize_and_pad(image, new_shape=cfg.image_size)
9
+ img_norm = normalization_input(image)
10
+ pred = infer(session, img_norm)
11
+ pred = postprocess(pred, cfg.conf_thres, cfg.iou_thres)[0]
12
+ paddings = np.array([padd_left, padd_top, padd_left, padd_top])
13
+ pred[:,:4] = (pred[:,:4] - paddings) / ratio
14
+ return pred
15
+
16
+ # Modify the visualize function to use the ImageFont module
17
+ def visualize(image, pred):
18
+ img_ = image.copy()
19
+ drawer = ImageDraw.Draw(img_)
20
+
21
+ # Create a dictionary to store the count of each object
22
+ object_counts = {}
23
+ for p in pred:
24
+ x1, y1, x2, y2, _, id = p
25
+ id = int(id)
26
+
27
+ # Increment the count of the object
28
+ if id not in object_counts:
29
+ object_counts[id] = 0
30
+ object_counts[id] += 1
31
+
32
+ # Draw the rectangle and label the object
33
+ drawer.rectangle((x1, y1, x2, y2), outline=IDX2COLORs[id], width=3)
34
+ # drawer.text((x2 + 5, y1), IDX2TAGs[id], fill=IDX2COLORs[id], font=ImageFont.truetype("arial.ttf", 16))
35
+
36
+ # Add a legend to the image
37
+ # drawer.text((0, 0), "", fill="#FFFFFF", font=ImageFont.truetype("arial.ttf", 16))
38
+ # for id, count in object_counts.items():
39
+ # drawer.text((0, 20 + 20 * id), f"{IDX2TAGs[id]}: {count}", fill=IDX2COLORs[id], font=ImageFont.truetype("arial.ttf", 16))
40
+
41
+ return img_
42
+
43
+
postprocess.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+
3
+ def convert_xywh_to_xyxy(bbox_array: np.array) -> np.array:
4
+ converted_boxes = np.zeros_like(bbox_array)
5
+ converted_boxes[:, 0] = bbox_array[:, 0] - bbox_array[:, 2] / 2 # x1 (top-left x)
6
+ converted_boxes[:, 1] = bbox_array[:, 1] - bbox_array[:, 3] / 2 # y1 (top-left y)
7
+ converted_boxes[:, 2] = bbox_array[:, 0] + bbox_array[:, 2] / 2 # x2 (bottom-right x)
8
+ converted_boxes[:, 3] = bbox_array[:, 1] + bbox_array[:, 3] / 2 # y2 (bottom-right y)
9
+
10
+ return converted_boxes
11
+
12
+
13
+ def calculate_iou(box1: np.array, box2: np.array) -> float:
14
+ x1_1, y1_1, x2_1, y2_1 = box1
15
+ x1_2, y1_2, x2_2, y2_2 = box2
16
+ # Calculate the coordinates of the intersection rectangle
17
+ x1_i = max(x1_1, x1_2)
18
+ y1_i = max(y1_1, y1_2)
19
+ x2_i = min(x2_1, x2_2)
20
+ y2_i = min(y2_1, y2_2)
21
+
22
+ # Calculate the area of intersection rectangle
23
+ intersection_area = max(0, x2_i - x1_i + 1) * max(0, y2_i - y1_i + 1)
24
+
25
+ # Calculate the area of both input rectangles
26
+ area1 = (x2_1 - x1_1 + 1) * (y2_1 - y1_1 + 1)
27
+ area2 = (x2_2 - x1_2 + 1) * (y2_2 - y1_2 + 1)
28
+
29
+ # Calculate IoU
30
+ iou = intersection_area / float(area1 + area2 - intersection_area)
31
+
32
+ return iou
33
+
34
+ def nms(bboxes: np.array, scores: np.array, iou_threshold: float) -> np.array:
35
+ selected_indices = []
36
+
37
+ # Sort bounding boxes by decreasing confidence scores
38
+ sorted_indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)
39
+
40
+ while len(sorted_indices) > 0:
41
+ current_index = sorted_indices[0]
42
+ selected_indices.append(current_index)
43
+
44
+ # Remove the current box from the sorted list
45
+ sorted_indices.pop(0)
46
+
47
+ indices_to_remove = []
48
+ for index in sorted_indices:
49
+ iou = calculate_iou(bboxes[current_index], bboxes[index])
50
+ if iou >= iou_threshold:
51
+ indices_to_remove.append(index)
52
+
53
+ # Remove overlapping boxes from the sorted list
54
+ sorted_indices = [i for i in sorted_indices if i not in indices_to_remove]
55
+
56
+ return selected_indices
57
+
58
+ def postprocess(prediction: np.array, conf_thres: float=0.15, iou_thres: float=0.45, max_det: int=300) -> np.array:
59
+ bs = prediction.shape[0] # batch size
60
+ xc = prediction[..., 4] > conf_thres # candidates
61
+ max_nms = 300 # maximum number of boxes into NMS
62
+ max_wh = 7680
63
+ output = [None] * bs
64
+
65
+ for xi, x in enumerate(prediction):
66
+ x = x[xc[xi]]
67
+ if len(x) == 0:
68
+ continue
69
+ x[:, 5:] *= x[:, 4:5]
70
+ # Define xywh2xyxy_numpy function or import it
71
+ box = convert_xywh_to_xyxy(x[:, :4])
72
+
73
+ # Detections matrix nx6 (xyxy, conf, cls)
74
+ conf = x[:, 5:].max(1)
75
+ max_conf_indices = x[:, 5:].argmax(1)
76
+ x = np.column_stack((box, conf, max_conf_indices.astype(float)))[conf > conf_thres]
77
+
78
+ n = len(x)
79
+ if n == 0:
80
+ continue
81
+ elif n > max_nms:
82
+ sorted_indices = np.argsort(-x[:, 4])
83
+ x = x[sorted_indices[:max_nms]]
84
+
85
+ # Batched NMS
86
+ c = x[:, 5:6] * max_wh # You should compute max_wh based on image dimensions
87
+ boxes, scores = x[:, :4] + c, x[:, 4]
88
+ # Define nms_boxes_numpy function or import it
89
+ i = nms(boxes, scores, iou_thres)
90
+ if len(i) > max_det:
91
+ i = i[:max_det]
92
+ output[xi] = x[i]
93
+ return output
preprocess.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Tuple
2
+ import cv2
3
+ import numpy as np
4
+
5
+ def resize_and_pad(image: np.array,
6
+ new_shape: Tuple[int, int],
7
+ padding_color: Tuple[int] = (144, 144, 144)
8
+ ) -> np.array:
9
+ h_org, w_org = image.shape[:2]
10
+ w_new, h_new = new_shape
11
+ padd_left, padd_right, padd_top, padd_bottom = 0, 0, 0, 0
12
+
13
+ #Padding left to right
14
+ if h_org >= w_org:
15
+ img_resize = cv2.resize(image, (int(w_org*h_new/h_org), h_new))
16
+ h, w = img_resize.shape[:2]
17
+ padd_left = (w_new-w)//2
18
+ padd_right = w_new - w - padd_left
19
+ ratio = h_new/h_org
20
+
21
+ #Padding top to bottom
22
+ if h_org < w_org:
23
+ img_resize = cv2.resize(image, (w_new, int(h_org*w_new/w_org)))
24
+ h, w = img_resize.shape[:2]
25
+ padd_top = (h_new-h)//2
26
+ padd_bottom = h_new - h - padd_top
27
+ ratio = w_new/w_org
28
+
29
+ image = cv2.copyMakeBorder(img_resize, padd_top, padd_bottom, padd_left, padd_right, cv2.BORDER_CONSTANT,None,value=padding_color)
30
+
31
+ return image, ratio, (padd_left, padd_top)
32
+
33
+ def normalization_input(image: np.array) -> np.array:
34
+ image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) #BGR to RGB
35
+ img = image.transpose((2, 0, 1)) # HWC to CHW
36
+ img = np.ascontiguousarray(img).astype(np.float32)
37
+ img /=255.0
38
+ img = img[np.newaxis, ...]
39
+ return img
requirements.txt ADDED
Binary file (2.36 kB). View file
 
test.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
utils.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import onnxruntime as ort
2
+ import numpy as np
3
+
4
+ def load_session(path: str) -> ort.InferenceSession:
5
+ providers = ['CPUExecutionProvider']
6
+ session = ort.InferenceSession(path, providers=providers)
7
+ return session
8
+
9
+ def infer(inference_session: ort.InferenceSession, input_data: np.array) -> np.array:
10
+ input_name = inference_session.get_inputs()[0].name
11
+ output_name = inference_session.get_outputs()[0].name
12
+ inference_inputs = {input_name: input_data}
13
+ outputs = inference_session.run(
14
+ [output_name],
15
+ inference_inputs
16
+ )
17
+ return outputs[0]