...

How To Build Sports Image Classification Model Using MobileNet

sports image classification

Train a MobileNet Sports Image Classifier with TensorFlow and Keras Transfer Learning

Introduction

This tutorial walks you through a practical workflow for sports image classification using MobileNet transfer learning in TensorFlow and Keras.
You will see how to prepare a clean data pipeline, adapt the MobileNet backbone, and train a compact model that recognizes 21 different sports with minimal compute.
By grounding the project in MobileNet transfer learning, we leverage rich ImageNet features and specialize them for sports image classification with a lightweight custom head.
The result is an efficient, reproducible approach that balances speed, accuracy, and maintainability for real-world datasets.

Check out our tutorial here : https://youtu.be/xORACIVRNd4&list=UULFTiWJJhaH6BviSWKLJUM9sg

Link for the full code here : https://ko-fi.com/s/56623ec7a0

You can find more tutorials in my blog : https://eranfeit.net/blog/


Code for MobileNet Sports Image Classifier with TensorFlow :

You can use theses dataset for this code :

LInk for the dataset1 : https://www.kaggle.com/datasets/gpiosenka/sports-classification

Link for the dataset2 : https://www.kaggle.com/code/sauravbhuyan/73-sports-image-classification/input

Data Pipeline and Environment Setup

Short Description
Set up a reliable data pipeline for sports image classification and normalize inputs for MobileNet transfer learning using ImageDataGenerator and MobileNet’s preprocessing utilities.

Elaborated Description
A robust input pipeline is the foundation of accurate sports image classification.
We organize the dataset into train, validation, and test directories and keep class names explicit to ensure stable label mappings across runs.
This structure is crucial for reproducibility and for decoding predictions back to human-readable sport categories.
Consistent directory layouts also make it easy to scale the project to additional sports or new data sources.

MobileNet expects inputs that are normalized in a specific way, and tf.keras.applications.mobilenet.preprocess_input handles that requirement for us.
Applying this preprocessing inside ImageDataGenerator ensures that every image sent to the model adheres to MobileNet’s training distribution.
This reduces input shift and helps MobileNet transfer learning converge faster with fewer surprises.
It also streamlines experimentation because data normalization is declaratively embedded in the pipeline.

Batching and target sizes matter when optimizing the training loop.
A target size of (224, 224) matches MobileNet defaults, and a small batch size maintains compatibility with limited GPU memory or CPU-only workflows.
Preserving shuffle=False for the test generator keeps index alignment intact, making it simple to map model outputs back to the correct images later.
These small choices add up to a smoother debugging and evaluation experience.

Verifying GPU availability and enabling memory growth prevents pre-allocation issues that can crash training sessions.
Even if you run on CPU, this code path remains stable, just a bit slower.
With the pipeline prepared, sports image classification becomes a matter of feeding well-formed batches into a well-understood backbone.
This careful setup is what allows MobileNet transfer learning to shine with minimal friction.

