Claude: Lens-Distortions
This commit is contained in:
9
setup/generateSets/Dockerfile
Normal file
9
setup/generateSets/Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
||||
FROM linuxserver/blender:latest
|
||||
|
||||
USER root
|
||||
|
||||
RUN pip3 install --no-cache-dir \
|
||||
numpy \
|
||||
opencv-python
|
||||
|
||||
WORKDIR /workspace
|
||||
Binary file not shown.
0
setup/generateSets/docker-compose.yaml
Normal file
0
setup/generateSets/docker-compose.yaml
Normal 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")
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user