SolarScan — solar panel condition classifier
YOLOv8n-cls fine-tuned to sort a photograph of a solar panel into one of six
conditions, and map that to a maintenance action. Trained for SolarScan, a
graduation project that turns a smartphone into a panel inspector.
Ships as both PyTorch (best.pt, 3 MB) and ONNX (best.onnx, 5.5 MB). The ONNX
export was verified against the PyTorch model under identical preprocessing —
maximum per-class probability difference 3.6e-07.
Classes and actions
| Class | Recommended action |
|---|
| Bird-drop | Cleaning required |
| Clean | No action needed |
| Dusty | Cleaning required |
| Electrical-damage | Technician inspection required |
| Physical-Damage | Panel replacement required |
| Snow-Covered | Remove snow |
Class indices are alphabetical, in the order above.
In the deployed service, a top-1 confidence below 0.5 is reported as
Unknown rather than a class — without that floor an image that isn't a solar
panel gets forced into one of the six.
Usage
1from ultralytics import YOLO
2
3model = YOLO("best.pt")
4r = model("panel.jpg")[0]
5print(r.names[r.probs.top1], float(r.probs.top1conf))
ONNX, with the preprocessing the model was trained under — resize the shortest
edge to 224 (bilinear, antialiased), centre-crop 224×224, scale to 0..1, NCHW:
1import numpy as np, onnxruntime as ort
2from PIL import Image
3
4NAMES = ["Bird-drop", "Clean", "Dusty", "Electrical-damage",
5 "Physical-Damage", "Snow-Covered"]
6
7im = Image.open("panel.jpg").convert("RGB")
8w, h = im.size
9s = 224 / min(w, h)
10im = im.resize((round(w * s), round(h * s)), Image.BILINEAR)
11w, h = im.size
12im = im.crop(((w - 224) // 2, (h - 224) // 2, (w - 224) // 2 + 224, (h - 224) // 2 + 224))
13x = (np.asarray(im, np.float32) / 255).transpose(2, 0, 1)[None]
14
15sess = ort.InferenceSession("best.onnx")
16p = sess.run(None, {sess.get_inputs()[0].name: x})[0][0]
17print(NAMES[int(p.argmax())], float(p.max()))
Training
| |
|---|
| Base | yolov8n-cls.pt (pretrained) |
| Task | Whole-image classification — no bounding box |
| Epochs | 10 |
| Image size | 224 × 224 |
| Batch | 16 |
| Optimizer | auto, lr0 0.01 |
| Device | CPU |
| Parameters | 1.44 M |
Per-epoch metrics are in results.csv.
Results
Validation split, 204 images, epoch 10:
| Metric | Value |
|---|
| top-1 accuracy | 0.9167 |
| top-5 accuracy | 0.9902 |
| validation loss | 0.2456 |
The confusion matrix (confusion_matrix.png) has a diagonal of 187/204, which is
exactly that 91.67%. The main confusion is Clean ↔ Dusty.
Read this before quoting the number
0.9167 is an upper bound, not field accuracy. Two measured reasons:
- The train/validation split has 35.8% overlap — some validation images also
appear in training. Bird-drop is the clearest tell: 94% of its validation set
is leaked, and its recall is correspondingly near-perfect.
- There is no held-out test split. Every figure here is validation-set.
The documented next step is an MD5-deduplicated split and a retrain. Two classes
also have very small validation sets — Physical-Damage (14) and
Electrical-damage (21) — so their per-class figures carry wide error bars.
Benchmarked against
Three architectures were trained and compared before one was chosen:
| Model | Task | Reported | Outcome |
|---|
| YOLOv8n-cls | Classification | 91.67% top-1 | Deployed — best on real phone photos, simplest to serve |
| ResNet-50 (+ crop) | Classification | ~97% val accuracy | Higher on paper, but two-stage and covered only 4 classes |
| Faster R-CNN + ResNet-50 FPN | Detection | 0.91 weighted acc / 0.86 macro-F1 | Produces boxes, but heavier and the box went unused |
The figures are not directly comparable — different tasks and class counts.
YOLOv8n-cls was selected on real-world smartphone performance and deployment
simplicity, not on the highest validation number.
Training data
A public solar panel image dataset — 930 images across the six classes, split
726 train / 204 validation, with a 3.65:1 class imbalance (Bird-drop 252 …
Physical-Damage 69).
The dataset is not my work and is not redistributed here. The six images in
examples/ come from its validation split and are included only to demonstrate
inference; credit belongs to the original authors.
Licence
The weights are released for portfolio and research use. No licence is asserted
over the training data, which is third-party — check the original dataset's
terms before any commercial use.
Deployment
In the full project the model is served behind FastAPI, consumed by a
Flutter app with a PHP/MySQL backend, returning class, confidence and
recommendation, with an analytics dashboard, anomaly alerts and CSV export.
A live in-browser demo of this model, running via ONNX Runtime Web, is at
https://saifeleslamelgalaly.github.io/.
Trained by Saif Elgalaly.