---
title: "How to add a dehaze with Claude (tested prompt and script)"
description: "The milky gray veil over the dull push-in lifts: the washed-out backdrop turns back into distinct teal, lilac and peach crystal facets, the bottle glass deepens to a rich purple and the acrylic block's edges stand out, while the film grain stays as fine as before. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/dehaze
---

# How to add a dehaze with Claude

The milky gray veil over the dull push-in lifts: the washed-out backdrop turns back into distinct teal, lilac and peach crystal facets, the bottle glass deepens to a rich purple and the acrylic block's edges stand out, while the film grain stays as fine as before.

Made by the script on this page, from our own test clip.

## Get this effect

1. ### Switch to Code in your [Claude Desktop App](https://claude.com/download)

2. ### Install the skill (once)

   ```
   Install the Recharm video effects skill: download https://recharm-growth-staging.s3.us-east-2.amazonaws.com/website/downloads/recharm-video-effects.zip and unzip it into ~/.claude/skills
   ```

3. ### Ask for the effect

   ```
   Add a dehaze to input.mp4
   Share of the measured haze to remove: 0.8
   Pixels across the square the dark channel searches for a dark pixel: 15
   ```

## Several videos, several effects, one ask

Put your clips in the folder, mention them with @, and ask for everything at once. Claude runs one tested script after another on each clip, then joins them.

Add a dehaze to **@clip1.mp4**, put a sale badge on **@clip2.mp4**, give **@clip3.mp4** a VHS look, then join all three with crossfades.

## Settings you can change

Ask Claude to change any of these, or edit them at the top of the script.

| Setting     | Default | What it does                                                                                                                          |
| ----------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `STRENGTH`  | `0.8`   | Share of the measured haze to remove. 1 removes it all and can look harsh; 0.5 is a gentle clean-up.                                  |
| `PATCH`     | `15`    | Pixels across the square the dark channel searches for a dark pixel. Bigger finds haze more reliably but can leave halos round edges. |
| `MIN_CLEAR` | `0.3`   | Floor on how clear any pixel is assumed to be. Raise it if very hazy areas turn dark or noisy.                                        |
| `SMOOTH`    | `40`    | Radius of the edge-aware filter that fits the haze map to the picture's edges.                                                        |
| `SAMPLES`   | `12`    | Frames sampled to measure the haze color, spread evenly through the clip.                                                             |
| `GRAIN`     | `2.0`   | Blur radius that splits off fine grain, which is kept as it is instead of being amplified along with the picture.                     |

## Without the skill

Don’t want to install the skill? Paste this prompt into Claude Code and Claude writes the script itself.

```
Remove the haze from input.mp4 with the dark channel prior, using Python with opencv-python-headless and numpy, and ffmpeg to read and write the video. Work in float RGB 0-1. Dark channel: the minimum of R, G and B at each pixel, then a 15x15 erosion (cv2.erode with a 15x15 kernel). Haze color: from 12 frames spread evenly through the clip, take the 0.1% of pixels with the brightest dark channel and average their colors; use that one haze color for the whole clip so it can't flicker. For each frame, transmission = 1 - 0.8 * dark channel of (frame / haze color), smoothed with a guided filter (box blurs of 40 pixels, eps 0.001) using the frame's grayscale as the guide, and floored at 0.3. Apply the correction only to a Gaussian-blurred copy of the frame (sigma 2): out = (blurred - haze) / transmission + haze + (frame - blurred), clipped to 0-1, so the grain isn't amplified. Do it on every frame from first to last. The dull gray backdrop, around RGB (150, 155, 157), should come out with clearly separate teal, lilac and peach facets, the side strips of backdrop about 1.8 times as saturated and twice as contrasty, and the whole frame about 15% darker (around 118 out of 255 instead of 142), with no clipped white. Don't crop, zoom or add anything else, and don't add any text (if you ever do, draw it with Pillow, not ffmpeg's drawtext). Keep the 540x960 size, 30fps, the 6 second length and the audio, encode H.264 yuv420p and save it as output.mp4.
```

## Run it without Claude

This is the exact script we tested. Save it as `dehaze.py` and run:

