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.