无垠的广袤 · 1 天前 · 上海

【“星睿O6”AI PC开发套件评测】数字识别

【“星睿O6”AI PC开发套件评测】数字识别

本文介绍了瑞莎星睿 O6 (Radxa Orion O6) 开发板实现单个和多个数字识别、网页手写数字识别的项目设计,包括准备工作、环境搭建、MNIST 数据集和 ONNX 模型、流程图、关键代码、网页设计以及效果演示等。

项目介绍

  • 准备工作:包括所需 Python 环境、数据集训练、模型下载、软件包的安装部署等;
  • 单个数字识别:通过预训练模型实现单张图片包含一个数字的识别和板端推理;
  • 多个数字识别:通过预训练模型实现单张图片包含多个数字的识别和板端推理;
  • 网页手写数字识别:通过 Web 网页端构建手写面板,O6 接收手写数字图片,调用预训练模型完成板端推理,回传识别结果至网页端。

MNIST 数据集

Modified National Institute of Standards and Technology (MNIST) 是一个包含手写数字的数据集,里面有上万张数字图片,每张图片都标注了对应的数字标签(0到9)。该数据集可用来训练识别手写数字的神经网络。

mnist.jpg

详见:yann.lecun.com

ONNX

Open Neural Network Exchange (ONNX) 是一个开放的生态系统,为 AI 开发人员提供支持 随着项目的发展选择正确的工具。

onnx.jpg

ONNX 为 AI 模型(包括深度学习和传统 ML)提供开源格式。它定义了一个可扩展的计算图模型,以及内置运算符和标准的定义 数据类型。

详见:onnx/onnx: Open standard for machine learning interoperability .

准备工作

系统安装及环境搭建详见:【“星睿O6”AI PC开发套件评测】介绍、系统安装 .

硬件连接

  • 若采用 SSH 远程登录操作,则需连接电源供电、网线;
  • 若采用本地登录,则需连接 HDMI 视频流传输线、USB 键盘连接线等;

库安装

  • 执行指令 sudo apt install python3-opencv 安装 OpenCV
  • 安装解析 ONNX 模型所需的 onnxruntime 库,安装网页所需 flask 库,终端执行
sudo apt install python3-pip
sudo pip3 install onnxruntime --break-system-packages
sudo apt install python3-flask

模型下载

  • 下载 模型文件至本地 ./model 文件;
mkdir model
cd model
wget https://github.com/onnx/models/blob/main/validated/vision/classification/mnist/model/mnist-12.onnx

详见:mnist | onnx/models · GitHub .

单个数字识别

采用预训练的 MNIST 轻量化的 onnx 模型实现单个数字识别的板端推理,并弹窗显示、终端打印识别结果。

流程图

flowchart TD
    A([开始])-->A1[读取图片] --> B[载入模型]
    B --> C[灰度读图]
    C --> F[反色]
    F --> G[调整尺寸]
    G --> J[ONNX推理]
    J --> K[ArgMax数字]
    K --> N[弹窗显示] --> P
    J --> L[计时]
    L --> M[终端打印]
    M --> P[按ESC键]
    P --> O([结束])

代码

终端执行 touch mnist_demo.py 指令新建文件,并使用 nano 文本编辑器添加如下代码

#!/usr/bin/env python3
import cv2, numpy as np, onnxruntime as ort, time, sys

model = './model/mnist-12.onnx'
img_p = sys.argv[1] if len(sys.argv) > 1 else './img/num2.jpg'

# ---------- 1. 载入模型 ----------
sess = ort.InferenceSession(model, providers=['CPUExecutionProvider'])
in_name  = sess.get_inputs()[0].name   # 'Input3'  shape [1,1,28,28]
out_name = sess.get_outputs()[0].name  # 'Plus214_Output_0' 10 类置信度

# ---------- 2. 读图并预处理 ----------
img = cv2.imread(img_p, cv2.IMREAD_GRAYSCALE)
if img is None:
    raise FileNotFoundError(img_p)

# 反二值化 + Resize 28×28 + 归一化 0~1
img = cv2.bitwise_not(img)          # 白底黑字→黑底白字
img = cv2.resize(img, (28, 28))
blob = img.astype(np.float32) / 255.0
blob = blob.reshape(1, 1, 28, 28)   # NCHW

# ---------- 3. 推理 ----------
t0 = time.time()
pred = sess.run([out_name], {in_name: blob})[0]  # [1,10]
digit = int(np.argmax(pred))
cost = (time.time() - t0) * 1000

# ---------- 4. 结果 ----------
print(f'识别结果: {digit}  (置信度: {pred[0,digit]:.3f})  耗时: {cost:.1f} ms')

# 简单弹窗(可选)
cv2.putText(img, f'{digit}', (5, 22), cv2.FONT_HERSHEY_SIMPLEX, 0.8, 255, 2)
cv2.imshow('digit', img)
cv2.waitKey(0)

