first commit
Browse files- .gitattributes +2 -0
- AX650/pld_650_npu3.axmodel +3 -0
- AX650/plr_650_npu3.axmodel +3 -0
- README.md +141 -0
- axmodel_infer_plate_end2end.py +309 -0
- axmodel_infer_pld.py +233 -0
- axmodel_infer_plr.py +81 -0
- config.json +0 -0
- det_res.jpg +3 -0
- plate_end2end_res.jpg +3 -0
- test.jpg +3 -0
- 苏A8A68Y.jpg +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
*.axmodel filter=lfs diff=lfs merge=lfs -text
|
AX650/pld_650_npu3.axmodel
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a25c77a90f7cafa42105a7abdfa5bccfc086c61e6a52db5790e4323d2a26f170
|
| 3 |
+
size 1198468
|
AX650/plr_650_npu3.axmodel
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ef96528b9b350d59ba35c282b61eee29ee9650a9e0e642db4e73ec43f001a69d
|
| 3 |
+
size 955647
|
README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: agpl-3.0
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
pipeline_tag: object-detection
|
| 6 |
+
tags:
|
| 7 |
+
- Axera
|
| 8 |
+
- License Plate Recognition
|
| 9 |
+
- NPU
|
| 10 |
+
- OCR
|
| 11 |
+
- Object Detection
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
# plate-axera
|
| 15 |
+
|
| 16 |
+
This version of **plate-axera** has been converted to run on the Axera NPU using **w8a16** quantization. There are two models included:
|
| 17 |
+
1. **pld_650_npu3.axmodel**: This model is trained to detect the license plate with label 'plate'.
|
| 18 |
+
2. **plr_650_npu3.axmodel**: This model is trained to recognize the characters of detected license plates, and also shows the color of the license plate.
|
| 19 |
+
|
| 20 |
+
## Supported Classes
|
| 21 |
+
Detection model supports the following classes:
|
| 22 |
+
1. **plate**
|
| 23 |
+
|
| 24 |
+
## Supported characters
|
| 25 |
+
Recognition model supports the following characters:
|
| 26 |
+
```
|
| 27 |
+
{"皖沪津渝冀晋蒙辽吉黑苏浙京闽赣鲁豫鄂湘粤桂琼川贵云藏陕甘青宁新警学港澳台使领挂OABCDEFGHJKLMNPQRSTUVWXYZ0123456789"}
|
| 28 |
+
```
|
| 29 |
+
## Supported colors
|
| 30 |
+
Recognition model supports the following colors:
|
| 31 |
+
```
|
| 32 |
+
['blue', 'green', 'yellow', 'white', 'black']
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
Compatible with Pulsar2 version: 5.2.
|
| 36 |
+
|
| 37 |
+
## Convert tools links:
|
| 38 |
+
|
| 39 |
+
For those who are interested in model conversion, you can try to export axmodel through:
|
| 40 |
+
- [The repo of AXera Platform](https://github.com/AXERA-TECH/ax-samples), where you can get the detailed guide.
|
| 41 |
+
- [Pulsar2 Link, How to Convert ONNX to axmodel](https://pulsar2-docs.readthedocs.io/en/latest/pulsar2/introduction.html)
|
| 42 |
+
|
| 43 |
+
## Support Platform
|
| 44 |
+
|
| 45 |
+
https://docs.m5stack.com/zh_CN/ai_hardware/AI_Pyramid-Pro
|
| 46 |
+
|
| 47 |
+
- **AX650N/AX8850**
|
| 48 |
+
- [M4N-Dock(爱芯派Pro)](https://wiki.sipeed.com/hardware/zh/maixIV/m4ndock/m4ndock.html)
|
| 49 |
+
- [AI Pyramid](https://docs.m5stack.com/zh_CN/ai_hardware/AI_Pyramid-Pro)
|
| 50 |
+
- [M.2 Accelerator card](https://docs.m5stack.com/en/ai_hardware/LLM-8850_Card)
|
| 51 |
+
|
| 52 |
+
## How to use
|
| 53 |
+
|
| 54 |
+
Download all files from this repository to the device.
|
| 55 |
+
|
| 56 |
+
### python env requirement
|
| 57 |
+
|
| 58 |
+
#### pyaxengine
|
| 59 |
+
|
| 60 |
+
https://github.com/AXERA-TECH/pyaxengine
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
wget https://github.com/AXERA-TECH/pyaxengine/releases/download/0.1.3.rc2/axengine-0.1.3-py3-none-any.whl
|
| 64 |
+
pip install axengine-0.1.3-py3-none-any.whl
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
### Inference with AX650 Host, such as M4N-Dock(爱芯派Pro)
|
| 68 |
+
|
| 69 |
+
#### Plate Detection
|
| 70 |
+
Input image:
|
| 71 |
+

|
| 72 |
+
|
| 73 |
+
run
|
| 74 |
+
```bash
|
| 75 |
+
python3 axmodel_infer_pld.py
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
```bash
|
| 79 |
+
root@ax650:~/plate-axera# python3 axmodel_infer_pld.py
|
| 80 |
+
[INFO] Available providers: ['AxEngineExecutionProvider', 'AXCLRTExecutionProvider']
|
| 81 |
+
[INFO] Using provider: AxEngineExecutionProvider
|
| 82 |
+
[INFO] Chip type: ChipType.MC50
|
| 83 |
+
[INFO] VNPU type: VNPUType.DISABLED
|
| 84 |
+
[INFO] Engine version: 2.12.0s
|
| 85 |
+
[INFO] Model type: 2 (triple core)
|
| 86 |
+
[INFO] Compiler version: 5.2 eccb31f5
|
| 87 |
+
class: plate left:597 top:417 right:759 bottom:475 conf: 88%
|
| 88 |
+
|
| 89 |
+
```
|
| 90 |
+
Output image:
|
| 91 |
+

|
| 92 |
+
|
| 93 |
+
#### Plate Recognition
|
| 94 |
+
Input image:
|
| 95 |
+