```
python3 dehaze.py input.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Dehaze: lift the gray haze off a flat, washed-out clip so it gains contrast, color and crisp edges.

Usage: python3 dehaze.py input.mp4 output.mp4
Needs ffmpeg and Python 3 with OpenCV and numpy (pip install opencv-python-headless numpy).
It uses the dark channel prior (He, Sun and Tang): in a clear shot almost every small patch has some
pixel that is nearly black in one channel, so whatever brightness the darkest channel still has is haze.
The haze color is measured once for the whole clip so the result can't flicker.
"""
import json
import subprocess
import sys

import cv2
import numpy as np

STRENGTH = 0.8     # share of the measured haze to remove (1 removes it all and can look harsh)
PATCH = 15         # pixels across the square the dark channel looks for a dark pixel in
MIN_CLEAR = 0.3    # floor on how clear any pixel is assumed to be, so thick haze isn't pushed to noise
SMOOTH = 40        # radius of the edge-aware filter that fits the haze map to the picture's edges
SAMPLES = 12       # frames sampled to measure the haze color
GRAIN = 2.0        # blur radius that splits off grain, which is kept as is instead of being amplified with the haze

def dark_channel(img):
    return cv2.erode(img.min(axis=2), np.ones((PATCH, PATCH), np.uint8))

def guided_filter(guide, src, radius, eps=1e-3):
    """Smooth src while keeping the edges of guide (He's guided filter, built from box blurs)."""
    blur = lambda x: cv2.boxFilter(x, -1, (radius, radius))
    mean_g, mean_s = blur(guide), blur(src)
    a = (blur(guide * src) - mean_g * mean_s) / (blur(guide * guide) - mean_g * mean_g + eps)
    b = mean_s - a * mean_g
    return blur(a) * guide + blur(b)

def haze_color(frames):
    """The average color of the haziest 0.1% of pixels: the brightest spots in the dark channel."""
    pixels = np.concatenate([f.reshape(-1, 3) for f in frames])
    dark = np.concatenate([dark_channel(f).ravel() for f in frames])
    top = np.argsort(dark)[-max(1, len(dark) // 1000):]
    return pixels[top].mean(axis=0)

def dehaze(img, haze):
    clear = 1 - STRENGTH * dark_channel(img / haze)
    clear = guided_filter(cv2.cvtColor(img, cv2.COLOR_RGB2GRAY), clear, SMOOTH)
    clear = np.maximum(clear, MIN_CLEAR)[..., None]
    base = cv2.GaussianBlur(img, (0, 0), GRAIN)
    return np.clip((base - haze) / clear + haze + (img - base), 0, 1)

def probe(path):
    info = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
                                               "stream=width,height", "-of", "json", path]))["streams"][0]
    return info["width"], info["height"]

def read_frames(path):
    ff = subprocess.Popen(["ffmpeg", "-v", "error", "-i", path, "-r", "30", "-f", "rawvideo", "-pix_fmt", "rgb24", "-"],
                          stdout=subprocess.PIPE)
    while chunk := ff.stdout.read(w * h * 3):
        yield np.frombuffer(chunk, np.uint8).reshape(h, w, 3).astype(np.float32) / 255
    ff.wait()

src, out = sys.argv[1:3]
w, h = probe(src)
frames = list(read_frames(src))
haze = haze_color(frames[::max(1, len(frames) // SAMPLES)])
ff = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}", "-r", "30",
                       "-i", "-", "-i", src, "-map", "0:v", "-map", "1:a?", "-c:v", "libx264", "-crf", "18",
                       "-pix_fmt", "yuv420p", "-c:a", "copy", "-shortest", out], stdin=subprocess.PIPE)
for f in frames:
    ff.stdin.write((dehaze(f, haze) * 255 + 0.5).astype(np.uint8).tobytes())
ff.stdin.close()
if ff.wait():
    sys.exit("ffmpeg failed")
```

## More color looks

- [Apply a LUT **Apply a LUT**](https://www.recharm.com/effects/apply-a-lut)
- [Black and white **Black and white**](https://www.recharm.com/effects/black-and-white)
- [Bleach bypass **Bleach bypass**](https://www.recharm.com/effects/bleach-bypass)
- [Cinematic color grade **Cinematic color grade**](https://www.recharm.com/effects/cinematic-color-grade)
- [Color match **Color match**](https://www.recharm.com/effects/color-match)

## Find 3x _winning ads_

Recharm cuts your ad footage into tagged, searchable clips: hooks, testimonials, product shots. Find the shots that win, add the effect with Claude, and ship 3x more ad edits.

[Start 14-day free trial](https://www.recharm.com/get-started)
