TensorFlow
MNIST
neural networks
image testing
machine learning

Tensorflow - Testing a mnist neural net with my own images

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

Training a neural network on MNIST is often the first milestone in a deep learning journey, but the real satisfaction comes from testing it with your own handwritten digits. The process is straightforward in principle — load an image, preprocess it, and feed it to the model — but the preprocessing details trip up almost everyone the first time. Understanding why each step matters will save you from staring at confidently wrong predictions.

Why Preprocessing Matters

MNIST images have very specific characteristics. Every digit is 28x28 pixels, grayscale, drawn in white on a black background, centered in the frame, and normalized to values between 0 and 1. Your phone camera or scanner produces images that violate most of these constraints. If you skip any preprocessing step, the model receives input that looks nothing like its training data, and it will produce nonsensical predictions regardless of how accurate it was on the test set.

Loading and Converting Your Image

Start by loading the image using PIL (Pillow) or OpenCV, then convert it to grayscale and resize it to 28x28 pixels.

Using PIL:

python
1from PIL import Image
2import numpy as np
3
4img = Image.open('my_digit.png').convert('L')  # Convert to grayscale
5img = img.resize((28, 28))                      # Resize to 28x28
6img_array = np.array(img)

Using OpenCV:

python
1import cv2
2import numpy as np
3
4img = cv2.imread('my_digit.png', cv2.IMREAD_GRAYSCALE)
5img = cv2.resize(img, (28, 28))

Inverting Colors: White-on-Black

MNIST digits are white strokes on a black background. Most people naturally write dark digits on white paper, which is the opposite. You must invert the image so the pixel values match what the model expects.

python
1# PIL image arrays: 0 = black, 255 = white
2# MNIST convention: high values = digit, low values = background
3# If your digit is dark on light background, invert:
4img_array = 255 - img_array

You can verify by plotting the image:

python
1import matplotlib.pyplot as plt
2
3plt.imshow(img_array, cmap='gray')
4plt.title('After inversion')
5plt.show()

The digit should appear as bright pixels on a dark background. If it does not, the model will see your background as the digit and perform poorly.

Normalizing and Reshaping for Model Input

MNIST pixel values are scaled to the range [0, 1]. Your image has values in [0, 255], so divide by 255. Then reshape the array to match the model's expected input shape.

python
1# Normalize to [0, 1]
2img_array = img_array.astype('float32') / 255.0
3
4# For a model expecting (batch_size, 28, 28):
5img_input = img_array.reshape(1, 28, 28)
6
7# For a model expecting (batch_size, 28, 28, 1) — e.g., CNN:
8img_input = img_array.reshape(1, 28, 28, 1)

Running the Prediction

With the image properly preprocessed, pass it to the model:

python
1import tensorflow as tf
2
3model = tf.keras.models.load_model('my_mnist_model.h5')
4
5prediction = model.predict(img_input)
6predicted_digit = np.argmax(prediction)
7confidence = np.max(prediction)
8
9print(f"Predicted digit: {predicted_digit}, Confidence: {confidence:.4f}")

If you want to see the probability distribution across all 10 digits:

python
for digit, prob in enumerate(prediction[0]):
    print(f"  {digit}: {prob:.4f}")

Complete End-to-End Example

Here is a full pipeline that loads, preprocesses, and predicts a custom digit image:

python
1import numpy as np
2from PIL import Image
3import tensorflow as tf
4
5def predict_custom_digit(image_path, model_path):
6    # Load and preprocess
7    img = Image.open(image_path).convert('L')
8    img = img.resize((28, 28))
9    img_array = np.array(img)
10
11    # Invert if needed (dark digit on light background)
12    if np.mean(img_array) > 127:
13        img_array = 255 - img_array
14
15    # Normalize
16    img_array = img_array.astype('float32') / 255.0
17
18    # Reshape for model
19    img_input = img_array.reshape(1, 28, 28, 1)
20
21    # Predict
22    model = tf.keras.models.load_model(model_path)
23    prediction = model.predict(img_input)
24
25    return np.argmax(prediction), np.max(prediction)
26
27digit, confidence = predict_custom_digit('my_seven.png', 'mnist_cnn.h5')
28print(f"Predicted: {digit} (confidence: {confidence:.2%})")

The automatic inversion check (np.mean > 127) works for most clean images. If your image has a noisy or gray background, you may need to apply thresholding first.

Why Predictions Go Wrong

Even with correct preprocessing, custom images sometimes produce wrong predictions. The most common reasons are:

Poor centering: MNIST digits are centered in the 28x28 frame by center of mass. If your digit is shifted to one corner, the model sees a pattern it was never trained on. You can center it by computing the center of mass and shifting the digit accordingly.

Stroke thickness mismatch: MNIST digits have a specific stroke thickness relative to the frame size. A very thin pen or a very thick marker produces digits that look foreign to the model. Resize your source image so the digit occupies roughly the same proportion of the frame as MNIST digits do.

Background noise: Shadows, paper texture, or uneven lighting create non-zero pixel values in the background. Apply a threshold to force background pixels to zero:

python
img_array[img_array < 50] = 0  # Clean up faint noise

Common Pitfalls

  • Forgetting to invert colors: This is the single most common mistake. The model sees a mostly-white image and has no idea what digit it represents.
  • Using the wrong reshape dimensions: A dense (fully connected) model expects (1, 784) or (1, 28, 28), while a CNN expects (1, 28, 28, 1). A shape mismatch either raises an error or silently misinterprets the data.
  • Skipping normalization: If pixel values remain in the 0-255 range instead of 0-1, every activation in the network is wildly different from training, producing garbage outputs.
  • Resizing with the wrong interpolation: Some interpolation methods introduce gray anti-aliasing artifacts. Use Image.LANCZOS (PIL) or cv2.INTER_AREA (OpenCV) when downscaling for the cleanest results.
  • Testing with stylized or unusual digit forms: MNIST was written by US Census Bureau employees and high school students. Unusual digit styles (European crossed 7s, looped 2s) may not match any training example closely enough for correct prediction.

Summary

  • MNIST models expect 28x28 grayscale images with white digits on a black background, normalized to values between 0 and 1.
  • Always invert colors if your original image has dark digits on a light background.
  • Reshape the input to match your model's architecture: (1, 28, 28) for dense networks, (1, 28, 28, 1) for CNNs.
  • Poor centering, wrong stroke thickness, and background noise are the leading causes of incorrect predictions on custom images.
  • Visualize your preprocessed image before feeding it to the model to verify it resembles actual MNIST samples.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

All Rights Reserved.