【Arduino UNO Q】 边缘 AI 视觉部署方案:手写数字识别

【Arduino UNO Q】 边缘 AI 视觉部署方案:手写数字识别

本文介绍了 Arduino UNO Q 开发板结合 OpenCV 和 MNIST 数据集实现手写数字识别的项目设计。

项目介绍

Arduino UNO Q 开发板结合 OpenCV 与 MNIST 数据集实现手写数字识别。

  • 准备工作:硬件连接、软件更新、环境搭建、模型下载等;
  • MNIST:使用 OpenCV 和 MNIST 数据集实现手写数字识别;
  • 网页设计:设计 Web 手写数字面板,实现网页端实时手写数字识别。

准备工作

包括硬件连接、系统安装、软件更新等。

详见:Arduino UNO Q 介绍、环境搭建、工程测试 .

硬件连接

这里采用 SSH 远程控制,使用 Type-C 数据线供电并 WiFi 联网即可。

软件更新

更新软件包

bash 复制代码
sudo apt update
sudo apt upgrade

环境搭建

  • 安装 OpenCV 和 opencv-data 软件包;

    bash 复制代码
    sudo apt install python3-opencv
    sudo apt install opencv-data
  • 执行如下指令验证

bash 复制代码
python3 -c "import cv2,sys;print(cv2.__version__,sys.version)"

终端打印 opencv 版本号。

模型下载

下载预训练的 MNIST 数字识别 ./model/mnist-12.onnx 模型

bash 复制代码
wget https://github.com/onnx/models/blob/main/validated/vision/classification/mnist/model/mnist-12.onnx

详见:mnist | github .

MNIST

使用 OpenCV 自带的 DNN 工具和 MNIST 数据集预训练模型,加载目标手写数字图片并打印识别结果。

流程图

开始
读取图片
加载模型
检测数字
打印识别结果
弹窗显示
按键退出
结束

代码

终端执行 touch mnist_demo.py 新建程序文件,添加如下代码

python 复制代码
#!/usr/bin/env python3
import cv2, numpy as np, time, sys

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

# ---------- 载入模型 ----------
net = cv2.dnn.readNetFromONNX(model)
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)

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

img_gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)  # 用于预处理
img_gray = cv2.bitwise_not(img_gray)
img28 = cv2.resize(img_gray, (28, 28))
blob = img28.astype(np.float32) / 255.0
blob = blob.reshape(1, 1, 28, 28)

# ---------- 推理 ----------
t0 = time.time()
net.setInput(blob)
logits = net.forward().squeeze()
exp = np.exp(logits - np.max(logits))
pred = exp / np.sum(exp)          # shape (10,)
digit = int(np.argmax(pred))
confidence = float(pred[digit])
cost = (time.time() - t0) * 1000

print(f'识别结果: {digit}  (置信度: {confidence:.3f})  耗时: {cost:.1f} ms')

# ---------- 可视化 ----------
show = img_bgr.copy()

# 阈值(白字黑底)
_, bin_img = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

# 数字轮廓
cnts, _ = cv2.findContours(bin_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if cnts:
    # 取最大轮廓(防噪点)
    cnt = max(cnts, key=cv2.contourArea)
    x, y, w_box, h_box = cv2.boundingRect(cnt)
    # 画框
    cv2.rectangle(show, (x, y), (x + w_box, y + h_box), (0, 255, 0), 3)

# ---------- 标签 ----------
text = f'{digit}'
font, scale, thick = cv2.FONT_HERSHEY_SIMPLEX, 0.8, 2
(tw, th), _ = cv2.getTextSize(text, font, scale, thick)
cv2.rectangle(show, (x, y - th - 6), (x + tw + 4, y), (0, 255, 0), cv2.FILLED)
cv2.putText(show, text, (x + 2, y - 4), font, scale, (0, 0, 255), thick, cv2.LINE_AA)

cv2.namedWindow("MNIST", cv2.WINDOW_NORMAL)
cv2.imshow('MNIST', show)
cv2.waitKey(0)

保存代码。

效果

  • 终端运行指令 python3 mnist_demo.py --image_path ./img/nums.jpg ,打印结果

  • 弹窗显示结果,按任意键关闭窗口;

    效果如下

网页设计

包括前端网页和后端 flask 服务器设计。

代码

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

python 复制代码
#!/usr/bin/env python3
"""
手写数字识别网页服务器
- 模型:mnist-12.onnx
- 推理:OpenCV-DNN(CPU)
"""
import cv2, numpy as np, base64, time
from flask import Flask, render_template_string, request, jsonify

app = Flask(__name__)
MODEL = './model/mnist-12.onnx'
net = cv2.dnn.readNetFromONNX(MODEL)
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)

