目录

Ultralytics Serving

Inference service based on Ultralytics

创建虚拟环境

python -m venv venv source venv/bin/activate

安装依赖包

创建 requirements.txt

fastapi
python-multipart
aiofiles
onnxruntime
ultralytics
uvicorn[standard]
gunicorn
pytest
httpx

安装

pip install -r requirements.txt

调试

创建 launch.json 文件,用于调试 FastAPI 应用。

.vscode/launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: FastAPI",
            "type": "python",
            "request": "launch",
            "module": "uvicorn",
            "args": [
                "app.main:app",
                "--reload"
            ],
            "jinja": true,
            "justMyCode": true
        }
    ]
}

创建项目的目录结构

ultralytics-serving
├── app
│   ├── __init__.py
│   └── main.py
├── asserts/
├── models/
├── requirements.txt
├── routers
│   └── __init__.py
├── static/
├── tests
│   └── __init__.py
└── utils
    └── __init__.py

构建镜像

Ultralytics Serving 镜像

amd64

docker buildx build --platform=linux/amd64 --pull --rm -f Dockerfile -t wangjunjian/ultralytics-serving:amd64 . --push

arm64

docker buildx build --platform=linux/arm64 --pull --rm -f Dockerfile -t wangjunjian/ultralytics-serving:arm64 . --push

镜像测试

docker run --rm -it -p 80:80/tcp ultralytics-serving:amd64
docker run --rm -it -p 80:80/tcp ultralytics-serving:arm64

开发

环境

conda 降低 Python 版本,依赖包需要重新安装

conda install python=3.10.9

调试

macOS 上 F11 快捷键不能 Step Into

打开【系统设置】->【桌面与程序坞】->【快捷键】->【显示桌面】,设置为 -

模型

模型可视化

查看模型的网络,层的名字,输入和输出的形状。

ONNXRuntime 推理 YOLO

import cv2
import numpy as np

img = cv2.imread("image.jpg", cv2.IMREAD_UNCHANGED)
resized = cv2.resize(img, (640,640), interpolation = cv2.INTER_AREA).astype(np.float32)
resized = resized.transpose((2, 0, 1))
resized = np.expand_dims(resized, axis=0)  # Add batch dimension

import onnxruntime as ort
ort_session = ort.InferenceSession("yolov5.onnx", providers=["CUDAExecutionProvider"])
# compute ONNX Runtime output prediction
ort_inputs = {ort_session.get_inputs()[0].name: resized}
ort_outs = ort_session.run(None, ort_inputs)

ONNX 模型的输入与输出的名字

input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
  • [ONNX Runtime Inference session.run() multiprocessing](https://stackoverflow.com/questions/70803924/onnx-runtime-inference-session-run-multiprocessing)

Python

持久化保存临时文件

file = tempfile.NamedTemporaryFile(delete=False)

设置临时文件后缀

file = tempfile.NamedTemporaryFile(suffix='.txt')

获得文件扩展名

import pathlib

print(pathlib.Path('yourPath.example').suffix) # '.example'
print(pathlib.Path("hello/foo.bar.tar.gz").suffixes) # ['.bar', '.tar', '.gz']

其它格式的图像保存为指定的格式

import cv2

# Load .png image
image = cv2.imread('image.png')

# Save .jpg image
cv2.imwrite('image.jpg', image, [int(cv2.IMWRITE_JPEG_QUALITY), 100])

数据的维度进行压缩,去掉维数为1的的维度

#tensor([[[-0.1313, -1.0998, -1.9624]]])     torch.Size([1, 1, 3]) 
a=torch.randn(1,1,3)

#tensor([-0.1313, -1.0998, -1.9624])     torch.Size([3])
b=torch.squeeze(a)

时间测量

tic = time.perf_counter()
predict(img)
toc = time.perf_counter()
print(f"Predict {toc - tic:0.4f} seconds")

加载 yaml 文件

config = yaml.load(open("config.yaml", "r"), Loader=yaml.FullLoader)

gunicorn

FastAPI

OpenCV 直接读取 UploadFile

@app.post("/analyze", response_model=Analyzer)
async def analyze_route(file: UploadFile = File(...)):
    contents = await file.read()
    nparr = np.fromstring(contents, np.uint8)
    img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)

