Files
orisys-sdk/patch_generation/patchgen_DD02C.py
T
2026-04-28 21:07:23 +08:00

217 lines
7.5 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import numpy as np
import cv2
from PIL import Image, ImageDraw
# ----------------------------------------------------------------------
# Utility functions from the original flat pattern generator
# (unchanged, except we now work with 0‑1 floats)
# ----------------------------------------------------------------------
def merge_intervals(intervals):
if not intervals:
return []
intervals.sort(key=lambda x: x[0])
merged = [intervals[0]]
for current in intervals[1:]:
last = merged[-1]
if current[0] <= last[1]:
merged[-1] = (last[0], max(last[1], current[1]))
else:
merged.append(current)
return merged
def get_allowed_intervals(neighbor_values, delta):
forbidden = []
for c in neighbor_values:
low = max(0, c - delta)
high = min(1, c + delta)
forbidden.append((low, high))
merged = merge_intervals(forbidden)
allowed = []
if merged:
if merged[0][0] > 0:
allowed.append((0, merged[0][0]))
for i in range(1, len(merged)):
allowed.append((merged[i-1][1], merged[i][0]))
if merged[-1][1] < 1:
allowed.append((merged[-1][1], 1))
else:
allowed.append((0, 1))
return allowed
def sample_from_allowed(allowed):
lengths = [end - start for start, end in allowed]
total_length = sum(lengths)
if total_length <= 0:
return None
point = np.random.rand() * total_length
cumulative = 0
for (start, end), length in zip(allowed, lengths):
cumulative += length
if point <= cumulative:
return np.random.uniform(start, end)
return allowed[-1][0]
def sample_channel(neighbor_values, delta):
allowed = get_allowed_intervals(neighbor_values, delta)
if not allowed:
return None
return sample_from_allowed(allowed)
def generate_valid_color(neighbors, r):
delta = np.sqrt(r)
color = np.zeros(3)
for channel in range(3):
neighbor_channel = [n[channel] for n in neighbors]
sampled = sample_channel(neighbor_channel, delta)
if sampled is None:
sampled = np.random.rand()
color[channel] = sampled
return color
# ----------------------------------------------------------------------
# Cylinder‑specific generator
# ----------------------------------------------------------------------
def generate_color_pattern_cylinder(
R=9.0, # cylinder radius (mm)
H=20.0, # cylinder height (mm)
fov_deg=150.0, # vertical FOV (degrees)
pixels_per_mm=100, # texture resolution
n_azimuth=36, # number of patches around the circumference
n_radial=20, # number of patches vertically (visible part)
r=0.2, # color randomness regulation factor
output_path='cylinder_pattern.png'
):
"""
Generate a texture map for the inner cylinder wall.
Patches are arranged so that, from a camera at the bottom
looking upward with perspective projection, they appear
uniform in angular and radial extent.
"""
# Camera half‑angle (rad)
half_fov = np.radians(fov_deg / 2.0)
# Visible height range (z_min = lowest point seen by camera)
z_min = R / np.tan(half_fov) if half_fov < np.pi/2 else 0.0
z_max = H
# Image plane radial coordinate r' = R / z
r_top = R / z_max # smallest r' (top)
r_bottom = R / z_min # largest r' (bottom)
# Define patch grid in image space
theta_step = 2 * np.pi / n_azimuth
r_step = (r_bottom - r_top) / n_radial
# Texture dimensions
width_px = int(np.round(2 * np.pi * R * pixels_per_mm))
height_px = int(np.round(H * pixels_per_mm))
print(f"Texture size: {width_px} x {height_px} pixels")
# Store patch colors in a 2D array (rows = radial index, columns = azimuth)
patch_colors = np.zeros((n_radial, n_azimuth, 3), dtype=np.float32)
# Generate colors row by row (top to bottom)
for j in range(n_radial): # j = 0 → top, j = n_radial-1 → bottom
r_start = r_top + j * r_step
r_end = r_start + r_step
# Corresponding z values (note: r increases → z decreases)
z_start = R / r_start # larger z (higher up)
z_end = R / r_end # smaller z (lower down)
v_start = z_start / H
v_end = z_end / H
# Ensure v_start > v_end (top has larger v)
if v_start < v_end:
v_start, v_end = v_end, v_start
for i in range(n_azimuth):
theta_start = i * theta_step
theta_end = theta_start + theta_step
u_start = theta_start / (2 * np.pi)
u_end = theta_end / (2 * np.pi)
# Collect neighbor colors (above and left)
neighbors = []
# Above neighbor (previous row, same column)
if j > 0:
neighbors.append(patch_colors[j-1, i])
# Left neighbor (same row, previous column) – wrap around
left_i = (i - 1) % n_azimuth
if i > 0 or j > 0: # at least one neighbor exists
neighbors.append(patch_colors[j, left_i])
if len(neighbors) == 0:
new_color = np.random.rand(3)
else:
new_color = generate_valid_color(neighbors, r)
patch_colors[j, i] = new_color
# Render the texture image
img = Image.new('RGB', (width_px, height_px), color=(255, 255, 255))
draw = ImageDraw.Draw(img)
for j in range(n_radial):
r_start = r_top + j * r_step
r_end = r_start + r_step
z_start = R / r_start
z_end = R / r_end
# Determine v for top and bottom edges
v_top = max(z_start, z_end) / H # larger z -> higher on cylinder
v_bottom = min(z_start, z_end) / H # smaller z -> lower on cylinder
for i in range(n_azimuth):
u_start = i * theta_step / (2 * np.pi)
u_end = (i+1) * theta_step / (2 * np.pi)
# Pixel coordinates (ensure integer ordering)
x0 = int(u_start * width_px)
x1 = int(u_end * width_px)
y0 = int(v_bottom * height_px)
y1 = int(v_top * height_px)
# Sanity: x0 < x1, y0 < y1
if x0 > x1:
x0, x1 = x1, x0
if y0 > y1:
y0, y1 = y1, y0
# Avoid degenerate rectangles (patch may span seam)
if x1 <= x0:
x1 = x0 + 1
if y1 <= y0:
y1 = y0 + 1
color = tuple((patch_colors[j, i] * 255).astype(np.uint8))
draw.rectangle([x0, y0, x1, y1], fill=color)
img.save(output_path)
print(f"Saved texture to {output_path}")
# Also return as numpy array (BGR for OpenCV)
img_np = np.array(img)[:, :, ::-1] # RGB -> BGR
return img_np
# ----------------------------------------------------------------------
# Example usage
# ----------------------------------------------------------------------
if __name__ == '__main__':
# Generate pattern with 36 azimuth patches and 20 radial patches
patch_size = 12
R = 9.0
H = 20.0
fov_deg = 150.0
pixels_per_mm = 100
n_azimuth = int(2*np.pi*R*pixels_per_mm / patch_size)
n_radial = int(H*pixels_per_mm / patch_size)
r = 0.05
pattern = generate_color_pattern_cylinder(
R=9.0, H=20.0, fov_deg=150.0, pixels_per_mm=100,
n_azimuth=n_azimuth, n_radial=n_radial, r=0.05
)
# Display with OpenCV
cv2.imshow('Cylinder Color Pattern', pattern)
cv2.waitKey(0)
cv2.destroyAllWindows()