fingerprint_correlate — IMGFORENSICS sensor op

• Data kinds: image2d × fingerprint → table

• Call: import fullseye as fs; fs.ledger.fingerprint_correlate(image, fingerprint, denoiser: 'str' = 'wiener', sigma: 'float' = 0.02, exclude: 'int' = 11) -> 'dict' (to call the implementation directly, import imgforensics; imgforensics.fingerprint_correlate(image, fingerprint, denoiser: 'str' = 'wiener', sigma: 'float' = 0.02, exclude: 'int' = 11) -> 'dict'; from the registry, opsimgforensics.get("fingerprint_correlate"))

Usage

Match one image against a fingerprint. It returns no verdict — an evidence statistic and its caveats.

Takes the circular cross-correlation of the residual W = I - denoise(I) and the reference signal I·K via FFT, and returns the peak of the normalized cross-correlation and the PCE (peak-to-correlation energy).

Returns (a dict of the table vocabulary):

pce PCE. No threshold is bundled (see caveats below)

ncc_peak peak value of the normalized cross-correlation ([-1, 1])

peak_shift position of the peak (dy, dx). If it is not (0, 0), the position is shifted

= a clue to suspect cropping / image stabilization / resizing

n_pixels number of pixels used

caveats list (strings) of what this number cannot tell

Returning peak_shift matters: a peak other than (0, 0) is either "the same sensor but cropped" or "a chance correlation". Which of the two it is, this op cannot decide. In measurements, matching against a different sensor always peaks somewhere other than (0, 0) ((-51, -60) (31, -8) etc., scattered every time), while the same sensor always peaks at (0, 0).

Recompression kills PRNU (tests/test_imgforensics.py::test_prnu_dies_under_recompression, fingerprint built from 8 images, 128x128):

========== ========== ==========

Recompr. PCE Ratio to none

========== ========== ==========

None 7246 1.00

Quality 95 6174 0.85

Quality 90 4214 0.58

Quality 75 1412 0.19

Quality 50 530 0.07

Quality 30 207 0.03

========== ========== ==========

So a low PCE may mean not "a different camera" but "no information left". Bundling a threshold here would turn it into a tool that mechanically judges compressed images to be "a different camera".

A path that is silently wrong (measured): if an ordinary image instead of a fingerprint is passed to fingerprint, the shape matches, so no exception is raised and a finite PCE is returned. A fingerprint is (H, W) float64, so it fully satisfies the existing image2d predicate and cannot be distinguished at run time. Therefore anything "not zero-mean" is rejected at the entry with ValueError (|mean| > 0.05 * std). Measured (tests/test_imgforensics.py::test_fingerprint_gate_rejects_plain_images):

============================== ==============

What was passed |mean|/std

============================== ============== sensor_fingerprint output 4.8e-18

Ordinary natural image 4.626

Dark image (mean 0.015) 4.626

High-contrast image 1.801

============================== ==============

The ratio is scale-invariant, so dark and bright images are rejected alike (more than 1.5 orders of magnitude away from the threshold 0.05).

Still not complete: an image you zero-meaned yourself passes the gate, and a finite value PCE = -5.97 is returned (measured). That is, the run-time check can guard only one side. This is the basis of the decision "do not let the fingerprint piggyback on image2d; give it its own vocabulary", detailed in the docstring of opsimgforensics.

References (sample data, literature)

• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).

• Operator provenance and references — the sources of the research/methods this op family came from.

• The canonical algorithm (author, year) and its uses are named in the family usage guide above.

Runnable examples (verified samples that actually call this op)

• image_forensics_audit — py -3.11 examples/image_forensics_audit.py

• poc_prnu_camera_fingerprint — py -3.11 examples/poc_prnu_camera_fingerprint.py

Ops the type connects to (they accept table as input)

evidence_quantile

Same category (sensor)

sensor_fingerprint · fingerprint_strength_map


*Provenance: imgforensics.py — IMGFORENSICS operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*

© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.