snibgo's ImageMagick pages

Setting the mean

We can tweak an image to match the brightness (and colour) of another.

Sample inputs

As example input images, we use:

set SRCA=dpt_lvs_sm.jpg
dpt_lvs_sm.jpg
set SRCB=toes_holed.png

This image is mostly transparent.

toes_holed.png

The methods

We want to change an image so the means of the colour channels become the desired results. In the general case we have three desired means, one per channel, so we will apply a process to each channel. There are many possible methods for this, but four simple methods are:

  1. Multiply values in each channel by a desired gain.
    vOut = vIn * gain
    where gain = new_mean / old_mean
    Zero values will remain at zero, with the greatest changes occuring at high values.

    If this process increases the mean of an image, its contrast will also increase.

  2. Add a desired bias to values in each channel.
    vOut = vIn + bias
    where bias = new_mean - old_mean

    If this process increases the mean of an image, highlights and shadows will increase by the same amount.

  3. Raise values to a desired power.
    vOut = vIn ^ pow
    where pow = log(new_mean) / log(old_mean)

    Zero values will remain at zero, 100% will remain at 100%, with the greatest changes occuring at middle values.

    If this process increases the mean of an image, its shadows will increase in contrast while highlights will decrease.

  4. Apply a process I call negative gain. Like gain, this changes the slope of the transfer curve, but instead of rotating it around 0%, the rotation is at 100%. We could implement this by negating the image, multiplying by the gain, then negating again. However, the script uses "-function polynomial" to achieve the same result.
    vOut = 1 - [ (1-vIn) * gain ]

    ... or ...

    vOut = vIn * gain + bias
    where gain = (new_mean - 1.0) / (old_mean - 1.0)
    and bias = 1.0 - gain

    100% values will remain at 100%, with the greatest changes occuring at low values.

    If this process increases the mean of an image, its contrast will decrease.

The scripts setMeanGain.bat, setMeanBias.bat, setMeanPow.bat and setMeanNegGain.bat implement these four methods. They take parameters:

Note that the gain, bias and negative gain methods can create values beyond 0 to 100%. If HDRI is not used, these will be clipped to the limits. Clipping may cause the new means not to be as predicted. If HDRI is used, channels will not be clipped, and Putting OOG back in the box could be used to push results into the range 0 to 100%.

The scripts can fail (gracefully) if the mean of any channel is zero or 100%, from division by zero. I use the scripts for ordinary photos where this doesn't occur. The scripts could be modified to handle graphics images that do average zero or 100%, at the expense of complexity and performance.

We change channels independently, so hues will generally shift. Indeed, these techniques can be considered as methods to change colour balance. Alternatively, the methods could be used to change just the L channel of images in HCL or Lab colorspaces.

The gain, bias and negative gain methods are all simplifications of the Gain and bias method.

Common standard

We might set any number of images to a common standard.

For example, we use the three methods to set the leaves image to a mean of 0.5 in all three channels.

call %PICTBAT%setMeanGain ^
  %SRCA% sm_cs1.png 0.5
sm_cs1.pngjpg
call %PICTBAT%setMeanBias ^
  %SRCA% sm_cs2.png 0.5
sm_cs2.pngjpg
call %PICTBAT%setMeanPow ^
  %SRCA% sm_cs3.png 0.5
sm_cs3.pngjpg
call %PICTBAT%setMeanNegGain ^
  %SRCA% sm_cs4.png 0.5
sm_cs4.pngjpg

For the aesthetics of ordinary photographs we usually want shadows to stay dark, which happens in the gain and power methods. The bias and negative gain methods tend to brighten shadows so are less useful.

Reference image

The script setMeanImg.bat finds the mean values of a reference image (%2), and calls one of the above scripts with those values. Hence, it transforms an image to look more like another image.

For example, we change the leaves image to look more like the grass.

call %PICTBAT%setMeanImg ^
  %SRCA% %SRCB% sm_ri1.png gain
sm_ri1.pngjpg
call %PICTBAT%setMeanImg ^
  %SRCA% %SRCB% sm_ri2.png bias
sm_ri2.pngjpg
call %PICTBAT%setMeanImg ^
  %SRCA% %SRCB% sm_ri3.png pow
sm_ri3.pngjpg
call %PICTBAT%setMeanImg ^
  %SRCA% %SRCB% sm_ri4.png neggain