### Import core scientific and deep learning libraries for the project.   from scipy.ndimage.measurements import label ### Import TensorFlow as the primary deep learning framework.   import tensorflow as tf ### Import operating system utilities for path changes.   import os ### Import Keras high-level API packaged with TensorFlow.   import keras ### Import optimizers for training configuration.   from tensorflow.keras import optimizers ### Import common Keras layers used later for model heads.   from tensorflow.keras.layers import Conv2D, Flatten, Dense ### Import the base Model class to stitch inputs and outputs.   from tensorflow.python.keras.engine.training import Model ### Import ImageDataGenerator to stream images from folders in batches.   from tensorflow.keras.preprocessing.image import ImageDataGenerator ### Import Adam optimizer for stable gradient updates.   from tensorflow.keras.optimizers import Adam  ### Import utilities for plotting, math, OpenCV operations, and TensorFlow internal helpers.   import matplotlib.pyplot as plt  import numpy as np import cv2 from tensorflow.python.ops.gradients_util import _Inputs  ### List available GPU devices to confirm acceleration.   physicalDevices = tf.config.experimental.list_physical_devices('GPU') ### Print the number of detected GPUs for transparency.   print('Num GPUs available : ', len(physicalDevices)) ### Enable memory growth to prevent TensorFlow from pre-allocating all GPU memory.   tf.config.experimental.set_memory_growth(physicalDevices[0], True) ### Visual separator for logs.   print('============================================')  ### Change working directory to the dataset root.   os.chdir('C:/SportsImages')  ### Define top-level split directories for the pipeline.   train_path = 'train' valid_path = 'valid' test_path = 'test'  ### Enumerate the 21 class names for stable label ordering.   class_names = [ "air hockey", "ampute football", "archery", "arm wrestling", "balance beam", "barell racing", "baseball", "basketball", "billiards", "bmx", "swimming", "table tennis", "tennis", "track bicycle", "tug of war", "uneven bars", "volleyball", "water polo", "weightlifting", "wheelchair basketball", "wheelchair racing", ]  ### Create a training generator with MobileNet preprocessing for normalized inputs.   train_batches = ImageDataGenerator(     preprocessing_function=tf.keras.applications.mobilenet.preprocess_input ).flow_from_directory(     directory=train_path, target_size=(224,224), classes=class_names, batch_size=10 )  ### Create a validation generator with the same preprocessing for consistency.   valid_batches = ImageDataGenerator(     preprocessing_function=tf.keras.applications.mobilenet.preprocess_input ).flow_from_directory(     directory=valid_path, target_size=(224,224), classes=class_names, batch_size=10 )  ### Create a non-shuffled test generator to preserve index → label mapping.   test_batches = ImageDataGenerator(     preprocessing_function=tf.keras.applications.mobilenet.preprocess_input ).flow_from_directory(     directory=test_path, target_size=(224,224), classes=class_names, batch_size=10, shuffle=False )  ### Draw one batch from training to quickly inspect images and labels.   imgs, labels = next(train_batches)  ### Define a helper to visualize a batch for sanity checks.   def plotImages(images_arr):     ### Create a wide figure with 10 axes for the batch.       flg, axes = plt.subplots(1, 10, figsize=(20, 20))     ### Flatten axes for easy iteration.       axes = axes.flatten()     ### Render each image and hide axes for a clean grid.       for img, ax in zip(images_arr, axes):         ax.imshow(img)         ax.axis('off')     ### Compact layout to avoid overlaps.       plt.tight_layout()     ### Show the figure on screen.       plt.show() 

Link for the full code here : https://ko-fi.com/s/56623ec7a0

Building a MobileNet Transfer Learning Classifier

Short Description
Convert a pretrained MobileNet into a task-specific head for sports image classification by cutting off the original classifier, adding a 21-way softmax, and selectively freezing layers for efficient MobileNet transfer learning.

Elaborated Description
MobileNet transfer learning capitalizes on features learned from large-scale datasets like ImageNet.
Instead of training from scratch, we reuse MobileNet’s general vision features—edges, textures, shapes—and adapt only the tail to sports image classification.
This approach markedly reduces training time while achieving competitive accuracy on modest datasets.
It is a proven recipe when compute and data are limited.

We tap a tensor near the end of the backbone—six layers from the last layer in this case—to replace the original classifier.
Attaching a compact Dense softmax with 21 units aligns the model with our exact number of sport classes.
This minimalist head reduces the risk of overfitting while still letting the model learn discriminative patterns across multiple sports.
It is a pragmatic balance between flexibility and simplicity.

Freezing most of the early layers preserves MobileNet’s general features and stabilizes gradients.
Unfreezing only the last 23 layers gives the model enough capacity to specialize in sports image classification without destroying the useful representations learned pretraining.
This fine-tuning strategy is central to MobileNet transfer learning, especially when datasets are not massive.
It also prevents training from becoming unnecessarily slow.

Compiling with Adam at a small learning rate and using categorical cross-entropy aligns with our one-hot labels from directory flows.
Tracking accuracy keeps training feedback intuitive, while metrics like loss guide optimization.
Because MobileNet transfer learning starts from a strong baseline, you often reach solid performance in relatively few epochs.
That efficiency is a key reason to pick MobileNet for production-minded projects.

### Load the pretrained MobileNet backbone with ImageNet weights.   mobile = tf.keras.applications.mobilenet.MobileNet()  ### Tap into a feature tensor six layers from the end to replace the original head.   x = mobile.layers[-6].output  ### Add a new Dense softmax layer with 21 units for our target classes.   output = Dense(units=21, activation='softmax')(x)  ### Stitch inputs and the new output together into a single Keras model.   model = Model(inputs=mobile.input, outputs=output)  ### Freeze earlier layers to preserve general features and reduce overfitting.   for layer in model.layers[:-23]:     layer.trainable = False  ### Inspect the architecture to confirm trainable vs non-trainable layers.   model.summary()  ### Compile with Adam, categorical cross-entropy, and accuracy for multi-class classification.   model.compile(optimizer=Adam(lr=0.0001), loss='categorical_crossentropy', metrics=['accuracy']) 

