Kurzes Handbuch zum Python-Skript batch_led_reflection.py 1. Zweck des Skripts Das Skript dient zur Stapelbearbeitung von Edelsteinfotos. Es fügt künstliche, aber natürlich wirkende LED-Lichtreflexe auf Facettenflächen hinzu. Ziel ist: Simulation eines neutralen LED-Lichts mit ca. 5600 Kelvin Verstärkung von Reflexionen auf linken, rechten und unteren Facetten keine globale Veränderung von Farbe, Sättigung oder Farbton Anwendung auf ganze Fotoordner 2. Grundprinzip Das Originalbild wird nicht direkt farblich verändert. Stattdessen erzeugt das Skript eine transparente Ebene über dem Bild. Auf dieser Ebene werden weiße Reflexflächen und schmale Kantenreflexe gezeichnet. Danach werden diese Reflexe weichgezeichnet und mit dem Originalbild kombiniert. Technisch bedeutet das: Python overlay = Image.new("RGBA", (w, h), (0, 0, 0, 0)) Es wird eine transparente Ebene erstellt. Python Image.alpha_composite(img, overlay) Diese Ebene wird anschließend über das Originalbild gelegt. 3. Wichtigste Funktionen 3.1 Funktion add_led_reflections() Python def add_led_reflections(img, strength=0.42, blur=10, seed=None): Diese Funktion bearbeitet ein einzelnes Bild. Sie fügt hinzu: große weiche LED-Reflexe kleine Gegenreflexe schmale Lichtkanten leicht diffuse Lichtwirkung Die Funktion gibt ein neues bearbeitetes Bild zurück. 3.2 Funktion process_folder() Python def process_folder(input_dir, output_dir, strength, blur, seed): Diese Funktion verarbeitet alle Bilder in einem Ordner. Sie erkennt unter anderem: Python .jpg .jpeg .png .webp .tif .tiff Jedes Bild wird geöffnet, mit add_led_reflections() bearbeitet und im Ausgabeordner gespeichert. 4. Wichtige Einstellwerte 4.1 strength Python --strength 0.42 Dieser Wert steuert die Lichtstärke der Reflexe. Wert Wirkung 0.20–0.30 sehr dezente Reflexe 0.35–0.45 natürliche mittlere LED-Reflexe 0.50–0.65 kräftige Produktfoto-Reflexe >0.70 schnell überbelichtet/künstlich Empfohlener Startwert: Bash --strength 0.42 4.2 blur Python --blur 10 Dieser Wert steuert die Weichzeichnung der Reflexe. Wert Wirkung 2–5 harte, kantige Reflexe 8–12 natürliche Facettenreflexe 15–25 sehr weiches, diffuses Licht >25 Reflexe wirken verwaschen Empfohlener Startwert: Bash --blur 10 4.3 led_white Python led_white = (255, 252, 245) Das ist die simulierte LED-Lichtfarbe. Der Wert ist nahezu weiß, aber leicht warm-neutral. Für 5600K ist das geeignet, ohne den Edelstein sichtbar zu verfärben. Mögliche Anpassungen: Wert Wirkung (255, 255, 255) sehr neutrales Weiß (255, 252, 245) leicht natürlich/warm (245, 250, 255) kühler LED-Eindruck Da Farbe und Sättigung des Steins unverändert bleiben sollen, sollte dieser Wert nur vorsichtig geändert werden. 5. Reflexflächen Die Reflexe werden hier definiert: Python reflections = [ [(0.07, 0.21), (0.30, 0.22), (0.27, 0.33), (0.10, 0.32)], ... ] Die Zahlen sind relative Bildkoordinaten. Beispiel: Python (0.07, 0.21) bedeutet: 7 % der Bildbreite von links 21 % der Bildhöhe von oben Dadurch funktioniert das Skript für verschiedene Bildgrößen. Änderung der Koordinaten bewirkt: kleinere Werte bei X → Reflex wandert nach links größere Werte bei X → Reflex wandert nach rechts kleinere Werte bei Y → Reflex wandert nach oben größere Werte bei Y → Reflex wandert nach unten 6. Alpha-Werte / Deckkraft Python alphas = [ int(150 * strength), int(105 * strength), int(135 * strength), int(120 * strength), ] Diese Werte bestimmen die Sichtbarkeit der einzelnen Reflexe. Höhere Werte bedeuten: hellere Reflexe stärkere LED-Wirkung höheres Risiko weißer, ausgebrannter Flächen Niedrigere Werte bedeuten: subtilere Reflexe mehr natürliche Steinstruktur bleibt sichtbar weniger Produktfoto-Effekt 7. Kantenreflexe Beispiel: Python edge.line( [(int(0.42*w), int(0.20*h)), (int(0.44*w), int(0.55*h))], fill=(*led_white, edge_alpha), width=max(1, int(w * 0.004)) ) Diese Linien simulieren schmale Lichtreflexe entlang von Facettenkanten. Wichtige Parameter: Python width=max(1, int(w * 0.004)) Je höher der Faktor, desto breiter die Lichtkante. Wert Wirkung 0.002 sehr feine Linie 0.004 natürliche Kantenreflexe 0.008 kräftige, sichtbare Lichtkante >0.010 meist zu künstlich 8. Speicherung der Dateien Python edited.save(out, quality=95, subsampling=0) Die Ausgabe erfolgt als JPG mit hoher Qualität. quality=95 bedeutet: gute Bildqualität geringe Kompression größere Datei Mögliche Werte: Wert Wirkung 85 kleinere Dateien, sichtbarere Kompression 95 sehr gute Qualität 100 sehr große Dateien, kaum praktischer Vorteil 9. Standardaufrufe Natürlicher Effekt: Bash python batch_led_reflection.py ./eingang ./ausgang --strength 0.42 --blur 10 Dezenter Effekt: Bash python batch_led_reflection.py ./eingang ./ausgang --strength 0.30 --blur 12 Kräftiger LED-Effekt: Bash python batch_led_reflection.py ./eingang ./ausgang --strength 0.55 --blur 8 Weicher Studioeffekt: Bash python batch_led_reflection.py ./eingang ./ausgang --strength 0.45 --blur 18 10. Praktische Empfehlungen Für Edelsteinfotos ist meist sinnvoll: Bash --strength 0.35 bis 0.50 --blur 8 bis 14 Nicht empfehlenswert: Bash --strength > 0.70 --blur < 3 Das führt oft zu harten, künstlichen oder überbelichteten Reflexen. 11. Code # batch_led_reflection.py # pip install pillow numpy from pathlib import Path from PIL import Image, ImageDraw, ImageFilter import argparse import random import numpy as np def add_led_reflections( img: Image.Image, strength=0.42, blur=10, seed=None ): """ Simuliert reduzierte LED-Reflexionen wie im letzten Ergebnis: - Licht von links/oben + leichter Gegenreflex rechts - 5600K neutral-kühles Weiß - keine globale Farb-, Ton- oder Sättigungsänderung """ if seed is not None: random.seed(seed) img = img.convert("RGBA") w, h = img.size overlay = Image.new("RGBA", (w, h), (0, 0, 0, 0)) draw = ImageDraw.Draw(overlay, "RGBA") # LED-Farbe ca. 5600K: neutral-kühles Weiß led_white = (255, 252, 245) # Hauptreflex links oben reflections = [ # links oben [ (0.07, 0.21), (0.30, 0.22), (0.27, 0.33), (0.10, 0.32), ], # links unten, schwächer [ (0.13, 0.58), (0.31, 0.57), (0.28, 0.70), (0.10, 0.69), ], # rechter oberer Facettenreflex [ (0.67, 0.25), (0.88, 0.28), (0.82, 0.39), (0.63, 0.36), ], # rechter unterer kleiner Reflex [ (0.72, 0.68), (0.89, 0.69), (0.86, 0.78), (0.70, 0.77), ], ] alphas = [ int(150 * strength), int(105 * strength), int(135 * strength), int(120 * strength), ] for poly, alpha in zip(reflections, alphas): pts = [(int(x * w), int(y * h)) for x, y in poly] draw.polygon(pts, fill=(*led_white, alpha)) # schmale Kantenreflexe edge_layer = Image.new("RGBA", (w, h), (0, 0, 0, 0)) edge = ImageDraw.Draw(edge_layer, "RGBA") edge_alpha = int(180 * strength) edge.line( [(int(0.42*w), int(0.20*h)), (int(0.44*w), int(0.55*h))], fill=(*led_white, edge_alpha), width=max(1, int(w * 0.004)) ) edge.line( [(int(0.55*w), int(0.45*h)), (int(0.78*w), int(0.43*h))], fill=(*led_white, edge_alpha), width=max(1, int(w * 0.003)) ) edge.line( [(int(0.58*w), int(0.61*h)), (int(0.80*w), int(0.56*h))], fill=(*led_white, int(edge_alpha * 0.8)), width=max(1, int(w * 0.003)) ) # weiche Lichtwirkung overlay = overlay.filter(ImageFilter.GaussianBlur(radius=blur)) edge_layer = edge_layer.filter(ImageFilter.GaussianBlur(radius=max(1, blur // 3))) result = Image.alpha_composite(img, overlay) result = Image.alpha_composite(result, edge_layer) return result.convert("RGB") def process_folder(input_dir, output_dir, strength, blur, seed): input_dir = Path(input_dir) output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) extensions = {".jpg", ".jpeg", ".png", ".webp", ".tif", ".tiff"} files = [p for p in input_dir.iterdir() if p.suffix.lower() in extensions] for file in files: img = Image.open(file) edited = add_led_reflections( img, strength=strength, blur=blur, seed=seed ) out = output_dir / f"{file.stem}_led_reflection.jpg" edited.save(out, quality=95, subsampling=0) print(f"Gespeichert: {out}") if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("input", help="Ordner mit Ausgangsfotos") parser.add_argument("output", help="Ausgabeordner") parser.add_argument("--strength", type=float, default=0.42, help="Lichtstärke, z.B. 0.25 bis 0.70") parser.add_argument("--blur", type=int, default=10, help="Weichzeichnung der Reflexe") parser.add_argument("--seed", type=int, default=None) args = parser.parse_args() process_folder( args.input, args.output, args.strength, args.blur, args.seed )