跳至正文
-
关注
IT极限技术分享汇

ITmax

IT极限技术分享汇

ITmax

  • 首页
  • 商店
  • 我的帐户
  • 结账
  • 购物车
  • 首页
  • 商店
  • 我的帐户
  • 结账
  • 购物车
关

搜索

技术分享

初识图像识别手写数字0-9基础

作者 news
2025年11月27日 6 分钟阅读
0

前言

初步探索了计算机识别手写数字的领域,将了解到的基本知识总结如下。

计算机如何”看”?

可先看此篇文章进行具体了解:浅谈计算机如何识别和存储图像
总结一句话:计算机“看”数字,就像人眼看数字一样,但它用的是像素和数学模型,而不是眼睛和大脑。

训练模型

  • 选用数据集: 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()    # 自动调整子图间距,避免重叠

    显示情况:
    image.png

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)
  1. flatten (译为压扁):

    • 作用:把28×28的图片压平成784个像素点的一维数组,
    • 输出形状:(None, 784),None = 批处理大小(可以是任意数量,比如32、64张图片一批);784 = 28×28 个像素点
    • 参数数量:0(只是重排操作,没有可学习的参数)
  2. dense:

    • 作用:全连接层,128个神经元
    • 输出形状:(None, 128)(每张图片输出128个特征值)
    • 参数数量:100,480个可学习参数,
    • 参数计算 = (输入维度 × 输出维度) + 输出维度偏置的个数,有了偏置,神经元可以“整体平移”,即使输入为 0 也可以输出非零值。
  3. 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)

    训练过程:
    image.png

    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()

    验证结果:
    image.png
    至此一个简洁的识别模型已经训练完成!


使用训练的模型识别我的手写图片

我利用Krita手写了0-9的图片,如下:
image.png

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(卷积神经网络)来进行训练。由于不太懂得这方面的专业知识,可能会存在很多问题,欢迎留言,我会积极改正!

https://segmentfault.com/a/1190000047401707

作者

news

关注我
其他文章
上一个

Dragonboat统一存储LogDB实现分析|得物技术

下一个

让企业决策像开挂!瑶池 Data Agent 全生命周期数据智能体使用全攻略

暂无评论!成为第一个。

发表回复 取消回复

您的邮箱地址不会被公开。 必填项已用 * 标注

Copyright 2026 — IT极限技术分享汇. 备案号:浙ICP备17006560号-2