Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Building a Food Image Classifier Web Application in Under an Hour

Tech May 17 9

Project Architecture Overview

Creating an image recognition application involves three primary stages: collecting a dataset, training a convolusional neural network, and deploying the model through a web interface. Each phase is outlined with concrete implementation steps.

1. Data Acquisition

Machine learning models require substantial example data. For a food classifier, we can automate image collection using a script that queries a search engine API.

Custom Image Crawler

The following Python class constructs search URLs, extracts image links from JSON responses, and saves the downloaded pictures into a directory tree.

import os
import re
import sys
import requests

class ImageFetcher:
    BASE_URL = "https://image.baidu.com/search/acjson?"
    HEADERS = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                      "AppleWebKit/537.36 (KHTML, like Gecko) "
                      "Chrome/102.0.0.0 Safari/537.36"
    }

    def __init__(self):
        self.query = sys.argv[1]
        self.pages = int(sys.argv[2])
        self.output_dir = sys.argv[3]

    def _build_params(self):
        param_list = []
        for p in range(1, self.pages + 1):
            offset = 30 * p
            param_list.append(
                'tn=resultjson_com&ipn=rj&ct=201326592&'
                'word={0}&queryWord={0}&pn={1}&rn=30&gsm={2}'.format(
                    self.query, offset, hex(offset)[2:])
            )
        return param_list

    def _resolve_image_urls(self, request_urls):
        collected = []
        for url in request_urls:
            response_text = requests.get(url, headers=self.HEADERS).text
            collected += re.findall(r'"thumbURL":"(.*?)"', response_text)
        return collected

    def _save_batch(self, url_list):
        path = os.path.join('..', self.output_dir)
        os.makedirs(path, exist_ok=True)
        for idx, img_url in enumerate(url_list):
            file_path = os.path.join(path, f"{idx}.jpg")
            with open(file_path, "wb") as f:
                f.write(requests.get(img_url, headers=self.HEADERS).content)
            if (idx + 1) % 30 == 0:
                print(f"Downloaded page {(idx + 1) // 30}")

    def execute(self):
        params = self._build_params()
        request_urls = [self.BASE_URL + p for p in params]
        image_urls = self._resolve_image_urls(request_urls)
        self._save_batch(image_urls)

if __name__ == '__main__':
    fetcher = ImageFetcher()
    fetcher.execute()

Organizing the Raw Data

After running the crawler for six food categories (donut, hamburger, ice-cream, pizza, rice, tart), the collected images are split into training, validation, and test sets.

import os
import shutil

def ensure_dir(path):
    if not os.path.exists(path):
        os.makedirs(path)

def partition_dataset(source_root, target_root, split=(0.6, 0.2, 0.2)):
    classes = os.listdir(source_root)
    train_r, val_r, _ = split
    for subset in ['train', 'val', 'test']:
        for cls in classes:
            ensure_dir(os.path.join(target_root, subset, cls))

    for cls in classes:
        images = os.listdir(os.path.join(source_root, cls))
        n = len(images)
        train_end = int(n * train_r)
        val_end = int(n * (train_r + val_r))
        splits_map = {
            'train': images[:train_end],
            'val': images[train_end:val_end],
            'test': images[val_end:]
        }
        for subset, subset_files in splits_map.items():
            print(f"Moving {cls} -> {subset}")
            for fname in subset_files:
                shutil.copyfile(
                    os.path.join(source_root, cls, fname),
                    os.path.join(target_root, subset, cls, fname)
                )
    print('Dataset split completed.')

if __name__ == '__main__':
    partition_dataset('foodData', 'foodData_cnn_split')

2. Model Training

TensorFlow/Keras offers a high-level API to design and train deep learning models. A convolutional neural network (CNN) is particularly effective for image classification.

from tensorflow.keras import layers, models
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import matplotlib.pyplot as plt
import os

IMG_SIZE = (224, 224)
BATCH = 30
CLASS_COUNT = 6

def build_cnn():
    model = models.Sequential([
        layers.Conv2D(32, (3, 3), activation='relu', input_shape=(*IMG_SIZE, 3)),
        layers.MaxPooling2D((2, 2)),
        layers.Conv2D(64, (3, 3), activation='relu'),
        layers.MaxPooling2D((2, 2)),
        layers.Conv2D(128, (3, 3), activation='relu'),
        layers.MaxPooling2D((2, 2)),
        layers.Conv2D(128, (3, 3), activation='relu'),
        layers.MaxPooling2D((2, 2)),
        layers.Flatten(),
        layers.Dropout(0.15),
        layers.Dense(512, activation='relu'),
        layers.Dense(CLASS_COUNT, activation='softmax')
    ])
    model.compile(
        optimizer='rmsprop',
        loss='categorical_crossentropy',
        metrics=['accuracy']
    )
    return model

def create_generators(train_dir, val_dir):
    augment_config = dict(
        rescale=1./255,
        rotation_range=40,
        width_shift_range=0.2,
        height_shift_range=0.2,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True,
        fill_mode='nearest'
    )
    train_gen = ImageDataGenerator(**augment_config)
    validation_gen = ImageDataGenerator(rescale=1./255)

    train_flow = train_gen.flow_from_directory(
        train_dir, target_size=IMG_SIZE, batch_size=BATCH, class_mode='categorical'
    )
    val_flow = validation_gen.flow_from_directory(
        val_dir, target_size=IMG_SIZE, batch_size=BATCH, class_mode='categorical'
    )
    return train_flow, val_flow

