a lot of progress

This commit is contained in:
2026-08-11 19:02:47 +02:00
parent 7bdaac855e
commit 62b309adf3
7 changed files with 178 additions and 9 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
import importlib
importlib.invalidate_caches()
import transition
print('has transform_images:', hasattr(transition, 'transform_images'))
print('transform_images object:', getattr(transition, 'transform_images', None))
+17 -9
View File
@@ -6,6 +6,7 @@ import matplotlib.pylab as plt
from matplotlib.widgets import Button from matplotlib.widgets import Button
from image_preparing import fix_image_resolution from image_preparing import fix_image_resolution
import transition
class ImageInterface: class ImageInterface:
@@ -96,15 +97,22 @@ class ImageInterface:
self.current_index = 0 self.current_index = 0
self._show_browser() self._show_browser()
def _open_shenanigans(self): def _open_shenanigans(self):
image_paths = self.image_sets.get("source", []) src_paths = self.image_sets.get("source", [])
selected_index = self.selected_indices.get("source") goal_paths = self.image_sets.get("goal", [])
if selected_index is None: if not src_paths or not goal_paths:
selected_index = self.current_index if self.current_category == "source" else 0 return
selected_path = None sel_src = self.selected_indices.get("source")
if image_paths: sel_goal = self.selected_indices.get("goal")
safe_index = max(0, min(selected_index, len(image_paths) - 1)) src_path = src_paths[sel_src] if sel_src is not None else src_paths[0]
selected_path = image_paths[safe_index] goal_path = goal_paths[sel_goal] if sel_goal is not None else goal_paths[0]
fix_image_resolution(selected_path) try:
import importlib
importlib.reload(transition)
except Exception:
pass
if not hasattr(transition, "transform_images"):
return
transition.transform_images(src_path, goal_path)
def _selected_image_name(self, category): def _selected_image_name(self, category):
selected_index = self.selected_indices.get(category) selected_index = self.selected_indices.get(category)
image_paths = self.image_sets.get(category, []) image_paths = self.image_sets.get(category, [])
+156
View File
@@ -0,0 +1,156 @@
from pathlib import Path
import numpy as np
from PIL import Image, ImageDraw
import matplotlib.pyplot as plt
from matplotlib.tri import Triangulation
from image_preparing import fix_image_resolution
def prepare_image(path):
pil = Image.open(path).convert("RGB").resize((800, 600))
return pil
def auto_correspondences_grid(pil, nx=8, ny=6):
w, h = pil.size
xs = np.linspace(0, w - 1, nx)
ys = np.linspace(0, h - 1, ny)
pts = []
for y in ys:
for x in xs:
pts.append([float(x), float(y)])
corners = [[0.0, 0.0], [w - 1.0, 0.0], [w - 1.0, h - 1.0], [0.0, h - 1.0]]
for c in corners:
if c not in pts:
pts.insert(0, c)
return np.array(pts, dtype=float), np.array(pts, dtype=float).copy()
def compute_delaunay_on_points(pts):
pts = np.asarray(pts)
tri = Triangulation(pts[:, 0], pts[:, 1])
return tri.triangles
def _affine_coeffs(from_tri, to_tri):
A = []
B = []
for (x_dst, y_dst), (x_src, y_src) in zip(from_tri, to_tri):
A.append([x_dst, y_dst, 1, 0, 0, 0])
A.append([0, 0, 0, x_dst, y_dst, 1])
B.append(x_src)
B.append(y_src)
sol, *_ = np.linalg.lstsq(np.array(A), np.array(B), rcond=None)
return tuple(sol.tolist())
def _warp_triangle(src_pil, src_tri, dst_tri, out_size, offset):
dx, dy = offset
dst_local = np.array(dst_tri) - np.array([dx, dy])
coeffs = _affine_coeffs(dst_local, src_tri)
return src_pil.transform(out_size, Image.AFFINE, coeffs, resample=Image.BILINEAR)
def reconstruct_goal_from_source(src_pil, pts1, pts2):
w, h = src_pil.size
pts1 = np.asarray(pts1)
pts2 = np.asarray(pts2)
triangles = compute_delaunay_on_points(pts2)
out = Image.new("RGB", (w, h))
for tri in triangles:
tri = np.asarray(tri, dtype=int)
src_tri = pts1[tri]
dst_tri = pts2[tri]
min_x = max(int(np.floor(dst_tri[:, 0].min())), 0)
max_x = min(int(np.ceil(dst_tri[:, 0].max())), w)
min_y = max(int(np.floor(dst_tri[:, 1].min())), 0)
max_y = min(int(np.ceil(dst_tri[:, 1].max())), h)
if max_x <= min_x or max_y <= min_y:
continue
out_w = max_x - min_x
out_h = max_y - min_y
warped = _warp_triangle(src_pil, src_tri, dst_tri, (out_w, out_h), (min_x, min_y))
mask = Image.new("L", (out_w, out_h), 0)
draw = ImageDraw.Draw(mask)
tri_local = [(x - min_x, y - min_y) for x, y in dst_tri.tolist()]
draw.polygon(tri_local, fill=255)
out.paste(warped, (min_x, min_y), mask)
return out
def transform_images(src_path, goal_path):
fix_image_resolution(src_path)
fix_image_resolution(goal_path)
src = prepare_image(src_path)
goal = prepare_image(goal_path)
frames = tile_reposition_transition(src, goal, grid=(40, 30), n_frames=24)
fig, ax = plt.subplots(figsize=(8, 6))
im = ax.imshow(frames[0])
ax.axis("off")
for frame in frames:
im.set_data(frame)
fig.canvas.draw_idle()
plt.pause(0.05)
plt.show()
def tile_reposition_transition(src_pil, goal_pil, grid=(40, 30), n_frames=24):
src = src_pil.convert("RGBA")
goal = goal_pil.convert("RGBA")
w, h = src.size
nx, ny = grid
def make_tiles(pil):
tiles = []
for iy in range(ny):
y0 = int(round(iy * h / ny))
y1 = int(round((iy + 1) * h / ny))
for ix in range(nx):
x0 = int(round(ix * w / nx))
x1 = int(round((ix + 1) * w / nx))
box = (x0, y0, x1, y1)
crop = pil.crop(box).convert("RGB")
arr = np.array(crop, dtype=float)
avg = arr.reshape(-1, 3).mean(axis=0)
center = ((x0 + x1) / 2.0, (y0 + y1) / 2.0)
tiles.append({"box": box, "img": crop, "avg": avg, "center": center})
return tiles
src_tiles = make_tiles(src)
goal_tiles = make_tiles(goal)
max_spatial = np.hypot(w, h)
src_available = set(range(len(src_tiles)))
mapping = {}
for g_idx, g in enumerate(goal_tiles):
best = None
best_cost = None
for s_idx in list(src_available):
s = src_tiles[s_idx]
color_dist = np.linalg.norm(g["avg"] - s["avg"]) / (255.0 * np.sqrt(3))
spatial_dist = np.linalg.norm(np.array(g["center"]) - np.array(s["center"])) / max_spatial
cost = color_dist + 0.35 * spatial_dist
if best_cost is None or cost < best_cost:
best_cost = cost
best = s_idx
if best is None:
best = src_available.pop()
else:
src_available.remove(best)
mapping[best] = g_idx
targets = {}
for s_idx, g_idx in mapping.items():
s = src_tiles[s_idx]
g = goal_tiles[g_idx]
targets[s_idx] = {"start": (s["box"][0], s["box"][1]), "end": (g["box"][0], g["box"][1]), "img": s["img"], "size": (s["box"][2]-s["box"][0], s["box"][3]-s["box"][1])}
frames = []
for k in range(n_frames + 1):
t = k / float(n_frames)
frame = Image.new("RGBA", (w, h), (0, 0, 0, 255))
for s_idx, info in targets.items():
sx, sy = info["start"]
ex, ey = info["end"]
cx = int(round(sx + (ex - sx) * t))
cy = int(round(sy + (ey - sy) * t))
frame.paste(info["img"], (cx, cy))
frames.append(frame.convert("RGB"))
final = Image.new("RGB", (w, h))
for s_idx, g_idx in mapping.items():
src_img = src_tiles[s_idx]["img"].convert("RGB")
x0, y0, x1, y1 = goal_tiles[g_idx]["box"]
final.paste(src_img, (x0, y0))
frames[-1] = final
return frames