copy_move op• データ種: image2d → table
• 呼び出し: import fullseye as fs; fs.ledger.copy_move_regions(image, method: 'str' = 'keypoint', min_matches: 'int' = 4, min_offset: 'float' = 16.0, offset_tol: 'float' = 2.0, ratio: 'float' = 0.6, patch: 'int' = 11, block: 'int' = 8, step: 'int' = 1, n_dct: 'int' = 10, min_variance: 'float' = 0.0001, max_feature_dist: 'float' = 0.02, neighbours: 'int' = 2, ransac_thresh: 'float' = 3.0, ransac_iters: 'int' = 300, seed: 'int' = 0) -> 'list' (実装を直接呼ぶなら import imgforensics; imgforensics.copy_move_regions(image, method: 'str' = 'keypoint', min_matches: 'int' = 4, min_offset: 'float' = 16.0, offset_tol: 'float' = 2.0, ratio: 'float' = 0.6, patch: 'int' = 11, block: 'int' = 8, step: 'int' = 1, n_dct: 'int' = 10, min_variance: 'float' = 0.0001, max_feature_dist: 'float' = 0.02, neighbours: 'int' = 2, ransac_thresh: 'float' = 3.0, ransac_iters: 'int' = 300, seed: 'int' = 0) -> 'list'、台帳から引くなら opsimgforensics.get("copy_move_regions"))
1 枚の画像の中の コピー&ムーブ(自己複製)領域の対を返す。table。
method="keypoint"(既定)
features.harris_corners でコーナーを取り、
features.describe_patches で正規化パッチ記述子を作り、
自分から min_offset px 以上離れた最近傍と Lowe の比率検定で対応を作る
(_self_match)。対応をシフトベクトルで束ね、群ごとに
mosaic.proj_match_points_ransac で幾何整合を確認する。
相似変換は fit_transform.vector_to_similarity(Umeyama)で当てて
similarity に入れる。
method="block"
Fridrich, Soukal & Lukáš 2003。block 角の重なりブロックを step px
刻みで取り(既定 step=1 —— これは飾りではない。下の「歩幅」参照)、
各ブロックの DCT 低周波 n_dct 係数を特徴にして辞書順に並べ、
辞書順で近い neighbours 件までを候補にし、特徴距離が
max_feature_dist 以下のものだけをシフトベクトルで数える。
回転・拡大には効かないが、角の少ない画像で keypoint 法より拾える。
分散が min_variance 未満のブロックは捨てる(一様な空を空にコピーしても同じ特徴になる = 検出器が必ず作る偽陽性の主因)。
歩幅 (step) を 1 にしてある理由(実測で決めた): ブロック法が「同じ特徴」を見つけられるのは、複製元と複製先が 同じ格子に乗ったときだけである。step=4 にすると、シフトが 4 の倍数でない複製(たとえば (110, 128))は 原理的に一度も一致しない。実測で step=4 は真のシフトを 1 件も返さず、代わりに偽の群を 60 件返した。step=1 なら真のシフトが第 1 群に来る。大きい画像で重いときは step を上げてよいが、上げた歩幅の倍数のシフトしか見つからなくなることを承知の上で上げること。
返りは領域対の list(対応数の多い順)。各要素:
offset シフト (dy, dx)(row, col)。向きは 位置で正規化してある
(辞書順で正になる向き)—— 添字で決めると同じ複製が
(110, 128) にも (-110, -128) にもなる(実測して直した)
n_matches その群の対応数
n_inliers RANSAC の内点数(method="block" では n_matches と同じ)
inlier_ratio 内点率 src_bbox / dst_bbox (r0, c0, r1, c1) src_points / dst_points (N, 2) の (row, col)
similarity Umeyama で当てた 3x3(method="keypoint" のみ、なければ None)
method 使った方法
caveats この結果が言えないこと
正解が手元にあるので当てられることを数で固定してある (tests/test_imgforensics.py::test_copy_move_finds_the_known_offset、256x256 のテクスチャ画像の (40, 32) にある 64x64 を (150, 160) へ複製 = 真のシフト (110, 128)):
============ ================= ============ ========= ==============
method 第 1 群の offset n_matches 群の数 誤差
============ ================= ============ ========= ==============
keypoint (110.0, 128.0) 15 1 0 px
block (110.0, 128.0) 3249 1 0 px
============ ================= ============ ========= ==============
偽陽性も測ってある: 改竄していない同じ種類の画像 3 枚では、keypoint 法・block 法とも群 0 件(seed 4/5/6)。
言えないこと(すべて同じテストで測ってある):
• 一様な領域(空・壁)は複製しなくても同じ特徴になる。上半分を一様な「空」にした(改竄していない)画像で、min_variance=1e-4 と 1e-6 はどちらも 0 件だが、min_variance=0 にすると 264 対の偽の群が 1 件出る(シフト (1, -247) = 空の中の適当な対応)。
• method="keypoint" は正規化パッチ記述子なので 回転に効かない。複製を回して貼ると群の数は 0 度 1 件 → 5 度 0 件 → 15 度 0 件 → 30 度 0 件。similarity に回転が入って返ることは実質ない。
• method="block" も 回転にまったく効かない(5 度で 0 件)。
• 検出ゼロ = 複製が無い、ではない。再圧縮を挟んだ複製は特徴距離が伸びて max_feature_dist を超える。実測(同じ複製画像を JPEG に通してから検出):
========== ============================ ==========================
品質 keypoint 第 1 群の n_matches block 第 1 群の n_matches
========== ============================ ==========================
無圧縮 15 3249
95 10 138
85 7 10
75 7 0 件(群なし)
========== ============================ ==========================
シフトはどの品質でも (110.0, 128.0) のまま正しい。壊れるのは「見つかるかどうか」で、見つかったときの答えではない。
• サンプルデータ カタログ(DL URL / ライセンス) — 2-D は skimage.data(BSD/public)+ 合成、3-D は実データ源(Stanford/PDS 等)の DL URL。
• 演算子の来歴・参考文献 — この op 族の元になった研究/手法の出典。
• アルゴリズムの正典(著者・年)と用途は上記ファミリ使い方ガイドに記載。
• image_forensics_audit — py -3.11 examples/image_forensics_audit.py
• poc_forensics_roc — py -3.11 examples/poc_forensics_roc.py
table を入力に取れる)copy_move)—
*Provenance: imgforensics.py — IMGFORENSICS operator registry. この per-op ノートは tools/opdocs.py md が自動生成(手編集しない)。*
© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.