Cleaning

White balance and histogram normalization for batch photos

The short answer

White balance removes a color cast by multiplying the red, green and blue channels by separate gains, so neutral things come out neutral. Auto white balance has to guess those gains, and the common gray-world method assumes the average color of a scene is gray. Histogram normalization stretches each channel so its darkest value becomes black and its brightest becomes white, which revives faded or hazy images. In a batch, apply one correction to images shot under the same light, fix outliers individually, and leave art and deliberately graded images alone.

Batch photos rarely match. A phone on auto white balance makes a fresh guess for every shot, indoor bulbs are orange, shade is blue, and old scans fade unevenly. One at a time, each image looks fine. Side by side in a training set, the differences jump out.

This guide explains what the two most useful batch fixes actually do to your pixels, with small worked examples. The formulas match how ImgTagger's batch color fixer calculates them, and we point out where other tools differ.

What a color cast is

Light sources have different colors. Tungsten bulbs are strongly orange, open shade is bluish, and some LEDs and fluorescent tubes add a green or magenta tint. Our eyes adapt, so paper looks white under all of them. A camera has to make that adjustment explicitly, and when it guesses wrong, every pixel picks up the same tint: a color cast.

In numbers, a cast is one channel running consistently high or low. A gray wall that should read about (140, 140, 140) might read (175, 140, 105) under a warm bulb: too much red, too little blue.

White balance is three multipliers

Most white balance corrections, ImgTagger's included, come down to a gain for each channel. Every red value is multiplied by the red gain, and likewise for green and blue. One set of gains covers the whole image, so making one neutral area neutral fixes the cast everywhere.

The hard part is choosing the gains. You can measure a neutral reference (a gray card, or a white wall or sheet of paper in the shot), use a preset for a known light source, or let an algorithm guess, which is what "auto" means.

From a color-picker reading to gains

Say a wall you know is neutral reads (200, 180, 150) in an image editor's color picker. Keep green as the reference and divide it by each channel:

red gain   = 180 / 200 = 0.90
green gain = 180 / 180 = 1.00
blue gain  = 180 / 150 = 1.20

Apply those gains and the wall becomes (180, 180, 180). In ImgTagger, enter them under White Balance → Custom. Green makes a good reference because it carries most of the brightness we perceive, so exposure barely changes.

The gray-world assumption

Auto white balance can't know which objects are neutral, so it needs a rule of thumb. The classic one is the gray-world assumption: across a varied scene, colors average out to gray, so any difference from gray must be the cast. ImgTagger's Auto mode applies exactly this rule, averaging every pixel in the image. An illustrative calculation:

  1. Average every pixel's red, green and blue values. Say the result is R 150, G 120, B 90.
  2. Average those three: (150 + 120 + 90) / 3 = 120. That is the target gray.
  3. Divide the target by each channel's average: red 120/150 = 0.80, green 120/120 = 1.00, blue 120/90 = 1.33.
PixelBefore (R, G, B)After ×0.80, ×1.00, ×1.33
Gray wall175, 140, 105140, 140, 140
White shirt240, 230, 210192, 230, 255 (blue was 280, clipped)

The wall is fixed. The shirt shows the main risk of gains above 1: values run past 255 and get clipped, tinting highlights slightly blue or cyan. ImgTagger clips only after brightness and contrast, so a small negative brightness can pull those values back into range.

Where gray-world fails

The assumption breaks when a scene really is dominated by one color: a forest, a beach, a sunset, a red car filling the frame. Auto then "corrects" real color by tinting toward the opposite hue, magenta for the forest and blue for the sunset. Other methods, such as white-patch (assume the brightest area is white), fail on other scenes. A measured reference beats any guess.

One Auto for the batch, or one per image?

For photos taken under one light, one correction applied to all of them usually beats correcting each separately. Per-image gray-world forces every average to gray, so a photo of someone in a red coat gets pushed toward cyan while the next shot doesn't. ImgTagger's global Auto measures the image you are previewing when you select it and applies those gains to the whole batch, so pick a representative image with varied colors first. Setting Auto on a single image instead, in its per-image controls, measures that image's own pixels, which suits the odd outlier.

Presets, auto and custom

Mode in ImgTaggerGains (R, G, B)Use it when
Warm Sunset1.15, 1.00, 0.90Images look too blue (shade, overcast, cool LEDs), or you want warmth
Cool Daylight0.90, 1.00, 1.15Images look too orange (tungsten bulbs, candlelight)
Neutral Indoor0.95, 1.00, 1.08Indoor shots are slightly orange from warm bulbs and Cool Daylight is too strong
AutoCalculated (gray-world)A batch from one light, measured on a varied image
CustomYour valuesYou measured a neutral area, or presets go too far

Warm Sunset and Cool Daylight are named for the look they produce, not the lighting they fix. A sunset photo is already orange, so to neutralize it you would reach for Cool Daylight. Neutral Indoor is the exception: its name describes the goal, a neutral result from warm indoor light. Fixed presets treat every image identically, which makes them the most predictable choice for large batches.

What a histogram shows

A histogram counts how many pixels sit at each level from 0 to 255, usually per channel:

  • Bunched to the left: dark or underexposed.
  • Bunched to the right: bright or overexposed.
  • A narrow hump with nothing near 0 or 255: flat, hazy or faded, like an old print scan.
  • A spike at 0 or 255: clipping, where detail is already lost.