|
| 96 |
+
|
| 97 |
+
run
|
| 98 |
+
```bash
|
| 99 |
+
python3 axmodel_infer_plr.py
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
```bash
|
| 103 |
+
root@ax650:~/plate-axera# python3 axmodel_infer_plr.py
|
| 104 |
+
[INFO] Available providers: ['AxEngineExecutionProvider', 'AXCLRTExecutionProvider']
|
| 105 |
+
[INFO] Using provider: AxEngineExecutionProvider
|
| 106 |
+
[INFO] Chip type: ChipType.MC50
|
| 107 |
+
[INFO] VNPU type: VNPUType.DISABLED
|
| 108 |
+
[INFO] Engine version: 2.12.0s
|
| 109 |
+
[INFO] Model type: 2 (triple core)
|
| 110 |
+
[INFO] Compiler version: 5.2 eccb31f5
|
| 111 |
+
Plate: [苏A8A68Y], score: 0.9997, color: [blue], score:1.0000
|
| 112 |
+
|
| 113 |
+
```
|
| 114 |
+
#### Plate det & Rec End2End
|
| 115 |
+
Input image:
|
| 116 |
+

|
| 117 |
+
|
| 118 |
+
run
|
| 119 |
+
```bash
|
| 120 |
+
python3 axmodel_infer_plate_end2end.py
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
```bash
|
| 124 |
+
root@ax650:~/plate-axera# python3 axmodel_infer_plate_end2end.py
|
| 125 |
+
[INFO] Available providers: ['AxEngineExecutionProvider', 'AXCLRTExecutionProvider']
|
| 126 |
+
[INFO] Using provider: AxEngineExecutionProvider
|
| 127 |
+
[INFO] Chip type: ChipType.MC50
|
| 128 |
+
[INFO] VNPU type: VNPUType.DISABLED
|
| 129 |
+
[INFO] Engine version: 2.12.0s
|
| 130 |
+
[INFO] Model type: 2 (triple core)
|
| 131 |
+
[INFO] Compiler version: 5.2 eccb31f5
|
| 132 |
+
[INFO] Using provider: AxEngineExecutionProvider
|
| 133 |
+
[INFO] Model type: 2 (triple core)
|
| 134 |
+
[INFO] Compiler version: 5.2 eccb31f5
|
| 135 |
+
Det---class:[plate], bbox:[597,417,759,475], conf:0.88
|
| 136 |
+
Rec---Plate:[川A2E7V7], score:0.9991, color:[blue], score:1.0000
|
| 137 |
+
Result saved to: ./plate_end2end_res.jpg
|
| 138 |
+
|
| 139 |
+
```
|
| 140 |
+
Output image:
|
| 141 |
+

