Dont move the view of the camara ony the Object
This commit is contained in:
132
np.py
132
np.py
@@ -3,15 +3,15 @@ import time
|
|||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw
|
||||||
import random # Importiert für Zufallszahlen
|
import random
|
||||||
|
|
||||||
# --- Hilfsfunktionen ---
|
# --- Hilfsfunktionen ---
|
||||||
|
|
||||||
def normalize(v):
|
def normalize(v):
|
||||||
""" Normalisiert einen Vektor (macht seine Länge zu 1). """
|
""" Normalisiert einen Vektor (macht seine Länge zu 1). """
|
||||||
norm = np.linalg.norm(v)
|
norm = np.linalg.norm(v)
|
||||||
if norm < 1e-9: # Sicherer Vergleich mit kleiner Toleranz
|
if norm < 1e-9:
|
||||||
return np.zeros_like(v) # Gibt Nullvektor zurück, wenn Norm zu klein ist
|
return np.zeros_like(v)
|
||||||
return v / norm
|
return v / norm
|
||||||
|
|
||||||
def look_at_matrix(eye, target, up):
|
def look_at_matrix(eye, target, up):
|
||||||
@@ -22,42 +22,31 @@ def look_at_matrix(eye, target, up):
|
|||||||
:param up: Up-Vektor der Kamera (zeigt, wo "oben" für die Kamera ist, np.array).
|
:param up: Up-Vektor der Kamera (zeigt, wo "oben" für die Kamera ist, np.array).
|
||||||
:return: 4x4 View-Matrix (NumPy-Array).
|
:return: 4x4 View-Matrix (NumPy-Array).
|
||||||
"""
|
"""
|
||||||
# Robustere Berechnung, falls eye und target sehr nah sind
|
|
||||||
if np.linalg.norm(eye - target) < 1e-6:
|
if np.linalg.norm(eye - target) < 1e-6:
|
||||||
# Wenn Kamera und Ziel fast am selben Ort sind, ist die Richtung undefiniert.
|
target = eye - np.array([0.0, 0.0, 1.0]) # Standard-Blickrichtung bei Kollision
|
||||||
# Wähle eine Standardrichtung, z.B. Blick entlang -Z der Welt vom 'eye'.
|
|
||||||
target = eye - np.array([0.0, 0.0, 1.0])
|
|
||||||
|
|
||||||
forward = normalize(eye - target) # Konvention: Kamera schaut entlang -Z
|
forward = normalize(eye - target)
|
||||||
|
|
||||||
# Prüfe, ob 'up' und 'forward' (fast) parallel sind
|
|
||||||
# Normalisiere 'up' vor dem Test für mehr Robustheit
|
|
||||||
up_normalized = normalize(up)
|
up_normalized = normalize(up)
|
||||||
|
|
||||||
if abs(np.dot(up_normalized, forward)) > 0.999:
|
if abs(np.dot(up_normalized, forward)) > 0.999:
|
||||||
# Fallback, wenn 'up' und 'forward' (fast) parallel sind
|
if abs(forward[1]) < 0.99:
|
||||||
if abs(forward[1]) < 0.99: # Wenn forward nicht fast vertikal ist
|
|
||||||
temp_up = np.array([0.0, 1.0, 0.0])
|
temp_up = np.array([0.0, 1.0, 0.0])
|
||||||
else: # Wenn forward fast vertikal ist
|
else:
|
||||||
temp_up = np.array([0.0, 0.0, -1.0 if forward[1] > 0 else 1.0])
|
temp_up = np.array([0.0, 0.0, -1.0 if forward[1] > 0 else 1.0])
|
||||||
right = normalize(np.cross(temp_up, forward))
|
right = normalize(np.cross(temp_up, forward))
|
||||||
else:
|
else:
|
||||||
# Nutze das normalisierte 'up' für die Berechnung
|
|
||||||
right = normalize(np.cross(up_normalized, forward))
|
right = normalize(np.cross(up_normalized, forward))
|
||||||
|
|
||||||
# Den tatsächlichen Up-Vektor der Kamera neu berechnen, damit er orthogonal ist
|
|
||||||
camera_up = normalize(np.cross(forward, right))
|
camera_up = normalize(np.cross(forward, right))
|
||||||
|
|
||||||
# Rotationsmatrix erstellen
|
|
||||||
rotation = np.identity(4)
|
rotation = np.identity(4)
|
||||||
rotation[0, 0:3] = right
|
rotation[0, 0:3] = right
|
||||||
rotation[1, 0:3] = camera_up
|
rotation[1, 0:3] = camera_up
|
||||||
rotation[2, 0:3] = forward
|
rotation[2, 0:3] = forward
|
||||||
|
|
||||||
# Translationsmatrix erstellen
|
|
||||||
translation = np.identity(4)
|
translation = np.identity(4)
|
||||||
translation[0:3, 3] = -eye
|
translation[0:3, 3] = -eye
|
||||||
|
|
||||||
# View-Matrix: Zuerst Translation, dann Rotation
|
|
||||||
view_mat = np.dot(rotation, translation)
|
view_mat = np.dot(rotation, translation)
|
||||||
return view_mat
|
return view_mat
|
||||||
|
|
||||||
@@ -65,79 +54,65 @@ def perspective_projection(point_cam_space, fov_deg, aspect_ratio, near, far):
|
|||||||
"""
|
"""
|
||||||
Projiziert einen Punkt aus dem Kamera-Koordinatenraum auf eine 2D-Ebene.
|
Projiziert einen Punkt aus dem Kamera-Koordinatenraum auf eine 2D-Ebene.
|
||||||
Gibt normalisierte Gerätekoordinaten (NDC) zurück (-1 bis +1).
|
Gibt normalisierte Gerätekoordinaten (NDC) zurück (-1 bis +1).
|
||||||
:param point_cam_space: Punkt in Kamera-Koordinaten (3D NumPy-Array).
|
|
||||||
:param fov_deg: Field of View (vertikal) in Grad.
|
|
||||||
:param aspect_ratio: Seitenverhältnis (Breite / Höhe) der Bildebene.
|
|
||||||
:param near: Nahe Clipping-Ebene.
|
|
||||||
:param far: Ferne Clipping-Ebene.
|
|
||||||
:return: Projizierter 2D-Punkt in NDC (NumPy-Array) oder None, wenn außerhalb.
|
|
||||||
"""
|
"""
|
||||||
# Kamera schaut entlang -Z, Z muss negativ sein und innerhalb der Clipping-Grenzen liegen
|
|
||||||
if point_cam_space[2] > -near or point_cam_space[2] < -far:
|
if point_cam_space[2] > -near or point_cam_space[2] < -far:
|
||||||
return None # Außerhalb des Sichtfelds (vor near oder hinter far)
|
return None
|
||||||
|
|
||||||
# Verhindert Division durch Null, falls Punkt genau auf der Kameraebene liegt (z=0)
|
|
||||||
if abs(point_cam_space[2]) < 1e-9:
|
if abs(point_cam_space[2]) < 1e-9:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Skalierungsfaktor basierend auf FOV
|
|
||||||
f = 1.0 / math.tan(math.radians(fov_deg) / 2.0)
|
f = 1.0 / math.tan(math.radians(fov_deg) / 2.0)
|
||||||
|
|
||||||
# Projizierte Koordinaten auf der Bildebene (NDC)
|
|
||||||
x_ndc = (f / aspect_ratio) * point_cam_space[0] / -point_cam_space[2]
|
x_ndc = (f / aspect_ratio) * point_cam_space[0] / -point_cam_space[2]
|
||||||
y_ndc = f * point_cam_space[1] / -point_cam_space[2]
|
y_ndc = f * point_cam_space[1] / -point_cam_space[2]
|
||||||
|
|
||||||
# Überprüfen, ob die projizierten Koordinaten innerhalb des NDC-Bereichs [-1, 1] liegen
|
|
||||||
if not (-1 <= x_ndc <= 1 and -1 <= y_ndc <= 1):
|
if not (-1 <= x_ndc <= 1 and -1 <= y_ndc <= 1):
|
||||||
return None # Außerhalb des seitlichen Sichtfelds
|
return None
|
||||||
|
|
||||||
return np.array([x_ndc, y_ndc])
|
return np.array([x_ndc, y_ndc])
|
||||||
|
|
||||||
def ndc_to_pixel(ndc_coords, width, height):
|
def ndc_to_pixel(ndc_coords, width, height):
|
||||||
""" Wandelt NDC-Koordinaten (-1 bis 1) in Pixelkoordinaten (0 bis width/height) um. """
|
""" Wandelt NDC-Koordinaten (-1 bis 1) in Pixelkoordinaten (0 bis width/height) um. """
|
||||||
pixel_x = (ndc_coords[0] + 1.0) / 2.0 * width
|
pixel_x = (ndc_coords[0] + 1.0) / 2.0 * width
|
||||||
# Y-Koordinate invertieren (NDC +Y ist oben, Pixel +Y ist unten)
|
pixel_y = (1.0 - ndc_coords[1]) / 2.0 * height # Y invertieren
|
||||||
pixel_y = (1.0 - ndc_coords[1]) / 2.0 * height
|
|
||||||
px = int(round(pixel_x))
|
px = int(round(pixel_x))
|
||||||
py = int(round(pixel_y))
|
py = int(round(pixel_y))
|
||||||
# Clipping auf Bildgrenzen
|
|
||||||
px = max(0, min(width - 1, px))
|
px = max(0, min(width - 1, px))
|
||||||
py = max(0, min(height - 1, py))
|
py = max(0, min(height - 1, py))
|
||||||
return px, py
|
return px, py
|
||||||
|
|
||||||
# --- Simulationsparameter ---
|
# --- Simulationsparameter ---
|
||||||
object_start_pos = np.array([0.0, 0.0, 10.0]) # Startposition des Objekts (x, y, z)
|
object_start_pos = np.array([0.0, 0.0, 10.0])
|
||||||
|
|
||||||
# --- NEU: Parameter für zufällige Bewegung ---
|
# --- NEU: Fixer Zielpunkt für die Kameras ---
|
||||||
# Einfacher Random Walk: In jedem Schritt eine zufällige Verschiebung
|
# Die Kameras schauen immer auf die ursprüngliche Startposition des Objekts.
|
||||||
random_step_scale = 0.8 # Maximale Verschiebung pro Achse pro Zeitschritt
|
# Alternativ könnte hier jeder andere feste Punkt im Raum definiert werden.
|
||||||
# Optional: Begrenzung des Bewegungsraums (Bounding Box)
|
fixed_camera_target = object_start_pos.copy()
|
||||||
# Damit das Objekt nicht zu weit wegdriftet
|
|
||||||
|
# Parameter für zufällige Bewegung
|
||||||
|
random_step_scale = 0.8
|
||||||
world_bounds_min = np.array([-10.0, -5.0, 5.0])
|
world_bounds_min = np.array([-10.0, -5.0, 5.0])
|
||||||
world_bounds_max = np.array([ 10.0, 5.0, 15.0])
|
world_bounds_max = np.array([ 10.0, 5.0, 15.0])
|
||||||
|
|
||||||
|
# Kameraeinstellungen
|
||||||
# Kameraeinstellungen (Namen ohne Leerzeichen für Dateinamen angepasst)
|
|
||||||
cameras = [
|
cameras = [
|
||||||
{
|
{
|
||||||
"name": "Kamera_1_Frontal",
|
"name": "Kamera_1_Frontal",
|
||||||
"pos": np.array([0.0, 0.0, 0.0]),
|
"pos": np.array([0.0, 0.0, 0.0]),
|
||||||
"target": object_start_pos, # Schaut initial auf den Startpunkt
|
"target": fixed_camera_target, # Verwendet den fixen Zielpunkt
|
||||||
"up": np.array([0.0, 1.0, 0.0]),
|
"up": np.array([0.0, 1.0, 0.0]),
|
||||||
"fov_deg": 60,
|
"fov_deg": 60,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Kamera_2_Seitlich_Links",
|
"name": "Kamera_2_Seitlich_Links",
|
||||||
"pos": np.array([-15.0, 0.0, 5.0]),
|
"pos": np.array([-15.0, 0.0, 5.0]),
|
||||||
"target": object_start_pos,
|
"target": fixed_camera_target, # Verwendet den fixen Zielpunkt
|
||||||
"up": np.array([0.0, 1.0, 0.0]),
|
"up": np.array([0.0, 1.0, 0.0]),
|
||||||
"fov_deg": 45,
|
"fov_deg": 45,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Kamera_3_Von_Oben",
|
"name": "Kamera_3_Von_Oben",
|
||||||
"pos": np.array([0.0, 15.0, 10.0]),
|
"pos": np.array([0.0, 15.0, 10.0]),
|
||||||
"target": object_start_pos,
|
"target": fixed_camera_target, # Verwendet den fixen Zielpunkt
|
||||||
"up": np.array([0.0, 0.0, -1.0]), # Blickrichtung -Y, Z nach unten ist "up"
|
"up": np.array([0.0, 0.0, -1.0]),
|
||||||
"fov_deg": 70,
|
"fov_deg": 70,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -146,15 +121,15 @@ cameras = [
|
|||||||
near_plane = 0.1
|
near_plane = 0.1
|
||||||
far_plane = 100.0
|
far_plane = 100.0
|
||||||
|
|
||||||
simulation_duration = 10 # Sekunden
|
simulation_duration = 10
|
||||||
time_step = 1 # Zeitintervall für "Aufnahmen" in Sekunden
|
time_step = 1
|
||||||
|
|
||||||
# --- Bildparameter ---
|
# Bildparameter
|
||||||
IMG_WIDTH = 320
|
IMG_WIDTH = 320
|
||||||
IMG_HEIGHT = 180
|
IMG_HEIGHT = 180
|
||||||
OUTPUT_DIR = "./bilder"
|
OUTPUT_DIR = "./bilder"
|
||||||
|
|
||||||
# --- Ausgabeverzeichnis erstellen ---
|
# Ausgabeverzeichnis erstellen
|
||||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
print(f"Bilder werden in '{OUTPUT_DIR}' gespeichert.")
|
print(f"Bilder werden in '{OUTPUT_DIR}' gespeichert.")
|
||||||
|
|
||||||
@@ -167,17 +142,12 @@ print("Starte Simulation...")
|
|||||||
while current_time <= simulation_duration:
|
while current_time <= simulation_duration:
|
||||||
print(f"\n--- Zeitpunkt: {current_time:.1f}s ---")
|
print(f"\n--- Zeitpunkt: {current_time:.1f}s ---")
|
||||||
|
|
||||||
# --- NEU: Objektposition zufällig aktualisieren ---
|
# Objektposition zufällig aktualisieren
|
||||||
# Generiere eine zufällige Verschiebung für diesen Zeitschritt
|
delta_x = (random.random() - 0.5) * 2 * random_step_scale
|
||||||
delta_x = (random.random() - 0.5) * 2 * random_step_scale # Zufallszahl zwischen -scale und +scale
|
|
||||||
delta_y = (random.random() - 0.5) * 2 * random_step_scale
|
delta_y = (random.random() - 0.5) * 2 * random_step_scale
|
||||||
delta_z = (random.random() - 0.5) * 2 * random_step_scale
|
delta_z = (random.random() - 0.5) * 2 * random_step_scale
|
||||||
random_displacement = np.array([delta_x, delta_y, delta_z])
|
random_displacement = np.array([delta_x, delta_y, delta_z])
|
||||||
|
|
||||||
# Wende die Verschiebung an
|
|
||||||
current_object_pos += random_displacement
|
current_object_pos += random_displacement
|
||||||
|
|
||||||
# Optional: Begrenze die Position auf die definierte Bounding Box
|
|
||||||
current_object_pos = np.clip(current_object_pos, world_bounds_min, world_bounds_max)
|
current_object_pos = np.clip(current_object_pos, world_bounds_min, world_bounds_max)
|
||||||
|
|
||||||
print(f"Objekt Welt-Position: ({current_object_pos[0]:.2f}, {current_object_pos[1]:.2f}, {current_object_pos[2]:.2f})")
|
print(f"Objekt Welt-Position: ({current_object_pos[0]:.2f}, {current_object_pos[1]:.2f}, {current_object_pos[2]:.2f})")
|
||||||
@@ -190,62 +160,58 @@ while current_time <= simulation_duration:
|
|||||||
print(f"\n Kamera {i+1}: {cam['name']}")
|
print(f"\n Kamera {i+1}: {cam['name']}")
|
||||||
print(f" Position: ({cam['pos'][0]:.2f}, {cam['pos'][1]:.2f}, {cam['pos'][2]:.2f})")
|
print(f" Position: ({cam['pos'][0]:.2f}, {cam['pos'][1]:.2f}, {cam['pos'][2]:.2f})")
|
||||||
|
|
||||||
# Kamera schaut immer auf die aktuelle Objektposition
|
# --- WICHTIGE ÄNDERUNG: Verwende den *fixen* Zielpunkt ---
|
||||||
current_target = current_object_pos
|
# Die Kamera schaut *nicht* mehr auf current_object_pos, sondern auf cam['target'],
|
||||||
# Seitenverhältnis basierend auf Bilddimensionen für die Projektion verwenden
|
# welches im Kamera-Dictionary auf fixed_camera_target gesetzt wurde.
|
||||||
|
current_target_for_view = cam['target'] # Dies ist jetzt immer fixed_camera_target
|
||||||
|
|
||||||
effective_aspect_ratio = IMG_WIDTH / IMG_HEIGHT
|
effective_aspect_ratio = IMG_WIDTH / IMG_HEIGHT
|
||||||
print(f" Schaut auf (Target): ({current_target[0]:.2f}, {current_target[1]:.2f}, {current_target[2]:.2f})")
|
# Ausgabe zeigt den fixen Zielpunkt an
|
||||||
|
print(f" Schaut auf (Fixer Target): ({current_target_for_view[0]:.2f}, {current_target_for_view[1]:.2f}, {current_target_for_view[2]:.2f})")
|
||||||
print(f" Up-Vektor: ({cam['up'][0]:.2f}, {cam['up'][1]:.2f}, {cam['up'][2]:.2f})")
|
print(f" Up-Vektor: ({cam['up'][0]:.2f}, {cam['up'][1]:.2f}, {cam['up'][2]:.2f})")
|
||||||
print(f" FOV: {cam['fov_deg']} Grad, Aspekt (für Projektion): {effective_aspect_ratio:.2f}")
|
print(f" FOV: {cam['fov_deg']} Grad, Aspekt (für Projektion): {effective_aspect_ratio:.2f}")
|
||||||
|
|
||||||
# View-Matrix berechnen
|
# View-Matrix mit dem fixen Zielpunkt berechnen
|
||||||
view_mat = look_at_matrix(cam['pos'], current_target, cam['up'])
|
view_mat = look_at_matrix(cam['pos'], current_target_for_view, cam['up'])
|
||||||
|
|
||||||
# Objektposition in Kamera-Koordinaten transformieren
|
# Objektposition in Kamera-Koordinaten transformieren
|
||||||
|
# Dieser Schritt ist weiterhin nötig, um das *bewegliche* Objekt zu projizieren
|
||||||
point_in_cam_space_h = np.dot(view_mat, object_pos_h)
|
point_in_cam_space_h = np.dot(view_mat, object_pos_h)
|
||||||
if point_in_cam_space_h[3] <= 1e-9: # Sicherer Vergleich mit w
|
if point_in_cam_space_h[3] <= 1e-9:
|
||||||
print(" Objekt hinter oder auf der Kameraebene (w <= 0).")
|
print(" Objekt hinter oder auf der Kameraebene (w <= 0).")
|
||||||
projected_point_ndc = None
|
projected_point_ndc = None
|
||||||
point_in_cam_space = np.array([np.nan, np.nan, np.nan]) # Ungültig
|
point_in_cam_space = np.array([np.nan, np.nan, np.nan])
|
||||||
else:
|
else:
|
||||||
point_in_cam_space = point_in_cam_space_h[:3] / point_in_cam_space_h[3]
|
point_in_cam_space = point_in_cam_space_h[:3] / point_in_cam_space_h[3]
|
||||||
print(f" Objekt in Kamera-Koordinaten: ({point_in_cam_space[0]:.2f}, {point_in_cam_space[1]:.2f}, {point_in_cam_space[2]:.2f})")
|
print(f" Bewegliches Objekt in Kamera-Koordinaten: ({point_in_cam_space[0]:.2f}, {point_in_cam_space[1]:.2f}, {point_in_cam_space[2]:.2f})") # Klärung der Ausgabe
|
||||||
|
|
||||||
# In 2D projizieren (NDC)
|
# Projiziere das *bewegliche* Objekt in 2D (NDC)
|
||||||
projected_point_ndc = perspective_projection(
|
projected_point_ndc = perspective_projection(
|
||||||
point_in_cam_space,
|
point_in_cam_space,
|
||||||
cam['fov_deg'],
|
cam['fov_deg'],
|
||||||
effective_aspect_ratio, # Benutze das tatsächliche Bildseitenverhältnis
|
effective_aspect_ratio,
|
||||||
near_plane,
|
near_plane,
|
||||||
far_plane
|
far_plane
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Bild erstellen und speichern ---
|
# Bild erstellen und speichern
|
||||||
img = Image.new('RGB', (IMG_WIDTH, IMG_HEIGHT), color='black')
|
img = Image.new('RGB', (IMG_WIDTH, IMG_HEIGHT), color='black')
|
||||||
draw = ImageDraw.Draw(img)
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
if projected_point_ndc is not None:
|
if projected_point_ndc is not None:
|
||||||
print(f" Projizierte 2D-Koordinaten (NDC): ({projected_point_ndc[0]:.3f}, {projected_point_ndc[1]:.3f})")
|
print(f" Projizierte 2D-Koordinaten des Objekts (NDC): ({projected_point_ndc[0]:.3f}, {projected_point_ndc[1]:.3f})") # Klärung der Ausgabe
|
||||||
# NDC in Pixelkoordinaten umwandeln
|
|
||||||
px, py = ndc_to_pixel(projected_point_ndc, IMG_WIDTH, IMG_HEIGHT)
|
px, py = ndc_to_pixel(projected_point_ndc, IMG_WIDTH, IMG_HEIGHT)
|
||||||
print(f" Pixelkoordinaten (im Bild): ({px}, {py})")
|
print(f" Pixelkoordinaten des Objekts (im Bild): ({px}, {py})")
|
||||||
# Einen weißen Punkt zeichnen (3x3 Quadrat für Sichtbarkeit)
|
|
||||||
draw.rectangle([(px-1, py-1), (px+1, py+1)], fill='white', outline='white')
|
draw.rectangle([(px-1, py-1), (px+1, py+1)], fill='white', outline='white')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(" Objekt befindet sich ausserhalb des Sichtbereichs (Clipping).")
|
print(" Bewegliches Objekt befindet sich ausserhalb des Sichtbereichs dieser Kamera.") # Klärung der Ausgabe
|
||||||
|
|
||||||
# Bild speichern
|
|
||||||
timestamp_str = f"{current_time:.1f}".replace('.', '_')
|
timestamp_str = f"{current_time:.1f}".replace('.', '_')
|
||||||
filename = f"{cam['name']}_t_{timestamp_str}.png"
|
filename = f"{cam['name']}_t_{timestamp_str}.png"
|
||||||
filepath = os.path.join(OUTPUT_DIR, filename)
|
filepath = os.path.join(OUTPUT_DIR, filename)
|
||||||
img.save(filepath)
|
img.save(filepath)
|
||||||
print(f" Bild gespeichert: {filepath}")
|
print(f" Bild gespeichert: {filepath}")
|
||||||
|
|
||||||
# Zum nächsten Zeitschritt gehen
|
|
||||||
current_time += time_step
|
current_time += time_step
|
||||||
# Keine Pause, um die Simulation schneller laufen zu lassen
|
|
||||||
# if current_time <= simulation_duration:
|
|
||||||
# time.sleep(0.1)
|
|
||||||
|
|
||||||
print("\nSimulation beendet.")
|
print("\nSimulation beendet.")
|
||||||
Reference in New Issue
Block a user