Claude: Lens-Distortions

This commit is contained in:
chk
2026-06-02 11:37:29 +02:00
parent ac81c2e0cb
commit 5ad956be81
91 changed files with 31707 additions and 23702 deletions

View File

@@ -0,0 +1,9 @@
FROM linuxserver/blender:latest
USER root
RUN pip3 install --no-cache-dir \
numpy \
opencv-python
WORKDIR /workspace

View File

View File

@@ -96,11 +96,13 @@ def split_pose(pose_entry):
def main():
USER_HOME = Path.home()
BASE = USER_HOME / "SynologyDrive" / "2026-AppServer-AppRobot" / "appRobotRendering"
BASE = Path(__file__).resolve().parents[2]
BLENDER_EXE = str("C:/Program Files/Blender Foundation/Blender 4.5/blender.exe")
ROBOT_JSON_FILE = str(BASE / "data" / "robot" / "robot.json")
OUTPUT_DIR = str(BASE / "data" / "simulation" / "debug")
BLENDER_EXE = str("C:/Program Files/Blender Foundation/Blender 4.5/blender.exe")
RENDER_PY = str(BASE / "setup" / "generateSets" / "render_robot.py")
RENDER_PNG = str(BASE / "data" / "simulation" / "debug" / "render.png")
OUTPUT_SET = str(BASE / "data" / "simulation")

View File