sm_ri4.pngjpg

Future

As noted above, some of these methods can cause values to go beyond 0 to 100%, and these will be clipped. The methods could use minima and maxima values from the image to determine the transformation closest to the desired mean that doesn't quite cause clipping.

The scripts setMean*.bat take statistics from an image, then modify that same image. Hence, they can't be used directly to adjust an entire image so that only a portion becomes the desired mean.

Scripts

For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.

setMeanGain.bat

rem From image %1, creates output %2 setting the mean by the "gain" method
rem (multiplying channel values).
@rem
@rem New mean is either %3 only (all 3 channels)
@rem or %3, %4 and %5 (each channel separately).
@rem New means should generally be in range 0.0 to 1.0.
@rem
@rem If any channel has mean of zero, this will fail with division by zero.
@rem
@rem Updated:
@rem   4-August-2022 for IM v7.
@rem

@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 smg

set OUTF=%2
if "%OUTF%"=="." set OUTF=
if "%OUTF%"=="" set OUTF=%OUTFILE%
set OUTFILE=%OUTF%

set new_mn_R=%3
set new_mn_G=%4
set new_mn_B=%5

if "%new_mn_R%"=="." set new_mn_R=0.5
if "%new_mn_G%"=="." set new_mn_G=0.5
if "%new_mn_B%"=="." set new_mn_B=0.5

if "%new_mn_R%"=="" set new_mn_R=0.5
if "%new_mn_G%"=="" set new_mn_G=%new_mn_R%
if "%new_mn_B%"=="" set new_mn_B=%new_mn_R%

echo new_mn_R=%new_mn_R% new_mn_G=%new_mn_G% new_mn_B=%new_mn_B%

set sFMT=^
gn_R=%%[fx:%new_mn_R%/p{0.0}.r]\n^
gn_G=%%[fx:%new_mn_G%/p{0.0}.g]\n^
gn_B=%%[fx:%new_mn_B%/p{0.0}.b]

set gn_R=
set gn_G=
set gn_B=

for /F "usebackq" %%L in (`%IMG7%magick ^
  %INFILE% ^
  -precision 19 ^
  -scale "1x1^!" ^
  -format "%sFMT%" ^
  info:`) do set %%L

if "%gn_R%"=="" exit /B 1
if "%gn_G%"=="" exit /B 1
if "%gn_B%"=="" exit /B 1

%IMG7%magick ^
  %INFILE% ^
  -channel R -evaluate Multiply %gn_R% ^
  -channel G -evaluate Multiply %gn_G% ^
  -channel B -evaluate Multiply %gn_B% ^
  +channel ^
  %OUTFILE%

if ERRORLEVEL 1 exit /B 1


call echoRestore

endlocal & set smgOUTFILE=%OUTFILE%

setMeanBias.bat

rem From image %1, creates output %2 setting the mean by the "bias" method
rem (adding to channel values).
@rem
@rem New mean is either %3 only (all 3 channels)
@rem or %3, %4 and %5 (each channel separately).
@rem New means should generally be in range 0.0 to 1.0.
@rem
@rem Updated:
@rem   4-August-2022 for IM v7.
@rem

@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 sma

set OUTF=%2
if "%OUTF%"=="." set OUTF=
if "%OUTF%"=="" set OUTF=%OUTFILE%
set OUTFILE=%OUTF%

set new_mn_R=%3
set new_mn_G=%4
set new_mn_B=%5

if "%new_mn_R%"=="." set new_mn_R=0.5
if "%new_mn_G%"=="." set new_mn_G=0.5
if "%new_mn_B%"=="." set new_mn_B=0.5

if "%new_mn_R%"=="" set new_mn_R=0.5
if "%new_mn_G%"=="" set new_mn_G=%new_mn_R%
if "%new_mn_B%"=="" set new_mn_B=%new_mn_R%

echo new_mn_R=%new_mn_R% new_mn_G=%new_mn_G% new_mn_B=%new_mn_B%

set sFMT=^
bias_R=%%[fx:100*(%new_mn_R%-p{0.0}.r)]\n^
bias_G=%%[fx:100*(%new_mn_G%-p{0.0}.g)]\n^
bias_B=%%[fx:100*(%new_mn_B%-p{0.0}.b)]