When the red, green and blue histograms sit at different positions, you are looking at a color cast.

Histogram normalization (stretching)

Normalization, also called a contrast stretch, maps the range each channel actually uses onto the full 0–255 range. ImgTagger applies this to each channel:

new value = (value − darkest) / (brightest − darkest) × 255

Here is an illustrative faded scan:

ChannelDarkestBrightestStretch factorSample pixel
Red40220255/180 ≈ 1.42130 → 128
Green30200255/170 = 1.50115 → 128
Blue50180255/130 ≈ 1.96115 → 128

The darkest values become black and the brightest become white, so the image gains contrast. And because each channel stretches separately, the slightly warm pixel (130, 115, 115) became neutral (128, 128, 128). Per-channel normalization doubles as a rough white balance, which helps faded prints and hurts images whose color is meant to lean one way.

The risks

  • Outliers block it. ImgTagger's plain min-max stretch uses the single darkest and brightest value. One black border pixel and one white highlight per channel, and nothing changes. Editors' "auto contrast" features typically ignore a small share of extreme pixels for this reason.
  • It shifts colors. A deliberately warm scene can lose its warmth along with its haze.
  • It amplifies noise. Above, blue is multiplied by almost 2, and so is any noise or JPEG blockiness in it.
  • It can cause banding. That blue channel used 131 distinct levels and still has only 131 after stretching, now with gaps between them. Smooth skies can show visible steps.
  • One image's range clips the others. Applying one image's range to a batch, as ImgTagger's global toggle does, clips pixels in other images that fall outside it. The per-image On setting measures each image's own range instead.

Normalization is not histogram equalization, which reshapes the histogram so pixel counts spread evenly. Equalization is far more aggressive and often looks unnatural on photos. ImgTagger doesn't offer it.

Gamma vs brightness vs contrast

These three are easy to confuse. Take a shadow (0), a midtone (64) and a highlight (230), and use each control to lift the midtone to about 101:

Adjustment0 →64 →230 →Effect
Brightness +3737101255Blacks turn gray; the highlight clips
Gamma 1.50101238Black and white stay put; midtones lift
Contrast +58 (ImgTagger)0101255Multiplies by 1.58, so highlights clip first

For exposure fixes, gamma is usually the gentlest choice because it bends the tone curve without moving pure black or white. Brightness shifts every value equally and washes out shadows. Most editors pivot contrast around mid-gray, but ImgTagger's multiplies from black, so on its own it acts like an exposure boost. Gamma conventions differ too: in ImgTagger each value is raised to the power 1/gamma, so a gamma above 1 brightens, while some tools work the other way round.

Why consistent color matters for datasets

A model learns whatever is consistent across its training images. If most photos of a subject share a warm cast, a fine-tuned model can learn the cast as part of the subject. Mixed casts cause a different problem: the same object appears in several colors, and the color words in your captions may not match the pixels.

Color also affects tagging. Auto-taggers read hair, eye and clothing colors from pixels, so a strong cast can change the color tags you get, and fully desaturated images tend to be tagged greyscale or monochrome. Fix color after removing duplicates and before running the auto tagger. The dataset preparation workflow puts all the steps in order.

Color fixes rarely interfere with duplicate detection. ImgTagger's duplicate finder compares small, normalized grayscale versions of each image (see how perceptual hashing works), so a uniform brightness or contrast change barely moves the result.

When to leave color alone

  • Illustrations and digital art. The colors were chosen, not captured, and gray-world will damage a deliberate palette.
  • Style datasets. If the goal is a look, such as a film stock or golden-hour warmth, the "cast" is the style.
  • Intentional mood. Candlelit rooms, sunsets and night scenes are meant to look warm or blue.
  • Monochrome and sepia. Per-channel gains or stretching add tints that were never there.
  • Images that already look consistent. Every export re-encodes JPEG and WebP files, which costs a little quality.

When in doubt, correct a copy, compare the thumbnails side by side, and keep whichever version looks more like the scene did.

Put this into practice

Apply white balance, histogram normalization, brightness, contrast, saturation and gamma to many images at once. It runs in your browser. Open the Batch Color Fixer.

Frequently asked questions

Should I crop or resize before color correcting?

Crop first if you can. Auto white balance and normalization both measure the whole image, so borders, frames and letterbox bars skew the averages and the extremes. Resizing hardly changes the averages, but downscaling blends away single-pixel extremes, which can let normalization stretch a little further.

Is it better to fix white balance in RAW files?

Yes, when you have them. A raw file holds more than 8 bits per channel and stores white balance as a setting rather than baking it in, so a raw converter can change it without clipping or banding. Browser tools such as ImgTagger work on decoded 8-bit images.

How do temperature and tint sliders relate to channel gains?

They are friendlier controls for the same idea. Temperature moves the balance between blue and amber, and tint between green and magenta. ImgTagger exposes the gains directly: to warm an image slightly, raise red a little and lower blue; to remove a green tint, lower green slightly.

How can I tell if a batch has a color cast?

Look at things that should be neutral, such as white shirts, gray pavement and paper, and compare skin tones across images. Casts are much easier to spot in a grid of thumbnails than one image at a time. In an editor with separate red, green and blue histograms, channels at clearly different positions point to a cast.