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
- Python 3.9+ — from python.org.
- Dependencies:
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 addpillow-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_resourcevscache_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_inputfor 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=Trueand 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.