初识图像识别手写数字0-9基础
前言
初步探索了计算机识别手写数字的领域,将了解到的基本知识总结如下。
计算机如何”看”?
可先看此篇文章进行具体了解:浅谈计算机如何识别和存储图像
总结一句话:计算机“看”数字,就像人眼看数字一样,但它用的是像素和数学模型,而不是眼睛和大脑。
训练模型
- 选用数据集: MNIST数据集,包括60,000 张训练图,10,000 张测试图,训练速度快
-
选用语言:python(运行在pycharm)
1.加载数据集:
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data() print(f" Training images: {len(x_train)}") print(f" Test images: {len(x_test)}") print(f" Image size: {x_train[0].shape} (28x28 pixels)")把机器学习想象成教小孩认数字:
- x_train = 给小孩看的图片(问题/特征)
- y_train = 告诉小孩的正确答案(答案/标签)
- x_test = 给小孩的考试题目(测试问题)
-
y_test = 考试的标准答案(测试答案)
打印结果:
Training images: 60000 Test images: 10000 Image size: (28, 28) (28x28 pixels)2.可视化展示MNIST数据集中的手写数字样本:
plt.figure(figsize=(12, 6)) # 创建画布,设置大小12x6英寸 print(" Showing first 12 digits from training set...") for i in range(12): plt.subplot(3, 4, i + 1) # 创建3行4列的子图,当前是第i+1个 plt.imshow(x_train[i], cmap='gray') # 显示第i张训练图片,用灰度色彩 plt.title(f'Digit: {y_train[i]}', fontsize=12) # 设置标题为真实标签 plt.axis('off') # 关闭坐标轴,让图片更干净 plt.suptitle('MNIST Handwritten Digits Samples', fontsize=16, fontweight='bold') plt.tight_layout() # 自动调整子图间距,避免重叠显示情况:
3.数据预处理中的归一化操作:
x_train = x_train / 255.0
x_test = x_test / 255.0
数据归一化的目的:
① 大数值会导致训练不稳定,如果输入都很大(0~255),梯度也会很大,会让模型震荡、难以收敛。
② 这是图像处理领域的标准做法,几乎所有深度学习任务都要做归一化(normalization)。
4.搭建简单的神经模型:
model = tf.keras.Sequential([
# 把 28x28 的二维图片拉成一条 784 长度的向量,方便神经网络处理
# Sequential 表示“按顺序堆叠的神经网络”,一层接一层,非常常见。
tf.keras.layers.Flatten(input_shape=(28, 28)),
# 全连接层:128 个神经元,用 ReLU 激活,负责提取特征
# 它负责从图像中提取特征(如:笔画方向、粗细、结构)。
tf.keras.layers.Dense(128, activation='relu'),
# 输出层:10 个神经元,对应数字 0~9,softmax 输出每个数字的概率
# softmax:用于分类,得到属于每一类的概率
tf.keras.layers.Dense(10, activation='softmax')
])
# 编译模型:选择优化器、损失函数、评估指标
# Adam 是一种动态调整学习率的优化算法
#
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
打印内容:
Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ flatten (Flatten) │ (None, 784) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense (Dense) │ (None, 128) │ 100,480 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_1 (Dense) │ (None, 10) │ 1,290 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
Total params: 101,770 (397.54 KB)
Trainable params: 101,770 (397.54 KB)
Non-trainable params: 0 (0.00 B)
-
flatten (译为压扁):
- 作用:把28×28的图片压平成784个像素点的一维数组,
- 输出形状:(None, 784),None = 批处理大小(可以是任意数量,比如32、64张图片一批);784 = 28×28 个像素点
- 参数数量:0(只是重排操作,没有可学习的参数)
-
dense:
- 作用:全连接层,128个神经元
- 输出形状:(None, 128)(每张图片输出128个特征值)
- 参数数量:100,480个可学习参数,
- 参数计算 = (输入维度 × 输出维度) + 输出维度偏置的个数,有了偏置,神经元可以“整体平移”,即使输入为 0 也可以输出非零值。
-
dense_1:
- 作用:输出层,10个神经元(对应0-9十个数字)
- 输出形状:(None, 10)(每张图片输出10个概率值)
- 参数数量:1,290个可学习参数
5.训练模型:
history = model.fit(x_train, y_train, epochs=5, # 5次完整遍历训练集 batch_size=32, validation_split=0.1, # 把训练数据的 10% 自动划分为验证集 verbose=1)训练过程:
6.验证模型是否能真正的识别数字:
plt.figure(figsize=(15, 8)) # 随机挑 12 张(不会重复) indices = np.random.choice(len(x_test), 12, replace=False) for i, idx in enumerate(indices): plt.subplot(3, 4, i + 1) test_image = x_test[idx] true_label = y_test[idx] prediction = model.predict(np.array([test_image]), verbose=0) # 让模型预测这张图片属于 0~9 中哪一个 predicted_label = np.argmax(prediction) # 找到概率最大的数字,就是预测值 confidence = np.max(prediction) # 模型对该预测的置信度(0~1) plt.imshow(test_image, cmap='gray') # Check if prediction is correct if predicted_label == true_label: color = 'green' result = "✓ Correct" else: color = 'red' result = "✗ Wrong" plt.title(f'True: {true_label} | Pred: {predicted_label}/n{result} | Conf: {confidence:.2f}', color=color, fontsize=10) plt.axis('off') plt.suptitle('Model Prediction Results (Green=Correct, Red=Wrong)', fontsize=16, fontweight='bold') plt.tight_layout()验证结果:
至此一个简洁的识别模型已经训练完成!
使用训练的模型识别我的手写图片
我利用Krita手写了0-9的图片,如下:
def recognize_digit(image_path):
"""使用 PIL 打开图片并用模型识别数字"""
try:
img = Image.open(image_path)
# 如果不是灰度图('L' 模式),则转换为灰度图
if img.mode != 'L':
img = img.convert('L')
print(f"📊 Original image size: {img.size}")
# 调整为 28x28(MNIST 使用的尺寸),使用高质量重采样
img_resized = img.resize((28, 28), Image.Resampling.LANCZOS)
# 转为 numpy 数组
img_array = np.array(img_resized)
# 自动检测并在必要时反转颜色(MNIST 是白底黑字)
# 如果平均灰度较高(接近白底),则反转为白底黑字
if np.mean(img_array) > 128:
img_array = 255 - img_array
print("🔄 检测到白色背景,已反转颜色")
# 归一化到 0-1(浮点数)
img_normalized = img_array / 255.0
print_pixel_table(img_array)
# 预测:注意 model 期望输入形状是 (batch, 28, 28)
prediction = model.predict(np.array([img_normalized]), verbose=0)
predicted_digit = np.argmax(prediction)
confidence = np.max(prediction)
print(f"/n🎯 预测结果:")
print(f" 🤖 预测数字: {predicted_digit}")
print(f" 📈 置信度: {confidence:.2%}")
print(f" 📁 图片文件: {os.path.basename(image_path)}")
# 显示所有概率(只显示大于 1% 的项,便于阅读)
print(f"/n📊 各数字概率:")
for i, prob in enumerate(prediction[0]):
if prob > 0.01: # 仅显示概率 > 1%
marker = "🎯" if i == predicted_digit else " "
print(f" {marker} 数字 {i}: {prob:.2%}")
# 根据置信度提供一个简单等级提示
print(f"/n 置信度等级:", end="")
if confidence > 0.9:
print("⭐⭐⭐⭐⭐ (非常确定)")
elif confidence > 0.7:
print("⭐⭐⭐⭐ (较好)")
elif confidence > 0.5:
print("⭐⭐⭐ (一般)")
else:
print("⭐ (置信度较低)")
return predicted_digit, confidence
except Exception as e:
print(f"识别出错: {e}")
return None
def print_pixel_table(img_array):
"""打印 28x28 原始像素值(0-255)"""
print(f"图片尺寸: {img_array.shape}")
# 打印 28 行 × 28 列像素值
for i in range(28):
row_values = []
for j in range(28):
row_values.append(f"{img_array[i, j]:3d}") # 格式对齐
print(" ".join(row_values))
这里以数字2为例打印结果:
输入图片路径(或输入 'q' 退出):my_handwriting/2.png
✅ 图片格式: PNG
✅ 图片模式: RGBA
✅ 图片尺寸: (400, 300)
📊 Original image size: (400, 300)
🔄 检测到白色背景,已反转颜色
图片尺寸: (28, 28)
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 3 4 4 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 1 1 0 5 80 108 101 10 0 1 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 1 0 0 113 185 155 113 146 133 0 3 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 1 0 17 178 147 31 0 0 0 150 6 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 2 9 173 88 0 0 4 5 2 136 21 0 1 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 11 47 0 4 1 0 1 0 137 21 0 1 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 0 0 0 0 3 144 7 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 2 0 0 0 1 0 24 143 0 1 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 2 1 60 125 0 4 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 4 0 126 73 0 3 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 2 21 165 4 2 3 3 1 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 4 0 129 86 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 2 0 47 161 0 0 10 68 72 30 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1 3 0 151 42 81 160 184 159 158 141 1 1 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 4 0 168 224 171 157 75 18 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 3 0 131 241 112 35 0 0 0 3 3 1 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 3 0 80 200 26 0 0 3 2 1 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 2 0 81 20 0 5 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
🎯 预测结果:
🤖 预测数字: 2
📈 置信度: 88.53%
📁 图片文件: 2.png
📊 各数字概率:
🎯 数字 2: 88.53%
数字 3: 3.64%
数字 6: 3.67%
数字 8: 1.82%
置信度等级:⭐⭐⭐⭐ (较好)
结语
至此,一个小型的图像识别基本完成,但是并不是所有的手写数字置信度等级都可以达到这么高,仅仅依靠这个小的模型,还是远远不够的,接下来会尝试利用CNN(卷积神经网络)来进行训练。由于不太懂得这方面的专业知识,可能会存在很多问题,欢迎留言,我会积极改正!