wzf19947 commited on
Commit
6ebe861
·
1 Parent(s): cc7adc8

first commit

Browse files
.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
+ ![](test.jpg)
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
+ ![](det_res.jpg)
92
+
93
+ #### Plate Recognition
94
+ Input image:
95
+ ![](苏A8A68Y.jpg)
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
+ ![](test.jpg)
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
+ ![](plate_end2end_res.jpg)
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

  • SHA256: d910296e4deef13e22de05bb4349d200281548cc72c5fb551b4cea87bc17d269
  • Pointer size: 131 Bytes
  • Size of remote file: 440 kB
plate_end2end_res.jpg ADDED

Git LFS Details

  • SHA256: 1df2e05355d78bbaf364e4a9ff825ff0cb2c36cb98958c0c553f948e44e09b52
  • Pointer size: 131 Bytes
  • Size of remote file: 440 kB
test.jpg ADDED

Git LFS Details

  • SHA256: 6d96e9ab8dc69d226e8e4ab9543c9de0590b2b11e351ed41b106c9cec54125d9
  • Pointer size: 131 Bytes
  • Size of remote file: 439 kB
苏A8A68Y.jpg ADDED

Git LFS Details

  • SHA256: 5e439109b2773ae9f8b71d3cfedea4e7fc951c3b205c53c49b3e48bf7fd13472
  • Pointer size: 130 Bytes
  • Size of remote file: 10.2 kB