← Computer Vision Project Library
Project 02 · Classification

Solar Panel Defects

EfficientNet fine-tuned on CPU; Grad-CAM revealed the model partly looked at the roof, not the dust.

82.9%test accuracy, 6 classes

Classifies photos of solar panels into 6 conditions (bird droppings, clean, dusty, electrical damage, physical damage, snow-covered) by fine-tuning a pretrained EfficientNet-B0, and shows why it decided with Grad-CAM heatmaps. Trained entirely on a laptop CPU.

Example images not shown: the dataset licence (licence not recorded, Kaggle) does not allow redistribution; see the dataset page.

Results

Held-out test set: 129 images the model never saw during training or model selection.

MetricValue
Test accuracy82.9 %
Macro F1 (all classes weighted equally)0.830
Training time~15 min (15 epochs × ~60 s, Intel Core Ultra 7 258V CPU)
ClassPrecisionRecallF1Test images
Bird-drop0.8390.8970.86729
Clean0.7220.8970.80029
Dusty0.8180.6430.72028
Electrical-damage1.0001.0001.00015
Physical-Damage0.8570.6000.70610
Snow-Covered0.8890.8890.88918

Figure: confusion matrix Figure: training curves

How it works

  1. Data split: stratified 70 / 15 / 15 train / validation / test split with a fixed seed, saved to CSV so training and evaluation always use the same images.
  2. Pretrained backbone: EfficientNet-B0 (ImageNet weights, via timm) with a new 6-class head. Small (5.3 M parameters), so full fine-tuning is practical on a CPU.
  3. Augmentation: random resized crops, horizontal/vertical flips, ±15° rotation and color jitter. Panels look valid from any orientation, so flips are free extra data.
  4. Class imbalance: Physical-Damage has 49 training images against 135 for Clean. The loss uses inverse-frequency class weights (plus label smoothing 0.1) so rare classes aren’t ignored.
  5. Training: AdamW with a one-cycle learning-rate schedule. The checkpoint with the best validation accuracy is kept.
  6. Explainability: Grad-CAM on the last convolutional layer highlights the regions that drove each prediction.

Images are decoded once and cached in RAM: the dataset lives on a USB key, and random reads from it would otherwise dominate training time.

Data

  • Source: “Faulty Solar Panel” image dataset (Kaggle), 6 classes of web-collected photos.
  • Size used: 869 images (Bird-drop 191, Clean 193, Dusty 190, Electrical-damage 103, Physical-Damage 69, Snow-Covered 123), split 611 / 129 / 129 (train / validation / test). The archive holds 885 images, but some filenames differ only by upper/lower case and overwrite each other when extracted on Windows.
  • Location: ../../cv-data/solar/Faulty_solar_panel/<class>/ (not committed).

Run it

# from the repo root, with the shared environment (see setup_env.ps1)
.venv\Scripts\activate
cd 02-solar-defect-classifier
python train.py --epochs 15     # ~15 min on CPU → ../../cv-data/solar/outputs/best.pt
python evaluate.py              # test metrics + figures in docs/
python app.py                   # web demo: upload a photo → prediction + Grad-CAM

What I learned / what didn’t work

  • Shortcut learning is visible. For the “Dusty” example above, Grad-CAM lights up the roof in the foreground, not the dust on the panels. The web-collected dusty photos often share a rooftop context, and the model partly learned that instead of the dust itself. This is the main risk with small scraped datasets, and exactly what Grad-CAM is for.
  • Dusty vs Clean is the hardest pair (Dusty recall 0.64). Light dust is subtle at 224×224, and some “clean” panels are photographed in dusty environments.
  • Physical-Damage has the fewest images (49 for training), and its recall (0.60) suffers despite class weighting. Thin cracks also get weak Grad-CAM attention.
  • Validation accuracy plateaued at ~79 % for several epochs before the learning-rate decay pushed it to 82.9 % (epoch 12; the test accuracy happens to be the same 82.9 %). With only 129 validation images, one image is ~0.8 %, so differences between epochs are noisy.

Next steps

  • Collect more Physical-Damage and Dusty images, or crop to the panel area to remove background shortcuts (e.g. a panel segmentation step first).
  • Higher input resolution (320–384 px) for thin cracks and light dust.
  • k-fold cross-validation for a more reliable accuracy estimate on this small dataset.

Tech stack

Python · PyTorch · timm (EfficientNet-B0) · torchvision · pytorch-grad-cam · scikit-learn · Gradio