← all work classical-computer-vision

Copy-move forgery

[![Python 3.10+](https://img.shields.io/badge/python-3.10+-3776AB?logo=python&logoColor=white)](https://www.python.org/)

Result

SrScene**Block matching**SIFT + similarityORB + similaritySIFT + translationSIFT blobs (no verify)
1penguin on pebbles · every stone resembles every stone**1.000**0.8550.8540.8510.748
2fighter jet · man-made straight edges**1.000**0.8730.8680.8600.789
3elephant herd · genuinely repeated objects**1.000**0.8610.8450.8480.836
4tortoise on rock · broken rock texture**1.000**0.8430.6610.8420.798

The finding

What it does

`mermaid flowchart LR A[Clean photo] --> B["Copy a TEXTURED patch
rotate / rescale it
paste it elsewhere"] B --> C[Exact mask of BOTH copies] B --> D1[Block matching] B --> D2[SIFT / ORB self-match] D2 --> E{Verify what
hypothesis?} E -->|nothing| F1[Paint blobs] E -->|a shift| F2[Translation verify] E -->|shift + rotate + scale| F3[Similarity verify] D1 & F1 & F2 & F3 --> G[Score vs BOTH copies] C --> G A2[UNTAMPERED photos] --> D1 & F1 & F2 & F3 D1 & F1 & F2 & F3 --> H[False alarms:
what does it accuse
a clean photo of?]

style C fill:#fef3c7,stroke:#d97706 style E fill:#dbeafe,stroke:#2563eb style H fill:#fee2e2,stroke:#dc2626 `

Two design choices carry the whole project:

The truth mask marks both copies (amber). After the paste, the source and the destination are *identical* — same content, same noise, same compression. Nothing in the image says which one came first. A detector that finds a duplicated pair has done its job; deciding which half is the forgery needs information the pixels do not contain (lighting direction, perspective, a second photograph). Scoring against the pasted region alone caps precision at about 0.5 for every method and measures that ambiguity instead of the detector.

Untampered images are scored too (red). A detector that marks part of every photograph you feed it is not a detector. On a clean image IoU, precision and recall are all undefined — the truth is empty — so the only honest number is the fraction of pixels flagged, and it should be zero. This is where the rotation-robust methods stop looking free.

---

Figures

ui_false_alarms.png
ui_false_alarms
ui_main.png
ui_main
ui_matches.png
ui_matches
ui_method_matrix.png
ui_method_matrix
ui_rotation_sweep.png
ui_rotation_sweep

Elsewhere

Code and full results on GitHub (code private)

Every number is read from the file the run produced. github.com/hammasbuilds · kaggle.com/muhammadhammas13