返回图像文件,使用内存中的图像

import io
from starlette.responses import StreamingResponse

app = FastAPI()

@app.post("/vector_image")
def image_endpoint(*, vector):
    # Returns a cv2 image array from the document vector
    cv2img = my_function(vector)
    res, im_png = cv2.imencode(".png", cv2img)
    return StreamingResponse(io.BytesIO(im_png.tobytes()), media_type="image/png")

主页显示静态文件

from fastapi import  FastAPI
from fastapi.staticfiles import StaticFiles

# Use this to serve a static/index.html
from starlette.responses import FileResponse 

app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")

@app.get("/")
async def read_index():
    return FileResponse('static/index.html')

主页显示使用模板

<html>
   <body>
      <h2></h2>
   </body>
</html>
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates


title = "Ultralytics Inference Serving API"

app = FastAPI(title=title, version="1.0.0")

app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="static")

@app.get("/")
async def index(request: Request):
    return templates.TemplateResponse("index.html", {"request": request, "title": title})

隐藏 /docs(Swagger UI) 页面上的 Schemas

swagger_ui_parameters={"defaultModelsExpandDepth": -1}

app = FastAPI(title="Ultralytics Inference Serving API", version="1.0.0",
              swagger_ui_parameters={"defaultModelsExpandDepth": -1})

修改图标(favicon)

OpenAPI

在文档中增加代码示例

HTML & CSS

在不能联接网络(内网)时,访问 / 会出现下面的错误。

INFO:     127.0.0.1:63009 - "GET /apple-touch-icon-precomposed.png HTTP/1.1" 404 Not Found
INFO:     127.0.0.1:63010 - "GET /apple-touch-icon.png HTTP/1.1" 404 Not Found
INFO:     127.0.0.1:63011 - "GET /favicon.ico HTTP/1.1" 404 Not Found

<head> 标签内加入下面的代码

<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">

在不能联接网络(内网)时,访问 /docs 或 /redoc 时会出现错误,且显示特别慢(分钟)。

[Error] Failed to load resource: 似乎已断开与互联网的连接。 (swagger-ui.css, line 0)
[Error] ReferenceError: Can't find variable: SwaggerUIBundle
[Error] Failed to load resource: 似乎已断开与互联网的连接。 (swagger-ui-bundle.js, line 0)
[Error] Failed to load resource: 似乎已断开与互联网的连接。 (favicon.png, line 0)

解决办法,注意 FastAPI 对象的参数:docs_url=None, redoc_url=None

from fastapi import FastAPI
from fastapi.openapi.docs import (
    get_redoc_html,
    get_swagger_ui_html,
    get_swagger_ui_oauth2_redirect_html,
)
from fastapi.staticfiles import StaticFiles

app = FastAPI(docs_url=None, redoc_url=None)

app.mount("/static", StaticFiles(directory="static"), name="static")

@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html():
    return get_swagger_ui_html(
        openapi_url=app.openapi_url,
        title=app.title + " - Swagger UI",
        oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
        swagger_js_url="/static/swagger-ui-bundle.js",
        swagger_css_url="/static/swagger-ui.css",
    )

@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False)
async def swagger_ui_redirect():
    return get_swagger_ui_oauth2_redirect_html()

@app.get("/redoc", include_in_schema=False)
async def redoc_html():
    return get_redoc_html(
        openapi_url=app.openapi_url,
        title=app.title + " - ReDoc",
        redoc_js_url="/static/redoc.standalone.js",
    )

超链接移除下划线

style="text-decoration: none"

<a href="docs" style="text-decoration: none">交互式开发文档(Swagger UI)</a>

图片居中显示

<div>
   <table width="100%" height="100%" align="center" valign="center">
   <tr><td>
      <img src="foo.jpg" alt="foo" />
   </td></tr>
   </table>
</div>

参考资料