snibgo's ImageMagick pages

Measuring blur

Some metrics for blurriness.

If we have a reference image and a blurred version, we can measure the difference (eg RMSE) caused by the blur. But we might not have a reference, merely a blurred version. Here are some ways of measuring blurriness of individual images.

We might use standard deviation, either of the entire image, or of the average (or maximum) SD of a sliding window. (Sliding window SD is fast when calculated by integral images). However, SD is independent of pixel locations. It measures contrast, either overall or within each window, but says nothing about whether the pixels are arranged to give a smooth gradient or sharp boundary.

A more complex method, described in a paper by Crete et al, is sometimes used. In a nutshell: we blur the image, and see how much it has changed. If it has changed a lot then it was sharp, but if it didn't change much then it was already blurred.

References

The method

We take a grayscale image and its blur in one dimension, and the slope magnitude of each. This is a measure of high-freqency amplitude, that blurring attenuates. Subtract the slope magnitudes to get the variation. Where the variation is high, the blur has removed high frequencies. Where the variation is low or zero, blurring has not removed high frequencies so it was already blurred.

The paper gives an algorithm:

  1. Make a grayscale of the input image, and call it F.
  2. Blur F horizontally, using a sliding window mean of 9x1 pixels, and call it BHor.
  3. Blur F vertically, using a sliding window mean of 1x9 pixels, and call it BVer.
  4. From F, calculate the absolute difference between each pixel and the pixel above. Call this image D_FVer.
  5. From F, calculate the absolute difference between each pixel and the pixel to the left. Call this image D_FHor.
  6. From BVer, calculate the absolute difference between each pixel and the pixel above. Call this image D_BVer.
  7. From BHor, calculate the absolute difference between each pixel and the pixel to the left. Call this image D_BHor.
  8. Calculate D_FVer minus D_BVer, clamping negative values to zero. Call this image VVer. (But I think the authors intended to say D_VVer.)
  9. Calculate D_FHor minus D_BHor, clamping negative values to zero. Call this image VHor. (But I think the authors intended to say D_VHor.)
  10. Sum the pixels in D_FVer. Call this s_FVer.
  11. Sum the pixels in D_FHor. Call this s_FHor.
  12. Sum the pixels in D_VVer. Call this s_VVer.
  13. Sum the pixels in D_VHor. Call this s_VHor.
  14. Calculate b_FVer = (s_FVer - s_VVer) / s_FVer = 1 - (s_VVer / s_FVer).
  15. Calculate b_FHor = (s_FHor - s_VHor) / s_FHor = 1 - (s_VHor / s_FHor).
  16. Calculate blur_F = max (b_FVer, b_FHor). This is between 0.0 ("best quality") and 1.0 ("worst quality").

If we take the paper literally: VVer and VHor are calculated but never used; D_VVer and D_VHor are used but never calculated. So I suppose the prefix D_ has been accidentally omitted.

The blur can be done with "-statistic Mean" or "-process integim", giving slightly different results due to different treatment at edges.

Summing pixels could be done by "-process integim" and using the last pixel, but we need one sum divided by another sum from same-sized images, so instead we can use "-scale 1x1!" to get the means.

If the input images have pixel values in the range 0 to 100%, then all intermediate images are also in that range, so HDRI is not required to cope with OOG. If we use "-process integim", that needs HDRI. Also, we calculate differences between adjacent pixels, and these are small numbers so we could easily lose precision when we take the mean and then divide, so HDRI is a good idea.

The script measureBlur.bat implements this. Here are some examples, also showing some standard deviation (SD) statistics.

Note that:

For each example, we calculate and show four statistics:

  1. SD: the overall image SD as calculated by IM, the "unbiased estimate of the population standard deviation";
  2. mb_score: the "No-Reference Perceptual Blur Metric";
  3. wsdMeanSD: the mean value of the 9x9 sliding window SD;
  4. wsdMaxSD: the maximum value of the 9x9 sliding window SD.

mb_score is calculated from intensity only. In these examples, the SD measurements are from all channels.

%IMG7%magick ^
  toes.png ^
  -format "SD=%%[fx:standard_deviation]\n" ^
  info: 

call %PICTBAT%measureBlur toes.png
call %PICTBAT%iiWinSD toes.png 9x9

echo mb_score=%mb_score% 
echo wsdMeanSD=%wsdMeanSD% 
echo wsdMaxSD=%wsdMaxSD% 
SD=0.153221
mb_score=0.4861911216334783 
wsdMeanSD=0.04570518 
wsdMaxSD=0.23287024 
toes.pngjpg
%IMG7%magick ^
  toes.png ^
  -blur 0x1 ^
  +write mb_blr.png ^
  -format "SD=%%[fx:standard_deviation]\n" ^
  info: 

