在 CanMV K210 入门和 AI 视觉项目中,MNIST 手写数字识别是一个很适合拆解的分类模型实验。它不像人脸检测那样输出目标框,而是把输入图像送入模型后,判断画面中的数字更接近 0 到 9 中的哪一类。
本实验使用 CanMV K210 运行 4.7_Mnist_recognition.py。程序通过摄像头采集画面,对图像进行开窗、灰度化、缩放、反色、字符增强和 AI 格式转换,再送入 KPU 执行 MNIST 分类模型推理。模型输出 10 个分类结果后,程序取置信度最高的一项作为识别数字,并在 LCD 画面上显示 num: x。
| 学习目标 | 说明 |
|---|---|
| 理解分类模型流程 | 掌握图像输入、预处理、KPU 推理、分类输出和结果显示之间的关系 |
| 区分分类与检测 | 理解 MNIST 输出数字类别,人脸检测输出目标位置框 |
| 掌握图像预处理 | 使用灰度化、缩放、反色和字符增强改善模型输入 |
| 理解 KPU 推理结果 | 从模型输出列表中找到最大值,并得到对应数字类别 |
| 建立调试思路 | 从摄像头画面、模型路径、输入尺寸、光照和数字书写方式排查问题 |
这个实验的重点不是让 LCD 显示一个数字那么简单,而是理解"图像如何变成分类结果"。摄像头采集到的是一张实时图像,经过预处理后变成适合模型输入的数据,KPU 输出的是 10 个类别的分数,程序再从这些分数中选出最可能的数字。
文章目录
理论基础
MNIST 手写数字识别属于图像分类任务。分类模型的目标是判断输入图像属于哪一个类别,本实验中的类别就是数字 0 到 9。和人脸检测不同,分类模型通常不会告诉目标在画面中的具体位置,也不会输出矩形框,而是输出每个类别对应的分数或置信度。
本实验中,摄像头输出的是 QVGA 图像,程序通过 sensor.set_windowing((224, 224)) 截取中间区域,让识别对象尽量处于画面中心。随后图像被转换成灰度图,并缩放为 112×112。MNIST 模型通常关注的是数字笔画形状,因此灰度化可以减少颜色干扰,缩放可以让输入尺寸匹配模型要求,反色和字符增强可以让数字轮廓更接近模型训练时的数据特征。
代码中的 out = kpu.run_with_output(img_mnist2, getlist=True) 会返回一个列表,这个列表可以理解成模型对 0 到 9 每个数字的判断结果。程序使用 max(out) 找到分数最高的一项,再用 out.index(max_mnist) 得到对应的数字编号。比如最大值出现在列表第 3 个位置,就认为当前图像更像数字 3。
下面的流程图展示的是 MNIST 手写数字识别的数据处理链路。它不是普通电路接线图,而是从摄像头图像到分类结果的 AI 推理流程。
摄像头采集
sensor.snapshot()
开窗取图
224×224 画面区域
灰度化处理
to_grayscale()
缩放图像
resize(112,112)
反色与字符增强
invert() / strech_char()
AI 输入格式转换
pix_to_ai()
KPU 分类推理
uint8_mnist_cnn_model.kmodel
输出 10 类分数
0 ~ 9
取最大分数索引
max() / index()
LCD 显示结果
num: x
SD 卡模型文件
/sd/uint8_mnist_cnn_model.kmodel
串口输出
print(display_str)
gc.collect()
内存回收
从这条链路可以看出,MNIST 识别效果不仅取决于模型文件,还取决于输入图像是否符合模型预期。数字是否居中、背景是否干净、光照是否稳定、笔画是否清晰、图像是否正确反色,都会影响最终分类结果。调试时不能只盯着 KPU 推理代码,也要观察 LCD 中的实际画面和预处理后的数字特征。
分类模型和检测模型的区别也可以通过这个实验理解。人脸检测模型需要输出目标框,因此后处理重点是坐标、宽高、置信度和 NMS;MNIST 分类模型只需要输出类别,因此后处理重点是从 10 个分数中找到最大值。前者回答"目标在哪里",后者回答"这是什么类别"。
硬件设施
本实验围绕 CanMV K210 的 AI 视觉分类能力展开,主要使用开发板、摄像头模块、LCD 显示屏、SD 卡模型文件和 KPU 神经网络加速器。代码没有使用按键、蜂鸣器、电机、传感器或 WiFi 模块,因此这些外设不作为本节重点。
运行效果截图可以放在这里。发布时建议放置一张 LCD 上显示摄像头画面和 num: x 识别结果的图片,方便读者直接观察数字识别现象。
| 硬件 / 软件 | 作用 | 说明 |
|---|---|---|
| CanMV K210 开发板 | 实验运行平台 | 执行 MicroPython 程序,调用摄像头、LCD 和 KPU |
| 摄像头模块 | 图像采集 | 通过 sensor.snapshot() 获取实时画面 |
| LCD 显示屏 | 结果显示 | 显示摄像头画面和识别到的数字结果 |
| KPU 神经网络加速器 | AI 推理 | 加载 kmodel 模型并执行手写数字分类 |
| SD 卡 | 模型文件存储 | 存放 /sd/uint8_mnist_cnn_model.kmodel |
| 串口控制台 | 调试输出 | 通过 print(display_str) 查看识别数字 |
sensor |
摄像头控制模块 | 完成摄像头初始化、图像格式、分辨率和开窗设置 |
image |
图像处理模块 | 完成灰度化、缩放、反色、字符增强和绘制文字 |
lcd |
LCD 显示模块 | 初始化屏幕并显示处理后的图像 |
maix.KPU |
KPU 模块 | 加载模型、执行推理并返回分类结果 |
gc |
内存管理模块 | 在循环中执行内存回收,降低长时间运行风险 |
摄像头、LCD、SD 卡和开发板的实物连接图可以放在这里。接线检查时重点确认摄像头排线、LCD 屏幕、SD 卡模型文件和开发板供电状态。
| 接口 / 资源 | 代码对象 | 对应硬件与说明 |
|---|---|---|
| 摄像头接口 | sensor.reset() / sensor.snapshot() |
初始化摄像头并持续采集图像帧 |
| LCD 接口 | lcd.init() / lcd.display(img) |
显示摄像头画面和识别结果 |
| KPU 模型文件 | /sd/uint8_mnist_cnn_model.kmodel |
从 SD 卡加载手写数字识别模型 |
| 摄像头开窗 | sensor.set_windowing((224, 224)) |
截取画面中心区域,减少无关背景干扰 |
| 灰度输入 | img.to_grayscale(1) |
将彩色图像转换为更适合数字识别的灰度图 |
| 缩放输入 | resize(112,112) |
将图像缩放到模型推理需要的尺寸 |
| 分类结果 | out.index(max_mnist) |
得到 0 到 9 中分数最高的数字类别 |
| LCD 文字显示 | img.draw_string(...) |
在画面左上角显示 num: x |
本实验不涉及普通 GPIO 接线,重点不是排查某根引脚是否接错,而是确认 AI 视觉链路是否完整。摄像头没有画面时,应先检查摄像头连接和初始化;LCD 不显示时,应先检查屏幕初始化和供电;模型加载失败时,应先检查 SD 卡、模型文件路径和文件名;识别结果不稳定时,应重点检查数字是否居中、图像是否清晰、背景是否干净。
| 实验现象 | 正常表现 | 异常提示 |
|---|---|---|
| 程序启动 | LCD 初始化,摄像头开始输出画面 | 黑屏时检查 LCD、摄像头和程序初始化 |
| 模型加载 | 程序正常进入主循环,无模型路径报错 | 报错时检查 /sd/uint8_mnist_cnn_model.kmodel 是否存在 |
| 数字进入画面 | LCD 左上角显示 num: x |
结果乱跳时检查数字位置、光照和背景 |
| 串口输出 | 串口持续打印 num: x |
没有输出时检查程序是否进入主循环 |
| 画面刷新 | LCD 持续显示摄像头画面 | 卡顿时减少额外操作,检查内存回收 |
| 长时间运行 | 程序持续识别并显示 | 卡死时检查模型资源、SD 卡和内存占用 |
软件代码
本实验的软件部分以 4.7_Mnist_recognition.py 为核心。程序导入 sensor、image、time、lcd、KPU 和 gc,随后完成 LCD 初始化、摄像头初始化、模型加载,并在主循环中持续采集图像、完成预处理、执行 KPU 分类推理、输出识别结果和刷新 LCD。
| 软件环境 | 作用 | 检查重点 |
|---|---|---|
| CanMV IDE | 编辑、运行和调试 K210 程序 | 能识别开发板串口,并能运行基础摄像头程序 |
| CanMV 固件 | 提供 sensor、lcd、KPU 等模块 |
固件需要支持 KPU 模型加载和图像处理函数 |
| SD 卡 | 存放模型文件 | /sd/uint8_mnist_cnn_model.kmodel 路径需要与代码一致 |
| 摄像头驱动 | 图像采集 | 能正常显示 QVGA RGB565 图像 |
| LCD 显示环境 | 结果显示 | 能正常显示摄像头画面和 num: x 文本 |
| 串口终端 | 调试输出 | 能看到识别数字输出和报错信息 |
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ----湖南创乐博智能科技有限公司----
# 文件名:4.7_Mnist_recognition.py
# 版本:V2.0
# author: zhulin
# 说明:Mnist - 手写数字识别实验
#####################################################
import sensor, image, time, lcd
from maix import KPU
import gc
lcd.init() # 初始化LCD显示屏
sensor.reset() # 复位并初始化摄像头
sensor.set_pixformat(sensor.RGB565) # 设置摄像头输出格式为 RGB565
sensor.set_framesize(sensor.QVGA) # 设置摄像头输出大小为 QVGA (320x240)
sensor.set_windowing((224, 224)) # 摄像头输出开窗
sensor.skip_frames(time = 500) # 等待摄像头稳定
sensor.set_hmirror(0) # 禁用水平镜像
clock = time.clock() # 创建一个clock对象,用来计算帧率
# 创建一个kpu对象
kpu = KPU()
# 加载模型
kpu.load_kmodel("/sd/uint8_mnist_cnn_model.kmodel")
while True:
gc.collect()
img = sensor.snapshot() # 拍照,获取一张图像
img_mnist1=img.to_grayscale(1) # 将图片转为灰度图
img_mnist2=img_mnist1.resize(112,112) # 将图片缩放为112x112
a=img_mnist2.invert() # 反转图片
a=img_mnist2.strech_char(1) # 预处理图片
a=img_mnist2.pix_to_ai() # 对image处理生成ai运算需要的r8g8b8格式存储
# 对输入图像进行kpu运算,并得出结果
out = kpu.run_with_output(img_mnist2, getlist=True)
max_mnist = max(out)
index_mnist = out.index(max_mnist)
#score = KPU.sigmoid(max_mnist)
display_str = "num: %d" % index_mnist
print(display_str)
a=img.draw_string(4,3,display_str,color=(0,0,0),scale=2)
lcd.display(img)
# 创建的kpu对象去初始化,释放模型内存
kpu.deinit()
这段程序可以分成三个层级。底层是 LCD、摄像头和 KPU 模型初始化,负责准备图像采集和模型推理环境;中间层是灰度化、缩放、反色、字符增强和 AI 格式转换,负责把摄像头图像处理成适合 MNIST 模型输入的数据;上层是分类结果计算、串口打印和 LCD 显示,负责把模型输出转换成可观察的数字结果。
初始化阶段中,sensor.set_pixformat(sensor.RGB565) 设置摄像头输出彩色图像,sensor.set_framesize(sensor.QVGA) 设置画面为 320×240,sensor.set_windowing((224, 224)) 截取中间区域。kpu.load_kmodel("/sd/uint8_mnist_cnn_model.kmodel") 从 SD 卡加载模型文件,这一步要求模型文件路径和文件名完全一致。
| 函数 / 语句 | 功能 | 对应现象 |
|---|---|---|
lcd.init() |
初始化 LCD 显示屏 | 屏幕准备显示图像 |
sensor.reset() |
复位并初始化摄像头 | 摄像头进入可采集状态 |
sensor.set_pixformat(sensor.RGB565) |
设置摄像头像素格式 | 获取彩色图像 |
sensor.set_framesize(sensor.QVGA) |
设置摄像头分辨率 | 输出 320×240 画面 |
sensor.set_windowing((224, 224)) |
设置摄像头开窗 | 截取中间区域作为主要识别画面 |
sensor.skip_frames(time=500) |
等待摄像头稳定 | 启动后画面更稳定 |
sensor.set_hmirror(0) |
禁用水平镜像 | 保持画面方向不镜像 |
KPU() |
创建 KPU 对象 | 准备加载模型并执行推理 |
kpu.load_kmodel() |
加载 MNIST 模型 | 从 SD 卡读取 kmodel 文件 |
gc.collect() |
执行内存回收 | 降低循环运行中的内存压力 |
sensor.snapshot() |
获取一帧摄像头图像 | 主循环持续采集画面 |
to_grayscale(1) |
转换为灰度图 | 减少颜色对数字分类的干扰 |
resize(112,112) |
缩放图像 | 匹配模型输入尺寸 |
invert() |
反转图像颜色 | 让数字和背景关系更接近模型输入特征 |
strech_char(1) |
字符增强处理 | 增强数字笔画特征 |
pix_to_ai() |
转换为 KPU 输入格式 | 图像可以送入 KPU 推理 |
run_with_output(..., getlist=True) |
执行模型推理并返回列表 | 输出 0 到 9 的分类结果 |
max(out) |
获取最高分类分数 | 找到模型最有把握的一类 |
out.index(max_mnist) |
获取最大值所在索引 | 得到识别数字 |
draw_string() |
在图像上绘制识别结果 | LCD 左上角显示 num: x |
lcd.display(img) |
刷新 LCD 显示 | 显示最新摄像头画面和识别文本 |
运行时,程序会不断执行"采集图像、预处理、KPU 推理、取最大分类结果、显示识别数字"的循环。如果画面中有清晰、居中、背景干净的手写数字,LCD 和串口会显示对应的 num: x。如果画面中没有数字,模型仍然会在 0 到 9 中选出一个最接近的类别,因此输出结果不一定代表真实有效目标,只代表当前输入图像在模型看来最像哪一个数字。
需要注意的是,代码中创建了 clock = time.clock(),但主循环没有调用 clock.tick() 或显示 FPS,因此这个对象在当前版本中没有实际参与结果显示。代码末尾的 kpu.deinit() 位于无限循环之后,正常运行时不会自动执行到这一行。如果后续要支持安全退出,可以再加入 try...except 或 try...finally 结构释放 KPU 资源。本篇保持原始实验代码结构,便于和课程文件一致。
扩展应用
MNIST 手写数字识别实验常见问题主要集中在摄像头、LCD、模型文件、输入图像质量和预处理效果几个方面。排查时建议先确认摄像头和 LCD 基础功能,再确认模型文件路径,最后再调整数字摆放、背景、光照和预处理方式。
| 问题现象 | 可能原因 | 处理思路 |
|---|---|---|
| 程序无法运行 | 文件没有传到开发板、固件不支持相关模块、库文件缺失 | 确认主程序在正确目录,检查 CanMV 固件版本和报错行号 |
| LCD 黑屏或无画面 | LCD 未初始化成功、摄像头未输出图像、程序卡在模型加载阶段 | 先运行基础摄像头显示程序,确认 LCD 和摄像头正常 |
| 模型加载失败 | uint8_mnist_cnn_model.kmodel 没有放到 /sd/,或文件名不一致 |
将模型文件复制到 SD 卡根目录,并核对代码路径 |
| 串口没有输出 | 程序没有进入主循环、串口终端未打开 | 加入基础 print() 或观察 LCD 是否刷新 |
| 识别结果乱跳 | 画面中没有明确数字,背景复杂,光照不稳定 | 使用白纸黑字或黑底白字,固定摄像头距离,保持背景干净 |
| 数字识别错误 | 数字不居中、笔画太细、倾斜严重、预处理效果不佳 | 将数字放到画面中心,增大笔画粗细,保持数字完整 |
| 每次都输出某个数字 | 输入图像特征过于单一,模型被背景干扰 | 调整背景和光照,让数字占据主要区域 |
| 画面颜色正常但结果不准 | 分类模型看的是预处理后的灰度图,不是原始彩色图 | 重点检查灰度、反色、缩放和字符增强后的输入效果 |
| 结果刷新慢 | 图像处理、KPU 推理和 LCD 刷新占用时间 | 减少额外打印和不必要图像操作 |
| 程序运行一段时间后卡顿 | 内存碎片或资源占用增加 | 保留 gc.collect(),减少循环中创建额外对象 |
MNIST 手写数字识别实验的价值不只在于复现实验现象,更在于建立从图像输入到分类输出的项目思路。在掌握基础代码后,可以把它和显示、通信、存储、按键菜单或其他模块组合成更完整的作品。
扩展效果图或项目运行截图可以放在这里。发布时建议放置手写数字卡片、LCD 识别画面、串口输出结果或不同数字识别对比画面。
| 应用场景 | 实现思路 | 可扩展能力 |
|---|---|---|
| 手写数字识别 | 将纸面数字放入摄像头画面,模型输出 0 到 9 的分类结果 | 可扩展数字卡片识别、课堂演示和模型推理教学 |
| AI 入门演示 | 展示图像分类模型如何在 K210 本地运行 | 可用于讲解模型输入、输出分数和分类结果 |
| 试卷数字采集 | 对简单手写数字区域进行识别 | 可扩展拍照保存、串口上传和结果记录 |
| 模型分类教学 | 对比分类模型和检测模型的输出差异 | 可衔接人脸检测、物体分类和目标识别实验 |
| 嵌入式推理练习 | 使用 KPU 在边缘设备上完成图像分类 | 可扩展更多自训练分类模型 |
| 视觉分类原型 | 将识别结果作为后续控制条件 | 可联动 LED、蜂鸣器、LCD 菜单或上位机通信 |
| 数字密码演示 | 连续识别多个数字形成简单输入序列 | 可扩展为门禁密码输入、实验互动和识别结果验证 |
从工程角度看,当前程序已经具备 AI 分类项目的基本分层。摄像头负责输入,图像处理负责预处理,KPU 负责推理,分类后处理负责选出最大分数,LCD 和串口负责输出结果。后续扩展时,可以单独替换模型、调整输入尺寸、改变预处理方式,或者把识别结果转换成外设动作,而不需要重写整个采集和显示流程。
总结
本实验通过 CanMV K210 完成了基于 KPU 的 MNIST 手写数字识别,核心能力包括摄像头初始化、图像开窗、灰度化、缩放、反色、字符增强、KPU 模型加载、分类推理、最大值结果选择、LCD 显示和串口输出。程序从摄像头输入开始,把图像处理成适合模型推理的格式,再把分类结果显示为 num: x,完整展示了 K210 图像分类实验的基本流程。
MNIST 手写数字识别是后续 AI 分类项目的基础入口。掌握这条链路后,可以继续扩展物体分类、数字识别交互、试卷数字采集、上位机结果上传、LCD 结果菜单和外设联动控制等内容。调试时应按"摄像头画面是否正常、LCD 是否显示、模型文件是否存在、预处理后数字是否清晰、分类结果是否稳定"的顺序排查,这样更容易定位问题。