copy_move opimage2d → tableimport 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"(既定)
:func:features.harris_corners でコーナーを取り、
:func:features.describe_patches で正規化パッチ記述子を作り、
自分から min_offset px 以上離れた最近傍と Lowe の比率検定で対応を作る
(:func:_self_match)。対応をシフトベクトルで束ね、群ごとに
:func:mosaic.proj_match_points_ransac で幾何整合を確認する。
相似変換は :func: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) のまま正しい。壊れるのは
「見つかるかどうか」で、見つかったときの答えではない。
py -3.11 examples/image_forensics_audit.pypy -3.11 examples/poc_forensics_roc.pytable を入力に取れる)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.