保存代码。

效果

终端执行 python mnist_demo.py ,弹窗显示处理后的图片

mnist_single_win.jpg

同时终端打印识别结果及置信度

mnist_single_recog_print.jpg

多个数字识别

采用预训练的 MNIST 轻量化的 onnx 模型实现多个数字识别的板端推理,并弹窗显示和终端打印识别结果。

流程图

flowchart TD
    A([开始]) --> B[载入模型]
    B --> C[读取图片]
    C --> F[灰度+反色]
    F --> G[中值滤波去噪]
    G --> H[OTSU二值化]
    H --> I[外轮廓]
    I --> J[遍历每个轮廓]
    J --> K{尺寸过滤}
    K --> M[调整尺寸]
    M --> N[归一化]
    N --> O[ONNX推理]
    O --> P[ArgMax获得数字]
    P --> Q[选框和标签]
    Q --> J
    J --> R[打印结果]
    R --> S[按ESC键]
    S --> T([结束])

代码

终端执行 touch mnist_multi.py 指令新建文件,并使用 nano 文本编辑器添加如下代码

#!/usr/bin/env python3
import cv2, numpy as np, onnxruntime as ort, time, sys

# ---------- 0. 载入 ONNX ----------
sess = ort.InferenceSession('./model/mnist-12.onnx', providers=['CPUExecutionProvider'])
in_name = sess.get_inputs()[0].name        # 'Input3'
out_name = sess.get_outputs()[0].name      # 'Plus214_Output_0'

# ---------- 1. 读图 ----------
img_path = sys.argv[1] if len(sys.argv) > 1 else './img/numbers2.jpg'
img = cv2.imread(img_path)
assert img is not None, 'can not read '+img_path
show = img.copy()

# ---------- 2. 预处理 ----------
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 白底黑字→黑底白字,方便找轮廓
binary = cv2.bitwise_not(gray)
# 轻模糊去噪
binary = cv2.medianBlur(binary, 3)
# 二值化
_, th = cv2.threshold(binary, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)

# ---------- 3. 找每个数字 ----------
cnts, _ = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
digits = []
for c in cnts:
    x, y, w, h = cv2.boundingRect(c)
    # 过滤太小/太扁区域
    if w < 5 or h < 15 or w > 200 or h > 200 or w/h > 1.2 or h/w > 4:
        continue
    # 往外扩一点
    pad = 4
    x1 = max(0, x-pad); y1 = max(0, y-pad)
    x2 = min(img.shape[1], x+w+pad); y2 = min(img.shape[0], y+h+pad)

    roi = th[y1:y2, x1:x2]
    # 缩放到 28×28
    roi = cv2.resize(roi, (28, 28), interpolation=cv2.INTER_AREA)
    roi = roi.astype(np.float32)/255.0
    roi = roi.reshape(1, 1, 28, 28)

    # ---------- 4. ONNX 推理 ----------
    pred = sess.run([out_name], {in_name: roi})[0]
    digit = int(np.argmax(pred))
    digits.append((x1, digit, pred[0, digit]))

    # 画框+结果
    cv2.rectangle(show, (x1, y1), (x2, y2), (0, 255, 0), 2)
    cv2.putText(show, str(digit), (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX,
                0.9, (0, 0, 255), 2)

# ---------- 5. 输出 ----------
print('found digits:', ''.join(str(d[1]) for d in sorted(digits)))
t = time.time()
print('total cost: %.0f ms' % ((time.time()-t)*1000))

cv2.imshow('multi-digit', show)
cv2.waitKey(0)

保存代码。

效果

终端执行 python mnist_multi.py 弹窗显示多个数字的识别结果;

mnist_multi_win.jpg

同时终端输出识别结果与耗时

mnist_multi_recog_print.jpg

网页手写数字识别

在实现单张数字图片识别的基础上,进一步实现网页手写数字识别,包括流程图、关键代码、HTML网页代码以及效果演示等。

流程图

flowchart TD
    A([开始]) --> A1[进入网页] --> B[手写数字]
    B --> C[发送图片]
    C --> D[Flask 接收]
    D --> E[调整尺寸]
    E --> F[ONNX推理]
    F --> H[回传结果]
    H --> I[前端显示]
    I --> B

文件目录

/home/ljl/MNIST
├── hnr_mnist_web.py      # 主程序 Flask 入口
├── model/
│   └── mnist-12.onnx     # ONNX 模型文件
└── web/
    └── index.html        # 网页文件

代码

终端执行 touch mnist_web.py 指令新建文件,并 nano mnist_web.py 添加如下代码

#!/usr/bin/env python3
import cv2, numpy as np, onnxruntime as ort, base64, io, time
from flask import Flask, render_template_string, request, jsonify

app = Flask(__name__)
MODEL = './model/mnist-12.onnx'
sess = ort.InferenceSession(MODEL, providers=['CPUExecutionProvider'])
in_name  = sess.get_inputs()[0].name   # Input3
out_name = sess.get_outputs()[0].name  # Plus214_Output_0

def softmax(x):
    """稳定版 softmax,返回概率数组"""
    x = x - np.max(x)          # 防溢出
    exp_x = np.exp(x)
    return exp_x / np.sum(exp_x)

def decode_canvas(data_url):
    header, encoded = data_url.split(',', 1)
    img = cv2.imdecode(np.frombuffer(base64.b64decode(encoded), np.uint8),
                       cv2.IMREAD_GRAYSCALE)
    return img

@app.route('/')
def index():
    with open('./web/index.html') as f:
        return render_template_string(f.read())

@app.route('/predict', methods=['POST'])
def predict():
    t0 = time.time()
    img = decode_canvas(request.json['image'])
    img = cv2.bitwise_not(img)          # 白底黑字-转换为-黑底白字
    img = cv2.resize(img, (28, 28))
    blob = img.astype(np.float32) / 255.0
    blob = blob.reshape(1, 1, 28, 28)   # NCHW
    pred = sess.run([out_name], {in_name: blob})[0][0] # logits
    prob = softmax(pred)                               # 归一化到 0~1
    digit = int(np.argmax(prob))
    conf  = float(prob[digit])          # 置信度
    cost  = (time.time() - t0) * 1000
    return jsonify({'digit': digit, 'conf': round(conf, 3), 'time': round(cost, 1)})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080, debug=False)

