Skip to content
ishaanpilarPublic

About

AI-assisted dust, dirt and scratch removal for product photography. Cleans the blemishes at full resolution and leaves the rest of the frame byte-identical.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

pristine

AI Dust, Dirt & Scratch Removal for Product Photography

Cleans dust, dirt, splatter and spots off product photos at full resolution — and leaves every other pixel exactly as it was.

before and after

$ pristine photo.jpg -o clean.jpg
photo.jpg   6000x4000   1,113 spots   ->   409,444 px cleaned, 98.29% untouched

Why not just ask an AI to clean the photo?

Because it hands you back a different product.

Give a 24MP photo to any image model and you get a 1–4MP regeneration — it redraws your product from a description. Tested on a real 24MP product shot, a single full-frame pass:

  • erased embossed lettering from a panel,
  • redrew a bracket with a different number of slots,
  • moved a logo several hundred pixels,
  • and softened the whole frame.

It looked clean. It was also no longer a photograph of the thing being sold.

pristine works the other way around. It finds the dirt, repairs only the dirt, and never touches anything else. Typically 98% of the frame comes out byte-identical to your original — same sharpness, same framing, same colour, same lens character. Because it isn't regenerating the photo, it can't invent detail that was never there.


Install

git clone https://github.com/ishaanpilar/pristine.git
cd pristine
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

Python 3.10+. No GPU, no model downloads, no API key. Runs offline.

Quick start

# 1. See what it WOULD remove, before changing anything
.venv/bin/python clean.py photo.jpg --dry-run --debug-dir dbg/

# 2. Clean it
.venv/bin/python clean.py photo.jpg -o clean.jpg

# 3. Or a whole shoot
.venv/bin/python clean.py shoot/*.jpg --out-dir cleaned/

Always look at dbg/<name>_mask.jpg first. Red is what will be removed. If your logo or serial number is red, raise --threshold. This one habit prevents every bad outcome the tool is capable of.


How it works

1. Find the dirt. A multi-scale morphological top-hat keeps features that are brighter than their surroundings and smaller than a given size — which is what dust, splatter and speckle look like, and what a panel edge or a bolt head does not. Three filters then remove the lookalikes:

Filter Removes Because
size sensor noise, huge regions dirt has a size range; shadows don't
shape specular highlights a highlight along an edge is a long thin streak; dirt is round
density lettering, logos, fine texture text is a crowd of strokes; dirt is isolated

That last one matters more than it sounds. A letter in a serial number is small and bright — nothing about its size or shape tells it apart from a speck. What does is company. Measuring structure density in a window and refusing to touch crowded areas cut damage to fine lettering from 6% to 0.8% in testing.

2. Repair each spot locally. Every blob is inpainted on its own small crop, not the whole frame. That keeps a 24MP file at ~30s and keeps each fill sourced from the surface it belongs to.

3. Put the texture back. Classical inpainting fills a hole with a smooth colour gradient — which is exactly what reads as a smudge on a textured surface. So the smooth fill is used for tone only, and the surface's own high-frequency texture is copied back in from clean pixels a few px away. This is the frequency-separation move a retoucher makes by hand, and it's the difference between "cleaned" and "obviously retouched".

Try --texture 0 to see it: moulded graining collapses into a flat patch.

4. Composite through a feathered mask, so only dirt pixels are ever written.


Tuning

Most photos need nothing. When they do, these are the knobs that matter:

Flag Default What it does
--threshold 2.6 Lower removes more, and risks real detail. The main dial.
--protect 0.30 Guards crowded areas — lettering, logos, fine texture. 0 disables.
--scales 21,41,81,141,241 Sizes of dirt to look for, in px. Raise the top for large deposits.
--max-area 40000 Bigger than this isn't dirt, it's a shadow or a part.
--polarity bright bright = light dirt on a dark product. Also dark, both.
--max-aspect 3.0 Drops thin streaks. Raise it if scratches are your target — scratches are thin too.
--texture 0.85 How much surface texture is restored over the fill.
--grain 0.4 Sensor grain re-injected so patches don't look plastic.

Two combinations worth knowing:

# Textured surface, no lettering in frame -- clean harder
.venv/bin/python clean.py photo.jpg -o clean.jpg --protect 0

# Big caked-on deposits
.venv/bin/python clean.py photo.jpg -o clean.jpg \
    --threshold 2.2 --scales 21,61,141,241,341 --max-area 150000

8-bit and 16-bit in, same out. ICC profile and EXIF preserved.


Results

Measured on a synthetic benchmark with ground-truth defect masks — 420 dust specks, 14 hairs and 5 scratches over a product with engraved branding, a fine serial line, a knurled dial and machined texture. Reproduce it yourself:

.venv/bin/python tests/make_fixtures.py
.venv/bin/python tests/evaluate.py
dust removed (scored on defect pixels) 69%
collateral change on clean pixels 0.32 / 255 levels
engraved branding sharpness 100.0% of original
knurled dial sharpness 100.0% of original

On a real 24MP product photograph: 1,113 spots found, 1.71% of the frame rewritten, 98.29% byte-identical, ~30 seconds, product lettering and geometry unchanged.

Scoring happens on the ground-truth defect pixels, not frame-wide averages. Dirt covers ~2% of a frame, so a frame-wide average is swamped by noise and reports nonsense — an early version of this benchmark claimed the tool made images worse for exactly that reason.


Limits

Worth knowing before you trust it on a deadline.

  • Large caked-on deposits survive. Past a certain size a top-hat detector stops calling it dirt. Widening --scales and --max-area helps. A learned detector is the real fix; that's the roadmap.
  • --protect trades cleaning for safety. On a textured surface with no lettering, --protect 0 cleans noticeably more. On anything with a serial number, leave it on.
  • Scratches need --max-aspect raised. By default thin streaks are spared, because that's how specular highlights are told apart from dirt.
  • It cleans photos, not products. If the item is genuinely filthy, washing it before the shoot beats any amount of software.

Also in this repo

File
clean.py the main tool — local, free, offline
detect.py the detector, importable on its own
gemini_clean.py optional: repairs with the Gemini API instead. Marginally better on heavy deposits, ~$2–4 and ~5 min per photo. Sends 1024px crops only, never the full frame, and validates every returned tile before use.
polish.py optional: merges a separately-produced AI-cleaned image back onto your original without losing sharpness
tests/ synthetic benchmark with ground-truth masks

License

MIT — see LICENSE.

About

AI-assisted dust, dirt and scratch removal for product photography. Cleans the blemishes at full resolution and leaves the rest of the frame byte-identical.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages