DEV Community

Devanshu Biswas
Devanshu Biswas

Posted on

I Built an Offline AI Crop Disease Identifier for Smart India Hackathon

🌐 Live demo: https://dev48v.infy.uk/solve/day3-crop-disease.html

Day 3 of SolveFromZero — pick a real hackathon brief, ship a working solution. Today's problem is a perennial Smart India Hackathon: 35% of Indian crop losses come from undiagnosed disease, and the village ag-extension officer can't visit every farm.

The fix: a phone app where the farmer photographs a sick leaf, AI returns the disease + a remedy in Hindi or Marathi. Runs fully offline on a ₹6,000 Android phone.

The hard constraints

Hardware ₹6,000 Android phone, 2 GB RAM
Network Patchy 2G in rural areas
Model size Must fit in < 20 MB to bundle with the app
Inference < 2 seconds per leaf photo
UX Hindi / Marathi / Tamil voice output (many farmers can't read)

These constraints rule out cloud APIs (no GPT-4V), large vision models (no 100M+ param ResNets), and English-only UI.

The pipeline

📷 Phone camera
   ↓
🖼  Pre-process (resize 224×224, normalize 0-1)
   ↓
🧠 MobileNetV2 (transfer-learned on PlantVillage) → softmax over 38 classes
   ↓
🏆 Top class + confidence
   ↓
📋 Lookup table: disease → Hindi remedy
   ↓
🔊 speechSynthesis.speak(remedy_in_hindi)
Enter fullscreen mode Exit fullscreen mode

5 stages. Each one ~20-30 lines of code. Total: ~200 lines for the whole app.

Stage 1 — PlantVillage dataset (free)

PlantVillage is the canonical academic dataset: 54,305 labeled leaf photos across 38 disease classes across 14 crops (tomato, potato, grape, corn, apple, ...). Public, free, no attribution required.

Download from Kaggle: https://www.kaggle.com/datasets/abdallahalidev/plantvillage-dataset

This dataset alone covers most South Asian crops.

Stage 2 — Transfer learning from MobileNetV2

Don't train a CNN from scratch — you'd need 100k+ images + days of GPU. Take MobileNetV2 (pre-trained on ImageNet, knows generic visual features), freeze the first 100 layers, replace the final classification head with one for 38 classes, fine-tune the last few layers on PlantVillage.

from tensorflow.keras.applications import MobileNetV2

base = MobileNetV2(weights="imagenet", include_top=False)
for layer in base.layers[:-30]:
    layer.trainable = False

model = Sequential([
    base,
    GlobalAveragePooling2D(),
    Dense(38, activation="softmax")
])

model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"])
model.fit(train_ds, validation_data=val_ds, epochs=10)
Enter fullscreen mode Exit fullscreen mode

~30 minutes on a free Colab T4 GPU. Hits ~95% validation accuracy.

Stage 3 — Quantize to fit on phone

MobileNetV2 fp32 = 14 MB. Quantize weights to int8 → 3.5 MB. Same accuracy ±1%.

converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = rep_data_gen
tflite_model = converter.convert()
# 14 MB → 3.5 MB, < 1% accuracy loss
Enter fullscreen mode Exit fullscreen mode

Now it fits in 1.5 MB gzipped over a 2G connection.

Stage 4 — Run in-browser with TensorFlow.js (no native code)

You can ship as a Progressive Web App — no Play Store hassle, works on any Android with a browser. Convert the Keras model to TF.js layers format and load it from JS:

const model = await tf.loadLayersModel("/model/model.json");

fileInput.onchange = async (e) => {
  const img = new Image();
  img.src = URL.createObjectURL(e.target.files[0]);
  await img.decode();

  const tensor = tf.browser.fromPixels(img)
    .resizeBilinear([224, 224])
    .toFloat().div(255).expandDims();

  const probs = await model.predict(tensor).data();
  const top = probs.indexOf(Math.max(...probs));

  console.log(CLASSES[top], probs[top]);
};
Enter fullscreen mode Exit fullscreen mode

Stage 5 — Remedy lookup + Hindi voice

const REMEDIES = {
  "tomato_late_blight": {
    treatment: "Spray 2g/L Mancozeb every 7 days for 3 weeks.",
    hindi: "मैनकोज़ेब 2 ग्राम/लीटर पानी में मिलाकर 7 दिन में छिड़काव करें।"
  },
  // ... 38 entries
};

const r = REMEDIES[CLASSES[top]];

const utter = new SpeechSynthesisUtterance(r.hindi);
utter.lang = "hi-IN";
speechSynthesis.speak(utter);
Enter fullscreen mode Exit fullscreen mode

Many farmers can't read. Voice output is the difference between "interesting demo" and "actually deployable tool."

Offline-first PWA (the critical detail)

Rural villages have spotty 2G. App must work without network after first install:

// service-worker.js
self.addEventListener("install", e => {
  e.waitUntil(
    caches.open("crop-v1").then(cache =>
      cache.addAll([
        "/", "/index.html", "/app.js",
        "/model/model.json",
        "/model/weights.bin"  // 3.5 MB
      ])
    )
  );
});
Enter fullscreen mode Exit fullscreen mode

After the first visit, the app + model are cached. Works in airplane mode. This is what makes the app actually deployable.

Try it now

3-tier page with simulated 4-leaf classifier + 9-step understanding:
https://dev48v.infy.uk/solve/day3-crop-disease.html

What this unlocks

Same pipeline (small CNN + lookup table + voice) works for:

Problem Model swap
Cattle disease ID PlantDoc-style cattle dataset
Weed identification DeepWeeds dataset
Soil-color → nutrient deficiency Custom dataset + simple classifier
Insect pest identification IP102 dataset

The CNN + offline PWA + multilingual voice = the template for any rural-village AI deployment.

What's next in SolveFromZero

Day 4: GitHub repo health scorer (Devpost). Paste a GitHub URL → get a health score based on stars, recent commits, issue/PR ratios.

🌐 All problems: https://dev48v.infy.uk/solvefromzero.php

Top comments (0)