pipeline/axo : extraction du Rhino, recalage sur la planche (1:640), maisons, arbres (positions réelles des blocs, couleurs de la planche), scène en mètres, sols depuis le PDF Illustrator vectoriel (calques pilotés), bulles de la page 15, planche de l'eau nettoyée. prototypes/axo-test : la page de test et son assembleur. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
56 lines
2.6 KiB
Python
56 lines
2.6 KiB
Python
import json, numpy as np
|
|
from PIL import Image, ImageDraw
|
|
R = json.load(open("rhino.json"))
|
|
img = np.asarray(Image.open("base100-1.png").convert("RGB")).astype(int)
|
|
H, W, _ = img.shape
|
|
r, g, b = img[..., 0], img[..., 1], img[..., 2]
|
|
bleu = ((b - r) > 30) & (b > 150) # eau
|
|
vert = ((g - r) > 6) & ((g - b) > 25) & (g > 170) # limites de parcelles (vert pâle)
|
|
cible = (bleu * 2.0 + vert * 1.0).astype(np.float32)
|
|
print("pixels bleus", bleu.sum(), "verts", vert.sum())
|
|
eau = [c for c in R["courbes"] if c["l"] == "B_EAU noues" and c["f"]]
|
|
parc = [c for c in R["courbes"] if c["l"] == "PARCELLES"]
|
|
allp = np.array([p for c in parc for p in c["p"]])
|
|
x0, y1 = allp[:, 0].min(), allp[:, 1].max()
|
|
def raster(s, shape):
|
|
im = Image.new("F", (shape[1], shape[0]), 0); d = ImageDraw.Draw(im)
|
|
for c in eau:
|
|
d.polygon([((x - x0) * s, (y1 - y) * s) for x, y in c["p"]], fill=2.0)
|
|
for c in parc:
|
|
d.line([((x - x0) * s, (y1 - y) * s) for x, y in c["p"]], fill=1.0, width=max(1, int(s * 1.2)))
|
|
return np.asarray(im, dtype=np.float32)
|
|
def correl(a, bimg):
|
|
P = (2 * H, 2 * W)
|
|
Fa = np.fft.rfft2(a, P); Fb = np.fft.rfft2(bimg, P)
|
|
c = np.fft.irfft2(Fa * np.conj(Fb), P)
|
|
k = np.unravel_index(np.argmax(c), c.shape)
|
|
return c[k], k
|
|
best = None
|
|
for s in np.arange(2.0, 9.0, 0.05):
|
|
rs = raster(s, (H, W))
|
|
v, k = correl(cible, rs)
|
|
v = v / (np.sqrt((rs ** 2).sum()) + 1e-6)
|
|
if best is None or v > best[0]: best = (v, s, k)
|
|
v, s, k = best
|
|
dy = k[0] if k[0] < H else k[0] - 2 * H
|
|
dx = k[1] if k[1] < W else k[1] - 2 * W
|
|
print("grossier : s=%.3f px/m dx=%d dy=%d score=%.1f" % (s, dx, dy, v))
|
|
# affinage
|
|
best2 = None
|
|
for s2 in np.arange(s - 0.06, s + 0.06, 0.005):
|
|
rs = raster(s2, (H, W)); v2, k2 = correl(cible, rs); v2 /= (np.sqrt((rs ** 2).sum()) + 1e-6)
|
|
if best2 is None or v2 > best2[0]: best2 = (v2, s2, k2)
|
|
v, s, k = best2
|
|
dy = k[0] if k[0] < H else k[0] - 2 * H
|
|
dx = k[1] if k[1] < W else k[1] - 2 * W
|
|
print("fin : s=%.4f px/m à 100 dpi dx=%d dy=%d" % (s, dx, dy))
|
|
# transformation mètres -> pixels (100 dpi) : px = (x - x0)*s + dx ; py = (y1 - y)*s + dy
|
|
T = {"dpi": 100, "s": float(s), "x0": float(x0), "y1": float(y1), "dx": int(dx), "dy": int(dy)}
|
|
json.dump(T, open("transfo.json", "w"))
|
|
# contrôle visuel : géométrie Rhino en rouge par-dessus le dessin
|
|
ov = Image.open("base100-1.png").convert("RGB"); d = ImageDraw.Draw(ov)
|
|
f = lambda x, y: ((x - x0) * s + dx, (y1 - y) * s + dy)
|
|
for c in parc: d.line([f(*p) for p in c["p"]], fill=(220, 30, 30), width=1)
|
|
for c in eau: d.line([f(*p) for p in c["p"]] + [f(*c["p"][0])], fill=(200, 0, 200), width=2)
|
|
ov.save("controle-recalage.png")
|