add qrcode python demo

Signed-off-by: luckyxue0908 <luckyxue0908@gmail.com>
This commit is contained in:
luckyxue0908 2026-03-26 17:21:51 +08:00
parent face4a7a96
commit 96c66766bb
7 changed files with 453 additions and 0 deletions

173
examples/qrcode/README.md Executable file
View file

@ -0,0 +1,173 @@
# qrcode
## 1.Overview
## 2.Model Download
- **Open Source model**
- **Open Source projects:**
- **Export Model Step:**
- **Install ultralytics**
pip install torch==2.4.1
pip install torchvision==0.19.1
pip install ultralytics==8.3.0
- **Download weights**
- **Export Model**
- **Exported Model**
link to amlogic server( **onnx model or quantized tflite**)
## 3. Model Conversion
```
cd model
Usage: ./adla_covnert.sh model_path adla_tookkit_path target_platform
example
```
| Parameter | Discription |
| ----------------- | ------------------------------------------------------------ |
| model_path | onnx model path |
| adla_tookkit_path | path to adla_toolkit |
| target_platform | Specify target platform. for A311D2 : PRODUCT_PID0XA003. for S905X5: PRODUCT_PID0XA005 |
## 4. Demo Run
### CPP
#### 1. Compile
**Prerequisites:**
- Android NDK (r25e recommended)
- `ANDROID_NDK_PATH` environment variable set
**Build:**
```bash
# Build for arm64-v8a
cd examples/qrcode/cpp
./build-android.sh -a arm64-v8a
```
The executable will be generated at `build/android/qrcode_demo` (Note: executable name may vary, verify in build folder).
#### 2. Run
```bash
# Push executable to device
adb push build/android/qrcode_demo /data/local/tmp/
adb push model/qrcode_int8_A311D2.adla /data/local/tmp/
adb push imgs /data/local/tmp/
# Run on device
adb shell
cd /data/local/tmp
chmod +x qrcode_demo
export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib)
# Usage: ./qrcode_demo <model_path> <image_dir>
./qrcode_demo gesture_int8_A311D2.adla ./imgs
```
**Note:** Replace `gesture_int8_A311D2.adla` with your actual model file path.
### Python
**Prerequisites:**
- Python 3.10
- Required packages: `numpy`, `opencv-python`, `amlnnlite`
**Install dependencies:**
```bash
pip install numpy opencv-python amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl
```
**Run on device:**
```bash
python qrcode.py \
--model-path ./qrcode_int8_A311D2.adla \
--image-dir ./imgs \
--run-cycles 1 \
--loglevel INFO
```
Argument Descriptions:
| Argument | Description |
| ----------------- | ------------------------------------------------------------ |
| --board-work-path | Work path on board, default is /data/local/tmp |
| --model-path | path to .adla model |
| --image-dir | Directory containing test images |
| --run-cycles | Number of inference cycles, default is 1 |
| --loglevel | Logging level: DEBUG / INFO / WARNING / ERROR, default is WARNING |
The script will automatically process all image files (`.jpg`, `.jpeg`, `.png`, `.bmp`) in the current directory and save results to a `{model_name}_result` folder.
## 5.Results
**Performance Feedback**
By setting the loglevel to INFO, the program provides real-time performance metrics upon completion. The console log will display essential hardware and execution details, including:
- Hardware Information: System and ADLA library versions.
- Model Overview: Basic input/output configurations.
- NPU Metrics: Total inference time (latency) and total DRAM bandwidth consumption.
**Detection Output**
For each image, the program prints the processing information, including inference performance (average time, FPS, and bandwidth), detection results (number of objects, confidence score, bounding box coordinates, and decoded QR code content), and the path to the saved output image.
```bash
============================================================
Processing image 1/1: test.jpg
============================================================
I Average time: 6.514913082122803 ms
I FPS: 153.49398803710938
I Bandwidth: 14.548721313476562 Mbytes
Detected 1 objects:
1. score=0.851
box=[211, 188, 499, 492]
text=http://qr.sandisk.com/Rcl5c
Result saved to: qrcode_result/test.jpg
```
The output images, featuring bounding boxes and decoded QR code results, will be saved to the `qrcode_result` folder. You can pull the result folder back to view it:
```bash
adb pull /data/local/tmp/gesture_result
```
![alt text](result.jpg)
**Profiling Visualization**
When `--loglevel` is set to `INFO`, a successful run of the Python demo will generate a folder named after the model (e.g., {model_name}) in the script directory. This folder contains 5 HTML files that provide a visual and detailed breakdown of per-layer performance:
- `hard_op_chart.html` & `soft_op_chart.html`: Hardware/Software op execution details.
- `dram_rd_chart.html` & `dram_wr_chart.html`: Bandwidth read/write distribution.
- `pie_charts_distribution.html`: Overall resource allocation.
You can pull the result folder back to view it:
```bash
adb pull /data/local/tmp/qrcode_int8_A311D2
```
Taking hard_op_chart.html as an example (shown below), each layer's ADLA operator name includes parentheses containing the index of the corresponding quantized .tflite layer(s); by default, these indices are suppressed, and operators are labeled generically as "hardware" or "software" without numerical suffixes.
![alt text](Visualization.png)

