Today, this article showed up in my technical-news feed about using Python
Pillow to discern the reliability of watermarking in AI-generated images.
https://arstechnica.com/ai/2026/07/tested-google-synthid-works-great-but-labeling-ai-content-may-be-a-losing-game/
As is my wont, I hacked out a Python script on Windows to test their
methods and I posted that script so that others can try it out too.
Newsgroups: rec.photo.digital,alt.comp.os.windows-10,comp.lang.python
Subject: PSA: Python Pillow was used for image crushing to foil Gemini AI
image identification
Date: Wed, 29 Jul 2026 09:31:42 -0700
Message-ID: <[email protected]>
Having never used Python Pillow, and while I was already writing code,
I decided to try to use it to foil camera sensor PRNU fingerprinting.
To that end, here's a script that others can test out, but I don't
know of a place to upload the original & the scrubbed image to test.
So it's just a guess if this script really scrubs PRNU fingerprints.
How would I know if it worked?
Do you know of a web site that compares two images to tell you if
both came from the same camera based on the unique PRNU fingerprint?
# prnu.py
# Obfuscate camera sensor PRNU fingerprints
# Place an image called input.jpg in the current directory.
# Run: python prnu.py
# ----------------------------------------------------------------------
# v1p6 20260729 Added EXIF stripping, tiny noise injection, dual-pass
scrubbing
# v1p5 20260729 Brought the margin in a few pixels to handle interpolation
# v1p4 20260729 Changed to trigonometry to figure out the crop angles
# v1p3 20260729 Switched to making the rotation triangles transparent
# v1p2 20260729 Further refined as a mask is needed to remove triangles
# v1p1 20260729 Refined crop to remove the white rotation edge triangles
# v1p0 20260729 Original version
# blur, rotate, crop, recompress, resize
# ----------------------------------------------------------------------
import math
import random
import numpy as np
from PIL import Image, ImageFilter
INPUT_IMAGE = "input.jpg"
OUTPUT_IMAGE = "scrubbed.jpg"
def maximal_inner_rect(w, h, angle):
"""
Compute the largest axis-aligned rectangle inside a rotated rectangle.
"""
theta = abs(angle)
if theta == 0:
return w, h
t = math.radians(theta)
W = w
H = h
W_prime = W * math.cos(t) - H * math.sin(t)
H_prime = H * math.cos(t) - W * math.sin(t)
return int(W_prime), int(H_prime)
def scrub_once(img):
"""
One full PRNU scrubbing pass:
blur ¡÷ rotate ¡÷ crop ¡÷ resize ¡÷ noise ¡÷ JPEG recompress
"""
# Blur to kill PRNU high-frequency noise
img = img.filter(ImageFilter.GaussianBlur(radius=1.2))
# Random slight rotation
angle = random.uniform(-2.0, 2.0)
rotated = img.rotate(angle, expand=True)
# Compute maximal inner rectangle
W, H = img.size
crop_w, crop_h = maximal_inner_rect(W, H, angle)
# Safety margin
margin = 3
crop_w = max(1, crop_w - 2 * margin)
crop_h = max(1, crop_h - 2 * margin)
# Center crop
cx, cy = rotated.size
left = (cx - crop_w) // 2
top = (cy - crop_h) // 2
right = left + crop_w
bottom = top + crop_h
cropped = rotated.crop((left, top, right, bottom))
# Optional slight resize
scale = random.uniform(0.97, 1.00)
new_w = max(1, int(cropped.width * scale))
new_h = max(1, int(cropped.height * scale))
resized = cropped.resize((new_w, new_h), Image.LANCZOS)
# Add tiny random noise (¡Ó3)
arr = np.array(resized).astype(np.int16)
noise = np.random.randint(-3, 4, arr.shape, dtype=np.int16)
arr = np.clip(arr + noise, 0, 255).astype(np.uint8)
resized = Image.fromarray(arr)
return resized
# Load image
img = Image.open(INPUT_IMAGE).convert("RGB")
# Strip EXIF metadata
img.info.pop("exif", None)
# First scrubbing pass
img = scrub_once(img)
# Second scrubbing pass (different random parameters)
img = scrub_once(img)
# Final JPEG recompression
quality = random.randint(70, 90)
img.save(OUTPUT_IMAGE, "JPEG", quality=quality)
print("Saved:", OUTPUT_IMAGE)
# end of prnu.py
--
Posted out of the goodness of my heart to help others & to learn from them.--
https://mail.python.org/mailman3//lists/python-list.python.org