Spaces:
Sleeping
Sleeping
Codelinhtinh
commited on
Commit
·
affa2df
0
Parent(s):
add
Browse files- .dockerignore +4 -0
- .gitignore +6 -0
- Dockerfile +23 -0
- README.md +80 -0
- api.py +54 -0
- app.py +26 -0
- configs.py +19 -0
- docker-compose.yml +16 -0
- main.ipynb +0 -0
- main.py +43 -0
- postprocess.py +93 -0
- preprocess.py +39 -0
- requirements.txt +0 -0
- test.ipynb +0 -0
- 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 |
+

|
| 42 |
+
|
| 43 |
+
3. Start the streamlit app in makefile:
|
| 44 |
+
```bash
|
| 45 |
+
streamlit run app.py
|
| 46 |
+
```
|
| 47 |
+
4. Import image and wait:
|
| 48 |
+

|
| 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]
|