Link for the full code here : https://ko-fi.com/s/56623ec7a0

Training, Evaluation, and OpenCV Visualization

Short Description
Train the adapted model, generate predictions for sports image classification, decode labels, and overlay results on images with OpenCV to quickly validate MobileNet transfer learning outcomes.

Elaborated Description
Training feeds the batched, normalized images through the adapted MobileNet and monitors validation signals to detect under- or over-fitting.
Because we rely on MobileNet transfer learning, early epochs often deliver strong improvements as the tail layers specialize to our sports.
If validation metrics plateau, you can extend epochs, unfreeze additional layers, or tune the learning rate to refine performance.
This loop is predictable and easy to iterate.

After training, inference produces a probability distribution over the 21 classes for each test image.
Using argmax converts these distributions into predicted indices, which we then map back to human-readable labels for sports image classification.
Keeping the test generator unshuffled ensures that prediction indices align with the underlying images.
This alignment simplifies debugging and targeted error analysis.

Qualitative checks with OpenCV accelerate insight.
Overlaying predicted labels on images with cv2.putText makes it obvious when the model succeeds or fails on edge cases.
These quick visuals help you detect patterns like recurring misclassifications between visually similar sports.
Armed with those insights, you can adjust data balance, augmentations, or unfreezing depth to improve MobileNet transfer learning results.

Saving a few annotated samples documents progress and communicates model behavior to non-technical stakeholders.
This is often more persuasive than raw numbers alone.
By pairing quantitative metrics with compelling visuals, you build trust in the sports image classification pipeline.
That combination makes it easier to justify next steps like broader deployment or targeted data collection.

### Fit the model on training data and validate on the held-out split.   model.fit(x=train_batches, validation_data=valid_batches, epochs=10, verbose=2)  ### Capture the ground-truth class indices from the test generator.   test_lables = test_batches.classes ### Print the raw label vector for quick inspection of indices.   print(test_lables)  ### Generate probability predictions for every test image.   predictions = model.predict(x=test_batches, verbose=0)  ### Examine the probability vector for a specific test example.   print(predictions[6]) ### Convert probabilities to a predicted class index via argmax.   class_index = np.argmax(predictions[6]) ### Map predicted index to the human-readable class name.   class_name_predicted = class_names[class_index] ### Map original index from test labels to the true class name.   class_name_original = class_names[test_lables[6]]  ### Log both predicted and original class names for cross-checking.   print('predict class 6', class_name_predicted) print('original class 6', class_name_original)  ### Draw the next test batch to fetch the corresponding image by position.   imgs, lables = next(test_batches) ### Select the sixth image for visualization.   img = imgs[6] ### Convert from RGB to BGR for OpenCV compatibility.   img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)  ### Overlay the predicted class name on the image for quick review.   img = cv2.putText(img, class_name_predicted, (5, 55), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2, cv2.LINE_AA) ### Display the annotated image in a window.   cv2.imshow('img', img) ### Wait for a key press before closing the window.   cv2.waitKey(0)  ### Step through additional batches to visualize more predictions.   imgs, lables = next(test_batches) imgs, lables = next(test_batches)  ### Decode class index for the 26th prediction in the flat list.   class_index = np.argmax(predictions[25]) ### Convert index to label for display.   class_name_predicted = class_names[class_index]  ### Select the corresponding image position within the current batch.   img = imgs[5] ### Convert to BGR and annotate with the predicted label.   img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) img = cv2.putText(img, class_name_predicted, (5, 55), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2, cv2.LINE_AA) ### Show and save the result for documentation.   cv2.imshow('img', img) cv2.waitKey(0) cv2.imwrite('test2.jpg', img)  ### Visualize another prediction at a different index for variety.   imgs, lables = next(test_batches) class_index = np.argmax(predictions[35]) class_name_predicted = class_names[class_index] img = imgs[5] img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) img = cv2.putText(img, class_name_predicted, (5, 55), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2, cv2.LINE_AA) cv2.imshow('img', img) cv2.waitKey(0) cv2.imwrite('test3.jpg', img) 

Link for the full code here : https://ko-fi.com/s/56623ec7a0


Here some test images :

mobilenet transfer learning
mobilenet transfer learning
mobilenet transfer learning
mobilenet transfer learning

Connect :

☕ Buy me a coffee — https://ko-fi.com/eranfeit

🖥️ Email : feitgemel@gmail.com

🌐 https://eranfeit.net

🤝 Fiverr : https://www.fiverr.com/s/mB3Pbb

Enjoy,

Eran

error: Content is protected !!
Eran Feit