更新到HyperLPR3版本

This commit is contained in:
tunmx
2023-02-27 15:47:55 +08:00
parent 7ae4d385e1
commit 0864e05f76
912 changed files with 8160 additions and 221461 deletions
+7
View File
@@ -0,0 +1,7 @@
.idea/
__pycache__
resource
build/
dist/
hyperlpr3.egg-info/
venv/
+11
View File
@@ -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))
+9
View File
@@ -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)))
+20
View File
@@ -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()
+77
View File
@@ -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()
+129
View File
@@ -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()
+37
View File
@@ -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
+4
View File
@@ -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
+79
View File
@@ -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)
+19
View File
@@ -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"),
)
+36
View File
@@ -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
+200
View File
@@ -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
+117
View File
@@ -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
+83
View File
@@ -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
+30
View File
@@ -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
+37
View File
@@ -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
"""
)
+35
View File
@@ -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