|
axmodel_infer_plate_end2end.py
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
import numpy as np
|
| 3 |
+
import axengine as axe
|
| 4 |
+
import argparse
|
| 5 |
+
import matplotlib
|
| 6 |
+
|
| 7 |
+
plate_colors=['blue', 'green', 'yellow', 'white', 'black']
|
| 8 |
+
|
| 9 |
+
class Colors:
|
| 10 |
+
|
| 11 |
+
def __init__(self):
|
| 12 |
+
self.palette = [self.hex2rgb(c) for c in matplotlib.colors.TABLEAU_COLORS.values()]
|
| 13 |
+
self.n = len(self.palette)
|
| 14 |
+
|
| 15 |
+
def __call__(self, i, bgr=False):
|
| 16 |
+
c = self.palette[int(i) % self.n]
|
| 17 |
+
return (c[2], c[1], c[0]) if bgr else c
|
| 18 |
+
|
| 19 |
+
@staticmethod
|
| 20 |
+
def hex2rgb(h):
|
| 21 |
+
return tuple(int(h[1 + i:1 + i + 2], 16) for i in (0, 2, 4))
|
| 22 |
+
|
| 23 |
+
colors = Colors()
|
| 24 |
+
|
| 25 |
+
def make_grid(nx=20, ny=20, i=0, strides=[8, 16, 32], anchors=[[31,28, 38,32, 60,83],[84,110, 133,118, 200,113]]):
|
| 26 |
+
y, x = np.arange(ny, dtype=np.int32), np.arange(nx, dtype=np.int32)
|
| 27 |
+
yv, xv = np.meshgrid(y, x, indexing="ij")
|
| 28 |
+
grid = np.stack((xv, yv), 2)
|
| 29 |
+
grid = np.expand_dims(grid, axis=0).repeat(len(anchors[0]) // 2, axis=0)
|
| 30 |
+
grid = np.expand_dims(grid, axis=0) - 0.5
|
| 31 |
+
anchor_grid = np.array(anchors[i]).reshape((1, len(anchors[0]) // 2, 1, 1, 2))
|
| 32 |
+
anchor_grid = anchor_grid.repeat(ny, axis=2).repeat(nx, axis=3)
|
| 33 |
+
return grid, anchor_grid
|
| 34 |
+
|
| 35 |
+
def sigmoid(x):
|
| 36 |
+
return 1 / (1 + np.exp(-x))
|
| 37 |
+
|
| 38 |
+
def softmax(lpr_pred):
|
| 39 |
+
max_out = np.max(lpr_pred, axis=1, keepdims=True)
|
| 40 |
+
exp_out = np.exp(lpr_pred - max_out)
|
| 41 |
+
sum_exp_out = np.sum(exp_out, axis=1, keepdims=True)
|
| 42 |
+
return exp_out / sum_exp_out
|
| 43 |
+
|
| 44 |
+
def xywh2xyxy(x):
|
| 45 |
+
y = np.copy(x)
|
| 46 |
+
y[..., 0] = x[..., 0] - x[..., 2] / 2
|
| 47 |
+
y[..., 1] = x[..., 1] - x[..., 3] / 2
|
| 48 |
+
y[..., 2] = x[..., 0] + x[..., 2] / 2
|
| 49 |
+
y[..., 3] = x[..., 1] + x[..., 3] / 2
|
| 50 |
+
return y
|
| 51 |
+
|
| 52 |
+
def letterbox(im, new_shape=(640, 640), color=(0, 0, 0), auto=False, scaleFill=False, scaleup=True, stride=32):
|
| 53 |
+
shape = im.shape[:2]
|
| 54 |
+
if isinstance(new_shape, int):
|
| 55 |
+
new_shape = (new_shape, new_shape)
|
| 56 |
+
|
| 57 |
+
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
|
| 58 |
+
if not scaleup:
|
| 59 |
+
r = min(r, 1.0)
|
| 60 |
+
|
| 61 |
+
ratio = r, r
|
| 62 |
+
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
|
| 63 |
+
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]
|
| 64 |
+
if auto:
|
| 65 |
+
dw, dh = np.mod(dw, stride), np.mod(dh, stride)
|
| 66 |
+
elif scaleFill:
|
| 67 |
+
dw, dh = 0.0, 0.0
|
| 68 |
+
new_unpad = (new_shape[1], new_shape[0])
|
| 69 |
+
ratio = new_shape[1] / shape[1], new_shape[0] / shape[0]
|
| 70 |
+
|
| 71 |
+
dw /= 2
|
| 72 |
+
dh /= 2
|
| 73 |
+
|
| 74 |
+
if shape[::-1] != new_unpad:
|
| 75 |
+
im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
|
| 76 |
+
|
| 77 |
+
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
|
| 78 |
+
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
|
| 79 |
+
im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)
|
| 80 |
+
|
| 81 |
+
return im, ratio, (dw, dh)
|
| 82 |
+
|
| 83 |
+
def scale_boxes(img1_shape, boxes, img0_shape, ratio_pad=None):
|
| 84 |
+
if ratio_pad is None:
|
| 85 |
+
gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1])
|
| 86 |
+
pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2
|
| 87 |
+
else:
|
| 88 |
+
gain = ratio_pad[0][0]
|
| 89 |
+
pad = ratio_pad[1]
|
| 90 |
+
|
| 91 |
+
boxes[..., [0, 2]] -= pad[0]
|
| 92 |
+
boxes[..., [1, 3]] -= pad[1]
|
| 93 |
+
boxes[..., :4] /= gain
|
| 94 |
+
return boxes
|
| 95 |
+
|
| 96 |
+
def nms(boxes, iou_thresh=0.65):
|
| 97 |
+
xmin, ymin, xmax, ymax = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
|
| 98 |
+
score = boxes[:, 4]
|
| 99 |
+
areas = (xmax - xmin + 1)*(ymax - ymin + 1)
|
| 100 |
+
order = score.argsort()[::-1]
|
| 101 |
+
|
| 102 |
+
keep = []
|
| 103 |
+
while order.size > 0:
|
| 104 |
+
i = order[0]
|
| 105 |
+
keep.append(i)
|
| 106 |
+
|
| 107 |
+
xxmin = np.maximum(xmin[i], xmin[order[1:]])
|
| 108 |
+
yymin = np.maximum(ymin[i], ymin[order[1:]])
|
| 109 |
+
xxmax = np.minimum(xmax[i], xmax[order[1:]])
|
| 110 |
+
yymax = np.minimum(ymax[i], ymax[order[1:]])
|
| 111 |
+
|
| 112 |
+
w = np.maximum(0, xxmax - xxmin + 1)
|
| 113 |
+
h = np.maximum(0, yymax - yymin + 1)
|
| 114 |
+
inter = w * h
|
| 115 |
+
|
| 116 |
+
iou = inter / (areas[i] + areas[order[1:]] - inter)
|
| 117 |
+
order = order[np.where(iou <= iou_thresh)[0] + 1]
|
| 118 |
+
|
| 119 |
+
return boxes[keep, :]
|
| 120 |
+
|
| 121 |
+
def nms_multi(boxes, conf_thresh=0.25, iou_thresh=0.65, max_num=300):
|
| 122 |
+
if len(boxes) == 0:
|
| 123 |
+
return boxes
|
| 124 |
+
|
| 125 |
+
boxes = boxes[np.where(boxes[:, 4] > conf_thresh)]
|
| 126 |
+
result = list()
|
| 127 |
+
|
| 128 |
+
cls_score = boxes[:, 5:]
|
| 129 |
+
max_cls_index = np.argmax(cls_score, axis=-1)
|
| 130 |
+
max_cls_score = np.max(cls_score, axis=-1)
|
| 131 |
+
|
| 132 |
+
dets = np.concatenate([boxes[:, :5], max_cls_score[:, np.newaxis], boxes[:, 4:5], max_cls_index[:, np.newaxis]], axis=-1)
|
| 133 |
+
dets[:, 6] = dets[:, 4] * dets[:, 5]
|
| 134 |
+
max_det = dets[:, 6].argsort()[::-1][:max_num]
|
| 135 |
+
dets = dets[max_det, :]
|
| 136 |
+
|
| 137 |
+
unique_label = np.unique(max_cls_index)
|
| 138 |
+
|
| 139 |
+
for c in unique_label:
|
| 140 |
+
det = dets[dets[:, -1] == c]
|
| 141 |
+
nmsed_det = nms(det, iou_thresh=iou_thresh)
|
| 142 |
+
if len(nmsed_det):
|
| 143 |
+
result.append(nmsed_det)
|
| 144 |
+
|
| 145 |
+
if len(result):
|
| 146 |
+
result = np.concatenate(result, axis=0)
|
| 147 |
+
return result
|
| 148 |
+
else:
|
| 149 |
+
return []
|
| 150 |
+
|
| 151 |
+
def GreedyDecode(preb, SEP_IDX=74, plate_string=""):
|
| 152 |
+
preb_label = list()
|
| 153 |
+
for j in range(preb.shape[0]):
|
| 154 |
+
preb_label.append(int(np.argmax(preb[j, :], axis=0)))
|
| 155 |
+
no_repeat_blank_label = list()
|
| 156 |
+
plate_index = list()
|
| 157 |
+
pre_c = preb_label[0]
|
| 158 |
+
if pre_c != SEP_IDX:
|
| 159 |
+
no_repeat_blank_label.append(pre_c)
|
| 160 |
+
plate_index.append(0)
|
| 161 |
+
for idx, c in enumerate(preb_label):
|
| 162 |
+
if (pre_c == c) or (c == SEP_IDX):
|
| 163 |
+
if c == SEP_IDX:
|
| 164 |
+
pre_c = c
|
| 165 |
+
continue
|
| 166 |
+
no_repeat_blank_label.append(c)
|
| 167 |
+
plate_index.append(idx)
|
| 168 |
+
pre_c = c
|
| 169 |
+
|
| 170 |
+
lpr_string = ''.join([plate_string[idx] for idx in no_repeat_blank_label])
|
| 171 |
+
lpr_score = 1
|
| 172 |
+
for idx, c in zip(plate_index, no_repeat_blank_label):
|
| 173 |
+
lpr_score *= preb[idx, c]
|
| 174 |
+
|
| 175 |
+
return lpr_string, lpr_score
|
| 176 |
+
|
| 177 |
+
def pld_inference(session_pld, input_name_pld, output_name_pld, img, opt):
|
| 178 |
+
img_letter, ratio, (dw, dh) = letterbox(img, opt.imgsz_pld)
|
| 179 |
+
input_data = np.expand_dims(img_letter, axis=0)[..., ::-1].transpose((0, 3, 1, 2)).astype(np.uint8)
|
| 180 |
+
|
| 181 |
+
outputs = session_pld.run(output_name_pld, {input_name_pld: input_data})
|
| 182 |
+
|
| 183 |
+
num_anchor = len(opt.anchors[0]) // 2
|
| 184 |
+
channel = len(opt.classes) + 5
|
| 185 |
+
predictions = list()
|
| 186 |
+
|
| 187 |
+
for i, output in enumerate(outputs):
|
| 188 |
+
bs, _, ny, nx = output.shape
|
| 189 |
+
output = sigmoid(output.reshape(bs, num_anchor, channel, ny, nx).transpose(0, 1, 3, 4, 2))
|
| 190 |
+
|
| 191 |
+
grid, anchor_grid = make_grid(nx, ny, i, opt.strides, opt.anchors)
|
| 192 |
+
|
| 193 |
+
xy, wh, conf = output[..., :2], output[..., 2:4], output[..., 4:]
|
| 194 |
+
|
| 195 |
+
xy = (xy * 2 + grid) * opt.strides[i]
|
| 196 |
+
wh = (wh * 2) ** 2 * anchor_grid
|
| 197 |
+
|
| 198 |
+
prediction = np.concatenate((xy, wh, conf), 4)
|
| 199 |
+
prediction = prediction.reshape(bs, num_anchor * nx * ny, channel)
|
| 200 |
+
|
| 201 |
+
prediction = xywh2xyxy(prediction)
|
| 202 |
+
prediction[..., 0:4:2] = np.clip(prediction[..., 0:4:2], a_min=0, a_max=opt.imgsz_pld[1])
|
| 203 |
+
prediction[..., 1:4:2] = np.clip(prediction[..., 1:4:2], a_min=0, a_max=opt.imgsz_pld[0])
|
| 204 |
+
|
| 205 |
+
predictions.append(prediction)
|
| 206 |
+
|
| 207 |
+
predictions = np.concatenate(predictions, axis=1).squeeze()
|
| 208 |
+
predictions = nms_multi(predictions)
|
| 209 |
+
if len(predictions) > 0:
|
| 210 |
+
predictions[:, :4] = scale_boxes(img_letter.shape[:2], predictions[:, :4], img.shape).round()
|
| 211 |
+
|
| 212 |
+
return predictions
|
| 213 |
+
|
| 214 |
+
def plr_inference(session_plr, input_name_plr, output_name_plr, plate_img, opt):
|
| 215 |
+
img_resized = cv2.resize(plate_img, (opt.imgsz_plr[1], opt.imgsz_plr[0]))
|
| 216 |
+
input_data = np.expand_dims(img_resized, axis=0)[..., ::-1].transpose((0, 3, 1, 2)).astype(np.uint8)
|
| 217 |
+
outputs = session_plr.run(output_name_plr, {input_name_plr: input_data})
|
| 218 |
+
|
| 219 |
+
SEP_IDX = len(opt.PLATE_STRING) - 1
|
| 220 |
+
for lpr_out, color_out in zip(*outputs):
|
| 221 |
+
|
| 222 |
+
lpr_pred = np.max(lpr_out, axis=1).T
|
| 223 |
+
lpr_pred = softmax(lpr_pred)
|
| 224 |
+
lpr_string, lpr_score = GreedyDecode(lpr_pred, SEP_IDX, opt.PLATE_STRING)
|
| 225 |
+
|
| 226 |
+
color_out = np.max(np.max(color_out, axis=1), axis=1).reshape((1, -1))
|
| 227 |
+
color_out = softmax(color_out)[0]
|
| 228 |
+
color_label = color_out.argmax()
|
| 229 |
+
color_score = color_out[color_label]
|
| 230 |
+
|
| 231 |
+
return lpr_string, lpr_score, color_label, color_score
|
| 232 |
+
|
| 233 |
+
def end2end_inference(opt):
|
| 234 |
+
|
| 235 |
+
providers = ["AxEngineExecutionProvider"]
|
| 236 |
+
session_pld = axe.InferenceSession(opt.weights_pld, providers=providers)
|
| 237 |
+
input_name_pld = session_pld.get_inputs()[0].name
|
| 238 |
+
output_name_pld = [output.name for output in session_pld.get_outputs()]
|
| 239 |
+
|
| 240 |
+
session_plr = axe.InferenceSession(opt.weights_plr, providers=providers)
|
| 241 |
+
input_name_plr = session_plr.get_inputs()[0].name
|
| 242 |
+
output_name_plr = [output.name for output in session_plr.get_outputs()]
|
| 243 |
+
|
| 244 |
+
img = cv2.imread(opt.source)
|
| 245 |
+
if img is None:
|
| 246 |
+
print(f"Failed to read image: {opt.source}")
|
| 247 |
+
return
|
| 248 |
+
|
| 249 |
+
dets = pld_inference(session_pld, input_name_pld, output_name_pld, img, opt)
|
| 250 |
+
|
| 251 |
+
results = []
|
| 252 |
+
if len(dets) > 0:
|
| 253 |
+
box_xyxy = dets[:, :4].astype(np.int32)
|
| 254 |
+
scores = dets[:, -2]
|
| 255 |
+
labels = dets[:, -1].astype(np.int32)
|
| 256 |
+
|
| 257 |
+
for (x1, y1, x2, y2), score, label in zip(box_xyxy, scores, labels):
|
| 258 |
+
x1, y1, x2, y2 = max(0, x1), max(0, y1), min(img.shape[1], x2), min(img.shape[0], y2)
|
| 259 |
+
if x2 > x1 and y2 > y1:
|
| 260 |
+
plate_img = img[y1:y2, x1:x2]
|
| 261 |
+
# You can do some expand or alignment for plate_img if needed
|
| 262 |
+
lpr_string, lpr_score, color_label, color_score = plr_inference(session_plr, input_name_plr, output_name_plr, plate_img, opt)
|
| 263 |
+
results.append({
|
| 264 |
+
'bbox': (x1, y1, x2, y2),
|
| 265 |
+
'det_score': score,
|
| 266 |
+
'plate': lpr_string,
|
| 267 |
+
'plate_score': lpr_score,
|
| 268 |
+
'color': plate_colors[color_label],
|
| 269 |
+
'color_score': color_score
|
| 270 |
+
})
|
| 271 |
+
|
| 272 |
+
print(f"Det---class:[{opt.classes[label]}], bbox:[{x1},{y1},{x2},{y2}], conf:{score:.2f}")
|
| 273 |
+
print(f"Rec---Plate:[{lpr_string}], score:{lpr_score:.4f}, color:[{plate_colors[color_label]}], score:{color_score:.4f}")
|
| 274 |
+
|
| 275 |
+
if opt.vis and len(results) > 0:
|
| 276 |
+
for res in results:
|
| 277 |
+
x1, y1, x2, y2 = res['bbox']
|
| 278 |
+
tl = 3 or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1
|
| 279 |
+
img = cv2.rectangle(img, (x1, y1), (x2, y2), colors(0, True), tl)
|
| 280 |
+
c1, c2 = (int(x1), int(y1)), (int(x2), int(y2))
|
| 281 |
+
tf = max(tl - 1, 1)
|
| 282 |
+
text = f"{res['plate']}:{res['plate_score']:.2f}"
|
| 283 |
+
t_size = cv2.getTextSize(text, 0, fontScale=tl / 6, thickness=tf)[0]
|
| 284 |
+
c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
|
| 285 |
+
cv2.rectangle(img, c1, c2, colors(0, True), -1, cv2.LINE_AA)
|
| 286 |
+
cv2.putText(img, text, (c1[0], c1[1] - 2), 0, tl / 6, [225, 255, 255], thickness=tf//2, lineType=cv2.LINE_AA)
|
| 287 |
+
|
| 288 |
+
cv2.imwrite(opt.save_name, img)
|
| 289 |
+
print(f"Result saved to: {opt.save_name}")
|
| 290 |
+
|
| 291 |
+
def parse_opt():
|
| 292 |
+
parser = argparse.ArgumentParser()
|
| 293 |
+
parser.add_argument("--weights_pld", type=str, default="./pld_650_npu3.axmodel", help="plate detection model path")
|
| 294 |
+
parser.add_argument("--weights_plr", type=str, default="./plr_650_npu3.axmodel", help="plate recognition model path")
|
| 295 |
+
parser.add_argument("--source", type=str, default="./test.jpg", help="img_path")
|
| 296 |
+
parser.add_argument("--anchors", type=float, default=[[23, 8, 57, 21, 76, 28],[93, 33, 86, 41, 116, 39], [120, 90, 156, 198, 373, 326]], help="anchor based anchors")
|
| 297 |
+
parser.add_argument("--strides", type=float, default=[8, 16, 32], help="model strides")
|
| 298 |
+
parser.add_argument("--imgsz_pld", nargs="+", type=int, default=[416, 416], help="PLD inference size h,w")
|
| 299 |
+
parser.add_argument("--imgsz_plr", nargs="+", type=int, default=[48, 192], help="PLR inference size h,w")
|
| 300 |
+
parser.add_argument("--classes", type=str, default=["plate"], help="classes num")
|
| 301 |
+
parser.add_argument("--PLATE_STRING", type=str, default=u"皖沪津渝冀晋蒙辽吉黑苏浙京闽赣鲁豫鄂湘粤桂琼川贵云藏陕甘青宁新警学港澳台使领挂OABCDEFGHJKLMNPQRSTUVWXYZ0123456789#", help="Plate string map")
|
| 302 |
+
parser.add_argument("--vis", default=True, help="visualize detect result")
|
| 303 |
+
parser.add_argument("--save_name", type=str, default="./plate_end2end_res.jpg", help="result img save path")
|
| 304 |
+
opt = parser.parse_args()
|
| 305 |
+
return opt
|
| 306 |
+
|
| 307 |
+
if __name__ == "__main__":
|
| 308 |
+
opt = parse_opt()
|
| 309 |
+
end2end_inference(opt)
|
axmodel_infer_pld.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
import numpy as np
|
| 3 |
+
import axengine as axe
|
| 4 |
+
import argparse
|
| 5 |
+
import matplotlib
|
| 6 |
+
|
| 7 |
+
class Colors:
|
| 8 |
+
|
| 9 |
+
def __init__(self):
|
| 10 |
+
self.palette = [self.hex2rgb(c) for c in matplotlib.colors.TABLEAU_COLORS.values()]
|
| 11 |
+
self.n = len(self.palette)
|
| 12 |
+
|
| 13 |
+
def __call__(self, i, bgr=False):
|
| 14 |
+
c = self.palette[int(i) % self.n]
|
| 15 |
+
return (c[2], c[1], c[0]) if bgr else c
|
| 16 |
+
|
| 17 |
+
@staticmethod
|
| 18 |
+
def hex2rgb(h):
|
| 19 |
+
return tuple(int(h[1 + i:1 + i + 2], 16) for i in (0, 2, 4))
|
| 20 |
+
|
| 21 |
+
colors = Colors()
|
| 22 |
+
def make_grid(nx=20, ny=20, i=0, strides=[8, 16, 32], anchors=[[31,28, 38,32, 60,83],[84,110, 133,118, 200,113]]):
|
| 23 |
+
"""Generates a mesh grid for anchor boxes"""
|
| 24 |
+
# shape = 1, len(anchors[i]) // 2, ny, nx, 2 # grid shape
|
| 25 |
+
y, x = np.arange(ny, dtype=np.int32), np.arange(nx, dtype=np.int32)
|
| 26 |
+
yv, xv = np.meshgrid(y, x, indexing="ij")
|
| 27 |
+
grid = np.stack((xv, yv), 2)
|
| 28 |
+
grid = np.expand_dims(grid, axis=0).repeat(len(anchors[0]) // 2, axis=0)
|
| 29 |
+
grid = np.expand_dims(grid, axis=0) - 0.5 #add grid offset, i.e. y = 2.0 * x - 0.5
|
| 30 |
+
# anchor_grid = np.array([anchor*strides[i] for anchor in anchors[i]]).reshape((1, len(anchors[0]) // 2, 1, 1, 2))
|
| 31 |
+
anchor_grid = np.array(anchors[i]).reshape((1, len(anchors[0]) // 2, 1, 1, 2))
|
| 32 |
+
anchor_grid = anchor_grid.repeat(ny, axis=2).repeat(nx, axis=3)
|
| 33 |
+
# print(anchor_grid.shape, shape)
|
| 34 |
+
return grid, anchor_grid
|
| 35 |
+
|
| 36 |
+
def sigmoid(x):
|
| 37 |
+
return 1 / (1 + np.exp(-x))
|
| 38 |
+
|
| 39 |
+
def xywh2xyxy(x):
|
| 40 |
+
"""Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right."""
|
| 41 |
+
y = np.copy(x)
|
| 42 |
+
y[..., 0] = x[..., 0] - x[..., 2] / 2 # top left x
|
| 43 |
+
y[..., 1] = x[..., 1] - x[..., 3] / 2 # top left y
|
| 44 |
+
y[..., 2] = x[..., 0] + x[..., 2] / 2 # bottom right x
|
| 45 |
+
y[..., 3] = x[..., 1] + x[..., 3] / 2 # bottom right y
|
| 46 |
+
return y
|
| 47 |
+
|
| 48 |
+
def letterbox(im, new_shape=(640, 640), color=(0, 0, 0), auto=False, scaleFill=False, scaleup=True, stride=32):
|
| 49 |
+
"""Resizes and pads image to new_shape with stride-multiple constraints, returns resized image, ratio, padding."""
|
| 50 |
+
shape = im.shape[:2] # current shape [height, width]
|
| 51 |
+
if isinstance(new_shape, int):
|
| 52 |
+
new_shape = (new_shape, new_shape)
|
| 53 |
+
|
| 54 |
+
# Scale ratio (new / old)
|
| 55 |
+
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
|
| 56 |
+
if not scaleup: # only scale down, do not scale up (for better val mAP)
|
| 57 |
+
r = min(r, 1.0)
|
| 58 |
+
|
| 59 |
+
# Compute padding
|
| 60 |
+
ratio = r, r # width, height ratios
|
| 61 |
+
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
|
| 62 |
+
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
|
| 63 |
+
if auto: # minimum rectangle
|
| 64 |
+
dw, dh = np.mod(dw, stride), np.mod(dh, stride) # wh padding
|
| 65 |
+
elif scaleFill: # stretch
|
| 66 |
+
dw, dh = 0.0, 0.0
|
| 67 |
+
new_unpad = (new_shape[1], new_shape[0])
|
| 68 |
+
ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] # width, height ratios
|
| 69 |
+
|
| 70 |
+
dw /= 2 # divide padding into 2 sides
|
| 71 |
+
dh /= 2
|
| 72 |
+
|
| 73 |
+
if shape[::-1] != new_unpad: # resize
|
| 74 |
+
im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
|
| 75 |
+
|
| 76 |
+
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
|
| 77 |
+
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
|
| 78 |
+
im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border for 2 sides
|
| 79 |
+
# im = cv2.copyMakeBorder(im, 0, int(dh), 0, int(dw), cv2.BORDER_CONSTANT, value=color) # add border for right and bottom
|
| 80 |
+
|
| 81 |
+
return im, ratio, (dw, dh)
|
| 82 |
+
|
| 83 |
+
def scale_boxes(img1_shape, boxes, img0_shape, ratio_pad=None):
|
| 84 |
+
"""Rescales (xyxy) bounding boxes from img1_shape to img0_shape, optionally using provided `ratio_pad`."""
|
| 85 |
+
if ratio_pad is None: # calculate from img0_shape
|
| 86 |
+
gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1]) # gain = old / new
|
| 87 |
+
pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2 # wh padding
|
| 88 |
+
# pad = (0, 0)
|
| 89 |
+
else:
|
| 90 |
+
gain = ratio_pad[0][0]
|
| 91 |
+
pad = ratio_pad[1]
|
| 92 |
+
|
| 93 |
+
boxes[..., [0, 2]] -= pad[0] # x padding
|
| 94 |
+
boxes[..., [1, 3]] -= pad[1] # y padding
|
| 95 |
+
boxes[..., :4] /= gain
|
| 96 |
+
return boxes
|
| 97 |
+
|
| 98 |
+
def nms(boxes, iou_thresh=0.65):
|
| 99 |
+
xmin, ymin, xmax, ymax = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
|
| 100 |
+
score = boxes[:, 4]
|
| 101 |
+
areas = (xmax - xmin + 1)*(ymax - ymin + 1)
|
| 102 |
+
order = score.argsort()[::-1]
|
| 103 |
+
|
| 104 |
+
keep = []
|
| 105 |
+
while order.size > 0:
|
| 106 |
+
i = order[0]
|
| 107 |
+
keep.append(i)
|
| 108 |
+
|
| 109 |
+
xxmin = np.maximum(xmin[i], xmin[order[1:]])
|
| 110 |
+
yymin = np.maximum(ymin[i], ymin[order[1:]])
|
| 111 |
+
xxmax = np.minimum(xmax[i], xmax[order[1:]])
|
| 112 |
+
yymax = np.minimum(ymax[i], ymax[order[1:]])
|
| 113 |
+
|
| 114 |
+
w = np.maximum(0, xxmax - xxmin + 1)
|
| 115 |
+
h = np.maximum(0, yymax - yymin + 1)
|
| 116 |
+
inter = w * h
|
| 117 |
+
|
| 118 |
+
iou = inter / (areas[i] + areas[order[1:]] - inter)
|
| 119 |
+
order = order[np.where(iou <= iou_thresh)[0] + 1] #索引需要加1
|
| 120 |
+
|
| 121 |
+
return boxes[keep, :]
|
| 122 |
+
|
| 123 |
+
def nms_multi(boxes, conf_thresh=0.25, iou_thresh=0.65, max_num=300):
|
| 124 |
+
if len(boxes) == 0:
|
| 125 |
+
return boxes
|
| 126 |
+
|
| 127 |
+
boxes = boxes[np.where(boxes[:, 4] > conf_thresh)]
|
| 128 |
+
result = list()
|
| 129 |
+
|
| 130 |
+
cls_score = boxes[:, 5:]
|
| 131 |
+
max_cls_index = np.argmax(cls_score, axis=-1)
|
| 132 |
+
max_cls_score = np.max(cls_score, axis=-1)
|
| 133 |
+
|
| 134 |
+
dets = np.concatenate([boxes[:, :5], max_cls_score[:, np.newaxis], boxes[:, 4:5], max_cls_index[:, np.newaxis]], axis=-1)
|
| 135 |
+
dets[:, 6] = dets[:, 4] * dets[:, 5]
|
| 136 |
+
max_det = dets[:, 6].argsort()[::-1][:max_num]
|
| 137 |
+
dets = dets[max_det, :]
|
| 138 |
+
|
| 139 |
+
unique_label = np.unique(max_cls_index)
|
| 140 |
+
|
| 141 |
+
for c in unique_label:
|
| 142 |
+
det = dets[dets[:, -1] == c]
|
| 143 |
+
nmsed_det = nms(det, iou_thresh=iou_thresh)
|
| 144 |
+
if len(nmsed_det):
|
| 145 |
+
result.append(nmsed_det)
|
| 146 |
+
|
| 147 |
+
if len(result):
|
| 148 |
+
result = np.concatenate(result, axis=0)
|
| 149 |
+
return result
|
| 150 |
+
else:
|
| 151 |
+
return []
|
| 152 |
+
|
| 153 |
+
def model_inference(opt):
|
| 154 |
+
|
| 155 |
+
providers = ["AxEngineExecutionProvider"]
|
| 156 |
+
session = axe.InferenceSession(opt.model, providers=providers)
|
| 157 |
+
|
| 158 |
+
input_name = session.get_inputs()[0].name
|
| 159 |
+
output_name = [output.name for output in session.get_outputs()]
|
| 160 |
+
|
| 161 |
+
img = cv2.imread(f'{opt.source}')
|
| 162 |
+
img_letter, ratio, (dw, dh) = letterbox(img, opt.imgsz)
|
| 163 |
+
input_data = np.expand_dims(img_letter, axis=0)[..., ::-1].transpose((0, 3, 1, 2)).astype(np.uint8)
|
| 164 |
+
|
| 165 |
+
outputs = session.run(output_name, {input_name:input_data})
|
| 166 |
+
|
| 167 |
+
num_anchor = len(opt.anchors[0]) // 2
|
| 168 |
+
channel = len(opt.classes) + 5
|
| 169 |
+
predictions = list()
|
| 170 |
+
|
| 171 |
+
for i, output in enumerate(outputs):
|
| 172 |
+
bs, _, ny, nx = output.shape # x(bs,255,20,20) to x(bs,3,20,20,85)
|
| 173 |
+
output = sigmoid(output.reshape(bs, num_anchor, channel, ny, nx).transpose(0, 1, 3, 4, 2))
|
| 174 |
+
|
| 175 |
+
grid, anchor_grid = make_grid(nx, ny, i, opt.strides, opt.anchors)
|
| 176 |
+
|
| 177 |
+
xy, wh, conf = output[..., :2], output[..., 2:4], output[..., 4:]
|
| 178 |
+
|
| 179 |
+
xy = (xy * 2 + grid) * opt.strides[i] # xy
|
| 180 |
+
wh = (wh * 2) ** 2 * anchor_grid # wh
|
| 181 |
+
|
| 182 |
+
prediction = np.concatenate((xy, wh, conf), 4)
|
| 183 |
+
prediction = prediction.reshape(bs, num_anchor * nx * ny, channel)
|
| 184 |
+
|
| 185 |
+
prediction = xywh2xyxy(prediction)
|
| 186 |
+
prediction[..., 0:4:2] = np.clip(prediction[..., 0:4:2], a_min=0, a_max=opt.imgsz[1])
|
| 187 |
+
prediction[..., 1:4:2] = np.clip(prediction[..., 1:4:2], a_min=0, a_max=opt.imgsz[0])
|
| 188 |
+
|
| 189 |
+
predictions.append(prediction)
|
| 190 |
+
|
| 191 |
+
predictions = np.concatenate(predictions, axis=1).squeeze()
|
| 192 |
+
# predictions format: [x1, y1, x2, y2, obj, cls_score, obj*cls_score, label]
|
| 193 |
+
predictions = nms_multi(predictions) #TODO multi label for one box
|
| 194 |
+
predictions[:, :4] = scale_boxes(img_letter.shape[:2], predictions[:, :4], img.shape).round()
|
| 195 |
+
|
| 196 |
+
if opt.vis:
|
| 197 |
+
box_xyxy = predictions[:, :4].astype(np.int32)
|
| 198 |
+
scores = predictions[:, -2]
|
| 199 |
+
labels = predictions[:, -1].astype(np.int32)
|
| 200 |
+
|
| 201 |
+
for (x1, y1, x2, y2), score, label in zip(box_xyxy, scores, labels):
|
| 202 |
+
print("class:",opt.classes[label], "left:%.0f" % x1,"top:%.0f" % y1,"right:%.0f" % x2,"bottom:%.0f" % y2, "conf:",'{:.0f}%'.format(float(score)*100))
|
| 203 |
+
tl = 3 or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1
|
| 204 |
+
img = cv2.rectangle(img, (x1, y1), (x2, y2), colors(label, True), tl)
|
| 205 |
+
c1, c2 = (int(x1), int(y1)), (int(x2), int(y2))
|
| 206 |
+
tf = max(tl - 1, 1)
|
| 207 |
+
t_size = cv2.getTextSize(f"{opt.classes[label]}:{score:.3f}", 0, fontScale=tl / 6, thickness=tf)[0]
|
| 208 |
+
c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
|
| 209 |
+
cv2.rectangle(img, c1, c2, colors(label, True), -1, cv2.LINE_AA)
|
| 210 |
+
cv2.putText(img, f"{opt.classes[label]}:{score:.3f}", (c1[0], c1[1] - 2), 0, tl / 6, [225, 255, 255], thickness=tf//2, lineType=cv2.LINE_AA)
|
| 211 |
+
|
| 212 |
+
cv2.imwrite(f'{opt.save_name}', img)
|
| 213 |
+
|
| 214 |
+
def parse_opt():
|
| 215 |
+
parser = argparse.ArgumentParser()
|
| 216 |
+
parser.add_argument("--model", nargs="+", type=str, default="./pld_650_npu3.axmodel", help="axmodel path")
|
| 217 |
+
parser.add_argument("--source", type=str, default="./test.jpg", help="img_path")
|
| 218 |
+
parser.add_argument("--anchors", type=float, default=[[23, 8, 57, 21, 76, 28],[93, 33, 86, 41, 116, 39], [120, 90, 156, 198, 373, 326]], help="anchor based anchors")
|
| 219 |
+
parser.add_argument("--strides", type=float, default=[8, 16, 32], help="model strides")
|
| 220 |
+
parser.add_argument("--imgsz", "--img", "--img-size", nargs="+", type=int, default=[416, 416], help="inference size h,w")
|
| 221 |
+
parser.add_argument("--classes", type=str, default=["plate"], help="classes num")
|
| 222 |
+
parser.add_argument("--conf-thres", type=float, default=0.25, help="confidence threshold")
|
| 223 |
+
parser.add_argument("--iou-thres", type=float, default=0.45, help="NMS IoU threshold")
|
| 224 |
+
parser.add_argument("--max-det", type=int, default=50, help="maximum detections per image")
|
| 225 |
+
parser.add_argument("--vis", default=True, help="visualize detect result")
|
| 226 |
+
parser.add_argument("--save_name", type=str, default="./det_res.jpg", help="detect img save path")
|
| 227 |
+
opt = parser.parse_args()
|
| 228 |
+
return opt
|
| 229 |
+
|
| 230 |
+
if __name__ == "__main__":
|
| 231 |
+
|
| 232 |
+
opt = parse_opt()
|
| 233 |
+
model_inference(opt)
|
axmodel_infer_plr.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
import numpy as np
|
| 3 |
+
import axengine as axe
|
| 4 |
+
import argparse
|
| 5 |
+
|
| 6 |
+
plate_colors=['blue', 'green', 'yellow', 'white', 'black']
|
| 7 |
+
|
| 8 |
+
def softmax(lpr_pred):
|
| 9 |
+
"maxpool, softmax"
|
| 10 |
+
max_out = np.max(lpr_pred, axis=1, keepdims=True)
|
| 11 |
+
exp_out = np.exp(lpr_pred - max_out)
|
| 12 |
+
sum_exp_out = np.sum(exp_out, axis=1, keepdims=True)
|
| 13 |
+
return exp_out / sum_exp_out
|
| 14 |
+
|
| 15 |
+
def GreedyDecode(preb, SEP_IDX=74, plate_string=""):
|
| 16 |
+
preb_label = list()
|
| 17 |
+
for j in range(preb.shape[0]):
|
| 18 |
+
preb_label.append(np.argmax(preb[j, :], axis=0))
|
| 19 |
+
no_repeat_blank_label = list()
|
| 20 |
+
plate_index = list()
|
| 21 |
+
pre_c = preb_label[0]
|
| 22 |
+
if pre_c != SEP_IDX:
|
| 23 |
+
no_repeat_blank_label.append(pre_c)
|
| 24 |
+
plate_index.append(0)
|
| 25 |
+
for idx, c in enumerate(preb_label): # dropout repeate label and blank label
|
| 26 |
+
if (pre_c == c) or (c == SEP_IDX):
|
| 27 |
+
if c == SEP_IDX:
|
| 28 |
+
pre_c = c
|
| 29 |
+
continue
|
| 30 |
+
no_repeat_blank_label.append(c)
|
| 31 |
+
plate_index.append(idx)
|
| 32 |
+
pre_c = c
|
| 33 |
+
|
| 34 |
+
lpr_string = ''.join([plate_string[idx] for idx in no_repeat_blank_label])
|
| 35 |
+
lpr_score = 1
|
| 36 |
+
for idx, c in zip(plate_index, no_repeat_blank_label):
|
| 37 |
+
lpr_score *= preb[idx, c]
|
| 38 |
+
|
| 39 |
+
return lpr_string, lpr_score
|
| 40 |
+
|
| 41 |
+
def onnx_inference(opt):
|
| 42 |
+
|
| 43 |
+
providers = ["AxEngineExecutionProvider"]
|
| 44 |
+
session = axe.InferenceSession(opt.weights, providers=providers)
|
| 45 |
+
|
| 46 |
+
input_name = session.get_inputs()[0].name
|
| 47 |
+
output_name = [output.name for output in session.get_outputs()]
|
| 48 |
+
|
| 49 |
+
img = cv2.imread(f'{opt.source}')
|
| 50 |
+
img = cv2.resize(img, (opt.imgsz[1], opt.imgsz[0]))
|
| 51 |
+
input_data = np.expand_dims(img, axis=0)[..., ::-1].transpose((0, 3, 1, 2)).astype(np.uint8)
|
| 52 |
+
outputs = session.run(output_name, {input_name:input_data})
|
| 53 |
+
|
| 54 |
+
SEP_IDX = len(opt.PLATE_STRING) - 1
|
| 55 |
+
for lpr_out, color_out in zip(*outputs):
|
| 56 |
+
|
| 57 |
+
lpr_pred = np.max(lpr_out, axis=1).T
|
| 58 |
+
lpr_pred = softmax(lpr_pred)
|
| 59 |
+
lpr_string, lpr_score = GreedyDecode(lpr_pred, SEP_IDX, opt.PLATE_STRING)
|
| 60 |
+
|
| 61 |
+
color_out = np.max(np.max(color_out, axis=1), axis=1).reshape((1, -1))
|
| 62 |
+
color_out = softmax(color_out)[0]
|
| 63 |
+
color_label = color_out.argmax()
|
| 64 |
+
color_score = color_out[color_label]
|
| 65 |
+
|
| 66 |
+
print(f"Plate: [{lpr_string}], score: {lpr_score:.4f}, color: [{plate_colors[color_label]}], score:{color_score:.4f}")
|
| 67 |
+
|
| 68 |
+
def parse_opt():
|
| 69 |
+
parser = argparse.ArgumentParser()
|
| 70 |
+
parser.add_argument("--weights", nargs="+", type=str, default="./plr_650_npu3.axmodel", help="axmodel path")
|
| 71 |
+
parser.add_argument("--source", type=str, default="./苏A8A68Y.jpg", help="img_path")
|
| 72 |
+
parser.add_argument("--imgsz", "--img", "--img-size", nargs="+", type=int, default=[48, 192], help="inference size h,w")
|
| 73 |
+
parser.add_argument("--PLATE_STRING", type=str, default=u"皖沪津渝冀晋蒙辽吉黑苏浙京闽赣鲁豫鄂湘粤桂琼川贵云藏陕甘青宁新警学港澳台使领挂OABCDEFGHJKLMNPQRSTUVWXYZ0123456789#", help="Plate string map")
|
| 74 |
+
opt = parser.parse_args()
|
| 75 |
+
|
| 76 |
+
return opt
|
| 77 |
+
|
| 78 |
+
if __name__ == "__main__":
|
| 79 |
+
|
| 80 |
+
opt = parse_opt()
|
| 81 |
+
onnx_inference(opt)
|
config.json
ADDED
|
File without changes
|
det_res.jpg
ADDED
|
Git LFS Details
|
plate_end2end_res.jpg
ADDED
|
Git LFS Details
|
test.jpg
ADDED
|
Git LFS Details
|
苏A8A68Y.jpg
ADDED
|
Git LFS Details
|