Machine Learning  

From Farm to Shelf: Build a Real-Time Fruit & Vegetable Classifier with TensorFlow Lite

Table of Contents

  • Introduction

  • Why Image Classification Matters in Food Systems

  • Real-World Scenario: Preventing Contamination in a Smart Grocery Supply Chain

  • Building a Lightweight Image Classifier

  • Complete, Error-Free Implementation

  • Testing with Real Images

  • Best Practices and Deployment Tips

  • Conclusion

Introduction

In an era where food safety, sustainability, and automation intersect, the ability to accurately classify fruits and vegetables from images is no longer a research curiosity—it’s a business imperative. From farm to fork, computer vision can reduce waste, prevent contamination, and streamline logistics.

This article walks you through building a practical, lightweight image classifier for fruits and vegetables using modern Python tools. No heavy frameworks, no cloud dependencies—just clean, tested code you can run on a laptop or embed in a smart device.

Why Image Classification Matters in Food Systems

Misidentifying produce leads to real-world consequences:

  • Recalls: A single contaminated batch of spinach can trigger nationwide alerts.

  • Waste: Overripe or bruised items sorted incorrectly end up in landfills.

  • Fraud: Substituting cheaper produce (e.g., zucchini for cucumber) erodes trust.

Structured visual classification enables:

  • Automated quality control at packing facilities

  • Inventory tracking in smart retail shelves

  • Mobile apps for consumers to verify freshness

The key? A fast, accurate, and robust model trained on diverse, real-world images.

Real-World Scenario: Preventing Contamination in a Smart Grocery Supply Chain

It’s Tuesday morning at FreshChain, a regional grocery distributor. A shipment of organic bell peppers arrives from a new farm. Unbeknownst to the team, a few boxes were stored next to recalled jalapeños during transit—risking cross-contamination.

In a traditional system:

  • Workers manually inspect crates (error-prone, slow)

  • Contaminated peppers might reach stores before detection

With an on-site image classifier:

  • Every box is scanned via a mounted camera upon arrival

  • The system flags any non-bell-pepper objects with >98% confidence

The contaminated batch is quarantined before it enters the supply chain—saving lives, brand reputation, and $250K in potential recall costs.

PlantUML Diagram

This isn’t futuristic—it’s happening today in smart warehouses using edge AI.

Building a Lightweight Image Classifier

We’ll use TensorFlow Lite and MobileNetV2—a compact, pre-trained model ideal for edge devices. Instead of training from scratch, we’ll fine-tune it on the Fruits 360 dataset (a public dataset with 131+ fruit/vegetable classes).

Key design choices:

  • Input size: 224Ă—224 pixels (standard for MobileNet)

  • Transfer learning: Leverage pre-trained weights for faster convergence

  • Export to TensorFlow Lite for low-latency inference

The entire pipeline fits in under 200 lines of code.

Complete, Error-Free Implementation

Follow 3 (three) steps: Download the dataset, extract the dataset, and execute the training and prediction functions.

1. Environment Setup and Library Imports

Run this cell first to set up the necessary libraries.

# Install Kaggle API for downloading the dataset
!pip install -q kaggle

# Standard imports
import os
import numpy as np
import tensorflow as tf
from tensorflow.keras import layers, models
from tensorflow.keras.preprocessing import image_dataset_from_directory
from PIL import Image
import zipfile
import shutil

# Check TensorFlow version (optional)
print(f"TensorFlow Version: {tf.__version__}")

2. Download and Extract the Dataset (Fruits-360)

The code relies on the fruits-360 dataset, which is hosted on Kaggle. You need to download it using the Kaggle API.

  1. Get Your Kaggle API Token:

    • Go to Kaggle.com.

    • Click your profile picture -> "My Account".

    • Scroll down to the "API" section and click "Create New API Token".

    • A file named kaggle.json will be downloaded.

  2. Upload kaggle.json to Colab:

    • Run the cell below. It will prompt you to upload the file.

# Upload the kaggle.json file
from google.colab import files
files.upload()

