diff --git a/python_scripts/DLM_Tree_Classification/Labelling_script b/python_scripts/DLM_Tree_Classification/Labelling_script new file mode 100644 index 0000000000000000000000000000000000000000..ee7810520c18b592baa11095a1ca3ce1d13ddf53 --- /dev/null +++ b/python_scripts/DLM_Tree_Classification/Labelling_script @@ -0,0 +1,391 @@ +# Labelling 2.0 (ohne Skala 0-200 und weißen Hintergrund) ACHTUNG - Es wurden mehrere Verarbeitungsmöglichkeiten implementiert mit minimalen Unterschieden. Ungewollte Verarbeitung sollte auskommentiert werden: +# Möglichkeit 1: +import pandas as pd +import geopandas as gpd +import os +import rasterio +import numpy as np +from shapely.geometry import box +import matplotlib.pyplot as plt +import matplotlib.patches as patches + +city_name = 'konstanz' + +gdf = gpd.read_file('baumkataster_stadt_' + city_name + '.geojson') +gdf.loc[:, 'id'] = list(gdf.index) +gdf = gdf.to_crs('EPSG:31468') + +df = pd.read_csv('baumkataster_classified_' + city_name + '.csv') +tree_class = df.Klasse + +# Importverzeichnis für die rgbi Bilder +datadir = city_name + '_aerial_images_rgbi' + +# Exportverzeichnis für die rgb Bilder +rgbdir = city_name + '_aerial_images_rgb' + +# Exportverzeichnis für die gelabelten Bilder +labeldir = city_name + '_labeled' + +# Verzeichnisse erstellen, falls noch nicht existent +if not os.path.exists(labeldir): + os.makedirs(labeldir) + +if not os.path.exists(rgbdir): + os.makedirs(rgbdir) + +filenames = os.listdir(datadir) + +# Distanz pro Pixel festlegen +gsd = 0.2 # m/px + +# Klassifizierungssystem und Farbgebung definieren +class_id = {'Großer Laubbaum': 0, + 'Kleiner Laubbaum': 1, + 'Großer Nadelbaum': 2, + 'Kleiner Nadelbaum': 3, + 'Busch/Hecke (Laub/Hartlaub)': 4, + 'Busch/Hecke (Nadel)': 5, + 'Unbekannt': 6} + +class_colors = {'Großer Laubbaum': 'red', + 'Kleiner Laubbaum': 'orange', + 'Großer Nadelbaum': 'darkgreen', + 'Kleiner Nadelbaum': 'lightgreen', + 'Busch/Hecke (Laub/Hartlaub)': 'purple', + 'Busch/Hecke (Nadel)': 'cornflowerblue', + 'Unbekannt': 'white'} + +# Liste zum Speichern problematischer Dateien +problematic_files = [] + +# Schleife für gesamten Ordner +for filename in filenames: + try: + # Überprüfen, ob die Datei eine Bilddatei ist (z.B. .tif oder .png) + if filename.endswith('.tif') or filename.endswith('.png'): + + # Öffnet Bilddatei + dataset = rasterio.open(os.path.join(datadir, filename)) + + # Erstellung BBox für Bildgrenzen + geom = box(*dataset.bounds) + + # Filtern der Bäume aus dem GeoDataFrame, die sich innerhalb der Bildgrenzen befinden + trees = gdf[gdf.intersects(geom)] + + # Vorbereitung der Bilddarstellung + f, ax = plt.subplots() + ax.axis('off') # Skalen und Achsen ausblenden + + # Erstellen eines RGB-Bildes für die Visualisierung + img = np.dstack(dataset.read()[:3]) + ax.imshow(img) + + # Speichern des Originalbilds ohne Bounding-Boxen + plt.savefig(os.path.join(rgbdir, filename.replace('.tif', '.png'))) + + # Erstellen und Speichern der Bounding-Boxen, wenn Bäume im Bild gefunden wurden + if len(trees) > 0: + for i, tree in trees.iterrows(): + if not np.isnan(tree.kronenbrei): + # Berechnen der Pixelkoordinaten des Baumes im Bild + xy = dataset.index(tree.geometry.x, tree.geometry.y) + + # Bestimmen der oberen linken Ecke der Bounding-Box basierend auf der Baumkrone + xy_center = (xy[0] - int(tree.kronenbrei/gsd/2), xy[1] - int(tree.kronenbrei/gsd/2)) + + # Speichern der Bounding-Box-Koordinaten und der Klassen-ID in einer .txt-Datei + with open(os.path.join(labeldir, filename.replace('.tif', '.txt')), 'a') as bboxf: + bboxf.write('{:d} {} {} {} {}\n'.format(class_id[tree_class.iloc[tree.id]], # Klassen-ID basierend auf dem Baumtyp + xy[1]/img.shape[1], # Normalisierte x-Koordinate + (xy[1] + int(tree.kronenbrei/gsd/2))/img.shape[1], # Normalisierte Breite + xy[0]/img.shape[0], # Normalisierte y-Koordinate + (xy[0] + int(tree.kronenbrei/gsd/2))/img.shape[1])) # Normalisierte Höhe + + # Erstellen eines Rechtecks (Bounding-Box) zur Visualisierung im Bild + rect = patches.Rectangle(np.array(xy_center)[::-1], # Position der Bounding-Box + tree.kronenbrei/gsd, # Breite der Bounding-Box + tree.kronenbrei/gsd, # Höhe der Bounding-Box + linewidth=1, # Linienstärke der BBox + edgecolor=class_colors[tree_class.iloc[tree.id]], facecolor='none') # Farbe der Bounding-Box basierend auf der Klasse + ax.add_patch(rect) + + # Speichern des Bildes mit den Bounding-Boxen als PNG-Datei + plt.savefig(os.path.join(labeldir, filename.replace('.tif', '_bbox.png'))) + plt.close() + + print(f"Processed: {filename}") + except Exception as e: + print(f"Error processing {filename}: {e}") + problematic_files.append(filename) + +# Problematische Dateien anzeigen +if problematic_files: + print("\nProblematic files:") + for problem_file in problematic_files: + print(problem_file) + + + + + + +# Möglichkeit 2: (Bilder unskaliert lassen) +import pandas as pd +import geopandas as gpd +import os +import rasterio +import numpy as np +from shapely.geometry import box +import matplotlib.pyplot as plt +import matplotlib.patches as patches + +city_name = 'wuerzburg' + +gdf = gpd.read_file('baumkataster_stadt_' + city_name + '.geojson') +gdf.loc[:, 'id'] = list(gdf.index) +gdf = gdf.to_crs('EPSG:31468') + +df = pd.read_csv('baumkataster_classified_' + city_name + '.csv') +tree_class = df.Klasse + +# Importverzeichnis für die rgbi Bilder +datadir = city_name + '_aerial_images_rgbi' + +# Exportverzeichnis für die rgb Bilder (ohne Bounding-Boxen) +rgbdir = city_name + '_aerial_images_rgb' + +# Exportverzeichnis für die gelabelten Bilder (mit oder ohne Bounding-Boxen) +labeldir = city_name + '_labeled' + +# Verzeichnisse erstellen, falls noch nicht existent +if not os.path.exists(labeldir): + os.makedirs(labeldir) + +if not os.path.exists(rgbdir): + os.makedirs(rgbdir) + +filenames = os.listdir(datadir) + +# Distanz pro Pixel festlegen +gsd = 0.2 # m/px + +# Klassifizierungssystem und Farbgebung definieren +class_id = {'Großer Laubbaum': 0, + 'Kleiner Laubbaum': 1, + 'Großer Nadelbaum': 2, + 'Kleiner Nadelbaum': 3, + 'Busch/Hecke (Laub/Hartlaub)': 4, + 'Busch/Hecke (Nadel)': 5, + 'Unbekannt': 6} + +class_colors = {'Großer Laubbaum': 'red', + 'Kleiner Laubbaum': 'orange', + 'Großer Nadelbaum': 'darkgreen', + 'Kleiner Nadelbaum': 'lightgreen', + 'Busch/Hecke (Laub/Hartlaub)': 'purple', + 'Busch/Hecke (Nadel)': 'cornflowerblue', + 'Unbekannt': 'white'} + +# Liste zum Speichern problematischer Dateien +problematic_files = [] + +# Schleife für gesamten Ordner +for filename in filenames: + try: + # Überprüfen, ob die Datei eine Bilddatei ist (z.B. .tif oder .png) + if filename.endswith('.tif') or filename.endswith('.png'): + + # Öffnet Bilddatei + dataset = rasterio.open(os.path.join(datadir, filename)) + + # Erstellung BBox für Bildgrenzen + geom = box(*dataset.bounds) + + # Filtern der Bäume aus dem GeoDataFrame, die sich innerhalb der Bildgrenzen befinden + trees = gdf[gdf.intersects(geom)] + + # Erstellen eines RGB-Bildes für die Visualisierung + img = np.dstack(dataset.read()[:3]) # Verwende nur die RGB-Kanäle + + # Speichern des Originalbilds im 'rgbdir'-Ordner (ohne Bounding-Boxen) + plt.imsave(os.path.join(rgbdir, filename.replace('.tif', '.png')), img) + + # Falls keine Bäume gefunden wurden, speichere das Bild dennoch im 'labeldir'-Ordner mit der Endung '_bbox.png' + if len(trees) == 0: + plt.imsave(os.path.join(labeldir, filename.replace('.tif', '_bbox.png')), img) + continue # Keine Bounding-Boxen, also überspringe den Rest des Loops + + # Bild und Achsen für Bounding-Box vorbereiten + fig, ax = plt.subplots(figsize=(img.shape[1] / 100, img.shape[0] / 100), dpi=100) # Bildgröße exakt festlegen + ax.imshow(img) + ax.axis('off') # Skalen und Achsen ausblenden + + # Erstellen und Speichern der Bounding-Boxen, wenn Bäume im Bild gefunden wurden + for i, tree in trees.iterrows(): + if not np.isnan(tree.kronenbrei): + # Berechnen der Pixelkoordinaten des Baumes im Bild + xy = dataset.index(tree.geometry.x, tree.geometry.y) + + # Bestimmen der oberen linken Ecke der Bounding-Box basierend auf der Baumkrone + xy_center = (xy[0] - int(tree.kronenbrei/gsd/2), xy[1] - int(tree.kronenbrei/gsd/2)) + + # Speichern der Bounding-Box-Koordinaten und der Klassen-ID in einer .txt-Datei + with open(os.path.join(labeldir, filename.replace('.tif', '.txt')), 'a') as bboxf: + bboxf.write('{:d} {} {} {} {}\n'.format(class_id[tree_class.iloc[tree.id]], # Klassen-ID basierend auf dem Baumtyp + xy[1]/img.shape[1], # Normalisierte x-Koordinate + (xy[1] + int(tree.kronenbrei/gsd/2))/img.shape[1], # Normalisierte Breite + xy[0]/img.shape[0], # Normalisierte y-Koordinate + (xy[0] + int(tree.kronenbrei/gsd/2))/img.shape[1])) # Normalisierte Höhe + + # Erstellen eines Rechtecks (Bounding-Box) zur Visualisierung im Bild + rect = patches.Rectangle(np.array(xy_center)[::-1], # Position der Bounding-Box + tree.kronenbrei/gsd, # Breite der Bounding-Box + tree.kronenbrei/gsd, # Höhe der Bounding-Box + linewidth=1, # Linienstärke der BBox + edgecolor=class_colors[tree_class.iloc[tree.id]], facecolor='none') # Farbe der Bounding-Box basierend auf der Klasse + ax.add_patch(rect) + + # Speichern des Bildes mit den Bounding-Boxen als PNG-Datei + plt.savefig(os.path.join(labeldir, filename.replace('.tif', '_bbox.png')), bbox_inches='tight', pad_inches=0) + plt.close() + + print(f"Processed: {filename}") + except Exception as e: + print(f"Error processing {filename}: {e}") + problematic_files.append(filename) + +# Problematische Dateien anzeigen +if problematic_files: + print("\nProblematic files:") + for problem_file in problematic_files: + print(problem_file) + + + + + +# Möglichkeit 3: (Das Gleiche noch einmal aber mittels Pillow) +import pandas as pd +import geopandas as gpd +import os +import rasterio +import numpy as np +from shapely.geometry import box +from PIL import Image, ImageDraw + +city_name = 'memmingen' + +gdf = gpd.read_file('baumkataster_stadt_' + city_name + '.geojson') +gdf.loc[:, 'id'] = list(gdf.index) +gdf = gdf.to_crs('EPSG:31468') + +df = pd.read_csv('baumkataster_classified_' + city_name + '.csv') +tree_class = df.Klasse + +# Importverzeichnis für die rgbi Bilder +datadir = city_name + '_aerial_images_rgbi' + +# Exportverzeichnis für die rgb Bilder (ohne Bounding-Boxen) +rgbdir = city_name + '_aerial_images_rgb' + +# Exportverzeichnis für die gelabelten Bilder (mit oder ohne Bounding-Boxen) +labeldir = city_name + '_labeled' + +# Verzeichnisse erstellen, falls noch nicht existent +if not os.path.exists(labeldir): + os.makedirs(labeldir) + +if not os.path.exists(rgbdir): + os.makedirs(rgbdir) + +filenames = os.listdir(datadir) + +# Distanz pro Pixel festlegen +gsd = 0.2 # m/px + +# Klassifizierungssystem und Farbgebung definieren +class_id = {'Großer Laubbaum': 0, + 'Kleiner Laubbaum': 1, + 'Großer Nadelbaum': 2, + 'Kleiner Nadelbaum': 3, + 'Busch/Hecke (Laub/Hartlaub)': 4, + 'Busch/Hecke (Nadel)': 5, + 'Unbekannt': 6} + +class_colors = {'Großer Laubbaum': 'red', + 'Kleiner Laubbaum': 'orange', + 'Großer Nadelbaum': 'darkgreen', + 'Kleiner Nadelbaum': 'lightgreen', + 'Busch/Hecke (Laub/Hartlaub)': 'purple', + 'Busch/Hecke (Nadel)': 'cornflowerblue', + 'Unbekannt': 'white'} + +# Liste zum Speichern problematischer Dateien +problematic_files = [] + +# Schleife für gesamten Ordner +for filename in filenames: + try: + # Überprüfen, ob die Datei eine Bilddatei ist (z.B. .tif oder .png) + if filename.endswith('.tif') or filename.endswith('.png'): + + # Öffnet Bilddatei + dataset = rasterio.open(os.path.join(datadir, filename)) + + # Erstellung BBox für Bildgrenzen + geom = box(*dataset.bounds) + + # Filtern der Bäume aus dem GeoDataFrame, die sich innerhalb der Bildgrenzen befinden + trees = gdf[gdf.intersects(geom)] + + # Erstellen eines RGB-Bildes für die Visualisierung + img = np.dstack(dataset.read()[:3]) # Verwende nur die RGB-Kanäle + img_pil = Image.fromarray(img.astype(np.uint8)) + + # Speichern des Originalbilds im 'rgbdir'-Ordner (ohne Bounding-Boxen) + img_pil.save(os.path.join(rgbdir, filename.replace('.tif', '.png'))) + + # Falls keine Bäume gefunden wurden, speichere das Bild dennoch im 'labeldir'-Ordner mit der Endung '_bbox.png' + if len(trees) == 0: + img_pil.save(os.path.join(labeldir, filename.replace('.tif', '_bbox.png'))) + continue # Keine Bounding-Boxen, also überspringe den Rest des Loops + + # Erstellen des Zeichenwerkzeugs für Bounding-Boxen + draw = ImageDraw.Draw(img_pil) + + # Erstellen und Speichern der Bounding-Boxen, wenn Bäume im Bild gefunden wurden + for i, tree in trees.iterrows(): + if not np.isnan(tree.kronenbrei): + # Berechnen der Pixelkoordinaten des Baumes im Bild + xy = dataset.index(tree.geometry.x, tree.geometry.y) + + # Bestimmen der oberen linken Ecke der Bounding-Box basierend auf der Baumkrone + xy_center = (xy[0] - int(tree.kronenbrei/gsd/2), xy[1] - int(tree.kronenbrei/gsd/2)) + + # Speichern der Bounding-Box-Koordinaten und der Klassen-ID in einer .txt-Datei + with open(os.path.join(labeldir, filename.replace('.tif', '.txt')), 'a') as bboxf: + bboxf.write('{:d} {} {} {} {}\n'.format(class_id[tree_class.iloc[tree.id]], # Klassen-ID basierend auf dem Baumtyp + xy[1] / img.shape[1], # Normalisierte x-Koordinate + (xy[1] + int(tree.kronenbrei / gsd / 2)) / img.shape[1], # Normalisierte Breite + xy[0] / img.shape[0], # Normalisierte y-Koordinate + (xy[0] + int(tree.kronenbrei / gsd / 2)) / img.shape[0])) # Normalisierte Höhe + + # Zeichnen der Bounding-Box + rect_xy = [xy_center[1], xy_center[0], xy_center[1] + tree.kronenbrei / gsd, xy_center[0] + tree.kronenbrei / gsd] + draw.rectangle(rect_xy, outline=class_colors[tree_class.iloc[tree.id]], width=2) + + # Speichern des Bildes mit den Bounding-Boxen als PNG-Datei + img_pil.save(os.path.join(labeldir, filename.replace('.tif', '_bbox.png'))) + + print(f"Processed: {filename}") + except Exception as e: + print(f"Error processing {filename}: {e}") + problematic_files.append(filename) + +# Problematische Dateien anzeigen +if problematic_files: + print("\nProblematic files:") + for problem_file in problematic_files: + print(problem_file)