# confidence
def softmax(x):
    x = x - np.max(x)
    return np.exp(x) / np.sum(np.exp(x))

# 解码前端画布 base64
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 = cv2.dnn.blobFromImage(img, 1/255.0, (28, 28), swapRB=False)
    # 发送至 Web
    net.setInput(blob)
    logits = net.forward()[0, :]
    prob = softmax(logits)
    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)

保存代码。

网页设计

新建网页文件 ./web/index.html 添加代码如下

python 复制代码
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>Arduino UNO Q MNIST</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>Arduino UNO Q MNIST</h2>
<canvas id="c" width="280" height="280"></canvas>
<div>
  <button id="clear">Clear</button>
  <button id="go">Recognize</button>
</div>
<p id="res">Waiting...</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='Waiting...';
};

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=
    `Result:${r.digit}  (Confidence: ${(r.conf*100).toFixed(1)}%)  Time: ${r.time} ms`;
};
</script>
</body>
</html>

保存代码。

效果

终端执行指令 python3 mnist_web.py 输出网页服务器地址等信息

浏览器中输入网址 http://192.168.31.109:8080 打开数字识别网页界面;

  • 使用鼠标或触摸屏在空白面板上手写数字;
  • 点击 Recognize 按钮,下方立即显示识别结果;
  • 点击 Clear 按钮清空手写板;

更多测试效果

总结

本文介绍了 Arduino UNO Q 开发板结合 OpenCV 与 MNIST 数据集实现手写数字识别,包括OpenCV 部署、流程图、关键代码和工程测试,为相关产品在边缘 AI 视觉应用的快速开发设计提供了参考。

相关推荐
Juicedata3 小时前
JuiceFS 企业版 5.3 特性详解:单文件系统支持超 5,000 亿文件,首次引入 RDMA
大数据·人工智能·机器学习·性能优化·开源
Piar1231sdafa3 小时前
蓝莓目标检测——改进YOLO11-C2TSSA-DYT-Mona模型实现
人工智能·目标检测·计算机视觉
愚公搬代码3 小时前
【愚公系列】《AI短视频创作一本通》002-AI引爆短视频创作革命(短视频创作者必备的能力)
人工智能
数据猿视觉3 小时前
新品上市|奢音S5耳夹耳机:3.5g无感佩戴,178.8元全场景适配
人工智能
2301_790300963 小时前
Python单元测试(unittest)实战指南
jvm·数据库·python
蚁巡信息巡查系统3 小时前
网站信息发布再巡查机制怎么建立?
大数据·人工智能·数据挖掘·内容运营
AI浩3 小时前
C-RADIOv4(技术报告)
人工智能·目标检测
Purple Coder3 小时前
AI赋予超导材料预测论文初稿
人工智能
Data_Journal3 小时前
Scrapy vs. Crawlee —— 哪个更好?!
运维·人工智能·爬虫·媒体·社媒营销
云边云科技_云网融合3 小时前
AIoT智能物联网平台:架构解析与边缘应用新图景
大数据·网络·人工智能·安全