PSA: Python Pillow was used for image crushing to foil Gemini AI image identification
Maria Sophia <[email protected]> Wed, 29 Jul 2026 09:31:42 -0700
| Newsgroups | rec.photo.digital,alt.comp.os.windows-10,comp.lang.python |
|---|---|
| Organization | BWH Usenet Archive (https://usenet.blueworldhosting.com) |
| Message-ID | <[email protected]> |
Python Pillow was used to try to foil AI image detection
https://arstechnica.com/ai/2026/07/tested-google-synthid-works-great-but-labeling-ai-content-may-be-a-losing-game/
I'm surprised it's so hard, but where it matters is for years,
we've been "destroying" images so that they can't be correlated
by their sensor defects, but if it's this difficult to destroy
the cryptography Google AI adds, that means it's not easy.
C:\> python --version
Python 3.14.1
C:\> python -m pip --version
pip 25.3 from C:\app\os\python\Lib\site-packages\pip (python 3.14)
C:\> python -m pip install --upgrade Pillow
Collecting Pillow
Downloading pillow-12.3.0-cp314-cp314-win_amd64.whl.metadata (9.3 kB)
Downloading pillow-12.3.0-cp314-cp314-win_amd64.whl (7.2 MB)
---------------------------------------- 7.2/7.2 MB 4.4 MB/s 0:00:01
Installing collected packages: Pillow
Successfully installed Pillow-12.3.0
[notice] A new release of pip is available: 25.3 -> 26.1.2
[notice] To update, run: python.exe -m pip install --upgrade pip
C:\> python crush.py
output/
gen_1.jpg
gen_2.jpg
...
gen_300.jpg
Where each file is one generation of degradation..
In the article, they repeatedly simulated an image being shared
over the Internet (compression, resizing, cropping, etc.).
And then they tested if Google Gemini SynthID could still identify it:
https://gemini.google.com (Ask: Check SynthID watermark)
Gemini will respond with one of:
a. "SynthID watermark detected"
b. "No SynthID watermark detected"
c. "This image may have been cropped too much to detect SynthID"
d. Or it may rate-limit you (the article mentions ~10 checks/day) .
Here is a working Python script that does the same thing.
Note this script is too aggressive, but that's easy to change.
# crush.py
# Use model: python crush.py
# Simulates image being shared on the net to see if the
# AI watermarks can still be found by Gemini testing.
# ---------------------------------------------------------
# v1p2 20260729
# Changed quality from (10, 40) to (60, 85)
# Changed scale from (0.80, 1.00) to (0.95, 1.00)
# Changed crop from every 50 generations to every 100
# Changed crop amount from 20% to 5%
# This makes it to about gen20 before it's unusable.
# v1p1 20260729
# It worked but it barfed when the image got too small
# Added a clamp so width/height never drop below 1px
# JPEG quality 10-40 is extremely destructive
# Resize 80-100% shrinks the image every single generation
# Crop 20% every 50 gens removes huge chunks
# This makes it to about gen12 before it's unusable.
# v1p0 20260729
# Simulate an image being shared over the Internet
# for the purpose of testing if it can be crushed
# enough such that it can no longer be identified.
# ---------------------------------------------------------
# crush.py
import random
import os
from PIL import Image
INPUT_IMAGE = "input.jpg"
GENERATIONS = 300
OUTPUT_DIR = "output"
os.makedirs(OUTPUT_DIR, exist_ok=True)
img = Image.open(INPUT_IMAGE)
for i in range(1, GENERATIONS + 1):
# Random JPEG compression quality between 10 and 40 was too much
# quality = random.randint(10, 40)
quality = random.randint(60, 85)
# Random resize factor between 80% and 100% was too much
# scale = random.uniform(0.80, 1.00)
scale = random.uniform(0.95, 1.00)
new_w = max(1, int(img.width * scale))
new_h = max(1, int(img.height * scale))
img = img.resize((new_w, new_h), Image.LANCZOS)
# Save this generation
out_path = f"{OUTPUT_DIR}/gen_{i}.jpg"
img.save(out_path, "JPEG", quality=quality)
# Reload it to simulate "download -> reupload"
img = Image.open(out_path)
# Crop every 50 generations was too much
# if i % 50 == 0:
if i % 100 == 0:
w, h = img.size
if w > 10 and h > 10:
# Crop amount of 20% was too much
# crop_amount = int(w * 0.20)
crop_amount = int(w * 0.05)
img = img.crop((crop_amount, crop_amount, w - crop_amount, h - crop_amount))
img.save(out_path)
print("Done! Check the output/ folder.")
# end of crush.py
--
Privacy is a million technical things, of which most people know about 3.