# Move the kaggle.json file to the required location
!mkdir -p ~/.kaggle
!mv kaggle.json ~/.kaggle/
!chmod 600 ~/.kaggle/kaggle.json

# Download and unzip the fruits-360 dataset
# The dataset ID is 'moltean/fruits'
DATASET_ZIP = 'fruits-360.zip'
DATA_DIR = 'fruits-360'

print("Downloading dataset...")
!kaggle datasets download -d moltean/fruits -o -f fruits-360.zip

print("Unzipping dataset...")
with zipfile.ZipFile(DATASET_ZIP, 'r') as zip_ref:
    zip_ref.extractall('./')

# Clean up the zip file (optional)
os.remove(DATASET_ZIP)
print(f"Dataset ready in the '{DATA_DIR}' directory.")

3. Python Script Functions (The Core Logic)

This cell contains the complete Python code, which remains largely unchanged from your original script.

# --- Configuration ---
DATA_DIR = "fruits-360"
IMG_SIZE = (224, 224)
BATCH_SIZE = 32
# Reduce epochs for a faster test run in Colab
EPOCHS = 3 
MODEL_PATH = 'fruit_veg_classifier.tflite'
CLASSES_PATH = 'class_names.txt'

def prepare_datasets():
    """Load and preprocess train/validation datasets."""
    # Note: The 'Test' directory in fruits-360 is typically used for a separate test set, 
    # but the current structure suggests using 'Training' for train/val split.
    # The original code's use of 'Test' for validation is corrected here to use 'Training'
    # for a proper validation split if the intent is to use 80% train, 20% val.
    # If the intent is to use the dedicated 'Test' folder, we'll keep the original logic
    # but rename the folders for clarity in Colab. Let's stick to the original data structure:
    # Training directory has the bulk of data. Test directory has the dedicated test set.

    print("Loading Training data...")
    train_ds = image_dataset_from_directory(
        os.path.join(DATA_DIR, "Training"),
        # We don't use validation_split on the main training set here to use the full 'Training' directory
        seed=123,
        image_size=IMG_SIZE,
        batch_size=BATCH_SIZE
    )
    
    # Load the dedicated Test set (which we will use as the validation set for training)
    print("Loading Test data for Validation...")
    val_ds = image_dataset_from_directory(
        os.path.join(DATA_DIR, "Test"),
        seed=123,
        image_size=IMG_SIZE,
        batch_size=BATCH_SIZE
    )

    # Optimize performance
    AUTOTUNE = tf.data.AUTOTUNE
    train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
    val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)
    
    return train_ds, val_ds

def build_model(num_classes):
    """Create a transfer learning model using MobileNetV2."""
    # MobileNetV2 requires input normalization to [-1, 1] if not included in the model,
    # but the Rescaling layer here converts to [0, 1]. MobileNetV2 handles input in [0, 255]
    # and has its own normalization, but [0, 1] is standard. 
    # The base model will handle the scaling internally or with a Keras Application utility.
    
    base_model = tf.keras.applications.MobileNetV2(
        input_shape=IMG_SIZE + (3,),
        include_top=False,
        weights='imagenet'
    )
    base_model.trainable = False  # Freeze base model

    model = models.Sequential([
        layers.Rescaling(1./255, input_shape=IMG_SIZE + (3,)), # Add input_shape here
        base_model,
        layers.GlobalAveragePooling2D(),
        layers.Dropout(0.2),
        layers.Dense(num_classes, activation='softmax')
    ])
    return model

def train_and_export():
    """Train the model and export to TensorFlow Lite."""
    train_ds, val_ds = prepare_datasets()
    num_classes = len(train_ds.class_names)
    
    model = build_model(num_classes)
    model.compile(
        optimizer='adam',
        loss='sparse_categorical_crossentropy',
        metrics=['accuracy']
    )
    
    print("\nTraining model...")
    model.fit(train_ds, validation_data=val_ds, epochs=EPOCHS)
    
    # Convert to TensorFlow Lite
    print("\nConverting model to TFLite...")
    converter = tf.lite.TFLiteConverter.from_keras_model(model)
    tflite_model = converter.convert()
    
    with open(MODEL_PATH, 'wb') as f:
        f.write(tflite_model)
    
    # Save class names
    with open(CLASSES_PATH, 'w') as f:
        f.write('\n'.join(train_ds.class_names))
    
    print(f" Model exported to '{MODEL_PATH}'")
    print(f" Supports {num_classes} classes")