for /F "usebackq" %%L in (`%IMG7%magick ^
  %INFILE% ^
  -precision 19 ^
  -scale "1x1^!" ^
  -format "%sFMT%" ^
  info:`) do set %%L

%IMG7%magick ^
  %INFILE% ^
  -channel R -evaluate Add %bias_R%%% ^
  -channel G -evaluate Add %bias_G%%% ^
  -channel B -evaluate Add %bias_B%%% ^
  +channel ^
  %OUTFILE%


call echoRestore

endlocal & set smaOUTFILE=%OUTFILE%

setMeanPow.bat

rem From image %1, creates output %2 setting the mean by the "power" method
rem (raising channel values be a power).
@rem
@rem New mean is either %3 only (all 3 channels)
@rem or %3, %4 and %5 (each channel separately).
@rem New means should generally be in range 0.0 to 1.0.
@rem
@rem If any channel has mean of zero, this will fail with division by zero.
@rem
@rem Updated:
@rem   4-August-2022 for IM v7.
@rem

@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 smp

set OUTF=%2
if "%OUTF%"=="." set OUTF=
if "%OUTF%"=="" set OUTF=%OUTFILE%
set OUTFILE=%OUTF%

set new_mn_R=%3
set new_mn_G=%4
set new_mn_B=%5

if "%new_mn_R%"=="." set new_mn_R=0.5
if "%new_mn_G%"=="." set new_mn_G=0.5
if "%new_mn_B%"=="." set new_mn_B=0.5

if "%new_mn_R%"=="" set new_mn_R=0.5
if "%new_mn_G%"=="" set new_mn_G=%new_mn_R%
if "%new_mn_B%"=="" set new_mn_B=%new_mn_R%

set sFMT=^
pow_R=%%[fx:log(%new_mn_R%)/log(p{0.0}.r)]\n^
pow_G=%%[fx:log(%new_mn_G%)/log(p{0.0}.g)]\n^
pow_B=%%[fx:log(%new_mn_B%)/log(p{0.0}.b)]

set pow_R=
set pow_G=
set pow_B=

for /F "usebackq" %%L in (`%IMG7%magick ^
  %INFILE% ^
  -precision 19 ^
  -scale "1x1^!" ^
  -format "%sFMT%" ^
  info:`) do set %%L

if "%pow_R%"=="" exit /B 1
if "%pow_G%"=="" exit /B 1
if "%pow_B%"=="" exit /B 1

%IMG7%magick ^
  %INFILE% ^
  -channel R -evaluate Pow %pow_R% ^
  -channel G -evaluate Pow %pow_G% ^
  -channel B -evaluate Pow %pow_B% ^
  +channel ^
  %OUTFILE%

if ERRORLEVEL 1 exit /B 1


call echoRestore

endlocal & set smgOUTFILE=%OUTFILE%

setMeanNegGain.bat

rem From image %1, creates output %2 setting the mean by the "negative gain" method
@rem
@rem New mean is either %3 only (all 3 channels)
@rem or %3, %4 and %5 (each channel separately).
@rem New means should generally be in range 0.0 to 1.0.
@rem
@rem If any channel has mean of 100%, this will fail with division by zero.
@rem
@rem Updated:
@rem   4-August-2022 for IM v7.
@rem

@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 smng

set OUTF=%2
if "%OUTF%"=="." set OUTF=
if "%OUTF%"=="" set OUTF=%OUTFILE%
set OUTFILE=%OUTF%

set new_mn_R=%3
set new_mn_G=%4
set new_mn_B=%5

if "%new_mn_R%"=="." set new_mn_R=0.5
if "%new_mn_G%"=="." set new_mn_G=0.5
if "%new_mn_B%"=="." set new_mn_B=0.5

if "%new_mn_R%"=="" set new_mn_R=0.5
if "%new_mn_G%"=="" set new_mn_G=%new_mn_R%
if "%new_mn_B%"=="" set new_mn_B=%new_mn_R%

echo new_mn_R=%new_mn_R% new_mn_G=%new_mn_G% new_mn_B=%new_mn_B%

set sFMT=^
gn_R=%%[fx:(%new_mn_R%-1)/(p{0.0}.r-1)]\n^
gn_G=%%[fx:(%new_mn_G%-1)/(p{0.0}.g-1)]\n^
gn_B=%%[fx:(%new_mn_B%-1)/(p{0.0}.b-1)]

