更新到HyperLPR3版本
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
.idea/
|
||||
__pycache__
|
||||
resource
|
||||
build/
|
||||
dist/
|
||||
hyperlpr3.egg-info/
|
||||
venv/
|
||||
@@ -0,0 +1,11 @@
|
||||
# 导入opencv库
|
||||
import cv2
|
||||
# 导入依赖包
|
||||
import hyperlpr3 as lpr3
|
||||
|
||||
# 实例化识别对象
|
||||
catcher = lpr3.LicensePlateCatcher()
|
||||
# 读取图片
|
||||
image = cv2.imread("../resource/images/test_img.jpg")
|
||||
# 识别结果
|
||||
print(catcher(image))
|
||||
@@ -0,0 +1,9 @@
|
||||
from .hyperlpr3 import LicensePlateCatcher
|
||||
from .common.typedef import *
|
||||
from .config.configuration import initialization
|
||||
|
||||
initialization()
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import click
|
||||
|
||||
|
||||
class AliasedGroup(click.Group):
|
||||
def get_command(self, ctx, cmd_name):
|
||||
rv = click.Group.get_command(self, ctx, cmd_name)
|
||||
if rv is not None:
|
||||
return rv
|
||||
matches = [
|
||||
x for x in self.list_commands(ctx) if x.startswith(cmd_name)
|
||||
]
|
||||
if not matches:
|
||||
return None
|
||||
elif len(matches) == 1:
|
||||
return click.Group.get_command(self, ctx, matches[0])
|
||||
ctx.fail('Too many matches: %s' % ', '.join(sorted(matches)))
|
||||
@@ -0,0 +1,20 @@
|
||||
import click
|
||||
from hyperlpr3.command.aliased_group import AliasedGroup
|
||||
from hyperlpr3.command.sample import sample
|
||||
from hyperlpr3.command.serve import rest
|
||||
|
||||
__all__ = ['cli']
|
||||
|
||||
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
||||
|
||||
|
||||
@click.command(cls=AliasedGroup, context_settings=CONTEXT_SETTINGS)
|
||||
def cli():
|
||||
pass
|
||||
|
||||
|
||||
cli.add_command(sample)
|
||||
cli.add_command(rest)
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
||||
@@ -0,0 +1,77 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import hyperlpr3 as lpr3
|
||||
import cv2
|
||||
import urllib
|
||||
import numpy as np
|
||||
import re
|
||||
import click
|
||||
from loguru import logger
|
||||
|
||||
type_list = ["蓝牌", "黄牌单层", "白牌单层", "绿牌新能源", "黑牌港澳", "香港单层", "香港双层", "澳门单层", "澳门双层", "黄牌双层"]
|
||||
|
||||
|
||||
def is_http_url(s):
|
||||
regex = re.compile(
|
||||
r'^(?:http|ftp)s?://' # http:// or https://
|
||||
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|'
|
||||
r'localhost|' # localhost...
|
||||
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
|
||||
r'(?::\d+)?' # optional port
|
||||
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
|
||||
|
||||
if regex.match(s):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def url_to_image(url):
|
||||
try:
|
||||
resp = urllib.request.urlopen(url)
|
||||
image = np.asarray(bytearray(resp.read()), dtype="uint8")
|
||||
image = cv2.imdecode(image, cv2.IMREAD_COLOR)
|
||||
except Exception as err:
|
||||
return None
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def get_image(path: str):
|
||||
image = None
|
||||
if is_http_url(path):
|
||||
# url
|
||||
image = url_to_image(path)
|
||||
else:
|
||||
# local path
|
||||
if path.split('.')[-1].lower() in ('jpg', 'png', 'jpeg', 'bmp',):
|
||||
image = cv2.imread(path)
|
||||
try:
|
||||
h, w, c = image.shape
|
||||
except Exception as err:
|
||||
logger.error("Failed to read image from path or url.")
|
||||
return False, None
|
||||
|
||||
return True, image
|
||||
|
||||
|
||||
@click.command(help="Exec HyperLPR3 Test Sample.")
|
||||
@click.option("-src", "--src", type=str, )
|
||||
@click.option("-det", "--det", default='low', type=click.Choice(['low', 'high']), )
|
||||
def sample(src, det):
|
||||
ret, image = get_image(src)
|
||||
if ret:
|
||||
if det == 'low':
|
||||
level = lpr3.DETECT_LEVEL_LOW
|
||||
else:
|
||||
level = lpr3.DETECT_LEVEL_HIGH
|
||||
catcher = lpr3.LicensePlateCatcher(detect_level=level)
|
||||
print("--" * 20)
|
||||
result = catcher(image)
|
||||
logger.info(f"共检测到车牌: {len(result)}")
|
||||
for res in result:
|
||||
code, conf, plate_type, box = res
|
||||
logger.success(f'[{type_list[plate_type]}]{code} {conf} {box}')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sample()
|
||||
@@ -0,0 +1,129 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from fastapi import FastAPI, APIRouter, UploadFile, File
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from typing import List
|
||||
from fastapi.responses import JSONResponse
|
||||
import numpy as np
|
||||
import cv2
|
||||
import hyperlpr3 as lpr3
|
||||
import uvicorn
|
||||
import click
|
||||
|
||||
type_list = ["蓝牌", "黄牌单层", "白牌单层", "绿牌新能源", "黑牌港澳", "香港单层", "香港双层", "澳门单层", "澳门双层", "黄牌双层"]
|
||||
|
||||
catcher = lpr3.LicensePlateCatcher(detect_level=lpr3.DETECT_LEVEL_HIGH)
|
||||
|
||||
class BaseResponse():
|
||||
def __init__(self, *args, **kwags) -> None:
|
||||
self.response = {
|
||||
'result': None,
|
||||
}
|
||||
# self.response.update(response)
|
||||
|
||||
def http_ok_response(self, response_data):
|
||||
self.response['result'] = response_data
|
||||
self.response['code'] = 5000
|
||||
self.response['msg'] = '请求成功'
|
||||
return JSONResponse(self.response)
|
||||
|
||||
def http_prermission_denied_response(self, response_data=None, error_msg=None):
|
||||
"""
|
||||
权限错误
|
||||
"""
|
||||
self.response['result'] = response_data
|
||||
self.response['code'] = 5005
|
||||
if error_msg:
|
||||
self.response['msg'] = error_msg
|
||||
else:
|
||||
self.response['msg'] = '权限校验失败,请重新检查权限'
|
||||
|
||||
return JSONResponse(self.response)
|
||||
|
||||
def http_request_parameter_error(self, response_data=None, error_msg=None):
|
||||
"""
|
||||
请求参数错误
|
||||
"""
|
||||
self.response['result'] = response_data
|
||||
self.response['code'] = 5007
|
||||
if error_msg:
|
||||
self.response['msg'] = error_msg
|
||||
else:
|
||||
self.response['msg'] = '提交参数异常,请重新检查接口参数'
|
||||
|
||||
return JSONResponse(self.response)
|
||||
|
||||
def http_server_error(self, response_data=None, error_msg=None):
|
||||
self.response['result'] = response_data
|
||||
self.response['code'] = 5009
|
||||
if error_msg:
|
||||
self.response['msg'] = error_msg
|
||||
else:
|
||||
self.response['msg'] = '服务异常'
|
||||
|
||||
return JSONResponse(self.response)
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="HyperLPR3-Api",
|
||||
version='0.0.9',
|
||||
docs_url='/api/v1/docs',
|
||||
description='HyperLPR3 Api Serving'
|
||||
)
|
||||
|
||||
origins = [
|
||||
'http://localhost.slsmart.com',
|
||||
'https://localhost.slsmart.com',
|
||||
'http://localhost',
|
||||
'http://localhost:8715'
|
||||
]
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=['*'],
|
||||
allow_headers=['*'],
|
||||
)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def running():
|
||||
'''当前api服务程序有在正常运行'''
|
||||
return """HyperLpr3 WebApi Server Running..."""
|
||||
|
||||
|
||||
@app.post("/api/v1/rec", tags=['车牌识别'])
|
||||
async def vehicle_license_plate_recognition(file: List[UploadFile] = File(...)):
|
||||
"""上传图片进行车牌识别,上传必须为图片类型png/jpg/jpge/wabp"""
|
||||
if len(file[0].filename) == 0:
|
||||
return BaseResponse().http_request_parameter_error(error_msg='单次上传图片不能为空')
|
||||
if len(file) > 1:
|
||||
return BaseResponse().http_request_parameter_error(error_msg='该接口仅支持单张图片上传')
|
||||
if len(file) == 1:
|
||||
if file[0].filename.rsplit('.', 1)[1].lower() not in ['png', 'jpeg', 'jpg', 'wabp']:
|
||||
return BaseResponse().http_request_parameter_error(error_msg='上传必须为图片类型png/jpg/jpge')
|
||||
content = await file[0].read()
|
||||
nparr = np.fromstring(content, np.uint8)
|
||||
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR).astype(np.uint8)
|
||||
plates = catcher(img)
|
||||
results = list()
|
||||
for code, conf, plate_type, box in plates:
|
||||
plate = dict(code=code, conf=float(conf), plate_type=type_list[plate_type], box=box)
|
||||
results.append(plate)
|
||||
return BaseResponse().http_ok_response({'plate_list': results})
|
||||
|
||||
|
||||
def get_application():
|
||||
return app
|
||||
|
||||
|
||||
@click.command(help="Exec HyperLPR3 WebApi Server.")
|
||||
@click.option("-host", "--host", default="0.0.0.0", type=str, )
|
||||
@click.option("-port", "--port", default=8715, type=int, )
|
||||
@click.option("-workers", "--workers", default=1, type=int, )
|
||||
def rest(host, port, workers):
|
||||
uvicorn.run(app="hyperlpr3.command.serve:app", host=host, port=port, workers=workers)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
rest()
|
||||
@@ -0,0 +1,37 @@
|
||||
import numpy as np
|
||||
import MNN
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class MNNAdapter(object):
|
||||
|
||||
def __init__(self, model_path: str, input_shape: tuple,
|
||||
dim_type: int = MNN.Tensor_DimensionType_Caffe, outputs_name=None, outputs_shape=None):
|
||||
self.interpreter = MNN.Interpreter(model_path)
|
||||
self.session = self.interpreter.createSession()
|
||||
self.input_tensor = self.interpreter.getSessionInput(self.session)
|
||||
self.input_shape = input_shape
|
||||
self.dim_type = dim_type
|
||||
self.outputs_name = outputs_name
|
||||
self.outputs_shape = outputs_shape
|
||||
|
||||
def inference(self, tensor: np.ndarray) -> np.ndarray:
|
||||
tensor = tensor.astype(np.float32)
|
||||
tmp_input = MNN.Tensor(self.input_shape, MNN.Halide_Type_Float, tensor, self.dim_type)
|
||||
self.input_tensor.copyFrom(tmp_input)
|
||||
self.interpreter.runSession(self.session)
|
||||
output_tensor = list()
|
||||
if self.outputs_name:
|
||||
if self.outputs_shape:
|
||||
for idx, shape in enumerate(self.outputs_shape):
|
||||
tmp_output = MNN.Tensor(shape, MNN.Halide_Type_Float, np.ones(shape).astype(np.float32), self.dim_type)
|
||||
tmp_tensor = self.interpreter.getSessionOutput(self.session, self.outputs_name[idx])
|
||||
tmp_tensor.copyToHostTensor(tmp_output)
|
||||
output_tensor.append(np.asarray(tmp_output.getData()))
|
||||
else:
|
||||
output_tensor = [np.asarray(self.interpreter.getSessionOutput(self.session, name).getData()) for name in self.outputs_name]
|
||||
else:
|
||||
output_tensor.append(self.interpreter.getSessionOutput(self.session).getData())
|
||||
res = np.asarray(output_tensor)
|
||||
|
||||
return res
|
||||
@@ -0,0 +1,4 @@
|
||||
token = ["blank", "'", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "J",
|
||||
"K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "云", "京", "冀", "吉", "学", "宁",
|
||||
"川", "挂", "新", "晋", "桂", "民", "沪", "津", "浙", "渝", "港", "湘", "琼", "甘", "皖", "粤", "航", "苏", "蒙", "藏", "警", "豫",
|
||||
"贵", "赣", "辽", "鄂", "闽", "陕", "青", "鲁", "黑", '领', '使', '澳', ]
|
||||
@@ -0,0 +1,300 @@
|
||||
import numpy as np
|
||||
import cv2
|
||||
import time
|
||||
from functools import wraps
|
||||
|
||||
|
||||
def find_the_adjacent_boxes(boxes: list):
|
||||
list_index = list()
|
||||
for i, a in enumerate(boxes):
|
||||
for j, b in enumerate(boxes):
|
||||
if i == j:
|
||||
continue
|
||||
if j in list_index:
|
||||
continue
|
||||
ax, ay, aw, ah = single_xyxy2cxcywh(a)
|
||||
bx, by, bw, bh = single_xyxy2cxcywh(b)
|
||||
dis = l2((ax, ay), (bx, by))
|
||||
if dis < 2 * aw or dis < 2 * bw:
|
||||
list_index.append(i)
|
||||
list_index.append(j)
|
||||
|
||||
list_index = set(list_index)
|
||||
|
||||
return list(list_index)
|
||||
|
||||
|
||||
|
||||
def l2(p1, p2):
|
||||
x0, y0 = p1
|
||||
x1, y1 = p2
|
||||
return np.sqrt((x0 - x1) ** 2 + (y0 - y1) ** 2)
|
||||
|
||||
|
||||
def single_xyxy2cxcywh(box):
|
||||
x1, y1, x2, y2 = box
|
||||
w = x2 - x1
|
||||
h = y2 - y1
|
||||
x = x1 + w / 2
|
||||
y = y1 + h / 2
|
||||
return x, y, w, h
|
||||
|
||||
|
||||
def sigmoid(x):
|
||||
return 1 / (1 + np.exp(-x))
|
||||
|
||||
|
||||
def xywh2xyxy(x):
|
||||
# Convert [x, y, w, h] to [x1, y1, x2, y2]
|
||||
y = np.copy(x)
|
||||
y[:, 0] = x[:, 0] - x[:, 2] / 2 # top left x
|
||||
y[:, 1] = x[:, 1] - x[:, 3] / 2 # top left y
|
||||
y[:, 2] = x[:, 0] + x[:, 2] / 2 # bottom right x
|
||||
y[:, 3] = x[:, 1] + x[:, 3] / 2 # bottom right y
|
||||
return y
|
||||
|
||||
|
||||
def process(input, mask, anchors, size):
|
||||
anchors = [anchors[i] for i in mask]
|
||||
grid_h, grid_w = map(int, input.shape[0:2])
|
||||
|
||||
box_confidence = sigmoid(input[..., 4])
|
||||
box_confidence = np.expand_dims(box_confidence, axis=-1)
|
||||
|
||||
box_class_probs = sigmoid(input[..., 5:])
|
||||
|
||||
box_xy = sigmoid(input[..., :2]) * 2 - 0.5
|
||||
|
||||
col = np.tile(np.arange(0, grid_w), grid_h).reshape(-1, grid_w)
|
||||
row = np.tile(np.arange(0, grid_h).reshape(-1, 1), grid_w)
|
||||
col = col.reshape(grid_h, grid_w, 1, 1).repeat(3, axis=-2)
|
||||
row = row.reshape(grid_h, grid_w, 1, 1).repeat(3, axis=-2)
|
||||
grid = np.concatenate((col, row), axis=-1)
|
||||
box_xy += grid
|
||||
box_xy *= (int(size[1] / grid_h), int(size[0] / grid_w))
|
||||
|
||||
box_wh = pow(sigmoid(input[..., 2:4]) * 2, 2)
|
||||
box_wh = box_wh * anchors
|
||||
|
||||
box = np.concatenate((box_xy, box_wh), axis=-1)
|
||||
|
||||
return box, box_confidence, box_class_probs
|
||||
|
||||
|
||||
def filter_boxes(boxes, box_confidences, box_class_probs, box_threshold, nms_threshold):
|
||||
"""Filter boxes with box threshold. It's a bit different with origin yolov5 post process!
|
||||
# Arguments
|
||||
boxes: ndarray, boxes of objects.
|
||||
box_confidences: ndarray, confidences of objects.
|
||||
box_class_probs: ndarray, class_probs of objects.
|
||||
# Returns
|
||||
boxes: ndarray, filtered boxes.
|
||||
classes: ndarray, classes for boxes.
|
||||
scores: ndarray, scores for boxes.
|
||||
"""
|
||||
boxes = boxes.reshape(-1, 4)
|
||||
box_confidences = box_confidences.reshape(-1)
|
||||
box_class_probs = box_class_probs.reshape(-1, box_class_probs.shape[-1])
|
||||
|
||||
_box_pos = np.where(box_confidences >= box_threshold)
|
||||
boxes = boxes[_box_pos]
|
||||
box_confidences = box_confidences[_box_pos]
|
||||
box_class_probs = box_class_probs[_box_pos]
|
||||
|
||||
class_max_score = np.max(box_class_probs, axis=-1)
|
||||
classes = np.argmax(box_class_probs, axis=-1)
|
||||
_class_pos = np.where(class_max_score * box_confidences >= box_threshold)
|
||||
|
||||
boxes = boxes[_class_pos]
|
||||
classes = classes[_class_pos]
|
||||
scores = (class_max_score * box_confidences)[_class_pos]
|
||||
|
||||
return boxes, classes, scores
|
||||
|
||||
|
||||
def nms_boxes(boxes, scores, nms_threshold):
|
||||
"""Suppress non-maximal boxes.
|
||||
# Arguments
|
||||
boxes: ndarray, boxes of objects.
|
||||
scores: ndarray, scores of objects.
|
||||
# Returns
|
||||
keep: ndarray, index of effective boxes.
|
||||
"""
|
||||
x = boxes[:, 0]
|
||||
y = boxes[:, 1]
|
||||
w = boxes[:, 2] - boxes[:, 0]
|
||||
h = boxes[:, 3] - boxes[:, 1]
|
||||
|
||||
areas = w * h
|
||||
order = scores.argsort()[::-1]
|
||||
|
||||
keep = []
|
||||
while order.size > 0:
|
||||
i = order[0]
|
||||
keep.append(i)
|
||||
|
||||
xx1 = np.maximum(x[i], x[order[1:]])
|
||||
yy1 = np.maximum(y[i], y[order[1:]])
|
||||
xx2 = np.minimum(x[i] + w[i], x[order[1:]] + w[order[1:]])
|
||||
yy2 = np.minimum(y[i] + h[i], y[order[1:]] + h[order[1:]])
|
||||
|
||||
w1 = np.maximum(0.0, xx2 - xx1 + 0.00001)
|
||||
h1 = np.maximum(0.0, yy2 - yy1 + 0.00001)
|
||||
inter = w1 * h1
|
||||
|
||||
ovr = inter / (areas[i] + areas[order[1:]] - inter)
|
||||
inds = np.where(ovr <= nms_threshold)[0]
|
||||
order = order[inds + 1]
|
||||
keep = np.array(keep)
|
||||
return keep
|
||||
|
||||
|
||||
def restore_bound_box(boxes: list, ratio: tuple, pad_size: tuple):
|
||||
if len(boxes) > 0:
|
||||
pad_width, pad_height = pad_size
|
||||
boxes_array = np.asarray(boxes)
|
||||
# print(boxes_array)
|
||||
boxes_array[:, 0] = (boxes_array[:, 0] - pad_width) / ratio[0]
|
||||
boxes_array[:, 1] = (boxes_array[:, 1] - pad_height) / ratio[1]
|
||||
boxes_array[:, 2] = (boxes_array[:, 2] - pad_width) / ratio[0]
|
||||
boxes_array[:, 3] = (boxes_array[:, 3] - pad_height) / ratio[1]
|
||||
# print(boxes_array)
|
||||
boxes = boxes_array.tolist()
|
||||
|
||||
return boxes
|
||||
|
||||
|
||||
def letterbox(im, new_shape=(640, 640), color=(0, 0, 0)):
|
||||
# Resize and pad image while meeting stride-multiple constraints
|
||||
shape = im.shape[:2] # current shape [height, width]
|
||||
if isinstance(new_shape, int):
|
||||
new_shape = (new_shape, new_shape)
|
||||
|
||||
# Scale ratio (new / old)
|
||||
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
|
||||
|
||||
# Compute padding
|
||||
ratio = r, r # width, height ratios
|
||||
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
|
||||
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
|
||||
|
||||
dw /= 2 # divide padding into 2 sides
|
||||
dh /= 2
|
||||
|
||||
if shape[::-1] != new_unpad: # resize
|
||||
im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
|
||||
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
|
||||
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
|
||||
im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border
|
||||
return im, ratio, (dw, dh)
|
||||
|
||||
|
||||
def cost(tag=''):
|
||||
try:
|
||||
'''
|
||||
:param tag: 装饰器的参数
|
||||
'''
|
||||
from loguru import logger
|
||||
def wrapper(fn):
|
||||
@wraps(fn)
|
||||
def wrapper_use_time(*args, **kw):
|
||||
'''
|
||||
函数传参方式 fn(arg1, arg2, param1=a, param2=b)
|
||||
:param args: 位置参数(arg1, arg2, ....) 传参方式 fn(arg1, arg2, ...)
|
||||
:param kw: 字典参数{param1=a, param2=b, .....} 传参方式 fn(param1=a, param2=b, ....)
|
||||
:return:
|
||||
'''
|
||||
t1 = time.time()
|
||||
try:
|
||||
res = fn(*args, **kw)
|
||||
except Exception as e:
|
||||
logger.error(f"@use_time %s(%s) execute error" % (fn.__name__, tag))
|
||||
return None
|
||||
else:
|
||||
t2 = time.time()
|
||||
logger.info(f"{tag}@UseTime: {t2 - t1}")
|
||||
return res
|
||||
|
||||
return wrapper_use_time
|
||||
|
||||
return wrapper
|
||||
except Exception as err:
|
||||
print(err)
|
||||
|
||||
|
||||
|
||||
def align_box(imgs, bbox, size=96, scale_factor=1.0, center_bias=0, borderValue=(0, 0, 0)):
|
||||
bias_x = (-1 + 2 * np.random.sample()) * center_bias
|
||||
bias_y = (-1 + 2 * np.random.sample()) * center_bias
|
||||
b_x1, b_y1, b_x2, b_y2 = bbox
|
||||
cx, cy = (b_x1 + b_x2) // 2, (b_y1 + b_y2) // 2
|
||||
w = b_x2 - b_x1
|
||||
h = b_y2 - b_y1
|
||||
cx += w * bias_x
|
||||
cy += h * bias_y
|
||||
|
||||
base_r = max(w, h)
|
||||
j_x = 0
|
||||
j_y = 0
|
||||
j_r = 0
|
||||
base_r += j_r
|
||||
r = int(base_r / 2 * scale_factor)
|
||||
cy -= int(base_r * 0)
|
||||
cx += j_x
|
||||
cy += j_y
|
||||
x1, y1, x2, y2 = cx - r, cy - r, cx + r, cy + r
|
||||
x3, y3 = cx - r, cy + r
|
||||
_x1, _y1, _x2, _y2, _x3, _y3 = [0, 0, size, size, 0, size]
|
||||
src = np.array([x1, y1, x2, y2, x3, y3], dtype=np.float32).reshape(3, 2)
|
||||
sv = np.asarray([[b_x1, b_y1, 1], [b_x2, b_y2, 1]])
|
||||
|
||||
dst = np.array([_x1, _y1, _x2, _y2, _x3, _y3], dtype=np.float32).reshape(3, 2)
|
||||
assert src.dtype == np.float32
|
||||
assert dst.dtype == np.float32
|
||||
assert src.shape == (3, 2)
|
||||
assert dst.shape == (3, 2)
|
||||
mat = cv2.getAffineTransform(src, dst)
|
||||
p = sv.dot(mat.T).reshape(-1)
|
||||
if type(imgs) == list:
|
||||
imgs = [cv2.warpAffine(img, mat, (size, size), borderValue=borderValue) for img in imgs]
|
||||
else:
|
||||
imgs = cv2.warpAffine(imgs, mat, (size, size), borderValue=borderValue)
|
||||
return imgs, p, mat
|
||||
|
||||
|
||||
def get_rotate_crop_image(img, points):
|
||||
'''
|
||||
img_height, img_width = img.shape[0:2]
|
||||
left = int(np.min(points[:, 0]))
|
||||
right = int(np.max(points[:, 0]))
|
||||
top = int(np.min(points[:, 1]))
|
||||
bottom = int(np.max(points[:, 1]))
|
||||
img_crop = img[top:bottom, left:right, :].copy()
|
||||
points[:, 0] = points[:, 0] - left
|
||||
points[:, 1] = points[:, 1] - top
|
||||
'''
|
||||
assert len(points) == 4, "shape of points must be 4*2"
|
||||
img_crop_width = int(
|
||||
max(
|
||||
np.linalg.norm(points[0] - points[1]),
|
||||
np.linalg.norm(points[2] - points[3])))
|
||||
img_crop_height = int(
|
||||
max(
|
||||
np.linalg.norm(points[0] - points[3]),
|
||||
np.linalg.norm(points[1] - points[2])))
|
||||
pts_std = np.float32([[0, 0], [img_crop_width, 0],
|
||||
[img_crop_width, img_crop_height],
|
||||
[0, img_crop_height]])
|
||||
points = points.astype(np.float32)
|
||||
# print(points.shape, pts_std.shape)
|
||||
M = cv2.getPerspectiveTransform(points, pts_std)
|
||||
dst_img = cv2.warpPerspective(
|
||||
img,
|
||||
M, (img_crop_width, img_crop_height),
|
||||
borderMode=cv2.BORDER_REPLICATE,
|
||||
flags=cv2.INTER_CUBIC)
|
||||
dst_img_height, dst_img_width = dst_img.shape[0:2]
|
||||
if dst_img_height * 1.0 / dst_img_width >= 1.5:
|
||||
dst_img = np.rot90(dst_img)
|
||||
|
||||
return dst_img
|
||||
@@ -0,0 +1,79 @@
|
||||
import numpy as np
|
||||
|
||||
PLATE_TYPE_BLUE = 0
|
||||
PLATE_TYPE_GREEN = 1
|
||||
PLATE_TYPE_YELLOW = 2
|
||||
|
||||
INFER_ONNX_RUNTIME = 0
|
||||
INFER_MNN = 1
|
||||
|
||||
DETECT_LEVEL_LOW = 0
|
||||
DETECT_LEVEL_HIGH = 1
|
||||
|
||||
MONO = 0 # 单层车牌
|
||||
DOUBLE = 1 # 双层车牌
|
||||
|
||||
UNKNOWN = -1 # 未知车牌
|
||||
BLUE = 0 # 蓝牌
|
||||
YELLOW_SINGLE = 1 # 黄牌单层
|
||||
WHILE_SINGLE = 2 # 白牌单层
|
||||
GREEN = 3 # 绿牌新能源
|
||||
BLACK_HK_MACAO = 4 # 黑牌港澳
|
||||
HK_SINGLE = 5 # 香港单层
|
||||
HK_DOUBLE = 6 # 香港双层
|
||||
MACAO_SINGLE = 7 # 澳门单层
|
||||
MACAO_DOUBLE = 8 # 澳门双层
|
||||
YELLOW_DOUBLE = 9 # 黄牌双层
|
||||
|
||||
|
||||
def code_filter(code: str) -> int:
|
||||
plate_type = UNKNOWN
|
||||
if code[0] == 'W' and code[1] == 'J':
|
||||
plate_type = WHILE_SINGLE
|
||||
elif len(code) == 8:
|
||||
plate_type = GREEN
|
||||
elif '学' in code:
|
||||
plate_type = BLUE
|
||||
elif '港' in code:
|
||||
plate_type = BLACK_HK_MACAO
|
||||
elif '澳' in code:
|
||||
plate_type = BLACK_HK_MACAO
|
||||
elif '警' in code:
|
||||
plate_type = WHILE_SINGLE
|
||||
elif '粤Z' in code:
|
||||
plate_type = BLACK_HK_MACAO
|
||||
|
||||
return plate_type
|
||||
|
||||
|
||||
class Plate(object):
|
||||
|
||||
def __init__(self,
|
||||
vertex: np.ndarray,
|
||||
plate_code: str,
|
||||
rec_confidence: float,
|
||||
det_bound_box,
|
||||
dex_bound_confidence: float,
|
||||
plate_type: int):
|
||||
assert vertex.shape == (4, 2)
|
||||
self.vertex = vertex
|
||||
self.det_bound_box = det_bound_box
|
||||
self.plate_code = plate_code
|
||||
self.rec_confidence = rec_confidence
|
||||
self.dex_bound_confidence = dex_bound_confidence
|
||||
|
||||
self.left_top, self.right_top, self.right_bottom, self.left_bottom = vertex
|
||||
self.plate_type = plate_type
|
||||
|
||||
def to_dict(self):
|
||||
return dict(plate_code=self.plate_code, rec_confidence=self.rec_confidence,
|
||||
det_bound_box=self.det_bound_box, plate_type=self.plate_type)
|
||||
|
||||
def to_result(self):
|
||||
return [self.plate_code, self.rec_confidence, self.plate_type, self.det_bound_box.tolist(),]
|
||||
|
||||
def __dict__(self):
|
||||
return self.to_dict()
|
||||
|
||||
def __str__(self):
|
||||
return str(self.to_dict())
|
||||
@@ -0,0 +1,36 @@
|
||||
import requests
|
||||
from tqdm import tqdm
|
||||
import zipfile
|
||||
import os
|
||||
from .settings import _DEFAULT_FOLDER_, _MODEL_VERSION_, _ONLINE_URL_
|
||||
|
||||
|
||||
def down_model_zip(url, save_path, is_unzip=False):
|
||||
resp = requests.get(url, stream=True)
|
||||
total = int(resp.headers.get('content-length', 0))
|
||||
name = os.path.join(save_path, os.path.basename(url))
|
||||
with open(name, 'wb') as file, tqdm(
|
||||
desc="pull",
|
||||
total=total,
|
||||
unit='iB',
|
||||
unit_scale=True,
|
||||
unit_divisor=1024,
|
||||
) as bar:
|
||||
for data in resp.iter_content(chunk_size=1024):
|
||||
size = file.write(data)
|
||||
bar.update(size)
|
||||
|
||||
if is_unzip:
|
||||
f = zipfile.ZipFile(name, "r")
|
||||
for file in f.namelist():
|
||||
f.extract(file, save_path)
|
||||
os.remove(name)
|
||||
|
||||
|
||||
def initialization(re_download=False):
|
||||
os.makedirs(_DEFAULT_FOLDER_, exist_ok=True)
|
||||
models_dir = os.path.join(_DEFAULT_FOLDER_, _MODEL_VERSION_)
|
||||
# print(models_dir)
|
||||
if not os.path.exists(models_dir) or re_download:
|
||||
target_url = os.path.join(_ONLINE_URL_, _MODEL_VERSION_) + '.zip'
|
||||
down_model_zip(target_url, _DEFAULT_FOLDER_, True)
|
||||
@@ -0,0 +1,19 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
_MODEL_VERSION_ = "20230228"
|
||||
|
||||
if 'win32' in sys.platform:
|
||||
_DEFAULT_FOLDER_ = os.path.join(os.environ['HOMEPATH'], ".hyperlpr3")
|
||||
else:
|
||||
_DEFAULT_FOLDER_ = os.path.join(os.environ['HOME'], ".hyperlpr3")
|
||||
|
||||
_ONLINE_URL_ = "https://tunm.oss-cn-hangzhou.aliyuncs.com/hyperlpr3/"
|
||||
|
||||
onnx_runtime_config = dict(
|
||||
det_model_path_320x=os.path.join(_MODEL_VERSION_, "onnx", "y5fu_320x_sim.onnx"),
|
||||
det_model_path_640x=os.path.join(_MODEL_VERSION_, "onnx", "y5fu_640x_sim.onnx"),
|
||||
rec_model_path=os.path.join(_MODEL_VERSION_, "onnx", "rpv3_mdict_160_r3.onnx"),
|
||||
cls_model_path=os.path.join(_MODEL_VERSION_, "onnx", "litemodel_cls_96x_r1.onnx"),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
from .config.settings import onnx_runtime_config as ort_cfg
|
||||
from .inference.pipeline import LPRMultiTaskPipeline
|
||||
from .common.typedef import *
|
||||
from os.path import join
|
||||
from .config.settings import _DEFAULT_FOLDER_
|
||||
|
||||
|
||||
class LicensePlateCatcher(object):
|
||||
|
||||
def __init__(self,
|
||||
inference: int = INFER_ONNX_RUNTIME,
|
||||
folder: str = _DEFAULT_FOLDER_,
|
||||
detect_level: int = DETECT_LEVEL_LOW,
|
||||
logger_level: int = 3):
|
||||
if inference == INFER_ONNX_RUNTIME:
|
||||
from hyperlpr3.inference.multitask_detect import MultiTaskDetectorORT
|
||||
from hyperlpr3.inference.recognition import PPRCNNRecognitionORT
|
||||
from hyperlpr3.inference.classification import ClassificationORT
|
||||
import onnxruntime as ort
|
||||
ort.set_default_logger_severity(logger_level)
|
||||
|
||||
if detect_level == DETECT_LEVEL_LOW:
|
||||
# print(join(folder, ort_cfg['det_model_path_320x']))
|
||||
det = MultiTaskDetectorORT(join(folder, ort_cfg['det_model_path_320x']), input_size=(320, 320))
|
||||
elif detect_level == DETECT_LEVEL_HIGH:
|
||||
det = MultiTaskDetectorORT(join(folder, ort_cfg['det_model_path_640x']), input_size=(640, 640))
|
||||
else:
|
||||
raise NotImplemented
|
||||
rec = PPRCNNRecognitionORT(join(folder, ort_cfg['rec_model_path']), input_size=(48, 160))
|
||||
cls = ClassificationORT(join(folder, ort_cfg['cls_model_path']), input_size=(96, 96))
|
||||
self.pipeline = LPRMultiTaskPipeline(detector=det, recognizer=rec, classifier=cls)
|
||||
else:
|
||||
raise NotImplemented
|
||||
|
||||
def __call__(self, image: np.ndarray, *args, **kwargs):
|
||||
return self.pipeline(image)
|
||||
@@ -0,0 +1,27 @@
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
|
||||
class HamburgerABC(metaclass=ABCMeta):
|
||||
|
||||
def __init__(self, input_size: tuple = None, *args,
|
||||
**kwargs):
|
||||
self.input_size = input_size
|
||||
|
||||
@abstractmethod
|
||||
def _run_session(self, data):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _postprocess(self, data):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _preprocess(self, image):
|
||||
pass
|
||||
|
||||
def __call__(self, image):
|
||||
flow = self._preprocess(image)
|
||||
flow = self._run_session(flow)
|
||||
result = self._postprocess(flow)
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,49 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
from .base.base import HamburgerABC
|
||||
from hyperlpr3.common.tools_process import cost
|
||||
|
||||
|
||||
def encode_images(image: np.ndarray):
|
||||
image_encode = image / 255.0
|
||||
if len(image_encode.shape) == 4:
|
||||
image_encode = image_encode.transpose(0, 3, 1, 2)
|
||||
else:
|
||||
image_encode = image_encode.transpose(2, 0, 1)
|
||||
image_encode = image_encode.astype(np.float32)
|
||||
|
||||
return image_encode
|
||||
|
||||
|
||||
class ClassificationORT(HamburgerABC):
|
||||
|
||||
def __init__(self, onnx_path, *args, **kwargs):
|
||||
import onnxruntime as ort
|
||||
super().__init__(*args, **kwargs)
|
||||
self.session = ort.InferenceSession(onnx_path, None)
|
||||
self.input_config = self.session.get_inputs()[0]
|
||||
self.output_config = self.session.get_outputs()[0]
|
||||
self.input_size = tuple(self.input_config.shape[2:])
|
||||
|
||||
# @cost('Cls')
|
||||
def _run_session(self, data) -> np.ndarray:
|
||||
result = self.session.run([self.output_config.name], {self.input_config.name: data})
|
||||
|
||||
return result[0]
|
||||
|
||||
def _postprocess(self, data) -> np.ndarray:
|
||||
return data
|
||||
|
||||
def _preprocess(self, image) -> np.ndarray:
|
||||
assert len(
|
||||
image.shape) == 3, "The dimensions of the input image object do not match. The input supports a single " \
|
||||
"image. "
|
||||
# print(self.input_size)
|
||||
image_resize = cv2.resize(image, self.input_size)
|
||||
encode = encode_images(image_resize)
|
||||
encode = encode.astype(np.float32)
|
||||
input_tensor = np.expand_dims(encode, 0)
|
||||
|
||||
return input_tensor
|
||||
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
from hyperlpr3.common.tools_process import *
|
||||
from .base.base import HamburgerABC
|
||||
|
||||
ANCHORS_MAP = {
|
||||
320: [[9.38281, 3.08398], [15.53125, 4.93750], [19.98438, 7.78906],
|
||||
[31.10938, 10.35156], [45.21875, 14.14844], [32.34375, 21.04688],
|
||||
[65.62500, 19.57812], [76.12500, 46.12500], [253.25000, 137.50000]],
|
||||
640: [[10, 13], [16, 30], [33, 23], [30, 61], [62, 45],
|
||||
[59, 119], [116, 90], [156, 198], [373, 326]]
|
||||
}
|
||||
|
||||
|
||||
def image_to_input_tensor(image):
|
||||
data = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||
data = data.transpose(2, 0, 1) / 255.0
|
||||
data = np.expand_dims(data, 0)
|
||||
data = data.astype(np.float32)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class Y5rkDetectorMNN(HamburgerABC):
|
||||
def __init__(self, mnn_path, box_threshold: float = 0.5, nms_threshold: float = 0.6, *args, **kwargs):
|
||||
from .common.mnn_adapt import MNNAdapter
|
||||
super().__init__(*args, **kwargs)
|
||||
self.box_threshold = box_threshold
|
||||
self.nms_threshold = nms_threshold
|
||||
self.input_shape = (1, 3, self.input_size[0], self.input_size[1])
|
||||
self.tensor_shape = ((1, 18, 40, 40), (1, 18, 20, 20), (1, 18, 10, 10))
|
||||
self.session = MNNAdapter(mnn_path, self.input_shape, outputs_name=['output', '335', '336'],
|
||||
outputs_shape=self.tensor_shape)
|
||||
assert self.input_size[0] == self.input_size[1]
|
||||
self.anchors = ANCHORS_MAP[self.input_size[0]]
|
||||
|
||||
def _run_session(self, data):
|
||||
outputs = self.session.inference(data)
|
||||
result = list()
|
||||
for idx, output in enumerate(outputs):
|
||||
result.append(output.reshape(self.tensor_shape[idx]))
|
||||
|
||||
return result
|
||||
|
||||
def _postprocess(self, data):
|
||||
ratio, (dw, dh) = self.temp_pack
|
||||
input0_data = data[0]
|
||||
input1_data = data[1]
|
||||
input2_data = data[2]
|
||||
input0_data = input0_data.reshape([3, -1] + list(input0_data.shape[-2:]))
|
||||
input1_data = input1_data.reshape([3, -1] + list(input1_data.shape[-2:]))
|
||||
input2_data = input2_data.reshape([3, -1] + list(input2_data.shape[-2:]))
|
||||
input_data = list()
|
||||
input_data.append(np.transpose(input0_data, (2, 3, 0, 1)))
|
||||
input_data.append(np.transpose(input1_data, (2, 3, 0, 1)))
|
||||
input_data.append(np.transpose(input2_data, (2, 3, 0, 1)))
|
||||
|
||||
boxes, classes, scores = self.decode_outputs(input_data, self.input_size)
|
||||
|
||||
boxes = restore_bound_box(boxes, ratio, (dw, dh))
|
||||
|
||||
return boxes, classes, scores
|
||||
|
||||
def _preprocess(self, image):
|
||||
h, w, _ = image.shape
|
||||
resize_img, ratio, (dw, dh) = letterbox(image, new_shape=(self.input_size[1], self.input_size[0]))
|
||||
data = image_to_input_tensor(resize_img)
|
||||
self.temp_pack = ratio, (dw, dh)
|
||||
|
||||
return data
|
||||
|
||||
def decode_outputs(self, input_data, size):
|
||||
masks = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
|
||||
anchors = self.anchors
|
||||
|
||||
boxes, classes, scores = [], [], []
|
||||
for input, mask in zip(input_data, masks):
|
||||
b, c, s = process(input, mask, anchors, size)
|
||||
b, c, s = filter_boxes(b, c, s, self.box_threshold, self.nms_threshold)
|
||||
boxes.append(b)
|
||||
classes.append(c)
|
||||
scores.append(s)
|
||||
|
||||
boxes = np.concatenate(boxes)
|
||||
boxes = xywh2xyxy(boxes)
|
||||
classes = np.concatenate(classes)
|
||||
scores = np.concatenate(scores)
|
||||
|
||||
nboxes, nclasses, nscores = [], [], []
|
||||
for c in set(classes):
|
||||
inds = np.where(classes == c)
|
||||
b = boxes[inds]
|
||||
c = classes[inds]
|
||||
s = scores[inds]
|
||||
|
||||
keep = nms_boxes(b, s, self.nms_threshold)
|
||||
|
||||
nboxes.append(b[keep])
|
||||
nclasses.append(c[keep])
|
||||
nscores.append(s[keep])
|
||||
|
||||
if not nclasses and not nscores:
|
||||
return None, None, None
|
||||
|
||||
boxes = np.concatenate(nboxes)
|
||||
classes = np.concatenate(nclasses)
|
||||
scores = np.concatenate(nscores)
|
||||
|
||||
return boxes, classes, scores
|
||||
|
||||
|
||||
class Y5rkDetectorORT(HamburgerABC):
|
||||
|
||||
def __init__(self, onnx_path, box_threshold: float = 0.5, nms_threshold: float = 0.6, *args, **kwargs):
|
||||
import onnxruntime as ort
|
||||
super().__init__(*args, **kwargs)
|
||||
self.box_threshold = box_threshold
|
||||
self.nms_threshold = nms_threshold
|
||||
self.session = ort.InferenceSession(onnx_path, None)
|
||||
self.inputs_option = self.session.get_inputs()
|
||||
self.outputs_option = self.session.get_outputs()
|
||||
input_option = self.inputs_option[0]
|
||||
input_size_ = tuple(input_option.shape[2:])
|
||||
self.input_size = tuple(self.input_size)
|
||||
if not self.input_size:
|
||||
self.input_size = input_size_
|
||||
assert self.input_size == input_size_, 'The dimensions of the input do not match the model expectations.'
|
||||
assert self.input_size[0] == self.input_size[1]
|
||||
self.input_name = input_option.name
|
||||
self.anchors = ANCHORS_MAP[self.input_size[0]]
|
||||
|
||||
def decode_outputs(self, input_data, size):
|
||||
masks = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
|
||||
anchors = self.anchors
|
||||
|
||||
boxes, classes, scores = [], [], []
|
||||
for input, mask in zip(input_data, masks):
|
||||
b, c, s = process(input, mask, anchors, size)
|
||||
b, c, s = filter_boxes(b, c, s, self.box_threshold, self.nms_threshold)
|
||||
boxes.append(b)
|
||||
classes.append(c)
|
||||
scores.append(s)
|
||||
|
||||
boxes = np.concatenate(boxes)
|
||||
boxes = xywh2xyxy(boxes)
|
||||
classes = np.concatenate(classes)
|
||||
scores = np.concatenate(scores)
|
||||
|
||||
nboxes, nclasses, nscores = [], [], []
|
||||
for c in set(classes):
|
||||
inds = np.where(classes == c)
|
||||
b = boxes[inds]
|
||||
c = classes[inds]
|
||||
s = scores[inds]
|
||||
|
||||
keep = nms_boxes(b, s, self.nms_threshold)
|
||||
|
||||
nboxes.append(b[keep])
|
||||
nclasses.append(c[keep])
|
||||
nscores.append(s[keep])
|
||||
|
||||
if not nclasses and not nscores:
|
||||
return None, None, None
|
||||
|
||||
boxes = np.concatenate(nboxes)
|
||||
classes = np.concatenate(nclasses)
|
||||
scores = np.concatenate(nscores)
|
||||
|
||||
return boxes, classes, scores
|
||||
|
||||
@cost("Detect")
|
||||
def _run_session(self, data):
|
||||
outputs = self.session.run([], {"images": data})
|
||||
|
||||
return outputs
|
||||
|
||||
def _postprocess(self, data):
|
||||
ratio, (dw, dh) = self.temp_pack
|
||||
input0_data = data[0]
|
||||
input1_data = data[1]
|
||||
input2_data = data[2]
|
||||
input0_data = input0_data.reshape([3, -1] + list(input0_data.shape[-2:]))
|
||||
input1_data = input1_data.reshape([3, -1] + list(input1_data.shape[-2:]))
|
||||
input2_data = input2_data.reshape([3, -1] + list(input2_data.shape[-2:]))
|
||||
input_data = list()
|
||||
input_data.append(np.transpose(input0_data, (2, 3, 0, 1)))
|
||||
input_data.append(np.transpose(input1_data, (2, 3, 0, 1)))
|
||||
input_data.append(np.transpose(input2_data, (2, 3, 0, 1)))
|
||||
|
||||
boxes, classes, scores = self.decode_outputs(input_data, self.input_size)
|
||||
|
||||
boxes = restore_bound_box(boxes, ratio, (dw, dh))
|
||||
|
||||
return boxes, classes, scores
|
||||
|
||||
def _preprocess(self, image):
|
||||
h, w, _ = image.shape
|
||||
resize_img, ratio, (dw, dh) = letterbox(image, new_shape=(self.input_size[1], self.input_size[0]))
|
||||
data = image_to_input_tensor(resize_img)
|
||||
self.temp_pack = ratio, (dw, dh)
|
||||
|
||||
return data
|
||||
@@ -0,0 +1,184 @@
|
||||
import numpy as np
|
||||
import cv2
|
||||
import copy
|
||||
from .base.base import HamburgerABC
|
||||
|
||||
|
||||
def xywh2xyxy(boxes):
|
||||
xywh = copy.deepcopy(boxes)
|
||||
xywh[:, 0] = boxes[:, 0] - boxes[:, 2] / 2
|
||||
xywh[:, 1] = boxes[:, 1] - boxes[:, 3] / 2
|
||||
xywh[:, 2] = boxes[:, 0] + boxes[:, 2] / 2
|
||||
xywh[:, 3] = boxes[:, 1] + boxes[:, 3] / 2
|
||||
return xywh
|
||||
|
||||
|
||||
def nms(boxes, iou_thresh): # nms
|
||||
index = np.argsort(boxes[:, 4])[::-1]
|
||||
keep = []
|
||||
while index.size > 0:
|
||||
i = index[0]
|
||||
keep.append(i)
|
||||
x1 = np.maximum(boxes[i, 0], boxes[index[1:], 0])
|
||||
y1 = np.maximum(boxes[i, 1], boxes[index[1:], 1])
|
||||
x2 = np.minimum(boxes[i, 2], boxes[index[1:], 2])
|
||||
y2 = np.minimum(boxes[i, 3], boxes[index[1:], 3])
|
||||
|
||||
w = np.maximum(0, x2 - x1)
|
||||
h = np.maximum(0, y2 - y1)
|
||||
|
||||
inter_area = w * h
|
||||
union_area = (boxes[i, 2] - boxes[i, 0]) * (boxes[i, 3] - boxes[i, 1]) + (
|
||||
boxes[index[1:], 2] - boxes[index[1:], 0]) * (boxes[index[1:], 3] - boxes[index[1:], 1])
|
||||
iou = inter_area / (union_area - inter_area)
|
||||
idx = np.where(iou <= iou_thresh)[0]
|
||||
index = index[idx + 1]
|
||||
return keep
|
||||
|
||||
|
||||
def restore_box(boxes, r, left, top):
|
||||
boxes[:, [0, 2, 5, 7, 9, 11]] -= left
|
||||
boxes[:, [1, 3, 6, 8, 10, 12]] -= top
|
||||
|
||||
boxes[:, [0, 2, 5, 7, 9, 11]] /= r
|
||||
boxes[:, [1, 3, 6, 8, 10, 12]] /= r
|
||||
return boxes
|
||||
|
||||
|
||||
def detect_pre_precessing(img, img_size):
|
||||
img, r, left, top = letter_box(img, img_size)
|
||||
img = img[:, :, ::-1].transpose(2, 0, 1).copy().astype(np.float32)
|
||||
img = img / 255
|
||||
img = img.reshape(1, *img.shape)
|
||||
return img, r, left, top
|
||||
|
||||
|
||||
def post_precessing(dets, r, left, top, conf_thresh=0.25, iou_thresh=0.5):
|
||||
choice = dets[:, :, 4] > conf_thresh
|
||||
dets = dets[choice]
|
||||
dets[:, 13:15] *= dets[:, 4:5]
|
||||
box = dets[:, :4]
|
||||
boxes = xywh2xyxy(box)
|
||||
score = np.max(dets[:, 13:15], axis=-1, keepdims=True)
|
||||
index = np.argmax(dets[:, 13:15], axis=-1).reshape(-1, 1)
|
||||
output = np.concatenate((boxes, score, dets[:, 5:13], index), axis=1)
|
||||
reserve_ = nms(output, iou_thresh)
|
||||
output = output[reserve_]
|
||||
output = restore_box(output, r, left, top)
|
||||
return output
|
||||
|
||||
|
||||
def letter_box(img, size=(640, 640)):
|
||||
h, w, c = img.shape
|
||||
r = min(size[0] / h, size[1] / w)
|
||||
new_h, new_w = int(h * r), int(w * r)
|
||||
top = int((size[0] - new_h) / 2)
|
||||
left = int((size[1] - new_w) / 2)
|
||||
|
||||
bottom = size[0] - new_h - top
|
||||
right = size[1] - new_w - left
|
||||
img_resize = cv2.resize(img, (new_w, new_h))
|
||||
img = cv2.copyMakeBorder(img_resize, top, bottom, left, right, borderType=cv2.BORDER_CONSTANT,
|
||||
value=(0, 0, 0))
|
||||
return img, r, left, top
|
||||
|
||||
|
||||
class MultiTaskDetectorMNN(HamburgerABC):
|
||||
|
||||
def __init__(self, mnn_path, box_threshold: float = 0.5, nms_threshold: float = 0.6, *args, **kwargs):
|
||||
from hyperlpr3.common.mnn_adapt import MNNAdapter
|
||||
super().__init__(*args, **kwargs)
|
||||
assert self.input_size[0] == self.input_size[1]
|
||||
self.box_threshold = box_threshold
|
||||
self.nms_threshold = nms_threshold
|
||||
self.input_shape = (1, 3, self.input_size[0], self.input_size[1])
|
||||
if self.input_size[0] == 320:
|
||||
self.tensor_shape = [(1, 6300, 15)]
|
||||
elif self.input_size[0] == 640:
|
||||
self.tensor_shape = [(1, 25200, 15)]
|
||||
self.session = MNNAdapter(mnn_path, self.input_shape, outputs_name=['output', ],
|
||||
outputs_shape=self.tensor_shape)
|
||||
|
||||
def _run_session(self, data):
|
||||
outputs = self.session.inference(data)
|
||||
result = list()
|
||||
for idx, output in enumerate(outputs):
|
||||
result.append(output.reshape(self.tensor_shape[idx]))
|
||||
result = np.asarray(result)
|
||||
|
||||
return result[0]
|
||||
|
||||
def _postprocess(self, data):
|
||||
r, left, top = self.tmp_pack
|
||||
|
||||
return post_precessing(data, r, left, top)
|
||||
|
||||
def _preprocess(self, image):
|
||||
img, r, left, top = detect_pre_precessing(image, self.input_size)
|
||||
self.tmp_pack = r, left, top
|
||||
|
||||
return img
|
||||
|
||||
|
||||
class MultiTaskDetectorDNN(HamburgerABC):
|
||||
|
||||
def __init__(self, onnx_path, box_threshold: float = 0.5, nms_threshold: float = 0.6, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.box_threshold = box_threshold
|
||||
self.nms_threshold = nms_threshold
|
||||
self.session = cv2.dnn.readNetFromONNX(onnx_path)
|
||||
self.input_shape = (1, 3, self.input_size[0], self.input_size[1])
|
||||
self.tensor_shape = [(1, 6300, 15)]
|
||||
|
||||
def _run_session(self, data):
|
||||
self.session.setInput(data)
|
||||
outputs = self.session.forward()
|
||||
|
||||
return outputs
|
||||
|
||||
def _postprocess(self, data):
|
||||
|
||||
r, left, top = self.tmp_pack
|
||||
|
||||
return post_precessing(data, r, left, top)
|
||||
|
||||
def _preprocess(self, image):
|
||||
img, r, left, top = detect_pre_precessing(image, self.input_size)
|
||||
self.tmp_pack = r, left, top
|
||||
|
||||
return img
|
||||
|
||||
|
||||
class MultiTaskDetectorORT(HamburgerABC):
|
||||
|
||||
def __init__(self, onnx_path, box_threshold: float = 0.5, nms_threshold: float = 0.6, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
import onnxruntime as ort
|
||||
self.box_threshold = box_threshold
|
||||
self.nms_threshold = nms_threshold
|
||||
self.session = ort.InferenceSession(onnx_path, providers=['CPUExecutionProvider'])
|
||||
self.inputs_option = self.session.get_inputs()
|
||||
self.outputs_option = self.session.get_outputs()
|
||||
input_option = self.inputs_option[0]
|
||||
input_size_ = tuple(input_option.shape[2:])
|
||||
self.input_size = tuple(self.input_size)
|
||||
if not self.input_size:
|
||||
self.input_size = input_size_
|
||||
assert self.input_size == input_size_, 'The dimensions of the input do not match the model expectations.'
|
||||
assert self.input_size[0] == self.input_size[1]
|
||||
self.input_name = input_option.name
|
||||
|
||||
def _run_session(self, data):
|
||||
result = self.session.run([self.outputs_option[0].name], {self.input_name: data})[0]
|
||||
|
||||
return result
|
||||
|
||||
def _postprocess(self, data):
|
||||
r, left, top = self.tmp_pack
|
||||
return post_precessing(data, r, left, top)
|
||||
|
||||
def _preprocess(self, image):
|
||||
img, r, left, top = detect_pre_precessing(image, self.input_size)
|
||||
self.tmp_pack = r, left, top
|
||||
|
||||
return img
|
||||
@@ -0,0 +1,117 @@
|
||||
import numpy as np
|
||||
|
||||
from hyperlpr3.common.typedef import *
|
||||
from hyperlpr3.common.tools_process import *
|
||||
|
||||
|
||||
class LPRMultiTaskPipeline(object):
|
||||
|
||||
def __init__(self, detector, recognizer, classifier):
|
||||
self.detector = detector
|
||||
self.recognizer = recognizer
|
||||
self.classifier = classifier
|
||||
|
||||
def run(self, image: np.ndarray) -> list:
|
||||
result = list()
|
||||
assert len(image.shape) == 3, "Input image must be 3 channels."
|
||||
assert image is not None, "Input image cannot be empty."
|
||||
outputs = self.detector(image)
|
||||
for out in outputs:
|
||||
rect = out[:4].astype(int)
|
||||
score = out[4]
|
||||
land_marks = out[5:13].reshape(4, 2).astype(int)
|
||||
layer_num = int(out[13])
|
||||
print(layer_num)
|
||||
pad = get_rotate_crop_image(image, land_marks)
|
||||
if layer_num == DOUBLE:
|
||||
# double
|
||||
h, w, _ = pad.shape
|
||||
line = int(h * 0.4)
|
||||
top = pad[:line, :, ]
|
||||
bottom = pad[line:, :]
|
||||
top_code, top_confidence = self.recognizer(top)
|
||||
bottom_code, bottom_confidence = self.recognizer(bottom)
|
||||
plate_code = top_code + bottom_code
|
||||
rec_confidence = (top_confidence + bottom_confidence) / 2
|
||||
# cv2.imshow("top", top)
|
||||
# cv2.imshow("bottom", bottom)
|
||||
# cv2.waitKey(0)
|
||||
else:
|
||||
plate_code, rec_confidence = self.recognizer(pad)
|
||||
if plate_code == '':
|
||||
continue
|
||||
if len(plate_code) >= 7:
|
||||
plate_type = code_filter(plate_code)
|
||||
if plate_type == UNKNOWN:
|
||||
cls = self.classifier(pad)
|
||||
idx = int(np.argmax(cls))
|
||||
if idx == PLATE_TYPE_YELLOW:
|
||||
if layer_num == DOUBLE:
|
||||
plate_type = YELLOW_DOUBLE
|
||||
else:
|
||||
plate_type = YELLOW_SINGLE
|
||||
elif idx == PLATE_TYPE_BLUE:
|
||||
plate_type = BLUE
|
||||
elif idx == PLATE_TYPE_GREEN:
|
||||
plate_type = GREEN
|
||||
plate = Plate(vertex=land_marks, plate_code=plate_code, det_bound_box=np.asarray(rect),
|
||||
rec_confidence=rec_confidence, dex_bound_confidence=score, plate_type=plate_type)
|
||||
result.append(plate.to_result())
|
||||
|
||||
return result
|
||||
|
||||
def __call__(self, image: np.ndarray, *args, **kwargs):
|
||||
return self.run(image)
|
||||
|
||||
|
||||
class LPRPipeline(object):
|
||||
|
||||
def __init__(self, detector, vertex_predictor, recognizer, ):
|
||||
self.detector = detector
|
||||
self.vertex_predictor = vertex_predictor
|
||||
self.recognizer = recognizer
|
||||
|
||||
# @cost("PipelineTotalCost")
|
||||
def run(self, image: np.ndarray) -> list:
|
||||
result = list()
|
||||
boxes, classes, scores = self.detector(image)
|
||||
fp_boxes_index = find_the_adjacent_boxes(boxes)
|
||||
image_blacks = list()
|
||||
if len(fp_boxes_index) > 0:
|
||||
for idx in fp_boxes_index:
|
||||
image_black = np.zeros_like(image)
|
||||
box = boxes[idx]
|
||||
x1, y1, x2, y2 = np.asarray(box).astype(int)
|
||||
image_black[y1:y2, x1:x2] = image[y1:y2, x1:x2]
|
||||
image_blacks.append(image_black)
|
||||
if boxes:
|
||||
fp = 0
|
||||
for idx, box in enumerate(boxes):
|
||||
det_confidence = scores[idx]
|
||||
if idx in fp_boxes_index:
|
||||
warped, p, mat = align_box(image_blacks[fp], box, scale_factor=1.2, size=96)
|
||||
fp += 1
|
||||
else:
|
||||
warped, p, mat = align_box(image, box, scale_factor=1.2, size=96)
|
||||
kps = self.vertex_predictor(warped)
|
||||
polyline = list()
|
||||
for point in kps:
|
||||
polyline.append([point[0], point[1], 1])
|
||||
polyline = np.asarray(polyline)
|
||||
inv = cv2.invertAffineTransform(mat)
|
||||
trans_points = np.dot(inv, polyline.T).T
|
||||
pad = get_rotate_crop_image(image, trans_points)
|
||||
# print(pad.shape)
|
||||
# cv2.imshow("pad", pad)
|
||||
# cv2.waitKey(0)
|
||||
plate_code, rec_confidence = self.recognizer(pad)
|
||||
if plate_code == '':
|
||||
continue
|
||||
plate = Plate(vertex=trans_points, plate_code=plate_code, det_bound_box=np.asarray(box),
|
||||
rec_confidence=rec_confidence, dex_bound_confidence=det_confidence)
|
||||
result.append(plate.to_dict())
|
||||
|
||||
return result
|
||||
|
||||
def __call__(self, image: np.ndarray, *args, **kwargs):
|
||||
return self.run(image)
|
||||
@@ -0,0 +1,233 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
from .base.base import HamburgerABC
|
||||
from hyperlpr3.common.tools_process import cost
|
||||
import math
|
||||
from hyperlpr3.common.tokenize import token
|
||||
|
||||
|
||||
def encode_images(image: np.ndarray, max_wh_ratio, target_shape, limited_max_width=160, limited_min_width=48):
|
||||
imgC = 3
|
||||
imgH, imgW = target_shape
|
||||
# cv2.imshow("image", image)
|
||||
# cv2.waitKey(0)
|
||||
assert imgC == image.shape[2]
|
||||
max_wh_ratio = max(max_wh_ratio, imgW / imgH)
|
||||
imgW = int((imgH * max_wh_ratio))
|
||||
imgW = max(min(imgW, limited_max_width), limited_min_width)
|
||||
h, w = image.shape[:2]
|
||||
ratio = w / float(h)
|
||||
ratio_imgH = math.ceil(imgH * ratio)
|
||||
ratio_imgH = max(ratio_imgH, limited_min_width)
|
||||
if ratio_imgH > imgW:
|
||||
resized_w = imgW
|
||||
else:
|
||||
resized_w = int(ratio_imgH)
|
||||
resized_image = cv2.resize(image, (resized_w, imgH))
|
||||
# print((resized_w, imgH))
|
||||
# padding_im1 = np.ones((imgH, imgW, imgC), dtype=np.uint8) * 128
|
||||
# padding_im1[:, 0:resized_w, :] = resized_image
|
||||
# cv2.imwrite("pad.jpg", padding_im1)
|
||||
|
||||
resized_image = resized_image.astype('float32')
|
||||
resized_image = (resized_image.transpose((2, 0, 1)) - 127.5) / 127.5
|
||||
# resized_image -= 0.5
|
||||
# resized_image *= 2
|
||||
padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32)
|
||||
padding_im[:, :, 0:resized_w] = resized_image
|
||||
|
||||
# np.save('fk.npy', padding_im)
|
||||
|
||||
return padding_im
|
||||
|
||||
|
||||
def get_ignored_tokens():
|
||||
return [0] # for ctc blank
|
||||
|
||||
|
||||
class PPRCNNRecognitionMNN(HamburgerABC):
|
||||
|
||||
def __init__(self, mnn_path, character_file, *args, **kwargs):
|
||||
from hyperlpr3.common.mnn_adapt import MNNAdapter
|
||||
super().__init__(*args, **kwargs)
|
||||
self.input_shape = (1, 3, self.input_size[0], self.input_size[1])
|
||||
self.session = MNNAdapter(mnn_path, input_shape=self.input_shape, outputs_name=['output'])
|
||||
self.character_list = token
|
||||
|
||||
def decode(self, text_index, text_prob=None, is_remove_duplicate=False):
|
||||
""" convert text-index into text-label. """
|
||||
result_list = []
|
||||
ignored_tokens = get_ignored_tokens()
|
||||
batch_size = len(text_index)
|
||||
for batch_idx in range(batch_size):
|
||||
char_list = []
|
||||
conf_list = []
|
||||
for idx in range(len(text_index[batch_idx])):
|
||||
if text_index[batch_idx][idx] in ignored_tokens:
|
||||
continue
|
||||
if is_remove_duplicate:
|
||||
# only for predict
|
||||
if idx > 0 and text_index[batch_idx][idx - 1] == text_index[batch_idx][idx]:
|
||||
continue
|
||||
char_list.append(self.character_list[int(text_index[batch_idx][idx])])
|
||||
if text_prob is not None:
|
||||
conf_list.append(text_prob[batch_idx][idx])
|
||||
else:
|
||||
conf_list.append(1)
|
||||
text = ''.join(char_list)
|
||||
result_list.append((text, np.mean(conf_list)))
|
||||
return result_list
|
||||
|
||||
def _run_session(self, data):
|
||||
output = self.session.inference(data)
|
||||
output = output.reshape(40, 6625)
|
||||
# print(output[:, 0])
|
||||
output = np.expand_dims([output], 0)
|
||||
return output
|
||||
|
||||
def _postprocess(self, data):
|
||||
prod = data[0]
|
||||
argmax = np.argmax(prod, axis=2)
|
||||
# print(argmax)
|
||||
rmax = np.max(prod, axis=2)
|
||||
# print(rmax)
|
||||
result = self.decode(argmax, rmax, is_remove_duplicate=True)
|
||||
|
||||
return result[0]
|
||||
|
||||
def _preprocess(self, image):
|
||||
assert len(
|
||||
image.shape) == 3, "The dimensions of the input image object do not match. The input supports a single " \
|
||||
"image. "
|
||||
h, w, _ = image.shape
|
||||
wh_ratio = w * 1.0 / h
|
||||
data = encode_images(image, wh_ratio, self.input_size, )
|
||||
data = np.expand_dims(data, 0)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class PPRCNNRecognitionORT(HamburgerABC):
|
||||
|
||||
def __init__(self, onnx_path, token_dict=token, *args, **kwargs):
|
||||
import onnxruntime as ort
|
||||
super().__init__(*args, **kwargs)
|
||||
self.session = ort.InferenceSession(onnx_path, None)
|
||||
self.input_config = self.session.get_inputs()[0]
|
||||
self.output_config = self.session.get_outputs()[0]
|
||||
self.input_size = self.input_config.shape[2:]
|
||||
# print(self.input_size)
|
||||
self.character_list = token_dict
|
||||
|
||||
def decode(self, text_index, text_prob=None, is_remove_duplicate=False):
|
||||
""" convert text-index into text-label. """
|
||||
result_list = []
|
||||
ignored_tokens = get_ignored_tokens()
|
||||
batch_size = len(text_index)
|
||||
for batch_idx in range(batch_size):
|
||||
char_list = []
|
||||
conf_list = []
|
||||
for idx in range(len(text_index[batch_idx])):
|
||||
if text_index[batch_idx][idx] in ignored_tokens:
|
||||
continue
|
||||
if is_remove_duplicate:
|
||||
# only for predict
|
||||
if idx > 0 and text_index[batch_idx][idx - 1] == text_index[batch_idx][idx]:
|
||||
continue
|
||||
# print(int(text_index[batch_idx][idx]))
|
||||
char_list.append(self.character_list[int(text_index[batch_idx][idx])])
|
||||
if text_prob is not None:
|
||||
conf_list.append(text_prob[batch_idx][idx])
|
||||
else:
|
||||
conf_list.append(1)
|
||||
text = ''.join(char_list)
|
||||
result_list.append((text, np.mean(conf_list)))
|
||||
return result_list
|
||||
|
||||
# @cost("Recognition")
|
||||
def _run_session(self, data) -> np.ndarray:
|
||||
result = self.session.run([self.output_config.name], {self.input_config.name: data})
|
||||
|
||||
return result
|
||||
|
||||
def _postprocess(self, data) -> tuple:
|
||||
if data:
|
||||
prod = data[0]
|
||||
argmax = np.argmax(prod, axis=2)
|
||||
rmax = np.max(prod, axis=2)
|
||||
result = self.decode(argmax, rmax, is_remove_duplicate=True)
|
||||
|
||||
return result[0]
|
||||
else:
|
||||
return '', 0.0
|
||||
|
||||
def _preprocess(self, image) -> np.ndarray:
|
||||
assert len(
|
||||
image.shape) == 3, "The dimensions of the input image object do not match. The input supports a single " \
|
||||
"image. "
|
||||
h, w, _ = image.shape
|
||||
wh_ratio = w * 1.0 / h
|
||||
data = encode_images(image, wh_ratio, self.input_size, )
|
||||
data = np.expand_dims(data, 0)
|
||||
# print(data.shape)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class PPRCNNRecognitionDNN(HamburgerABC):
|
||||
|
||||
def __init__(self, onnx_path, character_file, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.session = cv2.dnn.readNetFromONNX(onnx_path)
|
||||
self.input_shape = (1, 3, self.input_size[0], self.input_size[1])
|
||||
self.character_list = token
|
||||
|
||||
def decode(self, text_index, text_prob=None, is_remove_duplicate=False):
|
||||
result_list = []
|
||||
ignored_tokens = get_ignored_tokens()
|
||||
batch_size = len(text_index)
|
||||
for batch_idx in range(batch_size):
|
||||
char_list = []
|
||||
conf_list = []
|
||||
for idx in range(len(text_index[batch_idx])):
|
||||
if text_index[batch_idx][idx] in ignored_tokens:
|
||||
continue
|
||||
if is_remove_duplicate:
|
||||
# only for predict
|
||||
if idx > 0 and text_index[batch_idx][idx - 1] == text_index[batch_idx][idx]:
|
||||
continue
|
||||
char_list.append(self.character_list[int(text_index[batch_idx][idx])])
|
||||
if text_prob is not None:
|
||||
conf_list.append(text_prob[batch_idx][idx])
|
||||
else:
|
||||
conf_list.append(1)
|
||||
text = ''.join(char_list)
|
||||
result_list.append((text, np.mean(conf_list)))
|
||||
return result_list
|
||||
|
||||
def _run_session(self, data):
|
||||
self.session.setInput(data)
|
||||
outputs = self.session.forward()
|
||||
outputs = np.expand_dims(outputs, 0)
|
||||
# print(outputs.shape)
|
||||
|
||||
return outputs
|
||||
|
||||
def _postprocess(self, data):
|
||||
prod = data[0]
|
||||
argmax = np.argmax(prod, axis=2)
|
||||
rmax = np.max(prod, axis=2)
|
||||
result = self.decode(argmax, rmax, is_remove_duplicate=True)
|
||||
|
||||
return result[0]
|
||||
|
||||
def _preprocess(self, image):
|
||||
assert len(
|
||||
image.shape) == 3, "The dimensions of the input image object do not match. The input supports a single " \
|
||||
"image. "
|
||||
h, w, _ = image.shape
|
||||
wh_ratio = w * 1.0 / h
|
||||
data = encode_images(image, wh_ratio, self.input_size, )
|
||||
data = np.expand_dims(data, 0)
|
||||
|
||||
return data
|
||||
@@ -0,0 +1,83 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
from .base.base import HamburgerABC
|
||||
from hyperlpr3.common.tools_process import cost
|
||||
|
||||
|
||||
def encode_images(image: np.ndarray):
|
||||
image_encode = image / 255.0
|
||||
if len(image_encode.shape) == 4:
|
||||
image_encode = image_encode.transpose(0, 3, 1, 2)
|
||||
else:
|
||||
image_encode = image_encode.transpose(2, 0, 1)
|
||||
image_encode = image_encode.astype(np.float32)
|
||||
|
||||
return image_encode
|
||||
|
||||
|
||||
class BVTVertexMNN(HamburgerABC):
|
||||
|
||||
def __init__(self, mnn_path, *args, **kwargs):
|
||||
from .common.mnn_adapt import MNNAdapter
|
||||
super().__init__(*args, **kwargs)
|
||||
self.input_shape = (1, 3, self.input_size[0], self.input_size[1])
|
||||
self.session = MNNAdapter(mnn_path, self.input_shape)
|
||||
|
||||
def _run_session(self, data):
|
||||
outputs = self.session.inference(data)
|
||||
return outputs
|
||||
|
||||
def _postprocess(self, data):
|
||||
assert data.shape[0] == 1
|
||||
data = np.asarray(data).reshape(-1, 4, 2)
|
||||
data[:, :, 0] *= self.input_size[1]
|
||||
data[:, :, 1] *= self.input_size[0]
|
||||
|
||||
return data[0]
|
||||
|
||||
def _preprocess(self, image):
|
||||
assert len(
|
||||
image.shape) == 3, "The dimensions of the input image object do not match. The input supports a single " \
|
||||
"image. "
|
||||
image_resize = cv2.resize(image, self.input_size)
|
||||
encode = encode_images(image_resize)
|
||||
encode = encode.astype(np.float32)
|
||||
input_tensor = np.expand_dims(encode, 0)
|
||||
|
||||
return input_tensor
|
||||
|
||||
|
||||
class BVTVertexORT(HamburgerABC):
|
||||
|
||||
def __init__(self, onnx_path, *args, **kwargs):
|
||||
import onnxruntime as ort
|
||||
super().__init__(*args, **kwargs)
|
||||
self.session = ort.InferenceSession(onnx_path, None)
|
||||
self.input_config = self.session.get_inputs()[0]
|
||||
self.output_config = self.session.get_outputs()[0]
|
||||
self.input_size = self.input_config.shape[2:]
|
||||
|
||||
# @cost('Vertex')
|
||||
def _run_session(self, data) -> np.ndarray:
|
||||
result = self.session.run([self.output_config.name], {self.input_config.name: data})
|
||||
|
||||
return result[0]
|
||||
|
||||
def _postprocess(self, data) -> np.ndarray:
|
||||
assert data.shape[0] == 1
|
||||
data = np.asarray(data).reshape(-1, 4, 2)
|
||||
data[:, :, 0] *= self.input_size[1]
|
||||
data[:, :, 1] *= self.input_size[0]
|
||||
|
||||
return data[0]
|
||||
|
||||
def _preprocess(self, image) -> np.ndarray:
|
||||
assert len(
|
||||
image.shape) == 3, "The dimensions of the input image object do not match. The input supports a single " \
|
||||
"image. "
|
||||
image_resize = cv2.resize(image, self.input_size)
|
||||
encode = encode_images(image_resize)
|
||||
encode = encode.astype(np.float32)
|
||||
input_tensor = np.expand_dims(encode, 0)
|
||||
|
||||
return input_tensor
|
||||
@@ -0,0 +1,30 @@
|
||||
anyio==3.6.2
|
||||
certifi==2022.12.7
|
||||
charset-normalizer==3.0.1
|
||||
click==8.1.3
|
||||
coloredlogs==15.0.1
|
||||
fastapi==0.92.0
|
||||
flatbuffers==23.1.21
|
||||
h11==0.14.0
|
||||
humanfriendly==10.0
|
||||
idna==3.4
|
||||
importlib-metadata==6.0.0
|
||||
loguru==0.6.0
|
||||
mpmath==1.2.1
|
||||
numpy==1.21.6
|
||||
onnxruntime==1.14.0
|
||||
opencv-python==4.7.0.68
|
||||
packaging==23.0
|
||||
protobuf==4.22.0
|
||||
pydantic==1.10.5
|
||||
python-multipart==0.0.5
|
||||
requests==2.28.2
|
||||
six==1.16.0
|
||||
sniffio==1.3.0
|
||||
starlette==0.25.0
|
||||
sympy==1.10.1
|
||||
tqdm==4.64.1
|
||||
typing-extensions==4.5.0
|
||||
urllib3==1.26.14
|
||||
uvicorn==0.20.0
|
||||
zipp==3.14.0
|
||||
@@ -0,0 +1,37 @@
|
||||
# !/usr/bin/env python
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
__version__ = "0.1.1"
|
||||
|
||||
if __name__ == "__main__":
|
||||
setup(
|
||||
name="hyperlpr3",
|
||||
version=__version__,
|
||||
description="vehicle license plate recognition.",
|
||||
url="https://github.com/szad670401/HyperLPR",
|
||||
author="HyperInspire",
|
||||
author_email="tunmxy@163.com",
|
||||
keywords="vehicle license plate recognition",
|
||||
packages=find_packages(),
|
||||
classifiers=[
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
],
|
||||
install_requires=[
|
||||
"opencv-python",
|
||||
"onnxruntime",
|
||||
"tqdm",
|
||||
"requests",
|
||||
"fastapi",
|
||||
"uvicorn",
|
||||
"python-multipart",
|
||||
"loguru"
|
||||
],
|
||||
license="Apache License 2.0",
|
||||
zip_safe=False,
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
lpr3=hyperlpr3.command.cli:cli
|
||||
"""
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
|
||||
def cv2ImgAddText(img, text, left, top, textColor=(255, 0, 0), textSize=20):
|
||||
if (isinstance(img, np.ndarray)): # 判断是否OpenCV图片类型
|
||||
img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
|
||||
draw = ImageDraw.Draw(img)
|
||||
fontText = ImageFont.truetype("resource/font/platech.ttf", textSize, encoding="utf-8")
|
||||
draw.text((left, top), text, textColor, font=fontText)
|
||||
|
||||
return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)
|
||||
|
||||
|
||||
def draw_full(image: np.ndarray, plate_list: dict, ) -> np.ndarray:
|
||||
canvas = image.copy()
|
||||
for plate_info in plate_list:
|
||||
kps = plate_info['vertex']
|
||||
bdbox = plate_info['det_bound_box']
|
||||
text = plate_info['plate_code']
|
||||
x1, y1, x2, y2 = bdbox.astype(int)
|
||||
bd_y = y2 - y1
|
||||
bd_w = x2 - x1
|
||||
point_size = bd_w // 20
|
||||
line_size = bd_w // 40
|
||||
cv2.polylines(canvas, [kps.astype(np.int32)], True, (0, 0, 200), line_size, )
|
||||
for x, y in kps.astype(np.int32):
|
||||
cv2.line(canvas, (x, y), (x, y), (80, 240, 100), point_size)
|
||||
text_x = kps[0][0]
|
||||
text_y = kps[0][1] - bd_y // 1.4
|
||||
canvas = cv2ImgAddText(canvas, text, text_x, text_y, textSize=bd_w // 5)
|
||||
|
||||
|
||||
return canvas
|
||||
Reference in New Issue
Block a user