Re: PSA Using Python Pillow to foil camera image PRNU fingerprinting

Maria Sophia <[email protected]>
Newsgroups gmane.comp.python.general
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.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.