set gn_R=
set gn_G=
set gn_B=

for /F "usebackq" %%L in (`%IMG7%magick ^
  %INFILE% ^
  -precision 19 ^
  -scale "1x1^!" ^
  -format "%sFMT%" ^
  info:`) do set %%L

if "%gn_R%"=="" exit /B 1
if "%gn_G%"=="" exit /B 1
if "%gn_B%"=="" exit /B 1

for /F "usebackq" %%L in (`%IMG7%magick identify ^
  -precision 19 ^
  -format "bs_R=%%[fx:1-%gn_R%]\nbs_G=%%[fx:1-%gn_G%]\nbs_B=%%[fx:1-%gn_B%]" ^
  xc:`) do set %%L

%IMG7%magick ^
  %INFILE% ^
  -channel R ^
    -function Polynomial %gn_R%,%bs_R% ^
  -channel G ^
    -function Polynomial %gn_G%,%bs_G% ^
  -channel B ^
    -function Polynomial %gn_B%,%bs_B% ^
  +channel ^
  %OUTFILE%

if ERRORLEVEL 1 exit /B 1

call echoRestore

endlocal & set smngOUTFILE=%OUTFILE%

setMeanImg.bat

rem From image %1 and reference image %2.
rem changes channel means of %1 to match %2,
rem writing to output %3.
rem %4 is optional keyword for method: gain or bias or pow or neggain (and case).
@rem
@rem Updated:
@rem   4-August-2022 for IM v7.
@rem

@if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 smi

set REF_IMG=%2

set OUTF=%3
if "%OUTF%"=="." set OUTF=
if "%OUTF%"=="" set OUTF=%OUTFILE%
set OUTFILE=%OUTF%

set METH=%4
if "%METH%"=="." set METH=
if "%METH%"=="" set METH=gain


set mn_R=
set sFMT=^
mn_R=%%[fx:p{0.0}.r]\n^
mn_G=%%[fx:p{0.0}.g]\n^
mn_B=%%[fx:p{0.0}.b]

for /F "usebackq" %%L in (`%IMG7%magick ^
  %REF_IMG% ^
  -precision 19 ^
  -scale "1x1^!" ^
  -format "%sFMT%" ^
  info:`) do set %%L
if "%mn_R%"=="" exit /B 1

if /I "%METH%"=="gain" (
  call %PICTBAT%setMeanGain %INFILE% %OUTFILE% %mn_R% %mn_G% %mn_B%
  if ERRORLEVEL 1 exit /B 1
) else if /I "%METH%"=="bias" (
  call %PICTBAT%setMeanBias %INFILE% %OUTFILE% %mn_R% %mn_G% %mn_B%
  if ERRORLEVEL 1 exit /B 1
) else if /I "%METH%"=="pow" (
  call %PICTBAT%setMeanPow %INFILE% %OUTFILE% %mn_R% %mn_G% %mn_B%
  if ERRORLEVEL 1 exit /B 1
) else if /I "%METH%"=="neggain" (
  call %PICTBAT%setMeanNegGain %INFILE% %OUTFILE% %mn_R% %mn_G% %mn_B%
  if ERRORLEVEL 1 exit /B 1
) else (
  exit /B 1
)

call echoRestore

endlocal & set smiOUTFILE=%OUTFILE%

All images on this page were created by the commands shown, using:

%IMG7%magick -version
Version: ImageMagick 7.1.0-62 Q16-HDRI x64 32ce406:20230212 https://imagemagick.org
Copyright: (C) 1999 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI OpenCL 
Delegates (built-in): bzlib cairo freetype gslib heic jng jp2 jpeg jxl lcms lqr lzma openexr pangocairo png ps raqm raw rsvg tiff webp xml zip zlib
Compiler: Visual Studio 2022 (193431937)

Source file for this web page is setmean.h1. To re-create this web page, execute "procH1 setmean".


This page, including the images, is my copyright. Anyone is permitted to use or adapt any of the code, scripts or images for any purpose, including commercial use.

Anyone is permitted to re-publish this page, but only for non-commercial use.

Anyone is permitted to link to this page, including for commercial use.


Page version v1.0 26-Nov-2015.

Page created 20-Jul-2023 09:57:37.

Copyright © 2023 Alan Gibson.