保存代码。

网页设计

  • 终端执行 mkdir web 新建文件夹,用于存放网页文件;
  • 进入 web 文件夹,执行 touch index.html 指令新建网页文件,并 nano index.html 添加如下代码
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>Radxa Orion O6 手写识别</title>
<style>
  body{font-family:Arial,Helvetica,sans-serif;text-align:center;background:#f2f2f2}
  canvas{border:1px solid #999;display:block;margin:10px auto;background:#fff}
  button{font-size:18px;margin:6px;padding:6px 14px}
  #res{color:#d44;font-size:20px;font-weight:bold}
</style>
</head>
<body>
<h2>Radxa Orion O6 手写数字识别</h2>
<canvas id="c" width="280" height="280"></canvas>
<div>
  <button id="clear">清空</button>
  <button id="go">识别</button>
</div>
<p id="res">等待识别…</p>

<script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
ctx.fillStyle='#fff'; ctx.fillRect(0,0,280,280);
ctx.lineWidth=20; ctx.lineCap='round'; ctx.strokeStyle='#000';

let drawing=false;
function pos(e){const r=canvas.getBoundingClientRect();return [e.clientX-r.left,e.clientY-r.top];}
canvas.onmousedown=e=>{drawing=true;ctx.beginPath();ctx.moveTo(...pos(e));};
canvas.onmousemove=e=>{if(drawing){ctx.lineTo(...pos(e));ctx.stroke();}};
canvas.onmouseup=()=>drawing=false;
document.getElementById('clear').onclick=()=>{
  ctx.fillStyle='#fff';ctx.fillRect(0,0,280,280);document.getElementById('res').textContent='等待识别…';
};

document.getElementById('go').onclick=async()=>{
  const tmp=document.createElement('canvas');
  tmp.width=tmp.height=28;
  const tctx=tmp.getContext('2d');
  tctx.drawImage(canvas,0,0,28,28);
  const dataURL=tmp.toDataURL('image/png');
  const r=await fetch('/predict',{
    method:'POST',
    headers:{'Content-Type':'application/json'},
    body:JSON.stringify({image:dataURL})
  }).then(r=>r.json());
  document.getElementById('res').textContent=
    `识别结果:${r.digit}  (置信度 ${(r.conf*100).toFixed(1)}%)  耗时:${r.time} ms`;
};
</script>
</body>
</html>

保存代码。

效果

  • 终端执行指令 python3 mnist_web.py 运行程序;
  • 终端打印网页访问地址、访问信息等;

mnist_web_print.jpg

  • 同一局域网下,浏览器输入终端输出的网页地址 192.168.1.114:8080 打开目标网页;
  • 用鼠标或触摸板在面板区域手写数字,点击 识别 按钮,下方立即显示识别结果、置信度、耗时等信息;

hnr_mnist_web.jpg

  • 更多手写数字识别效果如下

mnist_web.jpg

  • 终端界面 Ctrl + C 即可结束进程。

总结

本文介绍了瑞莎星睿 O6 (Radxa Orion O6) 开发板实现网页手写数字识别的项目设计,包括准备工作、环境搭建、MNIST 数据集和 ONNX 模型、流程图、关键代码以及效果演示等,为相关产品在 AI 视觉领域的开发设计和快速应用提供了参考。

推荐阅读
关注数
2
文章数
14
MCU 开发者和爱好者
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息