call %PICTBAT%measureBlur mb_blr.png
call %PICTBAT%iiWinSD mb_blr.png 9x9

echo mb_score=%mb_score% 
echo wsdMeanSD=%wsdMeanSD% 
echo wsdMaxSD=%wsdMaxSD% 
SD=0.150146
mb_score=0.636357719157702 
wsdMeanSD=0.039165829 
wsdMaxSD=0.21599624 
mb_blr.pngjpg
%IMG7%magick ^
  toes.png ^
  -unsharp 0x1 ^
  +write mb_usm.png ^
  -format "SD=%%[fx:standard_deviation]\n" ^
  info: 

call %PICTBAT%measureBlur mb_usm.png
call %PICTBAT%iiWinSD mb_usm.png 9x9

echo mb_score=%mb_score% 
echo wsdMeanSD=%wsdMeanSD% 
echo wsdMaxSD=%wsdMaxSD% 
SD=0.155273
mb_score=0.4453480249103532 
wsdMeanSD=0.049572325 
wsdMaxSD=0.24469792 
mb_usm.pngjpg
%IMG7%magick ^
  toes.png ^
  -blur 0x20 ^
  +write mb_blr2.png ^
  -format "SD=%%[fx:standard_deviation]\n" ^
  info: 

call %PICTBAT%measureBlur mb_blr2.png
call %PICTBAT%iiWinSD mb_blr2.png 9x9

echo mb_score=%mb_score% 
echo wsdMeanSD=%wsdMeanSD% 
echo wsdMaxSD=%wsdMaxSD% 
SD=0.0960485
mb_score=0.993745171950103 
wsdMeanSD=0.0065275174 
wsdMaxSD=0.021335356 
mb_blr2.pngjpg
%IMG7%magick ^
  toes.png ^
  -colorspace Gray ^
  -auto-gamma ^
  +dither -posterize 2 ^
  +write mb_bw.png ^
  -format "SD=%%[fx:standard_deviation]\n" ^
  info: 

call %PICTBAT%measureBlur mb_bw.png
call %PICTBAT%iiWinSD mb_bw.png 9x9

echo mb_score=%mb_score% 
echo wsdMeanSD=%wsdMeanSD% 
echo wsdMaxSD=%wsdMaxSD% 
SD=0.498908
mb_score=0.1347216659037156 
wsdMeanSD=0.16117698 
wsdMaxSD=0.5 
mb_bw.pngjpg
%IMG7%magick ^
  toes.png ^
  -fill #abc -colorize 100 ^
  +write mb_col.png ^
  -format "SD=%%[fx:standard_deviation]\n" ^
  info: 

call %PICTBAT%measureBlur mb_col.png
call %PICTBAT%iiWinSD mb_col.png 9x9

echo mb_score=%mb_score% 
echo wsdMeanSD=%wsdMeanSD% 
echo wsdMaxSD=%wsdMaxSD% 
SD=0
mb_score=1 
wsdMeanSD=1.8558487e-07 
wsdMaxSD=1.7059715e-06 
mb_col.pngjpg

Sort the pixels in toes.png:

%IM7DEV%magick ^
  toes.png ^
  -set option:WW %%w ^
  -crop x1 +repage ^
  +append +repage ^
  -intensity Rec709Luminance ^
  -process sortpixels ^
  -flop ^
  -crop %%[WW]x1 ^
  -append +repage ^
  +write mb_srt.png ^
  -format "SD=%%[fx:standard_deviation]\n" ^
  info: 

call %PICTBAT%measureBlur mb_srt.png
call %PICTBAT%iiWinSD mb_srt.png 9x9

echo mb_score=%mb_score% 
echo wsdMeanSD=%wsdMeanSD% 
echo wsdMaxSD=%wsdMaxSD% 
SD=0.15322096
mb_score=0.7227789182307164 
wsdMeanSD=0.037480198 
wsdMaxSD=0.07201522 

The SD is unchanged from toes.png,
but mb_score shows we have more blur.

mb_srt.pngjpg

There is a fairly good correlation:

wsdMaxSD * 2 = 1 - mb_score

... roughly speaking.

Scripts

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

measureBlur.bat

rem From image %1,
rem writes No-Reference Perceptual Blur Metric
rem to environment variable %2 [default mb_score].
rem %3 is prefix for debugging images. Default: no images.
@rem
@rem Reference: http://im.snibgo.com/measblur.htm


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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 mb

set ENVVAR=%2
if "%ENVVAR%"=="." set ENVVAR=
if "%ENVVAR%"=="" set ENVVAR=mb_score

set DBG_PREF=%3