@@ -18,9 +18,11 @@ from mathutils import Matrix
# Holt dynamisch den Pfad zum aktuellen Benutzerverzeichnis (z.B. C:\Users\Name)
USER_HOME = Path.home()
BASE = Path(__file__).resolve().parents[2]
# Kombiniert den Benutzerpfad mit dem spezifischen Ordnerpfad und konvertiert direkt zu str
ROBOT_JSON_FILE = str(USER_HOME / "SynologyDrive" / "2026-AppServer-AppRobot" / "appRobotRendering" / "data" / "robot" / "robot.json")
OUTPUT_FILE = str(USER_HOME / "SynologyDrive" / "2026-AppServer-AppRobot" / "appRobotRendering" / "data" / "simulation" / "debug" / "render.png")
ROBOT_JSON_FILE = str(BASE / "data" / "robot" / "robot.json")
OUTPUT_FILE = str(BASE / "data" / "simulation" / "debug" / "render.png")
print("Using robot JSON file:", ROBOT_JSON_FILE)
print("Using output file:", OUTPUT_FILE)
@@ -88,6 +90,23 @@ marker_rot_max_deg = float(rendering_info.get("markerRotationMaxDeg", 0.0))
motion_blur = as_bool(rendering_info.get("motionBlur", False))
motion_blur_max_px = float(rendering_info.get("motionBlurMaxPx", 1.5))
# ── Linsen-/Kamerafehler ───────────────────────────────────────────
# (A) Kalibrier-Restfehler: die npz-Intrinsik leicht von der Wahrheit abweichen lassen
# (deterministisch pro Kamera -> über alle Posen konsistent). Das Bild bleibt ideal,
# nur die *angenommene* Kalibrierung ist falsch — wie bei realer Webcam-Kalibrierung.
intr_focal_err_pct = float(rendering_info.get("focalErrorPct", 0.0)) # ±% auf fx, fy
intr_principal_px = float(rendering_info.get("principalErrorPx", 0.0)) # ±px auf cx, cy
intr_residual_dist = rendering_info.get("residualDistortion", None) # [k1, k2] in dist_coeffs
# (B-D) photometrische Effekte (Compositor)
vignette = as_bool(rendering_info.get("vignette", False)) # C
vignette_strength = float(rendering_info.get("vignetteStrength", 0.25))
localized_blur = as_bool(rendering_info.get("localizedBlur", False)) # C
localized_blur_strength = float(rendering_info.get("localizedBlurStrength", 0.15))
sensor_noise = as_bool(rendering_info.get("sensorNoise", False)) # D
sensor_noise_strength = float(rendering_info.get("sensorNoiseStrength", 0.02))
lens_distortion = as_bool(rendering_info.get("lensDistortion", False)) # D (chromat. Aberration)
lens_distortion_strength = float(rendering_info.get("lensDistortionStrength", 0.01))
state: Dict[str, float] = {k: 0.0 for k in STATE_KEYS}
for source_name in ("defaultPosition", "recognized", "movements"):
source = robot.get(source_name, {}) or {}
@@ -518,6 +537,28 @@ camera_matrix = np.array([
# ideal synthetic camera
dist_coeffs = np.zeros((5, 1), dtype=np.float32)
# ── (A) Kalibrier-Restfehler: Intrinsik gezielt verfälschen ──
# Seed deterministisch aus der Kameraposition (NICHT hash() -> PYTHONHASHSEED), damit
# dieselbe Kamera über alle Posen denselben Kalibrierfehler trägt.
if intr_focal_err_pct > 0.0 or intr_principal_px > 0.0 or intr_residual_dist:
_cp = rendering_info.get("cameraPosition", [0, 0, 0])
_seed = abs(int(round(float(_cp[0]) * 1000 + float(_cp[1]) * 100 + float(_cp[2]) * 10))) + 17
_rng = random.Random(_seed)
if intr_focal_err_pct > 0.0:
fx *= 1.0 + _rng.uniform(-1.0, 1.0) * intr_focal_err_pct / 100.0
fy *= 1.0 + _rng.uniform(-1.0, 1.0) * intr_focal_err_pct / 100.0
if intr_principal_px > 0.0:
cx += _rng.uniform(-1.0, 1.0) * intr_principal_px
cy += _rng.uniform(-1.0, 1.0) * intr_principal_px
camera_matrix = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]], dtype=np.float32)
if intr_residual_dist:
_k = [float(v) for v in intr_residual_dist]
dist_coeffs = np.array([[_k[0] if len(_k) > 0 else 0.0],
[_k[1] if len(_k) > 1 else 0.0],
[0.0], [0.0], [0.0]], dtype=np.float32)
print(f"[render] (A) intrinsics jitter -> fx={fx:.1f} fy={fy:.1f} cx={cx:.1f} cy={cy:.1f} "
f"dist={dist_coeffs.ravel()[:2]}")
np.savez(
CALIBRATION_OUTPUT,
@@ -1045,27 +1086,127 @@ enable_best_device(scene)
# RENDER
# ============================================================
# ── leichtes Verwackeln: pro Aufnahme zufällig gerichteter kleiner Blur ──
if motion_blur and motion_blur_max_px > 0.0:
# ── Post-Processing: Verwackeln + Linsen-/Sensor-Effekte (B-D) ──
# Eine Compositor-Kette; jeder Effekt einzeln gekapselt, damit ein API-Problem den
# Render nicht abbricht (der betroffene Effekt wird dann nur übersprungen + gewarnt).
_post_active = ((motion_blur and motion_blur_max_px > 0.0) or lens_distortion or
localized_blur or vignette or lens_dirt or sensor_noise)
if _post_active:
scene.use_nodes = True
tree = scene.node_tree
for _n in list(tree.nodes):
tree.nodes.remove(_n)
rl = tree.nodes.new("CompositorNodeRLayers")
blur = tree.nodes.new("CompositorNodeBlur")
rl = tree.nodes.new("CompositorNodeRLayers")
cur = rl.outputs["Image"]
# (D) chromatische Aberration (Dispersion) — KEINE geometrische Verzeichnung (-> A/npz)
if lens_distortion and lens_distortion_strength > 0.0:
try:
ld = tree.nodes.new("CompositorNodeLensdist")
ld.use_projector = False
ld.inputs["Distort"].default_value = 0.0
ld.inputs["Dispersion"].default_value = min(1.0, lens_distortion_strength)
tree.links.new(cur, ld.inputs["Image"]); cur = ld.outputs["Image"]
print("[render] (D) chromatic aberration")
except Exception as _e:
print("[render][WARN] lens_distortion skipped:", _e)
# Verwackeln (Motion Blur), pro Aufnahme zufällig gerichtet
if motion_blur and motion_blur_max_px > 0.0:
try:
mb = tree.nodes.new("CompositorNodeBlur")
mb.filter_type = "GAUSS"; mb.use_relative = False
_ang = random.uniform(0.0, math.pi)
_amp = random.uniform(0.35, 1.0) * motion_blur_max_px
mb.size_x = max(0, int(round(abs(_amp * math.cos(_ang)))))
mb.size_y = max(0, int(round(abs(_amp * math.sin(_ang)))))
tree.links.new(cur, mb.inputs["Image"]); cur = mb.outputs["Image"]
print(f"[render] motion blur size=({mb.size_x},{mb.size_y})")
except Exception as _e:
print("[render][WARN] motion_blur skipped:", _e)
# (C) Rand-Unschärfe (Feldkrümmung): scharf in der Mitte, unscharf am Rand
if localized_blur and localized_blur_strength > 0.0:
try:
eb = tree.nodes.new("CompositorNodeBlur")
eb.filter_type = "GAUSS"; eb.use_relative = True
eb.factor_x = min(0.5, localized_blur_strength)
eb.factor_y = min(0.5, localized_blur_strength)
em = tree.nodes.new("CompositorNodeEllipseMask")
em.width = 0.7; em.height = 0.7
ex = tree.nodes.new("CompositorNodeMixRGB")
tree.links.new(cur, eb.inputs["Image"])
tree.links.new(em.outputs["Mask"], ex.inputs["Fac"])
tree.links.new(eb.outputs["Image"], ex.inputs[1]) # Fac=0 (Rand) -> Blur
tree.links.new(cur, ex.inputs[2]) # Fac=1 (Mitte) -> scharf
cur = ex.outputs["Image"]
print("[render] (C) edge blur")
except Exception as _e:
print("[render][WARN] localized_blur skipped:", _e)
# (C) Vignette: Randabdunklung über weiche Ellipse-Maske
if vignette and vignette_strength > 0.0:
try:
vm = tree.nodes.new("CompositorNodeEllipseMask")
vm.width = 1.0; vm.height = 1.0
vs = tree.nodes.new("CompositorNodeBlur")
vs.filter_type = "GAUSS"; vs.use_relative = True
vs.factor_x = 0.3; vs.factor_y = 0.3
vr = tree.nodes.new("CompositorNodeMapRange")
vr.inputs["From Min"].default_value = 0.0
vr.inputs["From Max"].default_value = 1.0
vr.inputs["To Min"].default_value = 1.0 - min(0.9, vignette_strength)
vr.inputs["To Max"].default_value = 1.0
vmul = tree.nodes.new("CompositorNodeMixRGB")
vmul.blend_type = "MULTIPLY"; vmul.inputs["Fac"].default_value = 1.0
tree.links.new(vm.outputs["Mask"], vs.inputs["Image"])
tree.links.new(vs.outputs["Image"], vr.inputs["Value"])
tree.links.new(cur, vmul.inputs[1])
tree.links.new(vr.outputs["Value"], vmul.inputs[2])
cur = vmul.outputs["Image"]
print("[render] (C) vignette")
except Exception as _e:
print("[render][WARN] vignette skipped:", _e)
# (B) Staub auf der Linse: wenige dunkle Flecken über dem ganzen Bild
if lens_dirt and lens_dirt_strength > 0.0:
try:
dtex = bpy.data.textures.new("lensDirtTex", type="VORONOI")
dt = tree.nodes.new("CompositorNodeTexture")
dt.texture = dtex
dramp = tree.nodes.new("CompositorNodeValToRGB")
dramp.color_ramp.elements[0].position = 0.0
dramp.color_ramp.elements[1].position = 0.12 # nur wenige Flecken
dramp.color_ramp.elements[0].color = (1, 1, 1, 1)
dramp.color_ramp.elements[1].color = (1.0 - min(0.9, lens_dirt_strength),) * 3 + (1.0,)
dmul = tree.nodes.new("CompositorNodeMixRGB")
dmul.blend_type = "MULTIPLY"; dmul.inputs["Fac"].default_value = 1.0
tree.links.new(dt.outputs["Value"], dramp.inputs["Fac"])
tree.links.new(cur, dmul.inputs[1])
tree.links.new(dramp.outputs["Image"], dmul.inputs[2])
cur = dmul.outputs["Image"]
print("[render] (B) lens dirt")
except Exception as _e:
print("[render][WARN] lens_dirt skipped:", _e)
# (D) Sensor-Rauschen: feines additives Rauschen
if sensor_noise and sensor_noise_strength > 0.0:
try:
ntex = bpy.data.textures.new("sensorNoiseTex", type="NOISE")
nt = tree.nodes.new("CompositorNodeTexture")
nt.texture = ntex
nmix = tree.nodes.new("CompositorNodeMixRGB")
nmix.blend_type = "ADD"
nmix.inputs["Fac"].default_value = min(0.3, sensor_noise_strength)
tree.links.new(cur, nmix.inputs[1])
tree.links.new(nt.outputs["Image"], nmix.inputs[2])
cur = nmix.outputs["Image"]
print("[render] (D) sensor noise")
except Exception as _e:
print("[render][WARN] sensor_noise skipped:", _e)
comp = tree.nodes.new("CompositorNodeComposite")
blur.filter_type = "GAUSS"
blur.use_relative = False
_ang = random.uniform(0.0, math.pi) # zufällige Verwackel-Richtung
_amp = random.uniform(0.35, 1.0) * motion_blur_max_px
blur.size_x = max(0, int(round(abs(_amp * math.cos(_ang)))))
blur.size_y = max(0, int(round(abs(_amp * math.sin(_ang)))))
rl.location = (-300, 0)
blur.location = (0, 0)
comp.location = (300, 0)
tree.links.new(rl.outputs["Image"], blur.inputs["Image"])
tree.links.new(blur.outputs["Image"], comp.inputs["Image"])
print(f"[render] motion blur size=({blur.size_x},{blur.size_y}) px")
tree.links.new(cur, comp.inputs["Image"])
bpy.ops.render.render(write_still=True)
print("Finished rendering:", OUTPUT_FILE)

File diff suppressed because it is too large Load Diff