Tech With Galvan
Back to Articles
AI8/18/2025 • 3 min read

Image Classifier using Python, Streamlit, and MobileNet

Classify any image with Python, Streamlit, and MobileNet — 1000 object categories, top-5 predictions, and confidence scores in real time.

Galvan
Galvan

Founder & Creator

Introduction

Image classification is the hello-world of deep learning — and with MobileNetV2, a pretrained model from TensorFlow Hub, it fits in about 60 lines. Upload any photo and the app returns its top-5 guesses from 1,000 categories (dogs, guitars, coffee mugs) with confidence scores. No training, no GPUs, no dataset wrangling: transfer learning means someone else’s millions of images are working for you.

This is the heavyweight sibling of the QR scanner — same upload-decode-display flow, but the “decode” step is a neural network forward pass.

Features

  • 1000-category classification — ImageNet’s full label space.
  • Top-5 predictions — confidence-ranked results with progress bars.
  • Fast model — MobileNetV2 runs on CPU in under a second.
  • Cached model loading — the 14MB weights download once.
  • Confidence threshold — filter out low-confidence noise.

Prerequisites

pip install streamlit tensorflow-cpu pillow numpy

Step 1: Create the Script

Save as classifier_app.py:

import streamlit as st
import numpy as np
from PIL import Image

st.set_page_config(page_title="Image Classifier", page_icon="🖼️")
st.title("🖼️ Image Classifier — MobileNetV2")


@st.cache_resource
def load_model():
    from tensorflow.keras.applications import MobileNetV2
    from tensorflow.keras.applications.mobilenet_v2 import decode_predictions
    model = MobileNetV2(weights="imagenet")
    return model, decode_predictions


model, decode = load_model()

uploaded = st.file_uploader("Upload an image", type=["png", "jpg", "jpeg"])
min_conf = st.slider("Minimum confidence %", 0, 100, 5)

if uploaded:
    img = Image.open(uploaded).convert("RGB")
    st.image(img, caption="Your image", width=350)

    if st.button("🔍 Classify", type="primary"):
        with st.spinner("Thinking..."):
            resized = img.resize((224, 224))
            arr = np.array(resized, dtype=np.float32)
            arr = np.expand_dims(arr, 0)

            preds = model.predict(arr, verbose=0)
            results = decode(preds, top=5)[0]

        st.subheader("Predictions")
        shown = 0
        for _, name, score in results:
            pct = score * 100
            if pct >= min_conf:
                st.markdown(f"**{name.replace('_', ' ').title()}**")
                st.progress(score, text=f"{pct:.1f}%")
                shown += 1
        if shown == 0:
            st.warning("Nothing above your confidence threshold.")

Step 2: Run the App

streamlit run classifier_app.py

The first run downloads the weights (one time, ~14MB). Try a pet photo, a kitchen object, a screenshot of a car — then try something deliberately weird and watch the model’s confidence crumble.

How It Works

MobileNetV2 is a convolutional network trained on ImageNet: 1.2M images across 1,000 categories. Preprocessing is minimal but non-negotiable — resize to 224×224 (its trained input size) and scale values the way it expects. Every pixel becomes a float; expand_dims adds the batch dimension because Keras models expect shape (samples, 224, 224, 3).

The output is 1,000 logits converted to a probability distribution — one score per category summing to 1.0. decode_predictions maps indices to human names and returns the top-k. The confidence slider filters that list client-side; a 3% “sea slug” guess is rarely what the user wanted to see.

@st.cache_resource is the performance hero: model loading takes seconds and hundreds of MB, so it must happen once per process, not once per rerun. This is different from cache_data (which caches values) — cache_resource caches objects like models and connections, the same pattern NLTK’s analyzer uses in the sentiment app.

Common Errors & Fixes

  • First prediction is very slow — weights are downloading and the graph is warming; subsequent runs are fast. Warm up at startup with a dummy predict if the delay bothers users.
  • PIL.UnidentifiedImageError — unsupported format (HEIC again); convert or add pillow-heif.
  • Predictions all wrong for line art / sketches — the model trained on photos; domain shift is real, not a bug.
  • Memory errors on tiny machines — use MobileNetV2(weights="imagenet", alpha=0.35) for a 4× smaller variant.

Key Concepts

  • Transfer learning — pretrained weights as a starting point; zero training here.
  • Input contracts — 224×224 RGB floats; models fail silently when fed surprises.
  • cache_resource vs cache_data — cache objects once, cache computed values per-input.
  • Top-k + threshold — showing ranked uncertainty beats showing one answer.

What to Try Next

  • Add camera input with st.camera_input for live classification.
  • Show grad-CAM heatmaps — overlay where the model looked (needs tf-keras-vis).
  • Swap in EfficientNet via one line and compare predictions.
  • Batch mode with accept_multiple_files=True and a results table, like the file organizer.

FAQ

Can I classify custom categories (my own products)?

Not with this model — it only knows ImageNet’s 1,000 classes. For custom categories you’d retrain the final layer (fine-tuning) on your own labeled images; MobileNet is actually the standard base for that.

Why CPU instead of GPU?

MobileNet is deliberately small — CPU inference is under a second, which is plenty for an app. tensorflow-cpu also avoids a 2GB CUDA install.

Why 224×224 specifically?

It’s the input size the architecture was designed and trained with. Feeding other sizes either errors out or forces a resize — the network’s learned filters expect that resolution.