if __name__ == '__main__':
    model = build_cnn()
    model.summary()

    train_dir = './foodData_cnn_split/train'
    valid_dir = './foodData_cnn_split/val'
    train_gen, valid_gen = create_generators(train_dir, valid_dir)

    steps_per_epoch = (300 * CLASS_COUNT) // BATCH
    val_steps = (150 * CLASS_COUNT) // BATCH

    history = model.fit(
        train_gen,
        steps_per_epoch=steps_per_epoch,
        epochs=3,
        validation_data=valid_gen,
        validation_steps=val_steps
    )

    model.save('food_classifier.h5')

    acc = history.history['accuracy']
    val_acc = history.history['val_accuracy']
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    epochs_range = range(1, len(acc) + 1)

    plt.figure()
    plt.plot(epochs_range, acc, 'bo', label='Train accuracy')
    plt.plot(epochs_range, val_acc, 'b', label='Validation accuracy')
    plt.title('Accuracy over epochs')
    plt.legend()

    plt.figure()
    plt.plot(epochs_range, loss, 'bo', label='Train loss')
    plt.plot(epochs_range, val_loss, 'b', label='Validation loss')
    plt.title('Loss over epochs')
    plt.legend()
    plt.show()

Key observations:

  • flow_from_directory expects class-wise subdirectories.
  • Data augmentation (rotation_range, horizontal_flip, etc.) expands training variety without requiring new images.
  • Only a few epochs are used for demonstration; real-world applications benefit from longer training and early stopping checks.

3. Web Deployment with Flask

A minimal Flask server integrates the trained model and provides a browser interface for image uploads and predictions.

Directory Layout

project_root/web/
├── static/
│   ├── Image/
│   └── CSS/
│       └── bootstrap.min.css
├── templates/
│   └── index.html
├── food_classifier.h5
├── predict.py
└── app.py

Flask Backend (app.py)

import os
import time
from flask import Flask, request, redirect, render_template, flash, url_for
from werkzeug.utils import secure_filename
from predict import classify_image

UPLOAD_FOLDER = os.path.join('static', 'Image')
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}

app = Flask(__name__)
app.config['SECRET_KEY'] = 'change-me-in-production'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

def is_allowed(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        if 'file' not in request.files:
            flash('Missing file part')
            return redirect(request.url)
        file = request.files['file']
        if file.filename == '':
            flash('No file selected')
            return redirect(request.url)
        if file and is_allowed(file.filename):
            stored_name = str(int(time.time())) + '_' + secure_filename(file.filename)
            save_path = os.path.join(app.config['UPLOAD_FOLDER'], stored_name)
            file.save(save_path)
            prediction, probabilities = classify_image(save_path)
            flash('Upload successful')
            return render_template(
                'index.html',
                filename=stored_name,
                prediction=prediction,
                probabilities=probabilities
            )
        else:
            flash('Only png, jpg, jpeg, gif files are allowed')
    return render_template('index.html')

@app.route('/display/<filename>')
def display(filename):
    return redirect(url_for('static', filename='Image/' + filename), code=301)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=9000, debug=True)

Prediction Module (predict.py)

import numpy as np
import tensorflow as tf
from PIL import Image

LABELS = ['donut', 'hamburger', 'ice-cream', 'pizza', 'rice', 'tart']
IMG_WIDTH, IMG_HEIGHT = 224, 224

def load_model():
    return tf.keras.models.load_model('food_classifier.h5')

def classify_image(filepath):
    img = Image.open(filepath).resize((IMG_WIDTH, IMG_HEIGHT))
    array = np.asarray(img).reshape(1, IMG_WIDTH, IMG_HEIGHT, 3)
    outputs = load_model().predict(array)
    index = np.argmax(outputs)
    confidence = outputs[0][index] * 100
    result_text = f"{LABELS[index]} ({confidence:.2f}%)"
    return result_text, [round(v * 100, 2) for v in outputs[0]]

HTML Template Skeleton (index.html)

A modern Bootstrap-based form allows users to choose an image, submit it, and view both the uploaded picture and classification details.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="{{ url_for('static', filename='CSS/bootstrap.min.css') }}">
    <title>Food Classifier</title>
</head>
<body>
    <div class="container py-4">
        <h1 class="mb-4">Food Image Classifier</h1>
        {% with messages = get_flashed_messages() %}
          {% if messages %}
            <div class="alert alert-info">
              {{ messages[0] }}
            </div>
          {% endif %}
        {% endwith %}

        <form method="POST" enctype="multipart/form-data">
            <div class="mb-3">
                <input class="form-control" type="file" name="file" required>
            </div>
            <button class="btn btn-primary" type="submit">Analyze</button>
        </form>

        {% if filename %}
        <hr>
        <div class="row">
            <div class="col-md-4">
                <img src="{{ url_for('display', filename=filename) }}" class="img-fluid">
            </div>
            <div class="col-md-8">
                <h3>{{ prediction }}</h3>
                <ul>
                {% for i in range(6) %}
                  <li>{{ labels[i] }}: {{ probabilities[i] }}%</li>
                {% endfor %}
                </ul>
            </div>
        </div>
        {% endif %}
    </div>
</body>
</html>

4. Running the Application

  1. Install dependencies: pip install tensorflow flask pillow
  2. Place the food_classifier.h5 file in the web directory.
  3. Execute python app.py and visit http://localhost:9000.
  4. Upload a food photo to receive a real-time classification with confidence scores.

The entire pipeline—from data scraping to a working web interface—can be completed rapidly. While the default three-epoch training yields modest accuracy, increasing epochs and dataset size substantially improves predictions.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.