【Raspberry Pi Sense HAT】网页手写数字识别与显示
本文介绍了 Raspberry Pi Sensor HAT 扩展板结合 MNIST 数据集和 ONNX 模型实现网页手写数字识别与显示的项目设计,包括环境搭建、工程设计、流程图、效果演示等。
项目介绍
准备工作:硬件连接、MNIST 预编译模型获取、Python 库安装等;
工程测试:流程图、后端 Flask 服务器代码、前端 Web 网页设计代码;
效果演示:程序运行、网页UI展示、矩阵 LED 显示、动态效果等。
MNIST
Modified National Institute of Standards and Technology (MNIST) 是一个包含手写数字的数据集,里面有上万张数字图片,每张图片都标注了对应的数字标签(0到9)。该数据集可用来训练识别手写数字的神经网络。


硬件连接
将 SENSE-HAT 与树莓派 CM0 对应 40pin 接口连接;
使用 Type-C 数据线连接开发板和 5V 电源;


环境搭建
创建并激活虚拟环境;
python -m venv .venv --system-site-packages source .venv/bin/activate
安装 OpenCV 和 onnxruntime 软件包;
pip install opencv-python onnxruntime
安装 SENSE-HAT 软件包;
sudo apt install sense-hat
模型下载
获取 onnx 模型文件;
wget https://github.com/onnx/models/blob/main/validated/vision/classification/mnist/model/mnist-12.onnx
工程目录
工程共包含三个文件,分别为 Flask 后端服务器代码、Web 前端代码、ONNX 模型。
~/mnist/ # 项目根目录(当前工作目录) ├── mnist_web.py # 主程序(Flask + ONNX Runtime + Sense HAT) ├── model/ │ └── mnist-12.onnx # 本地 ONNX 模型文件 └── web/ └── index.html # 网页前端文件(手写面板)
流程图

服务器后端
新建 mnist_web_sensehat.py 文件,添加如下代码
import os
import sys
import numpy as np
import onnxruntime as ort
from flask import Flask, send_from_directory, request, jsonify
from sense_hat import SenseHat
# ==================== 配置 ====================
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
MODEL_PATH = os.path.join(BASE_DIR, "model/mnist-12.onnx")
INDEX_PATH = os.path.join(BASE_DIR, "web/index.html")
HOST = "0.0.0.0" # 监听所有网络接口,允许局域网访问
PORT = 8081
sense = SenseHat()
sense.set_rotation(0)
red = (255, 0, 0)
# ==================== 加载模型 ====================
print("=" * 60)
print(" MNIST 手写数字识别服务")
print("=" * 60)
# 检查模型文件
if not os.path.exists(MODEL_PATH):
print(f"\\n错误: 模型文件不存在: {MODEL_PATH}")
print("\\n请下载模型文件:")
print(" wget https://github.com/onnx/models/raw/main/validated/vision/classification/mnist/model/mnist-12.onnx")
print("\\n或者使用 piwheels:")
print(" wget https://piwheels.org/simple/onnx-models/mnist-12.onnx")
sys.exit(1)
print(f"\\n模型路径: {MODEL_PATH}")
print(f"网页路径: {INDEX_PATH}")
# 优化会话选项
sess_options = ort.SessionOptions()
sess_options.inter_op_num_threads = 2 # 限制线程数,减少资源占用
sess_options.intra_op_num_threads = 2
sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
try:
session = ort.InferenceSession(
MODEL_PATH,
sess_options=sess_options,
providers=['CPUExecutionProvider']
)
print(f"\\n✓ 模型加载成功!")
print(f" 输入名称: {session.get_inputs()[0].name}")
print(f" 输入形状: {session.get_inputs()[0].shape}")
print(f" 输出名称: {session.get_outputs()[0].name}")
print(f" 输出形状: {session.get_outputs()[0].shape}")
except Exception as e:
print(f"\\n✗ 模型加载失败: {e}")
sys.exit(1)
# ==================== Flask 应用 ====================
app = Flask(__name__)
@app.route("/")
def index():
"""返回前端页面"""
if os.path.exists(INDEX_PATH):
return send_from_directory(os.path.join(BASE_DIR, "web"), "index.html")
else:
return "<h1>index.html not found</h1><p>请确认 index.html 路径</p>", 404
@app.route("/predict", methods=["POST"])
def predict():
"""
接收前端传来的 28x28 图像数据,返回推理结果
请求 JSON:
{"image": [784个float值]} // 28x28=784, 范围[0.0,1.0], 黑底白字
响应 JSON:
{
"digit": 7,
"confidence": 0.9823,
"probabilities": [0.001, 0.002, ..., 0.9823, ...]
}
"""
try:
data = request.get_json()
if not data or "image" not in data:
return jsonify({"error": "缺少 image 字段"}), 400
image_data = data["image"]
# 验证数据长度
if len(image_data) != 784:
return jsonify({"error": f"图像数据长度应为 784 (28x28),实际收到 {len(image_data)}"}), 400
# 转换为 numpy 数组
input_array = np.array(image_data, dtype=np.float32)
# 重塑为 (1, 1, 28, 28) - NCHW 格式
input_tensor = input_array.reshape(1, 1, 28, 28)
# 执行推理
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
outputs = session.run([output_name], {input_name: input_tensor})
logits = outputs[0][0] # 10 个 logits
# Softmax 转换为概率
exp_scores = np.exp(logits - np.max(logits)) # 数值稳定性
probabilities = exp_scores / np.sum(exp_scores)
# 获取预测结果
predicted_digit = int(np.argmax(probabilities))
confidence = float(probabilities[predicted_digit])
return jsonify({
"digit": predicted_digit,
"confidence": confidence,
"probabilities": probabilities.tolist()
})
sense.show_letter(str(predicted_digit), text_colour=red) # 显示推理结果
except Exception as e:
print(f"推理错误: {e}")
import traceback
traceback.print_exc()
return jsonify({"error": str(e)}), 500
@app.route("/health")
def health():
"""健康检查"""
return jsonify({"status": "ok", "model_loaded": True})
# ==================== 启动 ====================
if __name__ == "__main__":
print("\\n" + "=" * 60)
print(" 服务启动中...")
print(f" 访问地址: http://{HOST}:{PORT}")
print(f" 本机测试: http://127.0.0.1:{PORT}")
print("=" * 60 + "\\n")
app.run(
host=HOST,
port=PORT,
debug=False,
threaded=True
)保存代码。
网页前端
新建 web 文件夹并打开,新建 index.html 文件,添加如下代码

保存代码。
Web 测试
使用浏览器打开该 HTML 文件,正常显示数字识别前端面板设计,效果如下


效果演示
终端执行指令 python mnist_web_sensehat.py 运行 Flask 服务器后端;
依次加载模型、加载本地网页前端文件 index.html、启动服务器、打印访问地址 192.168.31.117:8081;


打开浏览器,输入网址 192.168.31.117:8081 并访问;
使用鼠标在左侧手写面板写出 0-9 数字,点击识别按钮;
右侧立即显示识别结果和各个数字的推理结果与置信度;
同时 SENSE-HAT 面板 LED 矩阵显示推理结果(数字 8);


动态演示
网页手写数字,点击识别按钮,显示结果,同时 LED 矩阵更新数字;

总结
本文介绍了 Raspberry Pi Sensor HAT 扩展板结合 MNIST 数据集和 ONNX 模型实现网页手写数字识别与显示的项目设计,包括环境搭建、工程设计、流程图、效果演示等,为相关产品在边缘 AI 领域的快速开发和应用设计提供了参考。
我要赚赏金