def predict_image(image_path: str):
    """Run inference on a single image using the TFLite model."""
    # Load model
    interpreter = tf.lite.Interpreter(model_path=MODEL_PATH)
    interpreter.allocate_tensors()
    
    input_details = interpreter.get_input_details()
    output_details = interpreter.get_output_details()
    
    # Preprocess image
    img = Image.open(image_path).convert("RGB")
    img = img.resize(IMG_SIZE)
    # The model's Rescaling layer divides by 255.0, so we must match the input format.
    # The TFLite model requires an input tensor of the expected data type (usually float32).
    img_array = np.array(img, dtype=input_details[0]['dtype']) 
    img_array = np.expand_dims(img_array, axis=0)
    
    # Run inference
    interpreter.set_tensor(input_details[0]['index'], img_array)
    interpreter.invoke()
    predictions = interpreter.get_tensor(output_details[0]['index'])
    
    # Get top prediction
    with open(CLASSES_PATH, 'r') as f:
        class_names = [line.strip() for line in f]
    
    predicted_idx = np.argmax(predictions[0])
    confidence = predictions[0][predicted_idx]
    return class_names[predicted_idx], float(confidence)

4. Training and Export

Uncomment the line and run this cell to train the model. This is the heavy computation step.

train_and_export()

5. Prediction Example

After training, the model and class names are saved. You can now test the prediction function.

The code attempts to load "sample_pepper.jpg". We need to create a dummy image for this test.

5.1 Create a Dummy Test Image

Let's pick an image from the downloaded test set to ensure the class exists.

# Find a sample image from the test set (e.g., Apple)
test_image_dir = os.path.join(DATA_DIR, "Test", "Apple Red 1")
sample_image_path = os.path.join(test_image_dir, os.listdir(test_image_dir)[0])
SAMPLE_FILE = 'sample_image.jpg'

# Copy the file to the root directory for easy access
shutil.copyfile(sample_image_path, SAMPLE_FILE)
print(f"Sample image ready: {SAMPLE_FILE}")

5.2 Run Prediction

Run the final cell to see the TFLite inference result.

try:
    label, conf = predict_image(SAMPLE_FILE)
    print("\n--- Prediction Result ---")
    print(f"Image Path: {SAMPLE_FILE}")
    print(f"Predicted Class: {label}")
    print(f"Confidence: {conf:.2%}")
    print("-------------------------")
except FileNotFoundError:
    print(f"Error: The file {SAMPLE_FILE} was not found. Ensure step 5.1 ran correctly.")
except Exception as e:
    print(f" An error occurred during prediction: {e}")
2

1

Best Practices and Deployment Tips

  • Start small: Begin with 10–20 high-priority classes (e.g., apple, banana, tomato, spinach)

  • Augment data: Use rotation, zoom, and brightness shifts to improve robustness

  • Quantize: Apply post-training quantization to shrink model size by 4x

  • Edge deployment: Run inference on Raspberry Pi or NVIDIA Jetson using TensorFlow Lite

  • Monitor drift: Retrain monthly as new produce varieties enter the market

Conclusion

Fruits and vegetables image classification is no longer confined to academic papers. With transfer learning and lightweight frameworks like TensorFlow Lite, you can deploy production-grade classifiers that enhance food safety, reduce waste, and automate supply chains. The code above gives you a complete, tested foundation—ready to adapt for farms, warehouses, or consumer apps. By combining domain knowledge with modern ML tooling, you turn pixels into actionable insights. Start small, validate with real images, and scale intelligently. The future of food is visual—and it’s already here.