Skip to content

Commit d40c665

Browse files
committed
feat: add image-detection microservice (#296)
1 parent e7a5b30 commit d40c665

File tree

3 files changed

+309
-0
lines changed

3 files changed

+309
-0
lines changed
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# Image Detection Microservice
2+
3+
## Overview
4+
5+
Image detection microservice that interacts with YOLO models using OpenVINO™ Model Server(OVMS) to perform object detection tasks.
6+
7+
## Model Validated
8+
9+
Model | Format | Device
10+
--- | --- | ---
11+
YOLO11n | OpenVINO™ IR | CPU, GPU, NPU
12+
YOLO11s | OpenVINO™ IR | CPU, GPU, NPU
13+
YOLO11m | OpenVINO™ IR | CPU, GPU, NPU
14+
YOLO11l | OpenVINO™ IR | CPU, GPU, NPU
15+
YOLO11x | OpenVINO™ IR | CPU, GPU, NPU
16+
17+
## Software Ingredient
18+
19+
Package | Version
20+
--- | ---
21+
openvino-dev | 2024.6.0
22+
opencv-python | 4.10.0.84
23+
ultralytics | 8.3.61
24+
python-multipart | 0.0.20
25+
ovmsclient | 2023.1
26+
grpcio | 1.69.0
27+
28+
## Prerequisites
29+
- [Docker Engine](https://docs.docker.com/engine/install/ubuntu/)
30+
31+
- Optional: [GPU Driver](https://github.com/intel/edge-developer-kit-reference-scripts/blob/main/gpu/arc/dg2/usecases/openvino/install_gpu.sh)
32+
33+
34+
## Download and prepare YOLO model
35+
36+
- The models directory are to be structured as such:
37+
```bash
38+
# Example
39+
models/
40+
└── 1/
41+
├── coco_labels.txt
42+
├── metadata.yaml
43+
├── yolo11n.bin
44+
└── yolo11n.xml
45+
```
46+
Where `1` here is represents the `model_version`
47+
48+
- Download the model
49+
>Note: Setup [step 1](#local-environment) first before running the commands below.
50+
51+
```python
52+
# Example
53+
from ultralytics import YOLO
54+
55+
model = YOLO("yolo11n.pt")
56+
```
57+
58+
- Convert models into OpenVINO™ IR format. This will create a directory `<model_name>_openvino_model` that will store the converted models:
59+
```bash
60+
mkdir -p models/ && cd models/
61+
yolo export model=<model_name>.pt format=openvino
62+
63+
# Example 1: Download model and convert it into OpenVINO™ IR format
64+
yolo export model=yolo11n.pt format=openvino
65+
66+
# Example 2: Specify the model's desired input size e.g. [1, 3, 640, 640]
67+
yolo export model=yolo11n.pt format=openvino imgsz=640,640
68+
```
69+
70+
71+
## Prepare image data for inference
72+
73+
- The data to be inferenced can be stored in this folder:
74+
```bash
75+
# Example
76+
images/
77+
└── coco_bike.png
78+
```
79+
80+
- Sample image to download:
81+
```bash
82+
mkdir -p images && cd images
83+
wget https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/image/coco_bike.jpg
84+
```
85+
86+
## Run server
87+
88+
### Local environment
89+
90+
1. Install OpenVINO™ Development Tools and other dependencies
91+
92+
```bash
93+
python3 -m venv venv
94+
95+
source venv/bin/activate
96+
97+
pip3 install --upgrade pip
98+
pip3 install -r requirements.txt
99+
```
100+
101+
### Docker
102+
103+
- Download docker images
104+
105+
```bash
106+
# For CPU
107+
docker pull openvino/model_server:latest
108+
109+
# For GPU
110+
docker pull openvino/model_server:latest-gpu
111+
```
112+
>Note: For NPU, you will need to build the docker image as shown [here](#infer-on-npu).
113+
114+
- From here, you can start the docker through various means depending on what accelerator(CPU, GPU or NPU) to run inference on.
115+
- Below are example commands to run docker and inference. Replace any arguments where necessary.
116+
117+
### Infer on CPU
118+
119+
```bash
120+
# Example docker run CPU
121+
docker run --rm -d -v $(pwd)/models:/models -p 9000:9000 -p 8000:8000 openvino/model_server:latest \
122+
--model_name yolo11n --model_path /models/yolo11n --port 9000 --rest_port 8000 --layout NHWC:NCHW
123+
```
124+
125+
```bash
126+
# Example inference with CPU
127+
python3 main.py --accelerator CPU --models_dir ./models --model_name yolo11n --image_path images/coco_bike.jpg --output_image_path result_image.jpg --ovms_address 127.0.0.1:9000
128+
```
129+
130+
### Infer on GPU
131+
132+
>Note: Replace GPU with GPU.1 if you have more than 1 GPUs
133+
```bash
134+
# Example docker run GPU
135+
docker run --rm -d --group-add $(stat -c "%g" /dev/dri/render* | head -n 1) -u $(id -u) --device=/dev/dri:/dev/dri -v $(pwd)/models:/models -p 9000:9000 -p 8000:8000 openvino/model_server:latest-gpu \
136+
--model_name yolo11n --model_path /models/yolo11n --port 9000 --rest_port 8000 --layout NHWC:NCHW
137+
```
138+
139+
```bash
140+
# Example inference with GPU
141+
python3 main.py --accelerator GPU --models_dir ./models --model_name yolo11n --image_path images/coco_bike.jpg --output_image_path result_image.jpg --ovms_address 127.0.0.1:9000
142+
```
143+
144+
### Infer on NPU
145+
146+
Build docker:
147+
```
148+
git clone https://github.com/openvinotoolkit/model_server.git
149+
150+
cd model_server
151+
make release_image NPU=1
152+
```
153+
154+
```bash
155+
# Example docker run NPU
156+
docker run --rm -d --device=/dev/accel:/dev/accel:rwm --group-add=$(stat -c "%g" /dev/dri/render* | head -n 1) -u $(id -u) -v $(pwd)/models:/models -p 9000:9000 -p 8000:8000 openvino/model_server:latest-npu \
157+
--model_name yolo11n --model_path /models/yolo11n --port 9000 --rest_port 8000 --layout NHWC:NCHW
158+
```
159+
160+
```bash
161+
# Example inference with NPU
162+
python3 main.py --accelerator NPU --models_dir ./models --model_name yolo11n --image_path images/coco_bike.jpg --output_image_path result_image.jpg --ovms_address 127.0.0.1:9000
163+
```
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Copyright (C) 2025 Intel Corporation
2+
#
3+
# This software and the related documents are Intel copyrighted materials,
4+
# and your use of them is governed by the express license under which they
5+
# were provided to you ("License"). Unless the License provides otherwise,
6+
# you may not use, modify, copy, publish, distribute, disclose or transmit
7+
# this software or the related documents without Intel's prior written
8+
# permission.
9+
#
10+
# This software and the related documents are provided as is, with no express
11+
# or implied warranties, other than those that are expressly stated in the License.
12+
13+
import argparse
14+
import cv2
15+
import numpy as np
16+
import openvino as ov
17+
import torch
18+
import datetime
19+
20+
from ovmsclient import make_grpc_client
21+
from pathlib import Path
22+
from ultralytics import YOLO
23+
24+
# Global variable to store the duration
25+
duration = 0
26+
27+
def load_model(config, model_version=1):
28+
core = ov.Core()
29+
models_dir = Path(config["models_dir"])
30+
models_dir.mkdir(exist_ok=True)
31+
model_path = models_dir / f"{config['model_name']}/{model_version}/{config['model_name']}.xml"
32+
33+
# Download and convert model
34+
det_model = YOLO(models_dir / f"{config['model_name']}.pt")
35+
if not model_path.exists():
36+
det_model.export(format="openvino", dynamic=True, half=True)
37+
38+
# Load model with OpenVINO
39+
det_ov_model = core.read_model(model_path)
40+
41+
ov_config = {}
42+
if config["accelerator"] != "CPU":
43+
det_ov_model.reshape({0: [1, 3, 640, 640]})
44+
if "GPU" in config["accelerator"] or (
45+
"AUTO" in config["accelerator"] and "GPU" in core.get_available_devices()
46+
):
47+
ov_config = {"GPU_DISABLE_WINOGRAD_CONVOLUTION": "YES"}
48+
49+
det_compiled_model = core.compile_model(det_ov_model, config["accelerator"], ov_config)
50+
return det_model, det_compiled_model
51+
52+
def setup_inference(det_model, det_compiled_model, config):
53+
def infer(*args):
54+
global duration
55+
client = make_grpc_client(config["ovms_address"])
56+
inputs = {"x": np.transpose(np.array(args[0]), (0, 2, 3, 1))} # Transpose input to match expected shape
57+
58+
start_time = datetime.datetime.now()
59+
results = client.predict(model_name=config["model_name"], inputs=inputs)
60+
end_time = datetime.datetime.now()
61+
62+
# Calculate duration to inference
63+
duration = (end_time - start_time).total_seconds() * 1000 # Duration in milliseconds
64+
65+
results = torch.from_numpy(results)
66+
return results
67+
68+
if det_model.predictor is None:
69+
custom = {
70+
"conf": 0.25,
71+
"batch": 1,
72+
"save": False,
73+
"mode": "predict",
74+
}
75+
args = {**det_model.overrides, **custom}
76+
det_model.predictor = det_model._smart_load("predictor")(
77+
overrides=args, _callbacks=det_model.callbacks
78+
)
79+
det_model.predictor.setup_model(model=det_model.model)
80+
det_model.predictor.model.ov_compiled_model = det_compiled_model
81+
det_model.predictor.inference = infer
82+
det_model.predictor.model.pt = False
83+
84+
def plot_diagram(det_model, image_path):
85+
res = det_model(image_path)
86+
result_image_with_labels = res[0].plot()[:, :, ::-1] # Convert from RGB to BGR for OpenCV
87+
return result_image_with_labels
88+
89+
def save_and_display_image(image, output_path, display, save):
90+
image_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
91+
if save:
92+
cv2.imwrite(output_path, image_bgr)
93+
94+
# Display the image
95+
if display:
96+
cv2.imshow("Detections", image_bgr)
97+
cv2.waitKey(0)
98+
cv2.destroyAllWindows()
99+
100+
def main(config):
101+
global duration
102+
det_model, det_compiled_model = load_model(config, config["model_version"])
103+
setup_inference(det_model, det_compiled_model, config)
104+
105+
result_image_with_labels = plot_diagram(det_model, config["image_path"])
106+
save_and_display_image(result_image_with_labels, config["output_image_path"], config["display"], config["save"])
107+
108+
# Perform inference and calculate instantaneous latency and FPS
109+
fps = 1000 / duration
110+
print(f"latency: {duration:.2f} ms")
111+
print(f"FPS: {fps:.2f}")
112+
113+
114+
if __name__ == "__main__":
115+
parser = argparse.ArgumentParser(description="YOLO Inference with OpenVINO and OVMS")
116+
parser.add_argument("--accelerator", type=str, default="CPU", help="Accelerator to use (CPU, GPU, AUTO)")
117+
parser.add_argument("--models_dir", type=str, default="./models", help="Directory to store models")
118+
parser.add_argument("--model_name", type=str, default="yolo11n", help="Name of the model")
119+
parser.add_argument("--model_version", type=int, default=1, help="Version of the model")
120+
parser.add_argument("--image_path", type=str, default="images/bus_2.jpg", help="Path to the input image")
121+
parser.add_argument("--output_image_path", type=str, default="result_image_with_labels.jpg", help="Path to save the output image")
122+
parser.add_argument("--ovms_address", type=str, default="127.0.0.1:9000", help="Address of the OVMS server")
123+
parser.add_argument("--display", type=bool, default=False, help="Display the output image")
124+
parser.add_argument("--save", type=bool, default=False, help="Save the output image")
125+
126+
args = parser.parse_args()
127+
128+
config = {
129+
"accelerator" : args.accelerator,
130+
"models_dir" : args.models_dir,
131+
"model_name" : args.model_name,
132+
"model_version" : args.model_version,
133+
"image_path" : args.image_path,
134+
"output_image_path": args.output_image_path,
135+
"ovms_address" : args.ovms_address,
136+
"display" : args.display,
137+
"save" : args.save
138+
}
139+
main(config)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
openvino-dev[onnx,pytorch,tensorflow]==2024.6.0
2+
opencv-python==4.10.0.84
3+
ultralytics==8.3.61
4+
python-multipart==0.0.20
5+
numpy<2.0.0
6+
grpcio==1.69.0
7+
ovmsclient==2023.1

0 commit comments

Comments
 (0)