BIN
examples/qrcode/Visualization.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

0
examples/qrcode/cpp/.gitkeep Executable file
View file

0
examples/qrcode/model/.gitkeep Executable file
View file

0
examples/qrcode/py/.gitkeep Executable file
View file

280
examples/qrcode/py/qrcode.py Executable file
View file

@ -0,0 +1,280 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 20242025 Amlogic, Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import os
import cv2
import glob
import argparse
import numpy as np
from pathlib import Path
from amlnnlite.api import AMLNNLite
def sigmoid(x):
return 1.0 / (1.0 + np.exp(-x))
def nms_xyxy(boxes, scores, iou_thres=0.5):
if len(boxes) == 0:
return []
boxes = np.asarray(boxes, dtype=np.float32)
scores = np.asarray(scores, dtype=np.float32)
x1 = boxes[:, 0]
y1 = boxes[:, 1]
x2 = boxes[:, 2]
y2 = boxes[:, 3]
areas = np.maximum(0.0, x2 - x1) * np.maximum(0.0, y2 - y1)
order = scores.argsort()[::-1]
keep = []
while order.size > 0:
i = order[0]
keep.append(i)
xx1 = np.maximum(x1[i], x1[order[1:]])
yy1 = np.maximum(y1[i], y1[order[1:]])
xx2 = np.minimum(x2[i], x2[order[1:]])
yy2 = np.minimum(y2[i], y2[order[1:]])
w = np.maximum(0.0, xx2 - xx1)
h = np.maximum(0.0, yy2 - yy1)
inter = w * h
union = areas[i] + areas[order[1:]] - inter
iou = inter / np.maximum(union, 1e-6)
inds = np.where(iou <= iou_thres)[0]
order = order[inds + 1]
return keep
def preprocess(img_path, input_size=(320, 320)):
img = cv2.imread(img_path)
if img is None:
return None, None, None
orig = img.copy()
img = cv2.resize(img, input_size)
img = img.astype(np.float32) / 255.0
img = np.expand_dims(img, axis=0)
return img, orig, orig.shape[:2]
def postprocess_qrcode(outputs, orig_shape, conf_thres=0.8, nms_thres=0.5, pad=40):
h0, w0 = orig_shape
sx = w0 / 320.0
sy = h0 / 320.0
out = outputs[0] if isinstance(outputs, (list, tuple)) else outputs
out = np.asarray(out)
if out.ndim == 4 and out.shape[0] == 1 and out.shape[1] == 1:
out = out[0, 0]
pred = out.transpose(1, 0)
elif out.ndim == 3 and out.shape[0] == 1:
out = out[0]
pred = out.transpose(1, 0)
else:
raise ValueError(f"Unexpected output shape: {out.shape}")
boxes_xyxy_320 = []
scores = []
for i in range(pred.shape[0]):
x, y, w, h, raw_score = pred[i]
score = sigmoid(raw_score)
if score < conf_thres:
continue
x1 = x - w / 2.0
y1 = y - h / 2.0
x2 = x + w / 2.0
y2 = y + h / 2.0
boxes_xyxy_320.append([float(x1), float(y1), float(x2), float(y2)])
scores.append(float(score))
keep = nms_xyxy(boxes_xyxy_320, scores, iou_thres=nms_thres)
results = []
for idx in keep:
x1, y1, x2, y2 = boxes_xyxy_320[idx]
score = scores[idx]
x1 = x1 * sx
y1 = y1 * sy
x2 = x2 * sx
y2 = y2 * sy
x1p = int(max(0, x1 - pad-10))
y1p = int(max(0, y1 - pad-15))
x2p = int(min(w0 - 1, x2 + pad-10))
y2p = int(min(h0 - 1, y2 + pad))
results.append({
"box": [x1p, y1p, x2p, y2p],
"score": score,
})
return results
def decode_qrcodes(orig, dets):
detector = cv2.QRCodeDetector()
decoded = []
h, w = orig.shape[:2]
for det in dets:
x1, y1, x2, y2 = map(int, det["box"])
score = det["score"]
x1 = max(0, min(x1, w - 1))
y1 = max(0, min(y1, h - 1))
x2 = max(0, min(x2, w - 1))
y2 = max(0, min(y2, h - 1))
if x2 <= x1 or y2 <= y1:
print(f"skip invalid box: {[x1, y1, x2, y2]}, score={score:.3f}")
continue
crop = orig[y1:y2, x1:x2].copy()
if crop.size == 0:
print(f"skip empty crop: {[x1, y1, x2, y2]}, score={score:.3f}")
continue
ch, cw = crop.shape[:2]
if cw < 20 or ch < 20:
print(f"skip too small crop: {[x1, y1, x2, y2]}, size=({cw},{ch}), score={score:.3f}")
continue
try:
text, points, _ = detector.detectAndDecode(crop)
except cv2.error as e:
print(f"skip cv2 decode error: {[x1, y1, x2, y2]}, score={score:.3f}, err={e}")
continue
decoded.append({
"box": [x1, y1, x2, y2],
"score": score,
"text": text,
"points": points,
})
return decoded
def draw_results(img, results):
vis = img.copy()
for r in results:
x1, y1, x2, y2 = r["box"]
score = r["score"]
text = r["text"]
cv2.rectangle(vis, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(
vis, f"{score:.3f}", (x1, max(0, y1 - 8)),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2
)
if text:
cv2.putText(
vis, text[:60], (x1, min(img.shape[0] - 10, y2 + 25)),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 2
)
return vis
def main():
parser = argparse.ArgumentParser(description="QRCode AMLNNLite Demo")
parser.add_argument('--board-work-path', type=str, default='/data/local/tmp')
parser.add_argument('--model-path', required=True, help='Path to .adla model')
parser.add_argument('--image-dir', required=True, help='Directory of test images')
parser.add_argument('--run-cycles', type=int, default=1, help='Inference cycles')
parser.add_argument('--loglevel', type=str, default='WARNING',
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'])
parser.add_argument('--conf-thres', type=float, default=0.8)
parser.add_argument('--nms-thres', type=float, default=0.5)
parser.add_argument('--pad', type=int, default=40)
args = parser.parse_args()
amlnn = AMLNNLite()
amlnn.config(
board_work_path=args.board_work_path,
model_path=args.model_path,
run_cycles=args.run_cycles,
loglevel=args.loglevel
)
amlnn.init()
image_files = sorted(glob.glob(os.path.join(args.image_dir, "*.[jp][pn][g]")))
if not image_files:
print(f"No images found in {args.image_dir}")
amlnn.uninit()
return
res_dir = "qrcode_result"
os.makedirs(res_dir, exist_ok=True)
for idx, img_path in enumerate(image_files, start=1):
print("=" * 60)
print(f"Processing image {idx}/{len(image_files)}: {Path(img_path).name}")
print("=" * 60)
inp, orig, orig_shape = preprocess(img_path)
if inp is None:
print(f"Failed to read: {img_path}")
continue
outputs = amlnn.inference(inp, inputs_data_format='NHWC')
dets = postprocess_qrcode(
outputs,
orig_shape,
conf_thres=args.conf_thres,
nms_thres=args.nms_thres,
pad=args.pad
)
results = decode_qrcodes(orig, dets)
if len(results) == 0:
print(" No objects detected")
else:
print(f" Detected {len(results)} objects:")
for i, r in enumerate(results, 1):
print(f" {i}. score={r['score']:.3f}")
print(f" box={r['box']}")
print(f" text={r['text']}")
vis = draw_results(orig, results)
save_path = os.path.join(res_dir, Path(img_path).name)
cv2.imwrite(save_path, vis)
print(f" Result saved to: {save_path}")
if args.loglevel == 'INFO':
print("\nPerformance analysis visualization starting...")
amlnn.visualize()
amlnn.uninit()
if __name__ == "__main__":
main()

BIN
examples/qrcode/result.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB