Re: PSA: Python Pillow was used for image crushing to foil Gemini AI image identification

Maria Sophia <[email protected]> Wed, 29 Jul 2026 22:09:42 -0700
Newsgroups gmane.comp.python.general
Organization BWH Usenet Archive (https://usenet.blueworldhosting.com)
Message-ID <[email protected]>
Lawrence D'Oliveiro wrote:
> On Wed, 29 Jul 2026 09:31:42 -0700, Maria Sophia wrote:
> 
>> 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.
> 
> One thing the article didn't mention, is trying to mix multiple
> AI-generated images in various ways, to see what that does to their
> respective SynthID markers. And applying various bandwidth-filtering
> techniques (because the SynthID marker apparently takes the form of an
> "audio waveform").

Hi Lawrence,

I had never heard of SynthID before this article popped up in my feed
today, so anything I say can be wrong (and likely is wrong), but it seems
to me that your instinct is spot on that if SynthID really behaves like a
kind of embedded "audio-like waveform" distributed across pixel space, then
mixing multiple AI-generated images or applying frequency-domain filtering
is perhaps just the kind of stress test that could reveal how fragile (or
how robust) the watermark actually is. 

The Ars Technica piece we're reading confirms the robustness under
compression, resizing, and moderate cropping, but, as you noted, it doesn't
explore multi-image blending or frequency-domain attacks at all.

If it's genuinely difficult to destroy SynthID without destroying the
image, then watermarking is much more robust than traditional sensor-noise
fingerprinting. 

To their credit, the article makes clear that SynthID is not designed to
resist adversarial attacks and will eventually be bypassed by determined
attackers, of which I am not one, but I did update the script today.

  # 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.
  # ---------------------------------------------------------
  # v1p3 20260729
  #      Further slowed degradation to about gen_100 or so
  # v1p2 20260729
  #      It worked but by gen_12 the image was unusable
  #      JPEG quality 10-40 is extremely destructive
  #      Resize 80-100% shrinks the image every single generation
  #      Crop 20% every 50 gens removes huge chunks
  #      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%
  # v1p1 20260729
  #      It worked but it barfed when the image got too small
  #      Added a clamp so width/height never drop below 1px
  # 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_slow.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):
  
      # Very gentle JPEG compression
      quality = random.randint(75, 95)
  
      # Only resize occasionally (every 10 generations)
      if i % 10 == 0:
          scale = random.uniform(0.97, 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
      img = Image.open(out_path)
  
      # Crop very rarely and very lightly
      if i % 100 == 0:
          w, h = img.size
          if w > 40 and h > 40:
              crop_amount = int(w * 0.03)  # 3% crop
              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
  # Craiyon, Nano Banana 2 in Gemini, ChatGPG (3 per day)
-- 
Usenet allows people with shared interests to discuss topics of import.