Downloads · 30 days
16
29% of all-time downloads
saifElgalaly/solarscan-yolov8n-cls
solarscan-yolov8n-cls is a image classification model from saifElgalaly. Use it when you need a label for an image. It is set up for ultralytics.
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.
Downloads · 30 days
16
29% of all-time downloads
All-time downloads
56
Public
Repo size
9.1 MB
Likes
0
Public
Click a slice to open those files.
.onnx5.8 MB · 62%
From the Hugging Face model README
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.
| 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.
from ultralytics import YOLO
model = YOLO("best.pt")
r = model("panel.jpg")[0]
print(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:
import numpy as np, onnxruntime as ort
from PIL import Image
NAMES = ["Bird-drop", "Clean", "Dusty", "Electrical-damage",
"Physical-Damage", "Snow-Covered"]
im = Image.open("panel.jpg").convert("RGB")
w, h = im.size
s = 224 / min(w, h)
im = im.resize((round(w * s), round(h * s)), Image.BILINEAR)
w, h = im.size
im = im.crop(((w - 224) // 2, (h - 224) // 2, (w - 224) // 2 + 224, (h - 224) // 2 + 224))
x = (np.asarray(im, np.float32) / 255).transpose(2, 0, 1)[None]
sess = ort.InferenceSession("best.onnx")
p = sess.run(None, {sess.get_inputs()[0].name: x})[0][0]
print(NAMES[int(p.argmax())], float(p.max()))
| 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.
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.
0.9167 is an upper bound, not field accuracy. Two measured reasons:
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.
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.
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.
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.
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.