from cdec import get_lat_lon_for
# Adresse festlegen
= 'Upsprunger Str. 67, 33154 Salzkotten'
adresse
# Breiten- und Längengrad bestimmen
= get_lat_lon_for(adresse)
coords coords
(51.6604721, 8.6024081)
Potenzialanalyse für Dachflächen-Photovoltaik
Im zweiten Teil geht es um die automatisierte Erkennung bereits installierter Photovoltaikanlagen auf Dachflächen. Dazu wird ein KI-Modell zur Objekterkennung verwendet, das Solaranlagen auf Luftaufnahmen (Orthofotos) identifiziert. Die Vorhersagen des Modells werden anschließend unter Verwendung geeigneter Metriken evaluiert.
14. September 2025
Im letzten Arbeitsblatt wurden geeignete Dachflächen für den Einsatz von Photovoltaikanlagen (PV-Anlagen) identifiziert.
In diesem interaktiven Arbeitsblatt sollen nun bereits vorhandene PV-Anlagen mithilfe von künstlicher Intelligenz auf Luftbildern erkannt werden. So können noch ungenutzte Dachflächen mit hohem Potenzial ermittelt werden.
Ziel dieses Arbeitsblatts ist es, eine Liste potenzieller Standorte zu erstellen. Diese Liste kann dazu beitragen, Eigentümer von geeigneten Dächern zu motivieren, PV-Anlagen auf ihren Dächern zu installieren.
Ein Orthophoto ist ein verzerrungsfreies und maßstabsgetreues Luftbild, das aus einem Flugzeug aufgenommen wurde.
Zunächst werden die Koordinaten (WGS 84) für unseren Untersuchungsbereich bestimmt.
from cdec import get_lat_lon_for
# Adresse festlegen
adresse = 'Upsprunger Str. 67, 33154 Salzkotten'
# Breiten- und Längengrad bestimmen
coords = get_lat_lon_for(adresse)
coords
(51.6604721, 8.6024081)
Mit dem Befehl download_orthophoto
kann ein Luftbild (Orthophoto) der Umgebung heruntergeladen werden.
Das Herunterladen und Anzeigen des Fotos kann einige Sekunden dauern. Die Vorschau hat eine geringere Auflösung (2500 x 2500 px) als das Original (10000 x 10000 px).
from cdec import download_orthophoto
# Lade Orthophoto herunter und zeige Vorschau
image = download_orthophoto(coords)
https://www.opengeodata.nrw.de/produkte/geobasis/lusat/akt/dop/dop_jp2_f10/dop10rgbi_32_472_5723_1_nw_2024.jp2
Lade Vorschau ...
(5 Minuten)
Die Angaben aus Aufgabe 2 in Kapitel 3 müssen in die erste Zelle dieses Kapitels übertragen werden.
Im weiteren Verlauf wird auf die Adresse Paulinenstraße 67, 32756 Detmold
mit den Koordinaten (51.93866553853838, 8.874417255128995)
Bezug genommen.
Für die Erkennung von PV-Anlagen wird ein Modell des maschinellen Lernens eingesetzt.
Das Modell wurde darauf trainiert, PV-Anlagen auf Orthofotos zu identifizieren und zu markieren (siehe Abb. 1). Dafür wurden dem Modell eine Auswahl an Orthophotos präsentiert, auf denen PV-Anlagen bereits manuell markiert wurden. Anhand dieser Beispiele (Trainingsdaten) hat das Modell gelernt, wie eine PV-Anlage aussieht, und kann diese nun automatisch erkennen.
Zunächst wird das Modell in den Arbeitsspeicher geladen.
WARNING ⚠️ no model scale passed. Assuming scale='n'.
from n params module arguments
0 -1 1 464 ultralytics.nn.modules.conv.Conv [3, 16, 3, 2]
1 -1 1 4672 ultralytics.nn.modules.conv.Conv [16, 32, 3, 2]
2 -1 1 7360 ultralytics.nn.modules.block.C2f [32, 32, 1, True]
3 -1 1 18560 ultralytics.nn.modules.conv.Conv [32, 64, 3, 2]
4 -1 2 49664 ultralytics.nn.modules.block.C2f [64, 64, 2, True]
5 -1 1 73984 ultralytics.nn.modules.conv.Conv [64, 128, 3, 2]
6 -1 2 197632 ultralytics.nn.modules.block.C2f [128, 128, 2, True]
7 -1 1 295424 ultralytics.nn.modules.conv.Conv [128, 256, 3, 2]
8 -1 1 460288 ultralytics.nn.modules.block.C2f [256, 256, 1, True]
9 -1 1 164608 ultralytics.nn.modules.block.SPPF [256, 256, 5]
10 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest']
11 [-1, 6] 1 0 ultralytics.nn.modules.conv.Concat [1]
12 -1 1 148224 ultralytics.nn.modules.block.C2f [384, 128, 1]
13 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest']
14 [-1, 4] 1 0 ultralytics.nn.modules.conv.Concat [1]
15 -1 1 37248 ultralytics.nn.modules.block.C2f [192, 64, 1]
16 -1 1 36992 ultralytics.nn.modules.conv.Conv [64, 64, 3, 2]
17 [-1, 12] 1 0 ultralytics.nn.modules.conv.Concat [1]
18 -1 1 123648 ultralytics.nn.modules.block.C2f [192, 128, 1]
19 -1 1 147712 ultralytics.nn.modules.conv.Conv [128, 128, 3, 2]
20 [-1, 9] 1 0 ultralytics.nn.modules.conv.Concat [1]
21 -1 1 493056 ultralytics.nn.modules.block.C2f [384, 256, 1]
22 [15, 18, 21] 1 751507 ultralytics.nn.modules.head.Detect [1, [64, 128, 256]]
model summary: 129 layers, 3,011,043 parameters, 3,011,027 gradients, 8.2 GFLOPs
Transferred 355/355 items from pretrained weights
Mit dem Methode predict
lässt nun sich eine Vorhersage des Modell für das entsprechende Bild erzeugen.
Im Bereich des maschinellen Lernens wird häufig der Begriff “Vorhersage” (Prediction) verwendet. Damit ist jedoch nicht zwangsläufig eine Aussage über die Zukunft gemeint. In diesem Fall ist damit das Ergebnis der Objekterkennung gemeint.
Schau dir das Ergebnis der Objekterkennung genauer an und suche nach Fehlern:
Welcher der beiden Fehlerarten ist deiner Meinung nach schwerwiegender? Begründe deine Antwort!
(15 Minuten)
TODO
Um zu überprüfen, wie gut ein Modell zur Objekterkennung geeignet ist, wird es evaluiert.
Dabei wird gemessen, wie genau und zuverlässig das Modell die Objekte erkennt. Zwei wichtige Maße (Metriken), die dabei oft verwendet werden, sind Precision (Genauigkeit) und Recall (Trefferquote).
Die Genauigkeit (Precision) gibt an, wie viele der vom Modell erkannten Objekte tatsächlich richtig erkannt wurden.
Beispiel: Angenommen, ein Objekterkennungsmodell identifiziert 50 Objekte auf Dachflächen als PV-Anlagen. Davon sind tatsächlich 45 PV-Anlagen (True Positives) und 5 Objekte sind keine PV-Anlagen, wurden aber fälschlicherweise als solche erkannt (False Positives).
Eine hohe Precision bedeutet, dass das Modell sehr genau darin ist, PV-Anlagen zu identifizieren, und nur wenige falsche Positive (Nicht-PV-Anlagen, die fälschlicherweise als PV-Anlagen erkannt wurden) erzeugt.
Die Trefferquote (Recall) gibt, wie viele der tatsächlich vorhandenen Objekte vom Modell erkannt wurden.
Beispiel: Angenommen, auf den analysierten Hausdächern befinden sich insgesamt 60 PV-Anlagen. Das Modell erkennt davon 45 korrekt (True Positives), übersieht jedoch 15 Anlagen (False Negatives), die tatsächlich existieren. Der Recall wäre dann:
Ein hoher Recall bedeutet, dass das Modell in der Lage ist, die meisten vorhandenen PV-Anlagen zu erkennen, also nur wenige davon übersieht.
Dem Modell wurden Orthophotos mit insgesamt 400 PV-Anlagen gezeigt. Davon hat das Modell 309 korrekt erkannt. In 108 Fällen erkannte das Modell fälschlicherweise eine PV-Anlage.
Vorhersage: PV-Anlage | Vorhersage: Hintergrund | |
---|---|---|
PV-Anlage. | 309 (TP) | 91 (FN) |
Hintergrund | 108 (FP) |
Berechne Precision und Recall für das Modell!
(10 Minuten)
In der Praxis ist es oft eine Herausforderung, ein Modell zu entwickeln, das sowohl eine hohe Precision als auch einen hohen Recall erreicht. Es kommt daher meist zu einen Kompromiss:
Nachdem nun erkannt wurde, wo bereits PV-Anlagen vorhanden sind, können diese Standorte von der bisher erstellten Liste entfernt werden.
Dazu wird zunächst das Zwischenergebnis aus dem letzten Arbeitsblatt geladen.
from cdec import read_file
solarkataster = read_file('data/solarkataster.json').set_index('id')
solarkataster
geb_id | richtung | neigung | dachtyp | himmel_kat | flaecheninhalt | total_energy_per_square_metre | total_energy | geometry | |
---|---|---|---|---|---|---|---|---|---|
id | |||||||||
0 | Geb64596ln0403_c | 269 | 15 | geneigt | West | 7.411919 | 1.500811e+06 | 1.112389e+07 | MULTIPOLYGON (((472254 5723718, 472254 5723720... |
1 | Geb77493ln0403_c | -1 | 0 | flach | Flach | 7.500000 | 1.526328e+06 | 1.144746e+07 | MULTIPOLYGON (((472548 5723151.5, 472546.5 572... |
2 | Geb67252ln0403_c | 263 | 44 | geneigt | West | 36.716183 | 1.444865e+06 | 5.304994e+07 | MULTIPOLYGON (((472177.5 5723508, 472177.5 572... |
3 | Geb68143ln0403_c | 175 | 51 | geneigt | Süd | 12.497905 | 2.077114e+06 | 2.595957e+07 | MULTIPOLYGON (((472206 5723548, 472207 5723548... |
4 | Geb73711ln0403_c | 82 | 21 | geneigt | Ost | 36.641219 | 1.413035e+06 | 5.177533e+07 | MULTIPOLYGON (((472698.5 5723757.5, 472698.5 5... |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
2552 | Geb62779ln0403_c | 176 | 50 | geneigt | Süd | 12.903848 | 2.079983e+06 | 2.683979e+07 | MULTIPOLYGON (((472358.906 5723561.578, 472359... |
2553 | Geb63299ln0403_c | 246 | 34 | geneigt | West | 32.292786 | 1.662412e+06 | 5.368391e+07 | MULTIPOLYGON (((472215 5723774.5, 472214.5 572... |
2554 | Geb76249ln0403_c | 178 | 26 | geneigt | Süd | 11.989565 | 1.961843e+06 | 2.352165e+07 | MULTIPOLYGON (((472931.5 5723834, 472931.5 572... |
2555 | Geb70353ln0403_c | 82 | 36 | geneigt | Ost | 88.135948 | 1.315017e+06 | 1.159003e+08 | MULTIPOLYGON (((472099 5723372.5, 472099 57233... |
2556 | Geb58712ln0403_c | 265 | 33 | geneigt | West | 60.672663 | 1.471228e+06 | 8.926334e+07 | MULTIPOLYGON (((472263 5723281, 472263 5723277... |
2557 rows × 9 columns
Nun können die Ergebnisse zusammenführt werden: Der folgende Code entfernt alle Dachflächen von Gebäuden aus dem Solarkataster, auf denen sich bereits eine PV-Anlage befindet.
Der folgenden Programmcode enthält einige Funktionen, die nicht ausführlich erklärt werden. Es ist nicht notwendig, dass du jeden Befehl nachvollziehen kannst.
import geopandas as gpd
# Schnittmenge zwischen den Daten bilden
intersection = gpd.sjoin(solarkataster, result, how='inner', predicate='intersects')
# Gebaeude IDs aus der Schnittmenge bestimmen
geb_ids = intersection['geb_id'].unique()
# Solarkatasterdaten anhand der Gebauede IDs filtern
solarkataster = solarkataster[~solarkataster['geb_id'].isin(geb_ids)]
# Daten visualiseren
solarkataster.explore(width=800, height=600)
Zuletzt sortieren werden die Daten absteigend nach der Bestrahlungsenergie pro Quadratmeter sortiert.
# Dachflächen nach Bestrahlungsenergie pro Quadratmeter sortieren
sorted_df = solarkataster.sort_values('total_energy_per_square_metre', ascending=False)
sorted_df
geb_id | richtung | neigung | dachtyp | himmel_kat | flaecheninhalt | total_energy_per_square_metre | total_energy | geometry | |
---|---|---|---|---|---|---|---|---|---|
id | |||||||||
13 | Geb71192ln0403_c | 180 | 47 | geneigt | Süd | 15.741250 | 2.083685e+06 | 3.279980e+07 | MULTIPOLYGON (((472498 5723301.5, 472498 57233... |
193 | Geb72502ln0403_c | 178 | 48 | geneigt | Süd | 38.898535 | 2.083154e+06 | 8.103162e+07 | MULTIPOLYGON (((472559 5723631, 472560 5723631... |
229 | Geb80497ln0403_c | 179 | 46 | geneigt | Süd | 12.616012 | 2.082779e+06 | 2.627636e+07 | MULTIPOLYGON (((472510 5723627.5, 472510.5 572... |
1810 | Geb74557ln0403_c | 177 | 48 | geneigt | Süd | 37.394520 | 2.082365e+06 | 7.786903e+07 | MULTIPOLYGON (((472492.5 5723627.5, 472493 572... |
1406 | Geb79875ln0403_c | 177 | 48 | geneigt | Süd | 53.928732 | 2.082365e+06 | 1.122993e+08 | MULTIPOLYGON (((472489 5723834, 472489 5723834... |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
1481 | Geb64117ln0403_c | 345 | 51 | geneigt | Nord | 8.750000 | 5.642711e+05 | 4.937372e+06 | MULTIPOLYGON (((472306.5 5723720, 472307 57237... |
669 | Geb65166ln0403_c | 354 | 50 | geneigt | Nord | 14.831913 | 5.544158e+05 | 8.223047e+06 | MULTIPOLYGON (((472392 5723454, 472392 5723454... |
921 | Geb65145ln0403_c | 354 | 50 | geneigt | Nord | 12.118879 | 5.544158e+05 | 6.718898e+06 | MULTIPOLYGON (((472396 5723454, 472396 5723454... |
918 | Geb65145ln0403_c | 355 | 50 | geneigt | Nord | 38.964160 | 5.531547e+05 | 2.155321e+07 | MULTIPOLYGON (((472399 5723454, 472399 5723454... |
1748 | Geb73538ln0403_c | 352 | 51 | geneigt | Nord | 8.500000 | 5.455292e+05 | 4.636998e+06 | MULTIPOLYGON (((472417.5 5723231, 472417.5 572... |
2062 rows × 9 columns
Mit Hilfe der Methode iloc[]
können die Einträge anhand der Reihenfolge der Daten ausgewählt werden.
geb_id | richtung | neigung | dachtyp | himmel_kat | flaecheninhalt | total_energy_per_square_metre | total_energy | geometry | |
---|---|---|---|---|---|---|---|---|---|
id | |||||||||
13 | Geb71192ln0403_c | 180 | 47 | geneigt | Süd | 15.741250 | 2.083685e+06 | 3.279980e+07 | MULTIPOLYGON (((472498 5723301.5, 472498 57233... |
193 | Geb72502ln0403_c | 178 | 48 | geneigt | Süd | 38.898535 | 2.083154e+06 | 8.103162e+07 | MULTIPOLYGON (((472559 5723631, 472560 5723631... |
229 | Geb80497ln0403_c | 179 | 46 | geneigt | Süd | 12.616012 | 2.082779e+06 | 2.627636e+07 | MULTIPOLYGON (((472510 5723627.5, 472510.5 572... |
1810 | Geb74557ln0403_c | 177 | 48 | geneigt | Süd | 37.394520 | 2.082365e+06 | 7.786903e+07 | MULTIPOLYGON (((472492.5 5723627.5, 472493 572... |
1406 | Geb79875ln0403_c | 177 | 48 | geneigt | Süd | 53.928732 | 2.082365e+06 | 1.122993e+08 | MULTIPOLYGON (((472489 5723834, 472489 5723834... |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
474 | Geb76275ln0403_c | 165 | 47 | geneigt | Süd | 38.859698 | 2.056823e+06 | 7.992752e+07 | MULTIPOLYGON (((472176.5 5723690.5, 472177 572... |
156 | Geb66901ln0403_c | 180 | 58 | geneigt | Süd | 14.489619 | 2.056763e+06 | 2.980171e+07 | MULTIPOLYGON (((472415 5723903, 472416.5 57239... |
1173 | Geb58849ln0403_c | 175 | 38 | geneigt | Süd | 45.011800 | 2.055992e+06 | 9.254389e+07 | MULTIPOLYGON (((472397 5723504.5, 472398.5 572... |
1030 | Geb61773ln0403_c | 175 | 38 | geneigt | Süd | 27.128010 | 2.055992e+06 | 5.577496e+07 | MULTIPOLYGON (((472334.41 5723544.5, 472334.5 ... |
1253 | Geb60563ln0403_c | 175 | 38 | geneigt | Süd | 19.252148 | 2.055992e+06 | 3.958226e+07 | MULTIPOLYGON (((472370 5723354.5, 472369 57233... |
100 rows × 9 columns
(10 Minuten)
sorted_df = solarkataster.sort_values('total_energy_per_square_metre', ascending=False)
sorted_df.iloc[:10]
geb_id | richtung | neigung | dachtyp | himmel_kat | flaecheninhalt | total_energy_per_square_metre | total_energy | geometry | |
---|---|---|---|---|---|---|---|---|---|
id | |||||||||
13 | Geb71192ln0403_c | 180 | 47 | geneigt | Süd | 15.741250 | 2.083685e+06 | 3.279980e+07 | MULTIPOLYGON (((472498 5723301.5, 472498 57233... |
193 | Geb72502ln0403_c | 178 | 48 | geneigt | Süd | 38.898535 | 2.083154e+06 | 8.103162e+07 | MULTIPOLYGON (((472559 5723631, 472560 5723631... |
229 | Geb80497ln0403_c | 179 | 46 | geneigt | Süd | 12.616012 | 2.082779e+06 | 2.627636e+07 | MULTIPOLYGON (((472510 5723627.5, 472510.5 572... |
1810 | Geb74557ln0403_c | 177 | 48 | geneigt | Süd | 37.394520 | 2.082365e+06 | 7.786903e+07 | MULTIPOLYGON (((472492.5 5723627.5, 472493 572... |
1406 | Geb79875ln0403_c | 177 | 48 | geneigt | Süd | 53.928732 | 2.082365e+06 | 1.122993e+08 | MULTIPOLYGON (((472489 5723834, 472489 5723834... |
926 | Geb65145ln0403_c | 177 | 48 | geneigt | Süd | 42.108134 | 2.082365e+06 | 8.768450e+07 | MULTIPOLYGON (((472399.5 5723450, 472400.5 572... |
2540 | Geb72885ln0403_c | 177 | 48 | geneigt | Süd | 10.420916 | 2.082365e+06 | 2.170015e+07 | MULTIPOLYGON (((472455.5 5723613.5, 472458.5 5... |
2413 | Geb70313ln0403_c | 177 | 49 | geneigt | Süd | 37.711134 | 2.081993e+06 | 7.851430e+07 | MULTIPOLYGON (((472533.5 5723629.5, 472539.5 5... |
672 | Geb65166ln0403_c | 177 | 49 | geneigt | Süd | 18.167287 | 2.081993e+06 | 3.782415e+07 | MULTIPOLYGON (((472389 5723451.5, 472395.162 5... |
333 | Geb73278ln0403_c | 177 | 49 | geneigt | Süd | 36.689935 | 2.081993e+06 | 7.638817e+07 | MULTIPOLYGON (((472417.5 5723246, 472421 57232... |
Der Algorithmus listet die Zehn besten Dachflächen hinsichtlich des durchschnittlichen Kwh-Outputs auf.
In diesem Notebook hast du gelernt, wie du:
@online{sparmann2025,
author = {Sparmann, Sören},
title = {Objekterkennung (Teil 2)},
date = {2025-09-14},
url = {https://material.cdec.io/modul_2/submodules/03_photovoltaik/02_objekterkennung.html},
langid = {de}
}
Step 1) Copy this code into the <head> of your site:
Step 2) In your article, create a link to and make sure the link text starts with a :colon, :like this, so Nutshell knows to make it expandable.
Step 3) That's all, folks! 🎉