a lot of progress
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 149 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
@@ -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, [])
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user