A Python tool for generating synthetic structured light patterns overlaid on images with depth data. This is designed for data augmentation to improve vision models for robotic grasping tasks.
Quick depth-based pattern overlay for existing images.
Realistic 3D scene rendering with projector and camera at different positions.
Decouple pattern projection from geometry - works with any depth map source!
Structured light is a 3D scanning technique that projects known patterns onto objects and analyzes the deformation to extract depth information. This tool provides three ways to generate synthetic structured light training data:
- 2D Pattern Generator: Fast pattern overlay onto existing depth maps
- 3D Simulator: Full geometric simulation with configurable projector and camera positions
- Depth-Based Projector: Pattern projection using only depth maps (works with real depth cameras!)
- Sinusoidal Stripe Patterns: Smooth vertical or horizontal fringes that deform based on depth
- Binary Stripe Patterns: Sharp black and white stripes with depth-based phase shifts
- IR Dot Patterns: Kinect-style infrared dot grids with depth displacement
- Deformed Grid Patterns: Regular grids warped by surface depth
- Depth-based Modulation: All patterns react to depth maps, simulating real structured light behavior
- Realistic Noise: Adds Gaussian noise and missing data regions to simulate real sensor artifacts
- RGB Overlay: Blend patterns onto existing RGB images with configurable intensity
- Cross-platform Display: Automatically detects display availability
- Shows interactive matplotlib window when display is available
- Saves to timestamped PNG files when running headless (Linux servers, WSL, SSH sessions)
- Output Management: All visualizations saved to
output/folder with timestamps
pip install -r requirements.txtOr manually:
# For 2D pattern generator
pip install numpy opencv-python matplotlib pillow
# For 3D simulator (includes above)
pip install numpy opencv-python matplotlib pillow trimesh pyrenderpython structured_light_generator.pyThis will:
- Generate a synthetic depth map with random box-like structures
- Create multiple structured light patterns
- Display a 3×3 grid of visualizations (if display available)
- Save the visualization to
output/structured_light_patterns_YYYYMMDD_HHMMSS.png
Without config (uses defaults):
python structured_light_3d.pyWith config file (recommended):
python structured_light_3d.py config_example.yamlThis will:
- Load configuration from YAML file (or use defaults)
- Create projector at specified position
- Build 3D scene with boxes at configured positions and orientations
- Setup camera at specified viewpoint
- Render the scene with multiple structured light patterns
- Generate both RGB and depth images from camera viewpoint
- Save visualizations to
output/structured_light_3d_*.png
from structured_light_generator import StructuredLightGenerator
import numpy as np
# Initialize generator
generator = StructuredLightGenerator(image_size=(640, 480))
# Load your depth map (or create synthetic one)
depth_map = np.load('your_depth_map.npy')
# Generate patterns
vertical_stripes = generator.create_stripe_pattern(
depth_map,
frequency=20.0,
orientation='vertical'
)
dot_pattern = generator.create_dot_pattern(
depth_map,
dot_spacing=30,
dot_size=3
)
# Add realistic noise
noisy_pattern = generator.add_realistic_noise(
vertical_stripes,
noise_level=0.05,
missing_regions=True
)
# Overlay on RGB image
rgb_image = cv2.imread('your_image.jpg')
result = generator.overlay_on_rgb(rgb_image, vertical_stripes, blend_factor=0.3)from structured_light_3d import (
StructuredLightProjector,
Scene3D,
StructuredLightRenderer
)
import numpy as np
# Create projector at custom position
projector = StructuredLightProjector(
position=np.array([0.5, -0.8, 1.5]),
look_at=np.array([0.0, 0.0, 0.0]),
fov=50.0,
resolution=(1024, 768)
)
# Build 3D scene
scene = Scene3D()
scene.add_ground_plane(size=3.0, height=-0.5)
scene.add_box(
size=(0.2, 0.3, 0.15),
position=np.array([0.0, 0.0, 0.1])
)
# Or generate random boxes
scene.generate_random_boxes(num_boxes=5, bounds=(1.0, 1.0, 0.4))
# Setup renderer with camera
renderer = StructuredLightRenderer(
projector=projector,
camera_position=np.array([1.2, 0.0, 1.0]),
camera_look_at=np.array([0.0, 0.0, 0.0]),
camera_resolution=(640, 480),
camera_fov=60.0
)
# Generate pattern and render
pattern = projector.create_stripe_pattern(frequency=15, orientation='vertical')
rgb_image, depth_map = renderer.render(
scene,
pattern,
ambient_light=0.3,
pattern_intensity=0.6
)
# rgb_image and depth_map are now ready to use for trainingGenerate depth from config geometry:
python run_depth_based.py config_example.yamlUse external depth map (numpy):
python run_depth_based.py config_example.yaml --depth my_depth.npyUse external depth map (PNG with scaling):
python run_depth_based.py config_example.yaml --depth my_depth.png --max-depth 5.0With default config:
python run_depth_based.pyGet help:
python run_depth_based.py --helpRun examples:
python example_depth_based.pyThe main script will:
- Load configuration from YAML file
- Load external depth map OR generate from scene geometry
- Project all configured patterns (respects
display: true/false) - Save individual patterns and comparison visualization
The example script will run three demos:
- Generate depth from geometry and project patterns
- Load external depth map and project patterns
- Compare depth quality (synthetic, noisy, filtered)
Option A: Generate synthetic depth from geometry
from structured_light_from_depth import (
CameraCalibration,
ProjectorCalibration,
DepthMapGenerator,
PatternProjector,
create_stripe_pattern
)
from structured_light_3d import Scene3D
import numpy as np
# Define camera calibration
camera_calib = CameraCalibration(
position=np.array([1.2, 0.0, 1.0]),
look_at=np.array([0.0, 0.0, 0.0]),
fov=60.0,
resolution=(640, 480)
)
# Define projector calibration
projector_calib = ProjectorCalibration(
position=np.array([0.5, -0.8, 1.5]),
look_at=np.array([0.0, 0.0, 0.0]),
fov=50.0,
resolution=(1024, 768)
)
# Generate depth from geometry (only needed for synthetic data)
scene = Scene3D()
scene.add_ground_plane()
scene.add_box(size=(0.3, 0.3, 0.3), position=np.array([0, 0, 0.15]))
depth_generator = DepthMapGenerator(camera_calib)
rgb_base, depth_map = depth_generator.render_depth(scene)
# Project pattern (no geometry needed!)
pattern_projector = PatternProjector(camera_calib, projector_calib)
pattern = create_stripe_pattern(projector_calib.resolution, frequency=15)
result = pattern_projector.project_pattern_backward(pattern, depth_map, rgb_base)Option B: Use external depth from real depth camera
from structured_light_from_depth import (
CameraCalibration,
ProjectorCalibration,
PatternProjector,
DepthMapIO,
create_stripe_pattern
)
# Load depth from RealSense, Kinect, etc.
depth_map = DepthMapIO.load_depth_png('path/to/depth.png', max_depth=5.0)
# Or: depth_map = DepthMapIO.load_depth_npy('path/to/depth.npy')
# Define calibrations (must match your hardware!)
camera_calib = CameraCalibration(
position=np.array([1.2, 0.0, 1.0]),
look_at=np.array([0.0, 0.0, 0.0]),
fov=60.0,
resolution=(640, 480)
)
projector_calib = ProjectorCalibration(
position=np.array([0.5, -0.8, 1.5]),
look_at=np.array([0.0, 0.0, 0.0]),
fov=50.0,
resolution=(1024, 768)
)
# Project pattern onto depth (no geometry needed!)
pattern_projector = PatternProjector(camera_calib, projector_calib)
pattern = create_stripe_pattern(projector_calib.resolution, frequency=15)
result = pattern_projector.project_pattern_backward(pattern, depth_map)The system supports two formats for external depth maps:
from structured_light_from_depth import DepthMapIO
# Save depth as 16-bit PNG
# depth_map should be in meters (float array)
DepthMapIO.save_depth_png(depth_map, 'output/depth.png', max_depth=5.0)
# Load depth from PNG
# Returns depth in meters (float array)
depth_map = DepthMapIO.load_depth_png('output/depth.png', max_depth=5.0)Properties:
- Format: Grayscale PNG, 16-bit unsigned integer
- Scaling: 0 → 0 meters, 65535 → max_depth meters
- Good for sharing with other tools and visualization
# Save depth as numpy array
# No scaling - preserves exact values in meters
DepthMapIO.save_depth_npy(depth_map, 'output/depth.npy')
# Load depth from numpy array
depth_map = DepthMapIO.load_depth_npy('output/depth.npy')Properties:
- Format: Raw numpy array (float32 or float64)
- No scaling - exact precision preserved
- Best for Python-only workflows
External depth maps must have these properties:
- Shape: (height, width) - 2D array matching camera resolution
- Units: Meters (distance from camera)
- Values:
- 0 or negative = invalid/no depth
- Positive = valid depth in meters
- Data type: float32 or float64
# Create color-coded visualization of depth
depth_colored = DepthMapIO.visualize_depth(depth_map)When using external depth maps from real cameras, you should use the actual camera intrinsics instead of computing from FOV.
In config file:
camera:
position: [1.2, 0.0, 1.0]
look_at: [0.0, 0.0, 0.0]
resolution: [640, 480]
# Optional: Specify actual camera intrinsics
# If provided, these override FOV-based computation
intrinsics:
fx: 381.36 # Focal length X (pixels)
fy: 381.36 # Focal length Y (pixels)
cx: 320.0 # Principal point X (usually width/2)
cy: 240.0 # Principal point Y (usually height/2)Get intrinsics from RealSense:
python get_realsense_intrinsics.pyThis will output the intrinsics in YAML format ready to copy into your config file.
import pyrealsense2 as rs
import numpy as np
from structured_light_from_depth import (
CameraCalibration, ProjectorCalibration,
PatternProjector, DepthMapIO, create_stripe_pattern
)
# Get depth from RealSense
pipeline = rs.pipeline()
config = rs.config()
config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)
profile = pipeline.start(config)
# Get camera intrinsics
depth_profile = profile.get_stream(rs.stream.depth)
intrinsics = depth_profile.as_video_stream_profile().get_intrinsics()
frames = pipeline.wait_for_frames()
depth_frame = frames.get_depth_frame()
depth_map = np.asanyarray(depth_frame.get_data()).astype(np.float32)
# RealSense depth is in millimeters, convert to meters
depth_map = depth_map / 1000.0
# Save for later use
DepthMapIO.save_depth_npy(depth_map, 'realsense_depth.npy')
# Define calibrations using actual intrinsics
camera_calib = CameraCalibration(
position=np.array([1.2, 0.0, 1.0]),
look_at=np.array([0.0, 0.0, 0.0]),
fov=60.0, # Not used when intrinsics provided
resolution=(intrinsics.width, intrinsics.height),
fx=intrinsics.fx,
fy=intrinsics.fy,
cx=intrinsics.ppx,
cy=intrinsics.ppy
)
projector_calib = ProjectorCalibration(
position=np.array([0.5, -0.8, 1.5]),
look_at=np.array([0.0, 0.0, 0.0]),
fov=50.0,
resolution=(1024, 768)
)
# Project pattern onto real depth data
pattern_projector = PatternProjector(camera_calib, projector_calib)
pattern = create_stripe_pattern(projector_calib.resolution, frequency=15)
result = pattern_projector.project_pattern_backward(pattern, depth_map)Or use config file with intrinsics:
# 1. Get intrinsics and save to config
python get_realsense_intrinsics.py # Copy output to config_realsense_example.yaml
# 2. Capture and save depth
# (use RealSense viewer or your own script)
# 3. Run with external depth
python run_depth_based.py config_realsense_example.yaml --depth realsense_depth.npy| Format | Best For |
|---|---|
| 16-bit PNG | Sharing with other tools, visualization, storage efficiency |
| Numpy .npy | Python-only workflow, exact precision, frequent loading/saving |
| Approach | Best For |
|---|---|
| 2D Pattern Generator | Quick prototyping, simple overlays, existing depth data |
| 3D Simulator | Full control over geometry, testing different scenes, ground truth data |
| Depth-Based Projector | Real depth camera data, RealSense/Kinect integration, no geometry knowledge |
The 3D simulator supports comprehensive YAML configuration for all parameters. See config_example.yaml for a full example.
# Projector position and settings
projector:
position: [x, y, z] # 3D position in meters
look_at: [x, y, z] # Point to aim at
fov: 50.0 # Field of view in degrees
resolution: [1024, 768] # Pattern resolution
# Camera position and settings
camera:
position: [x, y, z] # 3D position in meters
look_at: [x, y, z] # Point to aim at
fov: 60.0 # Field of view in degrees
resolution: [640, 480] # Image resolution
# Scene configuration
scene:
ground_plane:
enabled: true
size: 3.0 # Size in meters
height: -0.5 # Z-coordinate
color: [200, 200, 200] # RGB color
# Manual box placement with precise control
boxes:
- size: [0.25, 0.25, 0.25] # [width, depth, height]
position: [0.1, 0.1, 0.125] # [x, y, z] center
orientation: [0.0, 0.0, 45.0] # [roll, pitch, yaw] degrees
color: [150, 100, 100] # RGB or null for random
# Random box generation (optional)
random_boxes:
enabled: false
count: 5
size_range:
min: [0.10, 0.10, 0.10]
max: [0.30, 0.30, 0.30]
position_bounds:
x: [-0.5, 0.5]
y: [-0.5, 0.5]
z: [0.0, 0.4]
orientation_range:
roll: [0.0, 360.0]
pitch: [0.0, 360.0]
yaw: [0.0, 360.0]
# Pattern types to generate
patterns:
- type: "vertical_stripes"
frequency: 15.0
pattern_type: "sinusoidal" # or "binary"
- type: "dots"
dot_spacing: 40
dot_size: 4
# Rendering parameters
rendering:
ambient_light: 0.3
pattern_intensity: 0.6
# Output settings
output:
directory: "output"
prefix: "structured_light_3d"
save_individual_patterns: true
save_comparison: true- Projector & Camera: Full 3D positioning with look-at targets
- Box Placement: Precise control over position and orientation (roll, pitch, yaw)
- Euler Angles: Intuitive rotation specification in degrees
- Random Generation: Optional random box placement with configurable ranges
- Pattern Control: Configure which patterns to generate and their parameters
- Output Control: Customize output directory, filename prefix, and what to save
The script generates a 3×3 visualization grid showing:
- Vertical sinusoidal stripes
- Horizontal sinusoidal stripes
- Binary stripes
- IR dot pattern (Kinect-style)
- Deformed grid pattern
- Noisy pattern with missing regions
- Input depth map
- RGB + stripe pattern overlay
- RGB + dot pattern overlay
The script automatically detects whether a display is available:
- With Display (Mac, Linux desktop with X11): Opens interactive matplotlib window AND saves file
- Without Display (headless servers, SSH, WSL): Saves timestamped PNG to
output/folder - All output files are saved with timestamps like:
structured_light_patterns_20251022_083755.png
- Data Augmentation: Expand training datasets for depth-sensing neural networks
- Sim-to-Real Transfer: Train models on synthetic structured light before deploying to real sensors
- Algorithm Testing: Test structured light processing algorithms without hardware
- Educational: Understand how different structured light techniques work
Adjust pattern parameters to match your specific needs:
# Denser stripes
pattern = generator.create_stripe_pattern(depth_map, frequency=40.0)
# Larger dots
dots = generator.create_dot_pattern(depth_map, dot_spacing=50, dot_size=5)
# More realistic noise
noisy = generator.add_realistic_noise(pattern, noise_level=0.1, missing_regions=True)
# Stronger overlay
blended = generator.overlay_on_rgb(rgb, pattern, blend_factor=0.5)- Load your actual depth maps and RGB images
- Experiment with different pattern parameters
- Save patterns as additional training channels
- Integrate into your data pipeline
MIT License - feel free to use in your projects!
