A farmer standing in a field with two bars of signal does not care about your server’s GPU. The interesting constraint in the Plant Disease Detector was never accuracy on a benchmark — it was latency, privacy, and offline behaviour.
Start from the deployment target
Before training, I wrote down the budget: the model has to run in well under a second on a mid-range Android phone, fit inside a normal app download, and never upload a photo to a server.
- Input resolution: small enough for real-time inference.
- Quantisation: post-training int8 where accuracy held.
- Class set: the 38 PlantVillage classes, grouped so an ambiguous prediction can still say “likely fungal”.
Export, then verify the export
The model that wins on a validation split is not always the model that ships. A quantised model has different numerical behaviour, so compare the two:
interpreter = tf.lite.Interpreter(model_path="model.tflite")
interpreter.allocate_tensors()
# Compare argmax on a labelled sample of 200 images,
# not on the training set you already overfit to.
If quantisation moves accuracy by more than a point, narrow the input range or keep float16 for the last layers instead of giving up the speed.
The app is the product
The Flutter side gets the interesting work: camera capture with a crop guide, a queue for offline analysis, and a result screen that leads with a next action instead of a label.
- Confidence under 70%? Ask for a second photo rather than guessing.
- The advice copy matters more than the percentage.
- Store scans locally with a timestamp so a farmer can compare over a season.
What I would do next
- On-device fine-tuning on the farm’s own leaves.
- A disease-class weighting loss so the rare classes are not drowned by healthy leaf images.
- A tiny language model for local-language guidance, generated from a reviewed template.
The lesson generalises: for a used product, the optimiser is the user’s environment, not the leaderboard.