Equalising an image is often useful, but can uglify photographs. When the contrast introduced by equalisation is limited, results are more pleasing. The process can be adapted across the image.
This page builds on concepts developed in Blending tiles and Adaptive auto level and gamma
Scripts on this page assume that the version of ImageMagick in %IM7DEV% has been built with various process modules. See Process modules.
This page also experiments with a technique that makes an image's histogram Gaussian, ie bell-shaped.
An ImageMagick bug in version 6.9.1-6 causes "histogram:" images to be of MIFF format, even though they are named ".PNG". See bug report Histogram images. So I use an older version of IM for that task:
set IMH=%IMG691%
See:
This page uses two sample input files. They were both created by dcraw from 14-bit NEF files from a Nikon D800 camera, with gammas that maximised the standard deviation.
All operations are performed on the full-size images, but smaller versions are made for the web.
set IMH=%IM% set IMG=%IMG71120b%
set WEB_SIZE=-resize 600x600 -quality 40 set SRC1=eql_src1.tiff if not exist %SRC1% %IMG7%magick ^ %PICTLIB%20130713\AGA_1372_sRGB.tiff ^ -crop 5120x4840+0+0 +repage ^ %SRC1% %IMG7%magick ^ %SRC1% ^ -define histogram:unique-colors=false ^ +write histogram:eql_src1_hist.png ^ %WEB_SIZE% ^ eql_src1_sm.jpg |
|
set SRC2=eql_src2.tiff if not exist %SRC2% %IMG7%magick ^ %PICTLIB%20141203\AGA_2159_gms.tiff ^ -crop 5684x4864+1100+60 +repage ^ %SRC2% %IMG7%magick ^ %SRC2% ^ -define histogram:unique-colors=false ^ +write histogram:eql_src2_hist.png ^ %WEB_SIZE% ^ eql_src2_sm.jpg This very high contrast scene has clipped,
|
IM's "-equalization" is a useful first-pass tool for seeing what an image contains. It spreads the values equally so the same number of pixels are around 1% as around 50%. As photographs tend to have bell-shaped histograms, this increases the contrast in the centre, and decreases contrast in both shadows and highlights.
%IMG7%magick ^ %SRC1% ^ -equalize ^ -define histogram:unique-colors=false ^ +write histogram:eql_src1_eq_hist.png ^ %WEB_SIZE% ^ eql_src1_eq_sm.jpg Looks horrible. Shadows have clipped. |
|
%IMG7%magick ^ %SRC2% ^ -equalize ^ -define histogram:unique-colors=false ^ +write histogram:eql_src2_eq_hist.png ^ %WEB_SIZE% ^ eql_src2_eq_sm.jpg Looks okay. Highlights are clipped. |
Aesthetically, I like the result from the second image but not the first.
The difficulty with the first image is the narrow peak, with few pixels in the shadows or hightlights. This results in many pixels being pushed into the shadows and highlights.
We can equalise just the Lightness channel of the image in Lab colour space.
%IMG7%magick ^ %SRC1% ^ -colorspace Lab -channel R ^ -equalize ^ +channel -colorspace sRGB ^ -define histogram:unique-colors=false ^ +write histogram:eql_src1_eqt_hist.png ^ %WEB_SIZE% ^ eql_src1_eqt_sm.jpg |
|
%IMG7%magick ^ %SRC2% ^ -colorspace Lab -channel R ^ -equalize ^ +channel -colorspace sRGB ^ -define histogram:unique-colors=false ^ +write histogram:eql_src2_eqt_hist.png ^ %WEB_SIZE% ^ eql_src2_eqt_sm.jpg |
The results look somewhat artificial, with a good spread of tones but not of colours. The first image looks like a black and white photograph that has been coloured by hand.
We can fairly easily prevent clipping.
The script equSlope.bat takes an image, makes a histogram, calls equSlopeH.bat to process it, and applies the returned cumulative histogram as a clut to the image.
The script equSlopeH.bat takes a histogram and makes a cumulative histogram with specified toe and highlight slopes.
Sensible values for the four numeric parameters come from an examination of the histograms. FUTURE: I may write something to automate this. (A de-clipping script would also be useful.)
This doesn't remove clipping that has already occurred. It merely reduces or eliminates clipping caused by equalisation.
set eqshDEBUG=1 call %PICTBAT%equSlope %SRC1% 10 50 20 95 %IMG7%magick ^ %eqsOUTFILE% ^ -define histogram:unique-colors=false ^ +write histogram:eql_src1_eqs_hist.png ^ %WEB_SIZE% ^ eql_src1_eqs_sm.jpg There is no clipping. |
|
call %PICTBAT%equSlope %SRC2% 2 98 set eqshDEBUG= %IMG7%magick ^ %eqsOUTFILE% ^ -define histogram:unique-colors=false ^ +write histogram:eql_src2_eqs_hist.png ^ %WEB_SIZE% ^ eql_src2_eqs_sm.jpg Looks okay. It doesn't remove clipping that is already present. |
The script eqLimit.bat applies an equalisation to an image. It works by creating the histogram of the image, cumulating the histogram, and using this as a clut on the image. It usually limits the increase in contrast by capping the height of the histogram, and redistributing those counts between all the buckets, and repeating until the amount redistributed is less than or equal to 1%. (This threshold is modifiable: eqlDIFF_LIMIT.)
The parameter %2 is a standard deviation factor, sd_fact. It caps the height of the histogram to (mean + sd_fact * standard-deviation), and hence limits the slope of the clut, which in turn limits the increase in contrast.
This is the "contrast-limited" part of the process. It doesn't reduce or limit the contrast of the image. Rather, it limits the increase of contrast caused by equalisation.
The default value for %2 is 1.0, which seems good for most photographs, including those with a small mean. Setting it to 0.0 would limit the histogram to its mean, resulting in a small shift in contrast, so there is almost no equalisation. Setting it to a high value such as 9999 will mean (for ordinary photographs) there is no contrast limit, and the result is a conventional equalisation. I find that values between 0.0 and 3.0 are useful.
For example:
A tight limit, 0.5. call %PICTBAT%eqLimit %SRC1% 0.5 . . eql_src1_0_5.tiff %IMG7%magick ^ eql_src1_0_5.tiff ^ -define histogram:unique-colors=false ^ +write histogram:eql_src1_0_5_hist.png ^ %WEB_SIZE% ^ eql_src1_0_5_sm.jpg |
|
A looser limit, 2.0, gives a greater increase in contrast. call %PICTBAT%eqLimit %SRC1% 2.0 . . eql_src1_2_0.tiff %IMG7%magick ^ eql_src1_2_0.tiff ^ -define histogram:unique-colors=false ^ +write histogram:eql_src1_2_0_hist.png ^ %WEB_SIZE% ^ eql_src1_2_0_sm.jpg The greater contrast has increased the saturation. |
Both images, with default parameters to the script:
set eqlDEBUG=1 call %PICTBAT%eqLimit %SRC1% . . . eql_src1_cle.tiff %IMG7%magick ^ eql_src1_cle.tiff ^ -define histogram:unique-colors=false ^ +write histogram:eql_src1_cle_hist.png ^ %WEB_SIZE% ^ eql_src1_cle_sm.jpg |
|
:skip call %PICTBAT%eqLimit %SRC2% . . . eql_src2_cle.tiff %IMG7%magick ^ eql_src2_cle.tiff ^ -define histogram:unique-colors=false ^ +write histogram:eql_src2_cle_hist.png ^ %WEB_SIZE% ^ eql_src2_cle_sm.jpg set eqlDEBUG= |
Aesthetically, I like both results. The skin tones and colours look natural.
For the first image, the capped and normalised histogram from the input is:
This histogram is the same as the first histogram shown at the top of this page, but with all three channels shown together, and capped.
This histogram is cumulated, and this is used as a clut:
The bottom-left of this clut is nearly flat, so it will darken shadows.
Two parameters to the eqLimit.bat script allow us to increase the contrast in the shadows or highlights.
%3 is LIFT_SHADOW_PC, the percentage to raise the shadow area of the input histogram, before cumulating it.
set eqlDEBUG=1 call %PICTBAT%eqLimit %SRC1% . 75 . eql_src1_cles.tiff %IMG7%magick ^ eql_src1_cles.tiff ^ -define histogram:unique-colors=false ^ +write histogram:eql_src1_cles_hist.png ^ %WEB_SIZE% ^ eql_src1_cles_sm.jpg |
The "75" parameter creates a shoulder at the shadow end of the input histogram curve, at 75% of the height, so the histogram becomes:
This shoulder steepens the start of the cumulated slope, which increases shadow contrast by lightening dark pixels without significantly changing the brightness of the rest of the image:
ASIDE: the script eqLimit.bat finds the first histogram entry that is above 75%. But this histogram is "combed", eg it has entries that alternate between 0 and 100% when these really represent 50%. The script "decombs" the histogram, rather crudely, by blurring.
Similarly, we can increase the contrast in the highlights, or "drop the highlights", by raising the right-side of the histogram by 5% with a "drop-highlight" parameter:
set eqlDEBUG=1 call %PICTBAT%eqLimit %SRC1% . 75 5 eql_src1_clesh.tiff %IMG7%magick ^ eql_src1_clesh.tiff ^ -define histogram:unique-colors=false ^ +write histogram:eql_src1_clesh_hist.png ^ %WEB_SIZE% ^ eql_src1_clesh_sm.jpg |
So the histogram is:
And the cumulated slope is:
Some points about the "lift-shadow" and "drop-highlight" parameters:
FUTURE: It may be more natural to specify shadow and highlight modification more directly as a tweak to the clut, eg "add a 1 in 2 gradient to the shadow portion until this intersects the existing curve."
This is also called "contrast-limited adaptive histogram equalisation", CLAHE.
As with Adaptive auto level and gamma, we can blend either in 2x2 tiles with blending across the entire image (script eqlQtr.bat), or any number of tiles with blending between centres (script eqlTile.bat).
eqlQtr.bat takes the same parameters as eqLimit.bat above:
Adaptive equalisation takes the data from a smaller area so gives greater local contrast than non-adaptive. For the first image, this darkens the shadows by too much, so I use %3=80.
The adaptive scripts work by dividing the image into tiles, calling eqLimit.bat for each tile to calculate the clut, then applying each clut to a copy of the full image, and finally blending the versions together.
The blending for eqlQtr.bat is simple, so is done within a magick command. For eqlTile.bat, the blending is more complex so is done by calling the script blendTile.bat.
call %PICTBAT%eqlQtr %SRC1% . 80 . eql_src1_eqlq.tiff %IMG7%magick ^ %eqlqOUTFILE% ^ -define histogram:unique-colors=false ^ +write histogram:eql_src1_eqlq_hist.png ^ %WEB_SIZE% ^ eql_src1_eqlq_sm.jpg This is good. The figures pop out, even with no added saturation or sharpening. |
|
:skip2 call %PICTBAT%eqlQtr %SRC2% . . . eql_src2_eqlq.tiff %IMG7%magick ^ %eqlqOUTFILE% ^ -define histogram:unique-colors=false ^ +write histogram:eql_src2_eqlq_hist.png ^ %WEB_SIZE% ^ eql_src2_eqlq_sm.jpg This is also good. |
eqlTile.bat takes two more parameters: the number of tiles horizontally and vertically.
call %PICTBAT%eqlTile %SRC1% 1.0 99 2 . . eql_src1_eqlt.tiff %IMG7%magick ^ %eqltOUTFILE% ^ -define histogram:unique-colors=false ^ +write histogram:eql_src1_eqlt_hist.png ^ %WEB_SIZE% ^ eql_src1_eqlt_sm.jpg Good, though the face on the left is in darker shadow than the previous version. |
|
call %PICTBAT%eqlTile %SRC2% 0.8 . . . . eql_src2_eqlt.tiff %IMG7%magick ^ %eqltOUTFILE% ^ -define histogram:unique-colors=false ^ +write histogram:eql_src2_eqlt_hist.png ^ %WEB_SIZE% ^ eql_src2_eqlt_sm.jpg Good.
|
My conclusion is that adaptive equalisation with eqlQtr.bat and eqlTile.bat gives great results, but needs care to prevent local contrast from becoming too strong.
For fully automated processing, eqLimit.bat is a good choice.
Since writing this page, ImageMagick has acquired a -clahe operation. This has a similar effect to my eqlTile.bat script, but doesn't need to write intermediate results to disk so is much faster.
For comparison with equalisation techniques, we can change the histogram to be a Gaussian curve.
To show this working, we make an image with Gaussian-distributed noise, then equalise the source's histogram to this using the matchHisto.bat script.
%IMG7%magick ^ -size 500x400 ^ xc:gray50 ^ -attenuate 2.0 ^ +noise Gaussian ^ -define histogram:unique-colors=false ^ +write histogram:eql_noise_hist.png ^ eql_noise.png The histogram shows the inevitable clipping at both ends. |
|
call %PICTBAT%matchHisto ^ %SRC1% eql_noise.png eql_match_noise.tiff %IMG7%magick ^ eql_match_noise.tiff ^ -define histogram:unique-colors=false ^ +write histogram:eql_match_noise_hist.png ^ %WEB_SIZE% ^ eql_match_noise_sm.jpg The histogram is practically perfect (including the clipping).
|
|
Make the lightness a Gaussian distribution. set mhCOL_SP_IN=-colorspace Lab -channel R set mhCOL_SP_OUT=+channel -colorspace sRGB call %PICTBAT%matchHisto ^ %SRC1% eql_noise.png eql_match_noise2.tiff set mhCOL_SP_IN= set mhCOL_SP_OUT= %IMG7%magick ^ eql_match_noise2.tiff ^ -define histogram:unique-colors=false ^ +write histogram:eql_match_noise2_hist.png ^ %WEB_SIZE% ^ eql_match_noise2_sm.jpg The RGB histograms are not perfect, but we have no colour-shifts. The result isn't great, but is acceptable first-pass automatic processing. |
|
set mhCOL_SP_IN=-colorspace Lab -channel R set mhCOL_SP_OUT=+channel -colorspace sRGB call %PICTBAT%matchHisto %SRC2% ^ eql_noise.png eql_match_s2_noise2.tiff set mhCOL_SP_IN= set mhCOL_SP_OUT= %IMG7%magick ^ eql_match_s2_noise2.tiff ^ -define histogram:unique-colors=false ^ +write histogram:eql_match_s2_noise2_hist.png ^ %WEB_SIZE% ^ eql_match_s2_noise2_sm.jpg The RGB histograms are not perfect, but we have no colour-shifts. The result isn't great, but is acceptable first-pass automatic processing. |
We can do this operation more directly, without the noise image.
%IM7DEV%magick ^ ( %SRC1% ^ -process 'mkhisto cumul norm' ^ +depth +write eql_w_i1_chist.miff ^ +delete ^ ) ^ ( xc: ^ -process ^ 'mkgauss width 65536 sd 20%% zeroize cumul norm' ^ -delete 0 ^ -process 'mkhisto cumul norm' ^ +write eql_gauss.miff ^ ) ^ NULL: call %PICTBAT%graphLineCol ^ eql_w_i1_chist.miff . . . eql_w_i1_chist_glc.png call %PICTBAT%graphLineCol ^ eql_gauss.miff . . . eql_gauss_glc.png |
|
%IMG7%magick ^ eql_w_i1_chist.miff ^ eql_gauss.miff ^ -clut ^ eql_w_t_ixt.miff call %PICTBAT%graphLineCol ^ eql_w_t_ixt.miff . . . eql_w_t_ixt_glc.png |
|
%IMG7%magick ^ %SRC1% ^ eql_w_t_ixt.miff ^ -clut ^ -define histogram:unique-colors=false ^ +write histogram:eql_match_gauss_hist.png ^ %WEB_SIZE% ^ eql_match_gauss_sm.jpg Again, we have a colour shift. |
To avoid colour shift, we find the histogram of the source's L channel (of Lab), and we clut only the L channel.
%IM7DEV%magick ^ ( %SRC1% ^ -colorspace LAB -channel R -separate -set colorspace sRGB ^ -process 'mkhisto cumul norm' ^ +depth +write eql_w_i1_L_chist.miff ^ +delete ^ ) ^ ( xc: ^ -process ^ 'mkgauss width 65536 sd 20%% zeroize cumul norm' ^ -delete 0 ^ -process 'mkhisto cumul norm' ^ +write eql_gauss.miff ^ ) ^ NULL: call %PICTBAT%graphLineCol ^ eql_w_i1_L_chist.miff . . . eql_w_i1_L_chist_glc.png call %PICTBAT%graphLineCol ^ eql_gauss.miff . . . eql_gauss_glc.png |
|
%IMG7%magick ^ eql_w_i1_L_chist.miff ^ eql_gauss.miff ^ -clut ^ eql_w_t_L_ixt.miff call %PICTBAT%graphLineCol ^ eql_w_t_L_ixt.miff . . . eql_w_t_L_ixt_glc.png |
|
%IMG7%magick ^ %SRC1% ^ -colorspace Lab -channel R ^ eql_w_t_L_ixt.miff ^ -clut ^ +channel -colorspace sRGB ^ -define histogram:unique-colors=false ^ +write histogram:eql_match_gauss_L_hist.png ^ %WEB_SIZE% ^ eql_match_gauss_L_sm.jpg We have no colour shift. |
We put this into a script matchGauss.bat.
call %PICTBAT%matchGauss %SRC1% eql_s1_mg.tiff %IMG7%magick ^ %mgOUTFILE% ^ -define histogram:unique-colors=false ^ +write histogram:eql_s1_mg_hist.png ^ %WEB_SIZE% ^ eql_s1_mg_sm.jpg |
|
call %PICTBAT%matchGauss %SRC2% eql_s2_mg.tiff %IMG7%magick ^ %mgOUTFILE% ^ -define histogram:unique-colors=false ^ +write histogram:eql_s2_mg_hist.png ^ %WEB_SIZE% ^ eql_s2_mg_sm.jpg |
The match-Gaussian process could also limit the gain in contrast. However, as the source histograms are roughly Gaussian, the process doesn't increase contrast by much. Looking at the results, I can see no point in limiting contrast.
An adaptive match-Gaussian process might be more useful.
For auto-processing of photographs, matching the histogram to a Gaussian does not seem useful.
We don't need to keep all those large TIFF or MIFF files, so delete them.
del %SRC1% del %SRC2% del eql_*_*.tiff del eql*.miff
For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.
rem From image %1, makes other image rem using a cumulative histogram with specified slopes at shadow and highlight. rem rem %2 is percentage along x-axis for shadow slope. [0] rem %3 is percentage along x-axis for start of highlight slope. [100] rem %4 is percentage up y-axis for shadow slope. [same as %2] rem %5 is percentage up y-axis for start of highlight slope. [same as %3] rem %6 is optional output filename. @rem @rem Updated: @rem 27-July-2022 for IM v7. @rem @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal enabledelayedexpansion rem @call echoOffSave call %PICTBAT%setInOut %1 eqs if not "%6"=="" set OUTFILE=%6 set TMP_HIST=%INNAME%_%sioCODE%_hist.miff %IM7DEV%magick ^ %INFILE% ^ -colorspace Gray ^ -process 'mkhisto norm' ^ %TMP_HIST% if ERRORLEVEL 1 exit /B 1 call %PICTBAT%equSlopeH %TMP_HIST% %2 %3 %4 %5 if ERRORLEVEL 1 exit /B 1 %IM7DEV%magick ^ %INFILE% ^ %eqshOUTFILE% ^ -clut ^ %OUTFILE% if ERRORLEVEL 1 exit /B 1 goto end set AX=%2 set BX=%3 set AY=%4 set BY=%5 if "%AX%"=="." set AX= if "%BX%"=="." set BX= if "%AY%"=="." set AY= if "%BY%"=="." set BY= if "%AX%"=="" set AX=0 if "%BX%"=="" set BX=100 if "%AY%"=="" set AY=%AX% if "%BY%"=="" set BY=%BX% echo %AX% %BX% %AY% %BY% if not "%6"=="" set OUTFILE=%6 for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "Wm1=%%[fx:w-1]\nAXP=%%[fx:int(w*%AX%/100+0.5)]\nBXP=%%[fx:int(w*%BX%/100+0.5)]\nAXP2=%%[fx:w-int(w*%AX%/100+0.5)]\nBXP2=%%[fx:w-int(w*%BX%/100+0.5)]\n" ^ %INFILE%`) do set %%L echo AXP=%AXP% BXP=%BXP% AXP2=%AXP2% BXP2=%BXP2% if %AX%==0 ( set BKL= set WHR= ) else ( set BKL=-size %AXP%x1 xc:#000 -gravity West -composite set WHR=-size %AXP2%x1 xc:#fff -gravity East -composite set G1= ^( -clone 0 ^ -sparse-color bilinear 0,0,#000,%Wm1%,0,#fff ^ !WHR! ^ +write G1.png ^ ^) ^ -evaluate-sequence Min ) if %BX%==100 ( set BKR= set WHL= ) else ( set BKR=-size %BXP2%x1 xc:#000 -gravity East -composite set WHL=-size %BXP%x1 xc:#000 -gravity West -composite set G2= ^( -clone 0 ^ -sparse-color bilinear 0,0,#000,%Wm1%,0,#fff ^ !WHL! ^ +write G2.png ^ ^) ^ -evaluate-sequence Max ) %IM7DEV%magick ^ %INFILE% ^ %BKL% ^ %BKR% ^ -process 'cumulhisto norm' ^ +level %AY%%%,%BY%%% ^ %G1% ^ %G2% ^ %OUTFILE% if ERRORLEVEL exit /B 1 call %PICTBAT%graphLineCol %OUTFILE% :end call echoRestore @endlocal & set eqsOUTFILE=%OUTFILE%
rem From %1 a histogram, rem makes a cumulative histogram but with specified slopes at shadow and highlight. rem rem %2 is percentage along x-axis for shadow slope. [0] rem %3 is percentage along x-axis for start of highlight slope. [100] rem %4 is percentage up y-axis for shadow slope. [same as %2] rem %5 is percentage up y-axis for start of highlight slope. [same as %3] rem %6 is optional output filename. @rem @rem Updated: @rem 27-July-2022 for IM v7. @rem @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal enabledelayedexpansion rem @call echoOffSave call %PICTBAT%setInOut %1 eqsh set AX=%2 set BX=%3 set AY=%4 set BY=%5 if "%AX%"=="." set AX= if "%BX%"=="." set BX= if "%AY%"=="." set AY= if "%BY%"=="." set BY= if "%AX%"=="" set AX=0 if "%BX%"=="" set BX=100 if "%AY%"=="" set AY=%AX% if "%BY%"=="" set BY=%BX% rem echo %AX% %BX% %AY% %BY% if not "%6"=="" set OUTFILE=%6 for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "Wm1=%%[fx:w-1]\nAXP=%%[fx:int(w*%AX%/100+0.5)]\nBXP=%%[fx:int(w*%BX%/100+0.5)]\nAXP2=%%[fx:w-int(w*%AX%/100+0.5)]\nBXP2=%%[fx:w-int(w*%BX%/100+0.5)]\n" ^ %INFILE%`) do set %%L if "%Wm1%"=="" exit /B 1 rem echo AXP=%AXP% BXP=%BXP% AXP2=%AXP2% BXP2=%BXP2% if "%eqshDEBUG%"=="1" ( set WR_G1=+write %~n1_G1.png set WR_G2=+write %~n1_G2.png ) else ( set WR_G1= set WR_G2= ) if %AX%==0 ( set BKL= set G1= ) else ( set BKL=-size %AXP%x1 xc:#000 -gravity West -composite set G1=^( -size %AXP%x1 gradient:black-gray^(%AY%%%^) ^ -size %AXP2%x1 xc:#fff ^ +append ^ %WR_G1% ^ ^) ^ -evaluate-sequence Min ) if %BX%==100 ( set BKR= set G2= ) else ( set BKR=-size %BXP2%x1 xc:#000 -gravity East -composite set G2=^( -size %BXP%x1 xc:#000 ^ -size %BXP2%x1 gradient:gray^(%BY%%%^)-white ^ +append ^ %WR_G2% ^ ^) ^ -evaluate-sequence Max ) %IM7DEV%magick ^ %INFILE% ^ -set colorspace sRGB ^ -compose Over ^ %BKL% ^ %BKR% ^ -process 'cumulhisto norm' ^ +level %AY%%%,%BY%%% ^ %G1% ^ %G2% ^ %OUTFILE% if ERRORLEVEL 1 exit /B 1 if "%eqshDEBUG%"=="1" ( call %PICTBAT%graphLineCol %OUTFILE% call %PICTBAT%graphLineCol %~n1_G1.png call %PICTBAT%graphLineCol %~n1_G2.png ) call echoRestore @endlocal & set eqshOUTFILE=%OUTFILE%
rem From image %1, rem make contrast-limited histogram-equalised (with iterative redistribution) version. @rem @rem Optional parameters: @rem %2 is limiting factor SD_FACT, so limit = mean + SD_FACT * standard_deviation. @rem Default 1. @rem %3 is percentage lift for shadows. Maximum < 100. Default 0, no lift. @rem %4 is percentage drop for highlights. Maximum < 100. Default 0, no drop. @rem %5 is output file, or null: for no output. @rem @rem Can also use: @rem eqlDEBUG if 1, also creates curve histograms. @rem eqlDIFF_LIMIT iteration stops when the count redistributed is less than this percentage. @rem Set to a value >= 100 to prevent iteration. @rem Default 1.0. @rem eqlSUPPRESS_OUT if 1, suppresses output. @rem @rem Updated: @rem 26-July-2022 for IM v7. @rem @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal enabledelayedexpansion @call echoOffSave call %PICTBAT%setInOut %1 eql set SD_FACT=%2 if "%SD_FACT%"=="." set SD_FACT= if "%SD_FACT%"=="" set SD_FACT=1 set LIFT_SHADOW_PC=%3 if "%LIFT_SHADOW_PC%"=="." set LIFT_SHADOW_PC= if "%LIFT_SHADOW_PC%"=="" set LIFT_SHADOW_PC=0 set DROP_HIGHLIGHT_PC=%4 if "%DROP_HIGHLIGHT_PC%"=="." set DROP_HIGHLIGHT_PC= if "%DROP_HIGHLIGHT_PC%"=="" set DROP_HIGHLIGHT_PC=0 if not "%5"=="" set OUTFILE=%5 if "%5"=="" ( set EQL_BASE=%~n1_%sioCODE% ) else ( set EQL_BASE=%~n5_%sioCODE% ) if "%eqlDIFF_LIMIT%"=="" set eqlDIFF_LIMIT=1 set TMPEXT=miff for /F "usebackq" %%L in (`cygpath %TEMP%`) do set CYGTEMP=%%L %IM7DEV%magick ^ %INFILE% ^ -colorspace Lab -channel R -separate -set colorspace sRGB ^ -process 'mkhisto norm' ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% if ERRORLEVEL 1 exit /B 1 for /F "usebackq" %%L in (`%IMG7%magick ^ %TEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -precision 15 ^ -format "histcap=%%[fx:(mean+%SD_FACT%*standard_deviation)*100]" ^ info:`) do set %%L echo %0: histcap=%histcap% set nIter=0 :loop %IM7DEV%magick ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -channel RGB ^ -evaluate Min %histcap%%% ^ +channel ^ %CYGTEMP%\%EQL_BASE%_gchc_cap.%TMPEXT% for /F "usebackq" %%L in (`%IMG7%magick ^ %TEMP%\%EQL_BASE%_gch.%TMPEXT% ^ %TEMP%\%EQL_BASE%_gchc_cap.%TMPEXT% ^ -compose MinusSrc -composite ^ -precision 15 ^ -format "MeanDiffPC=%%[fx:mean*100]" ^ info:`) do set %%L echo %0: MeanDiffPC=%MeanDiffPC% %IM7DEV%magick ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -channel RGB ^ -evaluate Min %histcap%%% ^ +channel ^ -evaluate Add %MeanDiffPC%%% ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "DO_LOOP=%%[fx:%MeanDiffPC%>%eqlDIFF_LIMIT%?1:0]" ^ xc:`) do set %%L rem If max(eql_gch) > histcap + epsilon, repeat. rem OR rem if %MeanDiffPC% > epsilon, repeat set /A nIter+=1 if %DO_LOOP%==1 goto loop echo %0: nIter=%nIter% if %LIFT_SHADOW_PC%==0 if %DROP_HIGHLIGHT_PC%==0 %IM7DEV%magick ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -auto-level ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% if %LIFT_SHADOW_PC%==0 goto skipShad set WX=0 rem In following, "-blur" should really be "decomb". set DECOMB=-blur 0x1 for /F "usebackq tokens=2 delims=:, " %%A in (`%IM7DEV%magick ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -auto-level ^ +write %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ %DECOMB% ^ -threshold %LIFT_SHADOW_PC%%% ^ -process onewhite ^ NULL: 2^>^&1`) do set WX=%%A rem %0: echo WX=%WX% if "%WX%"=="none" ( %IMG7%magick ^ %TEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -fill #fff -colorize 100 ^ %TEMP%\%EQL_BASE%_gch.%TMPEXT% ) else if not %WX%==0 %IM7DEV%magick ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -size %WX%x1 xc:gray(%LIFT_SHADOW_PC%%%) ^ -gravity West -composite ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% :skipShad if %DROP_HIGHLIGHT_PC%==0 goto skipHigh set WX=0 rem In following, "-blur" should really be "decomb". set DECOMB=-blur 0x1 for /F "usebackq tokens=2 delims=:, " %%A in (`%IM7DEV%magick ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -auto-level ^ +write %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ %DECOMB% ^ -threshold %DROP_HIGHLIGHT_PC%%% ^ -flop ^ -process onewhite ^ NULL: 2^>^&1`) do set WX=%%A rem %0: echo WX=%WX% if "%WX%"=="none" ( %IMG7%magick ^ %TEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -fill #fff -colorize 100 ^ %TEMP%\%EQL_BASE%_gch.%TMPEXT% ) else if not %WX%==0 %IM7DEV%magick ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -size %WX%x1 xc:gray(%DROP_HIGHLIGHT_PC%%%) ^ -gravity East -composite ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% :skipHigh %IM7DEV%magick ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -process 'cumulhisto norm' ^ %CYGTEMP%\%EQL_BASE%_gchc_clhe_red.%TMPEXT% if /I "%OUTFILE%" NEQ "null:" if not "%eqlSUPPRESS_OUT%"=="1" %IMG7%magick ^ %INFILE% ^ %TEMP%\%EQL_BASE%_gchc_clhe_red.%TMPEXT% ^ -clut ^ %OUTFILE% if "%eqlDEBUG%"=="1" ( call %PICTBAT%graphLineCol %TEMP%\%EQL_BASE%_gch.%TMPEXT% . . 0 %EQL_BASE%_gch.png call %PICTBAT%graphLineCol %TEMP%\%EQL_BASE%_gchc_cap.%TMPEXT% . . 0 %EQL_BASE%_gchc_cap.png call %PICTBAT%graphLineCol %TEMP%\%EQL_BASE%_gchc_clhe_red.%TMPEXT% . . 0 %EQL_BASE%_gchc_clhe_red.png ) call echoRestore @endlocal & set eqlOUTFILE=%OUTFILE% &set eqlCLUT=%TEMP%\%EQL_BASE%_gchc_clhe_red.%TMPEXT%
rem Makes version of image %1 with histogram that matches the histogram of image %2. rem Optional %3 is output file. @rem @rem Can also use: @rem mhCOL_SP_IN eg -colorspace Lab -channel R @rem mhCOL_SP_OUT eg +channel -colorspace sRGB @rem mhDEBUG if 1, makes a file of the clut used. @rem @rem Updated: @rem 27-July-2022 for IM v7. @rem @if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal enabledelayedexpansion @call echoOffSave call %PICTBAT%setInOut %1 mh if not "%3"=="" set OUTFILE=%3 set DBG_NAME= set WR_CLUT= set DBG_H1= set DBG_H2= set WR_DBG_H1= set WR_DBG_H2= if "%mhDEBUG%"=="1" ( set DBG_NAME=%INNAME%_mhcl.miff set WR_CLUT= ^( +clone +write %INNAME%_mhcl.miff +delete ^) ) else if "%mhDEBUG%"=="2" ( set DBG_NAME=%INNAME%_mhcl.miff set WR_CLUT= ^( +clone +write %INNAME%_mhcl.miff +delete ^) set DBG_H1=%INNAME%_h1.miff set WR_DBG_H1=+write !DBG_H1! set DBG_H2=%INNAME%_h2.miff set WR_DBG_H2=+write !DBG_H2! ) %IM7DEV%magick ^ %INFILE% ^ %mhCOL_SP_IN% ^ ( -clone 0 ^ -process 'mkhisto cumul norm v' ^ %WR_DBG_H1% ^ ) ^ ( %2 ^ %mhCOL_SP_IN% ^ -process 'mkhisto cumul norm v' ^ %WR_DBG_H2% ^ -process 'mkhisto cumul norm v' ^ ) ^ ( -clone 1-2 -clut ) ^ -delete 1-2 ^ %WR_CLUT% ^ -clut ^ %mhCOL_SP_OUT% ^ %OUTFILE% if ERRORLEVEL 1 exit /B1 if not "%DBG_NAME%"=="" ( echo Also created %DBG_NAME% call %PICTBAT%graphLineCol %DBG_NAME% ) if not "%DBG_H1%"=="" ( echo Also created %DBG_H1% call %PICTBAT%graphLineCol %DBG_H1% ) if not "%DBG_H2%"=="" ( echo Also created %DBG_H2% call %PICTBAT%graphLineCol %DBG_H2% ) call echoRestore @endlocal & set mhOUTFILE=%OUTFILE%
rem Makes version of image %1 with L of Lab histogram that matches a Gaussian distribution. rem Optional %2 is output file. @rem @rem Can also use: @rem mgGAUSS eg width 65536 sd 20%% zeroize @rem @rem Updated: @rem 15-May-2021 IM v7. Output is %2 instead of %3. @rem @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal enabledelayedexpansion @call echoOffSave call %PICTBAT%setInOut %1 mg if not "%2"=="" if not "%2"=="." set OUTFILE=%2 set TEMPEXT=miff if "%mgGAUSS%"=="" set mgGAUSS=width 65536 sd 20%% zeroize set mgGAUSS=mkgauss %mgGAUSS% cumul norm %IM7DEV%magick ^ ( %INFILE% ^ -colorspace LAB -channel 0 -separate -set colorspace sRGB ^ -process 'mkhisto cumul norm' ^ +depth +write mg_w_i1_L_chist.%TEMPEXT% ^ +delete ^ ) ^ ( xc: ^ -process '%mgGAUSS%' ^ -delete 0 ^ -process 'mkhisto cumul norm' ^ +write mg_gauss.%TEMPEXT% ^ ) ^ NULL: %IMG7%magick ^ mg_w_i1_L_chist.%TEMPEXT% ^ mg_gauss.%TEMPEXT% ^ -clut ^ mg_w_t_L_ixt.%TEMPEXT% %IMG7%magick ^ %INFILE% ^ -colorspace Lab -channel 0 ^ mg_w_t_L_ixt.%TEMPEXT% ^ -clut ^ +channel -colorspace sRGB ^ %OUTFILE% call echoRestore @endlocal & set mgOUTFILE=%OUTFILE%
rem Applies contrast-limited histogram-equalisation rem (with iterative redistribution) as appropriate for four tiles, rem blending between the four quarters. @rem @rem Optional parameters: @rem %2 is limiting factor SD_FACT, so limit = mean + SD_FACT * standard_devition. @rem Default 1. @rem %3 is percentage lift for shadows. Default 0, no lift. @rem %4 is percentage drop for highlights. Default 0, no drop. @rem %5 is output file. @rem @rem Can also use: @rem eqlqDIFF_LIMIT iteration stops when the count redistribted is less than this percentage. @rem Default 1.0. @rem @rem Updated: @rem 27-July-2022 for IM v7. @rem @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal enabledelayedexpansion @call echoOffSave call %PICTBAT%setInOut %1 eqlq set P2=%2 if "%P2%"=="" set P2=. set P3=%3 if "%P3%"=="" set P3=. set P4=%4 if "%P4%"=="" set P4=. if not "%5"=="" set OUTFILE=%5 set eqlSUPPRESS_OUT=1 set eqlDIFF_LIMIT=%eqlqDIFF_LIMIT% %IMG7%magick %INFILE% -crop 2x2@ +adjoin eqlq_tmp.miff if ERRORLEVEL 1 exit /B 1 call %PICTBAT%eqLimit eqlq_tmp-0.miff %P2% %P3% %P4% eqlq_0 set eqlCLUT0=%eqlCLUT% call %PICTBAT%eqLimit eqlq_tmp-1.miff %P2% %P3% %P4% eqlq_1 set eqlCLUT1=%eqlCLUT% call %PICTBAT%eqLimit eqlq_tmp-2.miff %P2% %P3% %P4% eqlq_2 set eqlCLUT2=%eqlCLUT% call %PICTBAT%eqLimit eqlq_tmp-3.miff %P2% %P3% %P4% eqlq_3 set eqlCLUT3=%eqlCLUT% rem %IMG7%magick ^ rem %INFILE% ^ rem ( +clone %eqlCLUT0% -clut -write eqlq_tmp-0.miff +delete ) ^ rem ( +clone %eqlCLUT1% -clut -write eqlq_tmp-1.miff +delete ) ^ rem ( +clone %eqlCLUT2% -clut -write eqlq_tmp-2.miff +delete ) ^ rem ( +clone %eqlCLUT3% -clut -write eqlq_tmp-3.miff +delete ) ^ rem NULL: %IMG7%magick ^ %INFILE% ^ ( -clone 0 %eqlCLUT0% -clut ) ^ ( -clone 0 %eqlCLUT1% -clut ) ^ ( -clone 0 %eqlCLUT2% -clut ) ^ ( -clone 0 %eqlCLUT3% -clut ) ^ ( -clone 0 -sparse-color Bilinear 0,0,#000,%%[fx:w-1],0,#fff ) ^ -delete 0 ^ ( -clone 0,1,4 -composite ) ^ ( -clone 2,3,4 -composite ) ^ -delete 0-4 ^ ( -clone 0 -sparse-color Bilinear 0,0,#000,0,%%[fx:h-1],#fff ) ^ -composite ^ +depth ^ %OUTFILE% rem del eqlq_tmp-0.miff rem del eqlq_tmp-1.miff rem del eqlq_tmp-2.miff rem del eqlq_tmp-3.miff call echoRestore @endlocal & set eqlqOUTFILE=%OUTFILE%
rem Applies contrast-limited histogram-equalisation rem (with iterative redistribution) as appropriate for NxM tiles, rem blending between the tiles. @rem @rem Optional parameters: @rem %2 is limiting factor SD_FACT, so limit = mean + SD_FACT * standard_devition. @rem Default 1. @rem %3 is percentage lift for shadows. Default 0, no lift. @rem %4 is percentage drop for highlights. Default 0, no drop. @rem %5 is number of columns of tiles. Deafult 3. @rem %6 is number of rows of tiles. Deafult 3. @rem %7 is output file. @rem @rem Can also use: @rem eqltDEBUG if 1, also creates curve histograms. @rem eqltDIFF_LIMIT iteration stops when the count redistribted is less than this percentage. @rem Default 1.0. @rem eqltLINEAR if 1, composes in linear colourspace. @rem eqltCLEANUP if not 1, doesn't remove temporary files @rem @rem Updated: @rem 27-July-2022 for IM v7. @rem @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal enabledelayedexpansion @call echoOffSave call %PICTBAT%setInOut %1 eqlt set P2=%2 if "%P2%"=="" set P2=. set P3=%3 if "%P3%"=="" set P3=. set P4=%4 if "%P4%"=="" set P4=. set N_COL=%5 if "%N_COL%"=="." set N_COL= if "%N_COL%"=="" set N_COL=3 set N_ROW=%6 if "%N_ROW%"=="." set N_ROW= if "%N_ROW%"=="" set N_ROW=3 if not "%7"=="" set OUTFILE=%7 if "%eqltCLEANUP%"=="" set eqltCLEANUP=1 set eqlSUPPRESS_OUT=1 set eqlDIFF_LIMIT=%eqltDIFF_LIMIT% set VSN_LIST=%TEMP%\eqltVsn.lis del %VSN_LIST% 2>nul set CMD_FILE=%TEMP%\eqltCommand.bat for /F "usebackq" %%L in (`%IMG7%magick ^ %INFILE% ^ -crop "%N_COL%x%N_ROW%@" +repage ^ -precision 15 ^ -format "MAX_N=%%p\n" ^ -write INFO: ^ +adjoin ^ eqltTEMP_TILE.miff`) do @set %%L echo MAX_N=%MAX_N% set /A COL_X_ROWm1=%N_COL%*%N_ROW%-1 if not %MAX_N%==%COL_X_ROWm1% ( echo %0: Bug: %MAX_N% neq %COL_X_ROWm1% exit /B 1 ) set eqlLofLAB=0 echo %IMG7%magick %INFILE% >%CMD_FILE% if "%eqlLofLAB%"=="1" ( echo -colorspace Lab >>%CMD_FILE% set COL_SP_IN=-channel R set COL_SP_OUT=+channel -colorspace sRGB ) else ( set COL_SP_IN= set COL_SP_OUT= ) for /L %%i in (0,1,%MAX_N%) do ( call %PICTBAT%eqLimit eqltTEMP_TILE-%%i.miff %P2% %P3% %P4% eqlt_%%i set eqltCLUT%%i=!eqlCLUT! echo eqltCLUT%%i=!eqltCLUT%%i! set VSN=%TEMP%\eqltTEMP_TILE_%%i.miff echo ^( +clone %COL_SP_IN% !eqlCLUT! -clut %COL_SP_OUT% +write !VSN! +delete ^) >>%CMD_FILE% echo !VSN! >>%VSN_LIST% ) cPrefix /i%CMD_FILE% /r"^" echo NULL: >>%CMD_FILE% type %CMD_FILE% call %CMD_FILE% set btLINEAR=%eqltLINEAR% call %PICTBAT%blendTile %VSN_LIST% %N_COL% %N_ROW% %OUTFILE% if ERRORLEVEL 1 exit /B 1 set eqlSUPPRESS_OUT= set eqlDIFF_LIMIT= rem FIXME: cleanup if "%eqltCLEANUP%"=="1" ( del %VSN_LIST% del %CMD_FILE% del %TEMP%\eqlt*.BAK for /L %%i in (0,1,%MAX_N%) do ( del eqltTEMP_TILE-%%i.miff del %TEMP%\eqlt_%%i_*.miff del %TEMP%\eqltTEMP_TILE_%%i.miff ) ) call echoRestore @endlocal & set eqltOUTFILE=%OUTFILE%
All images on this page were created by the commands shown, using:
%IMG7%magick -version
Version: ImageMagick 7.1.1-20 Q16-HDRI x86 98bb1d4:20231008 https://imagemagick.org Copyright: (C) 1999 ImageMagick Studio LLC License: https://imagemagick.org/script/license.php Features: Cipher DPC HDRI OpenCL OpenMP(2.0) 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 (193532217)
%IM7DEV%magick -version
Version: ImageMagick 7.1.1-27 (Beta) Q32-HDRI x86_64 83eefaf2a:20240107 https://imagemagick.org Copyright: (C) 1999 ImageMagick Studio LLC License: https://imagemagick.org/script/license.php Features: Cipher DPC HDRI Modules OpenCL OpenMP(4.5) Delegates (built-in): bzlib cairo fftw fontconfig freetype heic jbig jng jpeg lcms ltdl lzma pangocairo png raqm raw rsvg tiff webp wmf x xml zip zlib zstd Compiler: gcc (11.3)
Source file for this web page is eqlimit.h1. To re-create this web page, execute "procH1 eqlimit".
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 19-Dec-2014.
Page created 25-Apr-2024 15:24:36.
Copyright © 2024 Alan Gibson.