We can tweak an image to match the brightness (and colour) of another.
As example input images, we use:
set SRCA=dpt_lvs_sm.jpg |
|
set SRCB=toes_holed.png This image is mostly transparent. |
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:
vOut = vIn * gain where gain = new_mean / old_meanZero 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.
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.
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.
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.
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 |
|
call %PICTBAT%setMeanBias ^ %SRCA% sm_cs2.png 0.5 |
|
call %PICTBAT%setMeanPow ^ %SRCA% sm_cs3.png 0.5 |
|
call %PICTBAT%setMeanNegGain ^ %SRCA% sm_cs4.png 0.5 |
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.
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 |
|
call %PICTBAT%setMeanImg ^ %SRCA% %SRCB% sm_ri2.png bias |
|
call %PICTBAT%setMeanImg ^ %SRCA% %SRCB% sm_ri3.png pow |
|
call %PICTBAT%setMeanImg ^ %SRCA% %SRCB% sm_ri4.png neggain |
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.
For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.
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%
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%
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%
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%
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.