加入摄像机网格笔画方向优化实验
用邻域差异筛选方向并保留原随机模式。已通过单元测试、纹理重现检查和多分辨率光流探针。
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
"""Choose stroke angles that make nearby camera-grid neighborhoods distinct."""
|
||||
|
||||
import math
|
||||
import random
|
||||
|
||||
import numpy as np
|
||||
|
||||
from camera_grid_stroke_geometry import stroke_angle
|
||||
|
||||
|
||||
ANGLE_COUNT = 12
|
||||
OFFSETS = ((1, 0), (0, 1), (1, 1), (1, -1),
|
||||
(2, 0), (0, 2), (2, 2), (2, -2))
|
||||
|
||||
|
||||
def _cell_template(angle, width_ratio, length_ratio, resolution):
|
||||
sample = 4
|
||||
side = resolution * sample
|
||||
axis = (np.arange(side, dtype=np.float64) + 0.5) / side - 0.5
|
||||
x, y = np.meshgrid(axis, axis)
|
||||
along = x * math.cos(angle) + y * math.sin(angle)
|
||||
across = -x * math.sin(angle) + y * math.cos(angle)
|
||||
half_body = (length_ratio - width_ratio) / 2
|
||||
outside = np.maximum(np.abs(along) - half_body, 0)
|
||||
binary = (outside * outside + across * across <= (width_ratio / 2) ** 2)
|
||||
return binary.reshape(resolution, sample, resolution, sample).mean(axis=(1, 3))
|
||||
|
||||
|
||||
def _angle_distances(width_ratio, length_ratio):
|
||||
angles = np.arange(ANGLE_COUNT) * math.pi / ANGLE_COUNT
|
||||
distances = np.zeros((ANGLE_COUNT, ANGLE_COUNT), dtype=np.float64)
|
||||
for resolution in (4, 8):
|
||||
templates = np.stack([_cell_template(a, width_ratio, length_ratio, resolution)
|
||||
for a in angles])
|
||||
diff = templates[:, None] - templates[None, :]
|
||||
distances += np.mean(diff * diff, axis=(2, 3)) / 2
|
||||
peak = distances.max()
|
||||
if peak > 0:
|
||||
distances /= peak
|
||||
return angles, distances
|
||||
|
||||
|
||||
def _neighborhood_pairs(nx, ny):
|
||||
windows_a = []
|
||||
windows_b = []
|
||||
incident = [[] for _ in range(nx * ny)]
|
||||
for y in range(1, ny - 1):
|
||||
for x in range(1, nx - 1):
|
||||
for dx, dy in OFFSETS:
|
||||
xx, yy = x + dx, y + dy
|
||||
if not (1 <= xx < nx - 1 and 1 <= yy < ny - 1):
|
||||
continue
|
||||
a = [(y + v) * nx + x + u for v in (-1, 0, 1)
|
||||
for u in (-1, 0, 1)]
|
||||
b = [(yy + v) * nx + xx + u for v in (-1, 0, 1)
|
||||
for u in (-1, 0, 1)]
|
||||
index = len(windows_a)
|
||||
windows_a.append(a)
|
||||
windows_b.append(b)
|
||||
for cell in set(a + b):
|
||||
incident[cell].append(index)
|
||||
return (np.asarray(windows_a, dtype=np.int32),
|
||||
np.asarray(windows_b, dtype=np.int32),
|
||||
[np.asarray(indices, dtype=np.int32) for indices in incident])
|
||||
|
||||
|
||||
def _pair_distances(assignments, distances, windows_a, windows_b):
|
||||
return distances[assignments[windows_a], assignments[windows_b]].mean(axis=1)
|
||||
|
||||
|
||||
def optimize_angles(nx, ny, seed, width_ratio=0.42, length_ratio=0.84,
|
||||
proposals=100000):
|
||||
"""Optimize 3x3 neighborhood separation within a two-cell search radius.
|
||||
|
||||
The 4/8-pixel camera templates are a cheap screening model. The rendered
|
||||
optical-flow probe remains the acceptance test for a candidate pattern.
|
||||
"""
|
||||
if nx < 5 or ny < 5 or proposals < 0:
|
||||
raise ValueError("require at least 5x5 cells and nonnegative proposals")
|
||||
angles, distances = _angle_distances(width_ratio, length_ratio)
|
||||
wa, wb, incident = _neighborhood_pairs(nx, ny)
|
||||
assignments = np.asarray([
|
||||
int(round(stroke_angle(x, y, seed) * ANGLE_COUNT / math.pi)) % ANGLE_COUNT
|
||||
for y in range(ny) for x in range(nx)
|
||||
], dtype=np.int16)
|
||||
initial = _pair_distances(assignments, distances, wa, wb)
|
||||
pair_loss = np.exp(-6 * initial)
|
||||
rng = random.Random(seed ^ 0x71C3A5)
|
||||
accepted = 0
|
||||
for _ in range(proposals):
|
||||
cell = rng.randrange(nx * ny)
|
||||
affected = incident[cell]
|
||||
if not len(affected):
|
||||
continue
|
||||
previous = assignments[cell]
|
||||
candidate = rng.randrange(ANGLE_COUNT - 1)
|
||||
if candidate >= previous:
|
||||
candidate += 1
|
||||
assignments[cell] = candidate
|
||||
trial = _pair_distances(assignments, distances, wa[affected], wb[affected])
|
||||
trial_loss = np.exp(-6 * trial)
|
||||
if trial_loss.sum() < pair_loss[affected].sum() - 1e-12:
|
||||
pair_loss[affected] = trial_loss
|
||||
accepted += 1
|
||||
else:
|
||||
assignments[cell] = previous
|
||||
final = _pair_distances(assignments, distances, wa, wb)
|
||||
report = {
|
||||
"pairs": len(initial),
|
||||
"proposals": proposals,
|
||||
"accepted": accepted,
|
||||
"initial_distance_p10": float(np.percentile(initial, 10)),
|
||||
"final_distance_p10": float(np.percentile(final, 10)),
|
||||
"initial_mean_loss": float(np.exp(-6 * initial).mean()),
|
||||
"final_mean_loss": float(pair_loss.mean()),
|
||||
}
|
||||
return angles[assignments].reshape(ny, nx), report
|
||||
Reference in New Issue
Block a user