Re: PSA Using Python Pillow to foil camera image PRNU fingerprinting
Maria Sophia <[email protected]> Mon, 3 Aug 2026 09:20:43 -0800
| Newsgroups | alt.comp.os.windows-10,rec.photo.digital,comp.lang.python |
|---|---|
| Organization | BWH Usenet Archive (https://usenet.blueworldhosting.com) |
| Message-ID | <[email protected]> |
Hi Lawrence,
Thanks for your advice, where I have been looking up the methods that you &
Piergiorgio suggested, both of which are far better than my original idea.
I liked your idea of building a PRNU fingerprint from a set of same-camera
calibration images of matching resolution and subtracting that fingerprint
from the target image to produce a PRNU-reduced scrubbed image.
That's what the prnuwash.py script attempted to accomplish, which was a
better method than the original method I had tried, since prnu.py used
blind PRNU estimation techniques to suppress sensor-specific fingerprints.
I think each of us adds more value to the problem set discussion, where
everyone can benefit from our ideas, even those who are only lurking here.
Below is a a script that reduces the real fingerprint in order to then
apply a stronger fake fingerprint to implement Piergiorgio's suggestion.
I had to add cv2 since it produces a more realistic PRNU overall.
pip3.exe install opencv-python
But I really need to also add BM3D as Piergiorgio had suggested.
python fakeprnu.py
Loaded: input.jpg resolution: (1067, 800, 3)
Denoised image to weaken original PRNU.
Generated synthetic PRNU map.
Applied synthetic PRNU multiplicatively.
Saved: fakeprnu.jpg
# --------------------------------------------------------------------
# fakeprnu.py
# A cv2-based PRNU scrubber that applies a fake fingerprint to an image.
# --------------------------------------------------------------------
# This script is intended to apply a fake fingerprint onto an image file.
# It's designed to hinder PRNU fingerprinting when posting images online.
# It does not require calibration images like the previous prnuwash.py
did.
#
# 1. Place the image you want to process as input.jpg
# 2. Run: python fakeprnu.py
# 3. Output: scrubbed.jpg
#
# The approach:
# A. Denoise the image to weaken the original PRNU
# B. Generate a synthetic fixed-pattern PRNU map
# C. Apply the synthetic PRNU multiplicatively:
# img_out = img_denoised * (1 + prnu_map)
#
# --------------------------------------------------------------------
# v1p1 20260803 reduced denoising from 10 to 5 due to visible blur effect
# WIP: BM3D should be added as it reduces noise without edge blur.
# v1p0 20260803 initial version implementing synthetic PRNU overlay
# --------------------------------------------------------------------
import cv2
import numpy as np
INPUT_IMAGE = "input.jpg"
OUTPUT_IMAGE = "fakeprnu.jpg"
# Step 1: Denoise image to weaken original PRNU
# Note the option to skip denoicing altogether
# img_denoised = img
# 10 was a bit too blurry
# def denoise_image(img, strength=10):
def denoise_image(img, strength=5):
print("Denoised image to weaken original PRNU.")
# Uses OpenCV fastNlMeansDenoisingColored
# This is not BM3D, but it is simple and available everywhere.
return cv2.fastNlMeansDenoisingColored(
img, None,
h=strength,
hColor=strength,
templateWindowSize=7,
searchWindowSize=21
)
# Step 2: Generate synthetic fixed-pattern PRNU
def generate_fake_prnu(shape, amplitude=0.02, smooth_kernel=21):
h, w, c = shape
# Start with random noise
noise = np.random.randn(h, w, c).astype(np.float32)
# Smooth to create spatial correlation
smooth = cv2.GaussianBlur(noise, (smooth_kernel, smooth_kernel), 0)
# Normalize to zero mean, unit variance
mean = np.mean(smooth)
std = np.std(smooth) + 1e-8
norm = (smooth - mean) / std
# Scale to desired amplitude
prnu_map = amplitude * norm
return prnu_map
# Step 3: Apply multiplicative fake PRNU
def apply_fake_prnu(img, prnu_map):
img_f = img.astype(np.float32) / 255.0
out = img_f * (1.0 + prnu_map)
out = np.clip(out, 0.0, 1.0)
out = (out * 255.0).astype(np.uint8)
return out
# Main
def main():
img = cv2.imread(INPUT_IMAGE, cv2.IMREAD_COLOR)
if img is None:
raise RuntimeError("Could not load input.jpg")
print("Loaded:", INPUT_IMAGE, "resolution:", img.shape)
# Step A: weaken original PRNU
img_denoised = denoise_image(img)
print("Denoised image to weaken original PRNU.")
# Step B: synthetic PRNU
fake_prnu = generate_fake_prnu(img.shape, amplitude=0.02,
smooth_kernel=21)
print("Generated synthetic PRNU map.")
# Step C: apply multiplicative PRNU
img_out = apply_fake_prnu(img_denoised, fake_prnu)
print("Applied synthetic PRNU multiplicatively.")
cv2.imwrite(OUTPUT_IMAGE, img_out)
print("Saved:", OUTPUT_IMAGE)
if __name__ == "__main__":
main()
# end of fakeprnu.py
--
On Usenet, we all try to help each other by leveraging knowledge.