Pixio patch similarity: 応用と分析(パッチ類似度ヒートマップ)
はじめに
こんにちは、しゅんです。
この記事は前編(公式の使い方)で得た特徴を使って、
「パッチ類似度の可視化と分析」 をやった記録です。
前編:
今回の記事のゴール
- 参照パッチに似ている領域をヒートマップで可視化
-
npz/json/csvを出して分析に使える形にするけど、長くなるので詳細データ貼らない - 複数参照点や集計のための補助スクリプトも用意する
作ったコード
-
pixio_patch_similarity.py
単一参照点のヒートマップ生成+npz/json/csv出力 -
pixio_patch_similarity_sweep.py
複数参照点を一括で可視化・集計 -
pixio_patch_similarity_analyze.py
既存出力から統計値をまとめる
import csv
import json
import requests
import torch
import numpy as np
from pathlib import Path
from PIL import Image, ImageDraw, ImageOps
from transformers import AutoImageProcessor, AutoModel
url = "http://images.cocodataset.org/val2017/000000039769.jpg"
# 画像をダウンロードしてRGBで読み込む
image = Image.open(requests.get(url, stream=True).raw).convert("RGB")
# 可視化の調整パラメータ
heatmap_alpha = 0.55 # 元画像との合成比率(上げると色が濃くなる)
clip_percentile = (0.05, 0.95) # 外れ値を抑えてコントラストを上げる
gamma = 0.6 # 1より小さいと明るい部分を強調
heatmap_colors = ((0, 32, 80), (255, 140, 0)) # 暗部→明部の色
# データ出力の設定(分析用)
save_data = True
output_prefix = "pixio_patch_similarity" # 出力ファイル名の共通プレフィックス
top_k = 20 # 類似度上位のパッチをCSVで出力
model_id = "facebook/pixio-vitl16"
# 画像前処理器とモデルをHugging Faceからロード
processor = AutoImageProcessor.from_pretrained(model_id, trust_remote_code=True)
model = AutoModel.from_pretrained(model_id, trust_remote_code=True)
model.eval()
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
# 途中層の特徴も取り出す
outputs = model(**inputs, output_hidden_states=True)
last_hidden_states_norm = outputs.last_hidden_state # LayerNorm後の [class tokens + patch tokens]
last_hidden_states = outputs.hidden_states[-1] # LayerNorm前の [class tokens + patch tokens]
# --- 新しい応用: パッチ類似度ヒートマップ(自己位置合わせ) ---
# 入力の縦横サイズを取得
pixel_values = inputs["pixel_values"]
_, _, input_h, input_w = pixel_values.shape
# モデルのパッチサイズ(通常16)を取得
patch_size = getattr(model.config, "patch_size", 16)
if isinstance(patch_size, (list, tuple)):
patch_h, patch_w = patch_size
else:
patch_h = patch_w = patch_size
# クラストークン数 = 全トークン数 - パッチトークン数
num_patches = (input_h // patch_h) * (input_w // patch_w)
num_tokens = last_hidden_states_norm.shape[1]
num_class_tokens = num_tokens - num_patches
if num_class_tokens < 0:
raise ValueError("Unexpected token layout: patch tokens exceed total tokens.")
# パッチトークンだけを取り出して、類似度計算のため正規化
patch_tokens = last_hidden_states_norm[:, num_class_tokens:, :]
patch_tokens = torch.nn.functional.normalize(patch_tokens, dim=-1)
grid_h = input_h // patch_h
grid_w = input_w // patch_w
patch_tokens = patch_tokens.reshape(1, grid_h, grid_w, -1)
# 参照したい位置(中心)を指定。ref_pixelを変えると他の領域を追跡できる
ref_pixel = (input_w // 2, input_h // 2)
ref_col = min(ref_pixel[0] // patch_w, grid_w - 1)
ref_row = min(ref_pixel[1] // patch_h, grid_h - 1)
ref_feat = patch_tokens[0, ref_row, ref_col, :]
# 参照パッチと各パッチのコサイン類似度を計算(パッチ解像度)
patch_sim = (patch_tokens * ref_feat).sum(dim=-1).squeeze(0) # [grid_h, grid_w]
sim_map = patch_sim.unsqueeze(0).unsqueeze(0) # [1, 1, grid_h, grid_w]
sim_map = torch.nn.functional.interpolate(
sim_map,
size=(input_h, input_w),
mode="bilinear",
align_corners=False,
).squeeze(0).squeeze(0)
# 0-1に正規化して可視化用マスクを作る(外れ値をクリップ)
low_q, high_q = clip_percentile
sim_low = torch.quantile(sim_map, low_q)
sim_high = torch.quantile(sim_map, high_q)
if (sim_high - sim_low) < 1e-6:
sim_low = sim_map.min()
sim_high = sim_map.max()
sim_clipped = sim_map.clamp(sim_low, sim_high)
sim_norm = (sim_clipped - sim_low) / (sim_high - sim_low + 1e-6)
sim_norm = sim_norm.pow(gamma)
# ヒートマップを作って元画像に重ねる
base = image.resize((input_w, input_h), resample=Image.BICUBIC)
mask = Image.fromarray((sim_norm.cpu().numpy() * 255).astype("uint8"), mode="L")
heatmap = ImageOps.colorize(mask, black=heatmap_colors[0], white=heatmap_colors[1])
overlay = Image.blend(base, heatmap, alpha=heatmap_alpha)
# 参照パッチ位置を円でマーキング
draw = ImageDraw.Draw(overlay)
cx = int(ref_col * patch_w + patch_w / 2)
cy = int(ref_row * patch_h + patch_h / 2)
radius = max(patch_h, patch_w) // 4
draw.ellipse((cx - radius, cy - radius, cx + radius, cy + radius), outline=(0, 255, 255), width=2)
# 結果を保存
overlay_path = f"{output_prefix}.png"
overlay.save(overlay_path)
print(f"Saved patch similarity heatmap to {overlay_path}")
# 分析用データを保存(Numpy配列 + メタ情報 + 上位パッチ一覧)
if save_data:
prefix = Path(output_prefix)
npz_path = prefix.with_suffix(".npz")
json_path = prefix.with_suffix(".json")
csv_path = prefix.with_name(f"{prefix.name}_topk.csv")
np.savez_compressed(
npz_path,
patch_sim=patch_sim.cpu().numpy().astype("float32"),
sim_map=sim_map.cpu().numpy().astype("float32"),
sim_norm=sim_norm.cpu().numpy().astype("float32"),
)
meta = {
"model_id": model_id,
"input_h": int(input_h),
"input_w": int(input_w),
"patch_h": int(patch_h),
"patch_w": int(patch_w),
"grid_h": int(grid_h),
"grid_w": int(grid_w),
"ref_pixel": [int(ref_pixel[0]), int(ref_pixel[1])],
"ref_row": int(ref_row),
"ref_col": int(ref_col),
"clip_percentile": [float(low_q), float(high_q)],
"gamma": float(gamma),
}
json_path.write_text(json.dumps(meta, indent=2), encoding="utf-8")
flat = patch_sim.flatten()
k = min(top_k, flat.numel())
top_vals, top_idx = torch.topk(flat, k)
with csv_path.open("w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["rank", "row", "col", "pixel_x", "pixel_y", "score"])
for rank, (val, idx) in enumerate(zip(top_vals.tolist(), top_idx.tolist()), start=1):
row = idx // grid_w
col = idx % grid_w
pixel_x = int(col * patch_w + patch_w / 2)
pixel_y = int(row * patch_h + patch_h / 2)
writer.writerow([rank, row, col, pixel_x, pixel_y, float(val)])
print(f"Saved data to {npz_path}, {json_path}, {csv_path}")
import csv
import json
from pathlib import Path
import numpy as np
import requests
import torch
from PIL import Image, ImageDraw, ImageOps
from transformers import AutoImageProcessor, AutoModel
url = "http://images.cocodataset.org/val2017/000000039769.jpg"
image = Image.open(requests.get(url, stream=True).raw).convert("RGB")
# Visualization params
heatmap_alpha = 0.55
clip_percentile = (0.05, 0.95)
gamma = 0.6
heatmap_colors = ((0, 32, 80), (255, 140, 0))
# Sweep params
ref_mode = "relative" # "relative" (0-1) or "pixel"
ref_points = [
(0.25, 0.25),
(0.5, 0.25),
(0.75, 0.25),
(0.25, 0.5),
(0.5, 0.5),
(0.75, 0.5),
(0.25, 0.75),
(0.5, 0.75),
(0.75, 0.75),
]
top_k = 10
save_npz = True
output_dir = Path("pixio_patch_similarity_sweep")
model_id = "facebook/pixio-vitl16"
processor = AutoImageProcessor.from_pretrained(model_id, trust_remote_code=True)
model = AutoModel.from_pretrained(model_id, trust_remote_code=True)
model.eval()
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs, output_hidden_states=True)
pixel_values = inputs["pixel_values"]
_, _, input_h, input_w = pixel_values.shape
patch_size = getattr(model.config, "patch_size", 16)
if isinstance(patch_size, (list, tuple)):
patch_h, patch_w = patch_size
else:
patch_h = patch_w = patch_size
num_patches = (input_h // patch_h) * (input_w // patch_w)
num_tokens = outputs.last_hidden_state.shape[1]
num_class_tokens = num_tokens - num_patches
if num_class_tokens < 0:
raise ValueError("Unexpected token layout: patch tokens exceed total tokens.")
patch_tokens = outputs.last_hidden_state[:, num_class_tokens:, :]
patch_tokens = torch.nn.functional.normalize(patch_tokens, dim=-1)
grid_h = input_h // patch_h
grid_w = input_w // patch_w
patch_tokens = patch_tokens.reshape(1, grid_h, grid_w, -1)
output_dir.mkdir(parents=True, exist_ok=True)
base = image.resize((input_w, input_h), resample=Image.BICUBIC)
def to_pixel(ref):
if ref_mode == "relative":
rx, ry = ref
px = int(round(rx * (input_w - 1)))
py = int(round(ry * (input_h - 1)))
else:
px, py = ref
px = max(0, min(px, input_w - 1))
py = max(0, min(py, input_h - 1))
return px, py
def normalize_map(sim_map):
low_q, high_q = clip_percentile
sim_low = torch.quantile(sim_map, low_q)
sim_high = torch.quantile(sim_map, high_q)
if (sim_high - sim_low) < 1e-6:
sim_low = sim_map.min()
sim_high = sim_map.max()
sim_clipped = sim_map.clamp(sim_low, sim_high)
sim_norm = (sim_clipped - sim_low) / (sim_high - sim_low + 1e-6)
sim_norm = sim_norm.pow(gamma)
return sim_norm, float(sim_low), float(sim_high)
summary_rows = []
topk_rows = []
for idx, ref in enumerate(ref_points):
ref_x, ref_y = to_pixel(ref)
ref_col = min(ref_x // patch_w, grid_w - 1)
ref_row = min(ref_y // patch_h, grid_h - 1)
ref_feat = patch_tokens[0, ref_row, ref_col, :]
patch_sim = (patch_tokens * ref_feat).sum(dim=-1).squeeze(0)
sim_map = torch.nn.functional.interpolate(
patch_sim.unsqueeze(0).unsqueeze(0),
size=(input_h, input_w),
mode="bilinear",
align_corners=False,
).squeeze(0).squeeze(0)
sim_norm, sim_low, sim_high = normalize_map(sim_map)
mask = Image.fromarray((sim_norm.cpu().numpy() * 255).astype("uint8"), mode="L")
heatmap = ImageOps.colorize(mask, black=heatmap_colors[0], white=heatmap_colors[1])
overlay = Image.blend(base, heatmap, alpha=heatmap_alpha)
draw = ImageDraw.Draw(overlay)
cx = int(ref_col * patch_w + patch_w / 2)
cy = int(ref_row * patch_h + patch_h / 2)
radius = max(patch_h, patch_w) // 4
draw.ellipse((cx - radius, cy - radius, cx + radius, cy + radius), outline=(0, 255, 255), width=2)
ref_id = f"{idx:02d}_r{ref_row}_c{ref_col}"
overlay_path = output_dir / f"heatmap_{ref_id}.png"
overlay.save(overlay_path)
if save_npz:
np.savez_compressed(
output_dir / f"data_{ref_id}.npz",
patch_sim=patch_sim.cpu().numpy().astype("float32"),
sim_map=sim_map.cpu().numpy().astype("float32"),
sim_norm=sim_norm.cpu().numpy().astype("float32"),
)
sim_norm_mean = float(sim_norm.mean())
sim_norm_std = float(sim_norm.std())
summary_rows.append(
[
ref_id,
int(ref_x),
int(ref_y),
int(ref_row),
int(ref_col),
float(patch_sim.min()),
float(patch_sim.max()),
float(patch_sim.mean()),
float(patch_sim.std()),
sim_norm_mean,
sim_norm_std,
float((sim_norm >= 0.7).float().mean()),
float((sim_norm >= 0.8).float().mean()),
float((sim_norm >= 0.9).float().mean()),
sim_low,
sim_high,
]
)
flat = patch_sim.flatten()
k = min(top_k, flat.numel())
top_vals, top_idx = torch.topk(flat, k)
for rank, (val, idx_flat) in enumerate(zip(top_vals.tolist(), top_idx.tolist()), start=1):
row = idx_flat // grid_w
col = idx_flat % grid_w
pixel_x = int(col * patch_w + patch_w / 2)
pixel_y = int(row * patch_h + patch_h / 2)
topk_rows.append(
[
ref_id,
rank,
int(row),
int(col),
pixel_x,
pixel_y,
float(val),
]
)
summary_path = output_dir / "summary.csv"
with summary_path.open("w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(
[
"ref_id",
"ref_pixel_x",
"ref_pixel_y",
"ref_row",
"ref_col",
"patch_sim_min",
"patch_sim_max",
"patch_sim_mean",
"patch_sim_std",
"sim_norm_mean",
"sim_norm_std",
"frac_ge_0.7",
"frac_ge_0.8",
"frac_ge_0.9",
"clip_low",
"clip_high",
]
)
writer.writerows(summary_rows)
topk_path = output_dir / "topk.csv"
with topk_path.open("w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["ref_id", "rank", "row", "col", "pixel_x", "pixel_y", "score"])
writer.writerows(topk_rows)
config_path = output_dir / "config.json"
config = {
"model_id": model_id,
"image_source": url,
"input_h": int(input_h),
"input_w": int(input_w),
"patch_h": int(patch_h),
"patch_w": int(patch_w),
"grid_h": int(grid_h),
"grid_w": int(grid_w),
"ref_mode": ref_mode,
"ref_points": ref_points,
"clip_percentile": [float(clip_percentile[0]), float(clip_percentile[1])],
"gamma": float(gamma),
"heatmap_alpha": float(heatmap_alpha),
"heatmap_colors": [list(heatmap_colors[0]), list(heatmap_colors[1])],
"top_k": int(top_k),
"save_npz": bool(save_npz),
}
config_path.write_text(json.dumps(config, indent=2), encoding="utf-8")
print(f"Saved {len(ref_points)} heatmaps to {output_dir}")
print(f"Saved summary to {summary_path}")
print(f"Saved top-k list to {topk_path}")
print(f"Saved config to {config_path}")
import csv
import json
from pathlib import Path
import numpy as np
output_prefix = "pixio_patch_similarity"
save_report = False
base_dir = Path(".")
npz_path = base_dir / f"{output_prefix}.npz"
json_path = base_dir / f"{output_prefix}.json"
csv_path = base_dir / f"{output_prefix}_topk.csv"
if not npz_path.exists():
raise FileNotFoundError(f"Missing {npz_path}")
if not json_path.exists():
raise FileNotFoundError(f"Missing {json_path}")
if not csv_path.exists():
raise FileNotFoundError(f"Missing {csv_path}")
meta = json.loads(json_path.read_text(encoding="utf-8"))
npz = np.load(npz_path)
patch_sim = npz["patch_sim"]
sim_norm = npz["sim_norm"]
def stats(arr):
return {
"min": float(arr.min()),
"max": float(arr.max()),
"mean": float(arr.mean()),
"std": float(arr.std()),
}
percentiles = {p: float(np.percentile(sim_norm, p)) for p in [50, 75, 90, 95, 99]}
fractions = {
"ge_0.7": float((sim_norm >= 0.7).mean()),
"ge_0.8": float((sim_norm >= 0.8).mean()),
"ge_0.9": float((sim_norm >= 0.9).mean()),
}
top_rows = []
with csv_path.open("r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for i, row in enumerate(reader):
if i >= 10:
break
top_rows.append(row)
report = {
"meta": meta,
"patch_sim_stats": stats(patch_sim),
"sim_norm_stats": stats(sim_norm),
"sim_norm_percentiles": percentiles,
"sim_norm_fractions": fractions,
"top10": top_rows,
}
print("Meta:", meta)
print("patch_sim:", report["patch_sim_stats"])
print("sim_norm:", report["sim_norm_stats"])
print("sim_norm percentiles:", percentiles)
print("sim_norm fractions:", fractions)
print("top10:", top_rows)
if save_report:
report_path = base_dir / f"{output_prefix}_report.json"
report_path.write_text(json.dumps(report, indent=2), encoding="utf-8")
print(f"Saved report to {report_path}")
仕組み
1) 入力とトークン構成
AutoImageProcessor が画像をテンソル化し、pixel_values を作る。
pixel_values.shape = [1, 3, 256, 256]- パッチサイズは
16なので、パッチ数は16 * 16 = 256 outputs.last_hidden_state.shape = [1, 264, 1024]
264 = 256 (patch tokens) + 8 (class tokens) という構成。
以降は class token を除いて、パッチトークンのみを使う。
2) パッチ特徴の取り出しと正規化
patch_tokens = last_hidden_state[:, num_class_tokens:, :]
patch_tokens = F.normalize(patch_tokens, dim=-1)
ここでの内積は コサイン類似度 になる。
3) 参照パッチの決定
ref_pixel = (128, 128)
ref_col = ref_pixel[0] // patch_w
ref_row = ref_pixel[1] // patch_h
ref_feat = patch_tokens[0, ref_row, ref_col, :]
指定した ref_pixel が属するパッチを基準にする。
4) 類似度マップの計算
patch_sim = (patch_tokens * ref_feat).sum(dim=-1) # [16, 16]
sim_map = interpolate(patch_sim) # [256, 256]
patch_sim はパッチ解像度の類似度、sim_map は画像解像度に拡大したもの。
5) 可視化用の正規化
sim_clipped = sim_map.clamp(q05, q95)
sim_norm = (sim_clipped - q05) / (q95 - q05)
sim_norm = sim_norm.pow(gamma)
- クリップ: 外れ値で色が潰れないようにする
- ガンマ補正: 明るい領域を強調する
sim_norm は 可視化用の正規化済み値 であり、
元の類似度とは別物である点に注意。
6) ヒートマップ化と合成
mask = (sim_norm * 255).astype("uint8")
heatmap = colorize(mask)
overlay = blend(base, heatmap, alpha)
最後に参照パッチ位置をシアンの円でマーキング。
実データの分析(実測値)
以下は pixio_patch_similarity.json / npz / csv から集計した 実測値。
メタ情報(jsonより)
- 入力サイズ: 256x256
- パッチサイズ: 16x16(グリッド 16x16)
- 参照パッチ: row=8, col=8(
ref_pixel=(128,128)) - クリップ: 5%〜95%、ガンマ: 0.6
分布(npzより)
| map | min | max | mean | std |
|---|---|---|---|---|
| patch_sim | 0.512 | 1.000 | 0.799 | 0.073 |
| sim_map | 0.519 | 0.992 | 0.799 | 0.060 |
| sim_norm | 0.000 | 1.000 | 0.678 | 0.250 |
sim_norm の分位点
- p50: 0.734
- p75: 0.858
- p90: 0.947
- p95: 1.000
- p99: 1.000
sim_norm の閾値割合
-
sim_norm >= 0.7: 56.3% -
sim_norm >= 0.8: 35.9% -
sim_norm >= 0.9: 16.8%
上位パッチ例(csvより)
上位は参照パッチ自身が含まれるので、1位は必ず参照位置。
- row 8 col 8 (x=136, y=136) score 1.000
- row 10 col 13 (x=216, y=168) score 0.945
- row 11 col 11 (x=184, y=184) score 0.933
- row 8 col 4 (x=72, y=136) score 0.931
- row 6 col 8 (x=136, y=104) score 0.930
ヒートマップの説明
- 明るい色: 参照パッチと似ている領域(相対的に上位)
- 暗い色: 参照パッチと似ていない領域
- 色は sim_norm(クリップ+ガンマ後) を可視化している
このため、ヒートマップの明暗は「相対評価」であり、
別画像・別パラメータ間では直接比較しないのが安全。
この画像での見え方
- 参照点は画像中央付近(右側の猫の胴体あたり)
- 高類似度は参照点周辺の毛並み・同系色の布地に広がる
- 質感が異なる領域は暗くなる
コードの解説
pixio_patch_similarity.py
-
入力とモデル
AutoImageProcessorで前処理し、AutoModelで特徴を取得。 -
トークン抽出
class token を除外し、パッチトークンのみを使用。 -
参照パッチ
ref_pixelをパッチ格子に変換して基準特徴を取得。 -
類似度計算
正規化済み特徴の内積でコサイン類似度を計算。 -
可視化
クリップ+ガンマ補正→色付け→元画像に合成。 -
データ保存
-
npz:patch_sim/sim_map/sim_norm -
json: 入力サイズ、パッチサイズ、参照位置など -
csv: 上位パッチの座標とスコア
-
pixio_patch_similarity_sweep.py
-
ref_pointsを複数設定してループ実行 - 参照点ごとの
heatmap_*.pngとdata_*.npzを出力 -
summary.csvで平均・分散・閾値割合をまとめる -
topk.csvに全参照点の上位パッチ一覧を集約
pixio_patch_similarity_analyze.py
- 既存の
npz/json/csvを読み込み - 分布統計や top10 を標準出力にまとめる
-
save_report=Trueで JSON レポートも保存可能
実行の結果
Meta: {'model_id': 'facebook/pixio-vitl16', 'input_h': 256, 'input_w': 256, 'patch_h': 16, 'patch_w': 16, 'grid_h': 16, 'grid_w': 16, 'ref_pixel': [128, 128], 'ref_row': 8, 'ref_col': 8, 'clip_percentile': [0.05, 0.95], 'gamma': 0.6}
patch_sim: {'min': 0.5117954015731812, 'max': 0.9999995827674866, 'mean': 0.7988789081573486, 'std': 0.07311728596687317}
sim_norm: {'min': 0.0, 'max': 0.9999969005584717, 'mean': 0.6780719757080078, 'std': 0.25016701221466064}
sim_norm percentiles: {50: 0.7339709997177124, 75: 0.8579611778259277, 90: 0.9474474191665649, 95: 0.9999885559082031, 99: 0.9999969005584717}
sim_norm fractions: {'ge_0.7': 0.56341552734375, 'ge_0.8': 0.3594818115234375, 'ge_0.9': 0.1680908203125}
top10: [{'rank': '1', 'row': '8', 'col': '8', 'pixel_x': '136', 'pixel_y': '136', 'score': '0.9999995827674866'}, {'rank': '2', 'row': '10', 'col': '13', 'pixel_x': '216', 'pixel_y': '168', 'score': '0.9448414444923401'}, {'rank': '3', 'row': '11', 'col': '11', 'pixel_x': '184', 'pixel_y': '184', 'score': '0.9332976341247559'}, {'rank': '4', 'row': '8', 'col': '4', 'pixel_x': '72', 'pixel_y': '136', 'score': '0.9305018186569214'}, {'rank': '5', 'row': '6', 'col': '8', 'pixel_x': '136', 'pixel_y': '104', 'score': '0.9300633072853088'}, {'rank': '6', 'row': '3', 'col': '9', 'pixel_x': '152', 'pixel_y': '56', 'score': '0.929740846157074'}, {'rank': '7', 'row': '10', 'col': '3', 'pixel_x': '56', 'pixel_y': '168', 'score': '0.9295473098754883'}, {'rank': '8', 'row': '10', 'col': '8', 'pixel_x': '136', 'pixel_y': '168', 'score': '0.9288248419761658'}, {'rank': '9', 'row': '9', 'col': '4', 'pixel_x': '72', 'pixel_y': '152', 'score': '0.9253356456756592'}, {'rank': '10', 'row': '12', 'col': '9', 'pixel_x': '152', 'pixel_y': '200', 'score': '0.9241706132888794'}]
実行方法
python pixio_patch_similarity.py
python pixio_patch_similarity_sweep.py
python pixio_patch_similarity_analyze.py
考察
-
patch_simが 0.5〜1.0 に集中しており、同一画像内では類似度が全体的に高い
→ 絶対値での判断より「相対比較」が有効 - クリップとガンマは「見た目」を大きく変える
→ 画像比較をするなら同じclip_percentile/gammaに固定するのが必須 -
sim_mapは双線形補間なので、細かい境界はぼける
→ 細かな構造を見たい場合はパッチサイズを小さくしたモデルを検討 - 上位パッチは参照パッチ周辺に集まりやすい
→ 参照点を複数試すことで、質感の違いがより見えやすくなる
まとめ
Pixioのパッチ特徴は「同じ質感・同じ物体の領域」を浮かび上がらせるのに使える。
可視化だけでなく、数値データとして保存すれば分析にも展開できる。
今回も最後まで読んでくれてありがとうございます