if "%DBG_PREF%"=="" (
  set WR_F=
  set WR_BHOR=
  set WR_BVER=
  set WR_D_FVER=
  set WR_D_FHOR=
  set WR_D_BVER=
  set WR_D_BHOR=
  set WR_D_VVER=
  set WR_D_VHOR=
) else (
  set WR_F=+write %DBG_PREF%_f
  set WR_BHOR=+write %DBG_PREF%_bhor
  set WR_BVER=+write %DBG_PREF%_bver
  set WR_D_FVER=+write %DBG_PREF%_d_fver
  set WR_D_FHOR=+write %DBG_PREF%_d_fhor
  set WR_D_BVER=+write %DBG_PREF%_d_bver
  set WR_D_BHOR=+write %DBG_PREF%_d_bhor
  set WR_D_VVER=+write %DBG_PREF%_d_vver
  set WR_D_VHOR=+write %DBG_PREF%_d_vhor
)

set TMPDIR=\temp\

for /F "usebackq" %%L in (`%IMG7%magick ^
  %INFILE% ^
  -colorspace Gray ^
  %WR_F% ^
  +write mpr:F ^
  ^( +clone ^
    -statistic Mean 9x1 ^
    %WR_BHOR% ^
    +write mpr:bhor ^
    +delete ^) ^
  ^( +clone ^
    -statistic Mean 1x9 ^
    %WR_BVER% ^
    +write mpr:bver ^
    +delete ^) ^
  ^( +clone ^) ^
  -geometry +0-1 ^
  -compose Difference -composite ^
  -geometry +0+0 ^
  %WR_D_FVER% ^
  +write mpr:d_fver ^
  +delete ^
  mpr:F ^( +clone ^) ^
  -geometry -1+0 ^
  -compose Difference -composite ^
  -geometry +0+0 ^
  %WR_D_FHOR% ^
  +write mpr:d_fhor ^
  +delete ^
  mpr:bver ^( +clone ^) ^
  -geometry +0-1 ^
  -compose Difference -composite ^
  -geometry +0+0 ^
  %WR_D_BVER% ^
  +write mpr:d_bver ^
  +delete ^
  mpr:bhor ^( +clone ^) ^
  -geometry -1+0 ^
  -compose Difference -composite ^
  -geometry +0+0 ^
  %WR_D_BHOR% ^
  +write mpr:d_bhor ^
  +delete ^
  mpr:d_fver mpr:d_bver ^
  -compose MinusSrc -composite -clamp ^
  %WR_D_VVER% ^
  +write mpr:d_vver ^
  +delete ^
  mpr:d_fhor mpr:d_bhor ^
  -compose MinusSrc -composite -clamp ^
  %WR_D_FHOR% ^
  +write mpr:d_vhor ^
  +delete ^
  ^( mpr:d_vver mpr:d_fver ^
    -scale "1x1^!" ^
    -compose DivideSrc -composite ^
  ^) ^
  ^( mpr:d_vhor mpr:d_fhor ^
    -scale "1x1^!" ^
    -compose DivideSrc -composite ^
  ^) ^
  -compose Darken -composite ^
  -negate ^
  -precision 16 ^
  -format "SCORE=%%[fx:mean]\n" ^
  info:`) do set %%L

call echoRestore

@endlocal & set mbOUTFILE=%OUTFILE%& set %ENVVAR%=%SCORE%

iiWinSd.bat

rem From image %1,
rem calculates numeric mean and maximum windowed standard deviation.
rem using the process module integral image method.
rem %2 window size eg 3x3

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 wsd

set WINDIMS=%2
if "%WINDIMS%"=="." set WINDIMS=
if "%WINDIMS%"=="" set WINDIMS=3x3

for /F "usebackq" %%L in (`%IM7DEV%magick ^
    %INFILE% ^
    ^( -clone 0 ^
       -evaluate Pow 2 ^
       -process 'integim' ^
       -process 'deintegim window %WINDIMS%' ^
    ^) ^
    ^( -clone 0 ^
       -process 'integim' ^
       -process 'deintegim window %WINDIMS%' ^
       -evaluate Pow 2 ^
    ^) ^
    -delete 0 ^
    -alpha off ^
    -compose MinusSrc -composite ^
    -evaluate Pow 0.5 ^
    -format "MeanSD=%%[fx:mean]\nMaxSD=%%[fx:maxima]\n" ^
    info:`) do set %%L

call echoRestore

endlocal & set wsdMeanSD=%MeanSD%& set wsdMaxSD=%MaxSD%

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)

To improve internet download speeds, some images may have been automatically converted (by ImageMagick, of course) from PNG or TIFF or MIFF to JPG.

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


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 8-August-2018.

Page created 02-Mar-2024 17:37:18.

Copyright © 2024 Alan Gibson.