当前位置:首页>python>少儿自学编程第44课:Python 用摄像头或图片实时识别手写数字(Sklearn版)

少儿自学编程第44课:Python 用摄像头或图片实时识别手写数字(Sklearn版)

  • 2026-09-02 15:38:18
少儿自学编程第44课:Python 用摄像头或图片实时识别手写数字(Sklearn版)

第43课我们用TensorFlow训练了手写数字识别模型,本来打算第44课用它配合摄像头做实时识别。

但因为电脑CPU不支持AVX指令集,TensorFlow死活跑不起来,折腾了半天。

Tyree说:“难道就没办法了吗?有没有一种不需要装那么重的东西就能识别数字的方法?”

我说:“当然有。第41课我们用 `scikit-learn` 训练了一个数字识别模型,准确率也很高。而且它非常轻,不用装几百兆的库,运行也快。”

他很开心:“那我们就用那个吧!省得再跟TensorFlow较劲了。”

今天课程我们就用 `scikit-learn` 训练的SVM模型,配合OpenCV,实现图片和摄像头的手写数字实时识别。

不需要TensorFlow,不需要GPU,普通电脑就能跑。

01. 准备工作

1.1 安装必要的库

pip install opencv-python numpy scikit-learn joblib matplotlib

这些库体积小、安装快,而且不会遇到AVX兼容问题。

1.2 先训练一个SVM模型并保存

如果你在第41课已经训练过并保存了模型(`digits_model.pkl`),可以跳过这一步。否则,运行以下代码生成模型文件:

⭐重点讲解:

`load_digits()` 加载的是8x8像素的手写数字图片数据集,共1797张。

`train_test_split` 将数据分成训练集(80%)和测试集(20%),`random_state=42` 保证每次分割结果一样。

`SVC(gamma=0.001, C=100)` 是支持向量机分类器,这两个参数是常用的默认值,能取得不错的效果。

`joblib.dump()` 将训练好的模型保存为 `.pkl` 文件,以后加载时不需要重新训练,直接 `joblib.load()` 即可。

02. 从图片文件识别手写数字

2.1 准备一张手写数字图片

用手机在白纸上写一个数字,拍照后裁剪成正方形,保存为`test.png`。或者用画图工具画一个。

2.2 编写识别代码

⭐重点讲解(逐段分析):

`joblib.load("digits_model.pkl")`:加载之前保存的模型文件。如果文件不存在或路径不对,会报 `FileNotFoundError`,所以要确保模型文件在代码同级目录下。

`def preprocess_image(img):` 这个函数是识别的核心,它把用户任意拍摄的图片转换成模型能理解的数字矩阵。

`if len(img.shape) == 3:` 判断图片是否彩色(三维数组)。

如果是,用 `cv2.cvtColor` 转为灰度图(二维),因为颜色信息对数字识别没有帮助,反而增加计算量。

`cv2.resize(img, (8, 8))`:将图片缩放到8×8像素。因为 `digits` 数据集中的图片就是8×8的,模型输入必须是64个像素。

如果原图太大或太小,缩放后数字可能会变形,但只要数字居中且清晰,模型通常能正确识别。

`cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)` 进行二值化:大于127的像素变为255(纯白),小于等于127的变为0(纯黑)。

这样可以去除背景噪点,突出数字形状。阈值 `127` 是经验值,如果图片偏暗或偏亮,可以适当调整。

`img = 255 - img`:反转颜色。因为我们平时拍照是白纸黑字(数字是黑色,背景白色),而 `digits` 数据集是黑底白字(数字白色,背景黑色)。如果不反转,模型会把背景当作数字,完全认不出来。

`img = img / 255.0 * 16`:将像素值从0~255缩放到0~16,因为 `digits` 数据集的像素值范围是0~16(浮点数)。这一步归一化后,像素值分布与训练时一致,模型预测更稳定。

`img.reshape(1, -1)`:将8×8的二维数组展平成一维数组(64个像素),并变成形状 `(1, 64)`,因为模型要求输入是“样本数 × 特征数”的形式,这里1张图片,64个特征。

`model.predict(processed)` 返回一个数组,如 `[5]`,表示预测的数字是5。`pred[0]` 取出这个值。

Tyree第一次运行时报错 `ValueError: Expected 2D array, got 1D array instead`,是因为他忘记 `reshape` 了。加上后就好了。

03. 从摄像头实时识别

3.1 编写摄像头识别代码

下面是运行后的画面:

⭐重点讲解(逐段分析):

`cv2.VideoCapture(0, cv2.CAP_DSHOW)` 打开摄像头,`0` 是设备编号(通常第一个摄像头)。

`cv2.CAP_DSHOW` 

指定使用 DirectShow 后端(Windows下更稳定)。

如果打不开,可以去掉 

`cv2.CAP_DSHOW` 试试。

`cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)` 设置采集分辨率,降低分辨率可以提高帧率。如果摄像头不支持,会静默失败。

ROI(感兴趣区域)用 `roi_size = 200`,表示取200×200像素的正方形区域,位于画面正中央(`roi_x` 和 `roi_y` 计算居中)。

用户把写有数字的纸放在画面中央,程序就能提取这个区域进行识别。

`roi = frame[roi_y:roi_y+roi_size, roi_x:roi_x+roi_size]` 是数组切片,提取矩形区域。

注意 OpenCV 中坐标顺序是 `(列, 行)`,但切片索引是 `[行, 列]`,所以先 `y` 后 `x`。

`preprocess_roi(roi)` 与图片处理的函数类似,将ROI区域转化为模型输入。

`model.predict(processed)[0]` 预测并取出结果。

`cv2.rectangle` 

绘制绿色方框,`cv2.putText` 在左上角显示识别出的数字。

`cv2.waitKey(1) & 0xFF == ord('q')` 检测是否按下 `q` 键,按 `q` 退出循环。

Tyree第一次运行时发现识别率很低,他调整了二值化阈值从127降到100,并且让数字写得大一点、居中一些,识别率明显提升。

预处理参数需要根据实际图像调整,不能一成不变。

04. 图像预处理详解

灰度化,去掉颜色信息,数字识别只需要形状,颜色反而增加干扰

缩放8x8:匹配digits数据集尺寸,模型输入必须是64个像素

二值化:转为纯黑白,消除灰度噪声,突出数字轮廓

反转:白底黑字→黑底白字,digits数据集是黑底白字

归一化:像素值0~16,digits数据集像素范围是0~16

展平:8x8 → 64维向量,模型输入必须是一维向量

05. 完整代码

下面写把完整的代码整出来运行:

下图是摄像头识别的数字

06. 今天学到了什么

sklearn模型保存与加载:

`joblib.dump()` 和 `joblib.load()` 比TensorFlow更轻量。 

digits数据集规格:8x8像素,像素值0~16。 

图像预处理流水线:灰度化、缩放、二值化、反转、归一化、展平。

实时摄像头识别:逐帧读取、提取ROI、预测、绘制结果。 

Tyree最终用轻量级的方案成功实现了摄像头识别手写数字,不用TensorFlow,运行流畅,而且准确率也有90%以上。

他说原来不用TensorFlow也能做AI,还更快!

好了,今天课程就讲到这里。

下一课内容:语音识别入门——让电脑听懂你说的话,做一个简单的语音助手雏形。

————热门推荐————

少儿自学编程第40课:打包游戏成exe文件——把你用Python做的作品发给朋友

少儿自学编程第43课:用Python,MNIST手写数字识别——亲手训练一个AI

少儿自学编程第36课:太空避障——完整游戏设计与难度平衡

自学编程第7课:turtle画图入门(画一个正方形,五角形,螺旋形,三角形)

自学编程第6课:列表+循环+判断(做一个学生成绩分析器)

自学编程第3课:变量与数学计算(做一个年龄计算器)

自学编程第2课:用input让电脑问你名字(做一个打招呼程序)

自学编程第一步:安装Python和Thonny(零基础图文教程)

(本系列教程每天更新,欢迎关注收藏)

图‖文 宝月小鹿 转载须标明出处

最新文章

随机文章