Tests Of Processing Using Sean Burke's IMFFT With IM Q16

See IMFFT

Images Displayed Below Are Often JPG, But Processing Was Done As PNG or TIFF To Preserve Quality

Simple FFT/IFT Round Trip

FFT to produce tiff stack of frames:

path-to-imfft/trunk/demo f lena.png


IFT of tiff stack to regenerate image:

path-to-imfft/trunk/demo i lena_F_Q16.tiff

original image roundtrip result

compare -metric rmse lena.png lena_F_Q16_rt_Q16.tiff null:
128.13 (0.00195514)

compare -metric psnr lena.png lena_F_Q16_rt_Q16.tiff null:
54.1765



Simple Binary Square Pattern - Demo Magnitude And Phase Of FFT
original image

FFT to produce tiff stack of frames:

path-to-imfft/trunk/demo f square30.png

Then create magnitude and phase (below):

convert square30_F_Q16.tiff[0] square30_F_Q16.tiff[2] \
square30_F_Q16.tiff[4] square30_mag.png

convert square30_F_Q16.tiff[1] square30_F_Q16.tiff[3] \
square30_F_Q16.tiff[5] square30_phase.png

Linearly Stretch Magnitude: min->0%, max->100%

convert square30_F_Q16_mag.png -contrast-stretch 0% square30_F_Q16_mag_cs0.png

Generate Spectrum From Magnitude (scale=100):

convert square30_F_Q16_mag.png -contrast-stretch 0% \
-fx "log(99*u+1)/log(100)" -fill "rgb(1,1,1)" \
-opaque black square30_F_Q16_spec100.png

Generate Spectrum From Magnitude (scale=1000):

convert square30_F_Q16_mag.png -contrast-stretch 0% \
-fx "log(999*u+1)/log(1000)" -fill "rgb(1,1,1)" \
-opaque black square30_F_Q16_spec1000.png

Generate Nearly Equivalent Spectrum, But Faster:

convert square30_F_Q16_mag.png -contrast-stretch 0% \
-gamma 2.5 -fill "rgb(1,1,1)" -opaque black \
square30_F_Q16_spec_g2p5.png

Generate Nearly Equivalent Spectrum, But Faster:

convert square30_F_Q16_mag.png -contrast-stretch 0% \
-gamma 3.5 -fill "rgb(1,1,1)" -opaque black \
square30_F_Q16_spec_g3p5.png



Simple Patterns - Demo Of Spectra Concepts
  1. High frequencies in the FFT (corresponding to rapidly varying intensities in the original image)
    lie near the outer parts of the spectrum.

  2. Low frequencies in the FFT (corresponding to slowly varying intensities in the original image)
    lie near the center of the spectrum.

  3. The zero frequency (DC) point in the spectrum for an NxM original image lies at coordiniage
    N/2-1,M/2 in the spectrum image.

  4. The intensity value in the magnitude image at the DC point is equal the average graylevel
    in the original image.

  5. Edges in an image give rise to transform components lying along lines perpedicular to the edges.

  6. Smaller objects have more spread-out transforms; Larger objects have more compressed transform.

  7. The transform of uniform object lies along a line perpendicular to the dimension of the object.

  8. Gratings (evenly repeated grid lines) produce an array of dots perpendicular to the grating lines.

  9. The transform of a constant rectangle of dimension x=a,y=b in image an size NxM is a sinc function:
    sinc(pi*a/N)*sinc(pi*b/M), where sinc(x)=sin(x)/(x). The spacing between the troughs in the
    spectrum will be Dx=N/a and Dy=M/b.

  10. The transform of a constant circle of diameter d in an image size NxN is a jinc function: jinc(pi*d/N),
    where jinc(x)=J1(r)/(r) and J1(r) is the Bessel function of the first kind of order one.
    The spacing from the center to the first trough in the spectrum will be Dr=1.22*N/d.

  11. The transform of a Gaussian (Normal) function of sigma=d in an image size NxN is a Gaussian
    function. The sigma of the Gaussian function in the spectrum will be sigma=N/d.

  12. The transform of a set of grid lines of spacing x=a,y=b in image size NxM is an array of dots:
    The spacing of the dots in the spectrum will be Dx=N/a and Dy=M/b.

Square

Generate Spectrum From Magnitude (scale=1000):

convert square30_F_Q16_mag.png -contrast-stretch 0% \
-fx "log(999*u+1)/log(1000)" -fill "rgb(1,1,1)" \
-opaque black square30_F_Q16_spec1000.png

Rectangle 16x32

Generate Spectrum From Magnitude (scale=1000):

convert rect16x32_F_Q16_mag.png -contrast-stretch 0% \
-fx "log(999*u+1)/log(10000)" -fill "rgb(1,1,1)" \
-opaque black rect16x32_F_Q16_spec1000.png

Circle Diameter 16

Generate Spectrum From Magnitude (scale=5000):

convert circle16_F_Q16_mag.png -contrast-stretch 0% \
-fx "log(4999*u+1)/log(5000)" -fill "rgb(1,1,1)" \
-opaque black circle16_F_Q16_spec5000.png

Circle Diameter 30

Generate Spectrum From Magnitude (scale=5000):

convert circle30_F_Q16_mag.png -contrast-stretch 0% \
-fx "log(4999*u+1)/log(5000)" -fill "rgb(1,1,1)" \
-opaque black circle30_F_Q16_spec5000.png

Circle Diameter 60

Generate Spectrum From Magnitude (scale=100000):

convert circle60_F_Q16_mag.png -contrast-stretch 0% \
-fx "log(99999*u+1)/log(5000)" -fill "rgb(1,1,1)" \
-opaque black circle60_F_Q16_spec100000.png

Gaussian Sigma=16

Generate Spectrum From Magnitude (scale=100000):

convert gaussian_128_sigma16_clut_F_Q16_mag.png \
-contrast-stretch 0% -fx "log(99999*u+1)/log(100000)" \
-fill "rgb(1,1,1)" -opaque black \
gaussian_128_sigma16_clut_F_Q16_mag_spec100000.png

Profile Of Gauss Sigma 16

Profile Of Magnitude Of Gauss Sigma 16

Gaussian Sigma=8

Generate Spectrum From Magnitude (scale=100000):

convert gaussian_128_sigma8_clut_F_Q16_mag.png \
-contrast-stretch 0% -fx "log(99999*u+1)/log(100000)" \
-fill "rgb(1,1,1)" -opaque black \
gaussian_128_sigma16_clut_F_Q16_mag_spec100000.png

Profile Of Gauss Sigma 8

Profile Of Magnitude Of Gauss Sigma 8

Grid 8x16

Generate Spectrum From Magnitude (scale=100000):

convert grid8x16_F_Q16_mag.png -contrast-stretch 0% \
-fx "log(99999*u+1)/log(5000)" -fill "rgb(1,1,1)" \
-opaque black grid8x16_F_Q16_spec100000.png



Reconstruction From Magnitude Or Phase Only

FFT to produce tiff stack of frames:

path-to-imfft/trunk/demo f lena.png

Generate Magnitude and Phase Images:

convert lena_F_Q16.tiff[0] lena_F_Q16.tiff[2] \
lena_F_Q16.tiff[4] lena_mag.png

convert lena_F_Q16.tiff[1] lena_F_Q16.tiff[3] \
lena_F_Q16.tiff[5] square30_phase.png

Generate Spectrum From Magnitude (scale=100000):

convert lena_F_Q16_mag.png -contrast-stretch 0% \
-fx "log(99999*u+1)/log(5000)" -fill "rgb(1,1,1)" \
-opaque black lena_F_Q16_spec100000.png
phase

Combine Magnitude With Flat Gray Image:

convert -size 256x256 xc:gray gray.png
convert \( lena_F_Q16_mag.png -channel R -separate \) \
\( gray.png -channel R -separate \) \
\( lena_F_Q16_mag.png -channel G -separate \) \
\( gray.png -channel G -separate \) \
\( lena_F_Q16_mag.png -channel B -separate \) \
\( gray.png -channel B -separate \) \
lena_mag_gray.tiff

IFT of tiff stack to regenerate image:

path-to-imfft/trunk/demo i lena_mag_gray_F_Q16.tiff

Combine Flat Gray Image With Phase:

convert -size 256x256 xc:gray gray.png
convert \( gray.png -channel R -separate \) \
\( lena_F_Q16_phase.png -channel R -separate \) \
\( gray.png -channel G -separate \) \
\( lena_F_Q16_phase.png -channel G -separate \) \
\( gray.png -channel B -separate \) \
\( lena_F_Q16_phase.png -channel B -separate \) \
lena_gray_phase.tiff

IFT of tiff stack to regenerate image:

path-to-imfft/trunk/demo i lena_gray_phase_F_Q16.tiff

magnitude only reconstruction phase only reconstruction


Dynamic Range Change By Coefficient Rooting: (Raising Magnitude To A Power)
original

FFT to produce tiff stack of frames:

path-to-imfft/trunk/demo f lena.png

Generate Magnitude and Phase Images:

convert lena_F_Q16.tiff[0] lena_F_Q16.tiff[2] \
lena_F_Q16.tiff[4] lena_mag.png

convert lena_F_Q16.tiff[1] lena_F_Q16.tiff[3] \
lena_F_Q16.tiff[5] square30_phase.png

Generate Spectrum From Magnitude (scale=100000):

convert lena_F_Q16_mag.png -contrast-stretch 0% \
-fx "log(99999*u+1)/log(5000)" -fill "rgb(1,1,1)" \
-opaque black lena_F_Q16_spec100000.png
phase
Increase Dynamic Range:
Raise Magnitude To Power Less Than 1.0

convert lena_F_Q16_mag.png -fx "pow(u,0.9)" \
lena_F_Q16_mag_powp9.png
Decrease Dynamic Range:
Raise Magnitude To Power Greater Than 1.0

convert lena_F_Q16_mag.png -fx "pow(u,1.1)" \
lena_F_Q16_mag_pow1p1.png

Combine Modified Magnitude With Phase:

convert \( lena_F_Q16_mag_powp9.png -channel R -separate \) \
\( lena_F_Q16_phase.png -channel R -separate \) \
\( lena_F_Q16_mag_powp9.png -channel G -separate \) \
\( lena_F_Q16_phase.png -channel G -separate \) \
\( lena_F_Q16_mag_powp9.png -channel B -separate \) \
\( lena_F_Q16_phase.png -channel B -separate \) \
lena_mag_powp9_phase.tiff

IFT of tiff stack to regenerate image:

path-to-imfft/trunk/demo i lena_mag_powp9_phase_F_Q16.tiff

Combine Modified Magnitude With Phase:

convert \( lena_F_Q16_mag_pow1p1.png -channel R -separate \) \
\( lena_F_Q16_phase.png -channel R -separate \) \
\( lena_F_Q16_mag_powp1p1.png -channel G -separate \) \
\( lena_F_Q16_phase.png -channel G -separate \) \
\( lena_F_Q16_mag_powp1p1.png -channel B -separate \) \
\( lena_F_Q16_phase.png -channel B -separate \) \
lena_mag_pow1p1_phase.tiff

IFT of tiff stack to regenerate image:

path-to-imfft/trunk/demo i lena_mag_pow1p1_phase_F_Q16.tiff

increased dynamic range decreased dynamic range
original


Image With "Noise" Pattern - Demo Pattern Filtering By Notch Masking FFT
original noisy image

(image from http://www.roborealm.com/help/FFT.php)

Perform FFT to produce tiff stack of frames:

path-to-imfft/trunk/demo f FFT_src.png

Then create magnitude and phase (below):

convert FFT_src_F_Q16.tiff[0] FFT_src_F_Q16.tiff[2] \
FFT_src_F_Q16.tiff[4] FFT_src_F_Q16_mag.png

convert FFT_src_F_Q16.tiff[1] FFT_src_F_Q16.tiff[3] \
FFT_src_F_Q16.tiff[5] FFT_src_F_Q16_phase.png

Generate Spectrum From Magnitude (scale=100000):

convert FFT_src_F_Q16_mag.png -contrast-stretch 0% \
-fx "log(99999*u+1)/log(100000)" -fill "rgb(1,1,1)" \
-opaque black FFT_src_F_Q16_spec100000.png

Phase

Mask Spectrum in GIMP or Photoshop

Threshhold To Create Binary Notch Mask
From Masked Spectrum

convert FFT_src_F_Q16_spec100000_masked.jpg \
-threshold 1 FFT_scr_F_Q16_mask.jpg

Apply Notch Mask To FFT Stack:

convert \( FFT_src_F_Q16.tiff -coalesce \) null: \
FFT_scr_F_Q16_mask.png -compose multiply \
-layers Composite FFT_src_F_Q16_filtfft.tiff

Then Perform IFT On Masked FFT Stack:

path-to-imfft/trunk/demo i FFT_src_F_Q16_filtfft.tiff

Original Noisy Image

Create Difference Image And Normalize To See Noise Pattern That Was Removed:

convert FFT_src.png FFT_src_F_Q16_filtfft_rt_Q16.tiff \
-compose difference -composite -normalize FFT_src_F_Q16_fft_diff_norm.png



Image With A Second "Noise" Pattern - Demo Pattern Filtering By Notch Masking FFT
original noisy image

(image from http://www.mediacy.com/index.aspx?page=AH_FFTExample)

Perform FFT to produce tiff stack of frames:

path-to-imfft/trunk/demo f clown.png

Then create magnitude and phase (below):

convert clown_F_Q16.tiff[0] clown_F_Q16.tiff[2] \
clown_F_Q16.tiff[4] clown_F_Q16_mag.png

convert clown_F_Q16.tiff[1] clown_F_Q16.tiff[3] \
clown_F_Q16.tiff[5] clown_F_Q16_phase.png

Generate Spectrum From Magnitude (scale=100000):

convert clown_F_Q16_mag.png -contrast-stretch 0% \
-fx "log(99999*u+1)/log(100000)" -fill "rgb(1,1,1)" \
-opaque black clown_F_Q16_spec100000.png

Phase

Mask Spectrum in GIMP or Photoshop

Threshhold To Create Binary Notch Mask
From Masked Spectrum

convert clown_F_Q16_spec100000_masked.jpg \
-threshold 1 clown_F_Q16_mask.jpg

Apply Notch Mask To FFT Stack:

convert \( clown_F_Q16.tiff -coalesce \) null: \
clown_F_Q16_mask.png -compose multiply \
-layers Composite clown_F_Q16_filtfft.tiff

Then Perform IFT On Masked FFT Stack:

path-to-imfft/trunk/demo i clown_F_Q16_filtfft.tiff

Original Noisy Image

Create Difference Image And Normalize To See Noise Pattern That Was Removed:

convert clown.png clown_F_Q16_filtfft_rt_Q16.tiff \
-compose difference -composite -normalize clown_F_Q16_fft_diff_norm.png



Image With Grid Pattern - Demo Pattern Filtering By Notch Masking FFT
original image

Perform FFT to produce tiff stack of frames:

path-to-imfft/trunk/demo f lena_grid16.png

Then create magnitude and phase (below):

convert lena_grid16_F_Q16.tiff[0] lena_grid16_F_Q16.tiff[2] \
lena_grid16_F_Q16.tiff[4] lena_grid16_F_Q16_mag.png

convert lena_grid16_F_Q16.tiff[1] lena_grid16_F_Q16.tiff[3] \
lena_grid16_F_Q16.tiff[5] lena_grid16_F_Q16_phase.png

Generate Spectrum From Magnitude (scale=100000):

convert lena_grid16_F_Q16_mag.png -contrast-stretch 0% \
-fx "log(99999*u+1)/log(100000)" -fill "rgb(1,1,1)" \
-opaque black lena_grid16_F_Q16_spec100000.png

Phase

Mask Spectrum in GIMP or Photoshop

Threshhold To Create Binary Notch Mask
From Masked Spectrum

convert lena_grid16_F_Q16_spec100000_masked.jpg \
-threshold 1 lena_grid16_F_Q16_mask.jpg

Apply Notch Mask To FFT Stack:

convert \( lena_grid16_F_Q16.tiff -coalesce \) null: \
lena_grid16_F_Q16_mask.png -compose multiply \
-layers Composite lena_grid16_F_Q16_masked.tiff

Then Perform IFT On Masked FFT Stack:

path-to-imfft/trunk/demo i lena_grid16_F_Q16_masked.tiff

Original Noisy Image



Low Pass Filtering: Blurring

(Larger Masks Generate Less Blurring And Smaller Masks Generate More Blurring)
original image

Perform FFT to produce tiff stack of frames:

path-to-imfft/trunk/demo f lena.png

Create Simple Circular Low Pass Filter Mask:
(Diameter 92)

convert -size 256x256 xc:black -fill white -stroke white \
-draw "circle 127,128 173,128" circle92.png

Apply Mask To Image And Do IFT:

convert \( lena_F_Q16.tiff -coalesce \) null: \
circle92.png -compose multiply -layers Composite \
lena_mask_circle92_F_Q16_filtfft.tiff

path-to-imfft/trunk/demo i lena_mask_circle92_F_Q16_filtfft.tiff

Create Simple Circular Low Pass Filter Mask:
(Diameter 64)

convert -size 256x256 xc:black -fill white -stroke white \
-draw "circle 127,128 159,128" circle64.png

Apply Mask To Image And Do IFT:

convert \( lena_F_Q16.tiff -coalesce \) null: \
circle64.png -compose multiply -layers Composite \
lena_mask_circle64_F_Q16_filtfft.tiff

path-to-imfft/trunk/demo i lena_mask_circle64_F_Q16_filtfft.tiff

Create Gaussian Low Pass Filter Mask:
(Sigma 48)

convert -size 256x256 xc: -fx \
"xx=i-w/2; yy=j-h/2; rr=hypot(xx,yy)/(sqrt(2)*256); exp(-(rr*rr)/(pow(48/256,2)))" \
gauss48.png

Apply Mask To Image And Do IFT:

convert \( lena_F_Q16.tiff -coalesce \) null: \
gauss48.png -compose multiply -layers Composite \
lena_mask_gauss48_F_Q16_filtfft.tiff

path-to-imfft/trunk/demo i lena_mask_gauss48_F_Q16_filtfft.tiff

Create Gaussian Low Pass Filter Mask:
(Sigma 32)

convert -size 256x256 xc: -fx \
"xx=i-w/2; yy=j-h/2; rr=hypot(xx,yy)/(sqrt(2)*256); exp(-(rr*rr)/(pow(32/256,2)))" \
gauss32.png

Apply Mask To Image And Do IFT:

convert \( lena_F_Q16.tiff -coalesce \) null: \
gauss32.png -compose multiply -layers Composite \
lena_mask_gauss32_F_Q16_filtfft.tiff

path-to-imfft/trunk/demo i lena_mask_gauss32_F_Q16_filtfft.tiff



High Pass Filtering: Edge Extraction And Sharpening

original image

Perform FFT to produce tiff stack of frames:

path-to-imfft/trunk/demo f lena.png

Create Simple Circular Low Pass Filter Mask And Negate:
(Diameter 32)

convert -size 256x256 xc:black -fill white -stroke white \
-draw "circle 127,128 143,128" -negate circle32neg.png

Apply Mask To Image And Do IFT:

convert \( lena_F_Q16.tiff -coalesce \) null: \
circle32neg.png -compose multiply -layers Composite \
lena_mask_circle32neg_F_Q16_filtfft.tiff

path-to-imfft/trunk/demo i lena_mask_circle32neg_F_Q16_filtfft.tiff

Simple High Pass Filter Mask

Normalize Filtered Image To Enhance Edges:

convert lena_mask_circle32neg_F_Q16_filtfft.tiff \
-normalize lena_mask_circle32neg_F_Q16_filtfft_norm.tiff

Create Gaussian Low Pass Filter Mask And Negate:
(Sigma 48)

convert -size 256x256 xc: -fx \
"xx=i-w/2; yy=j-h/2; rr=hypot(xx,yy)/(sqrt(2)*256); exp(-(rr*rr)/(pow(16/256,2)))" \
-negate gauss16neg.png

Apply Mask To Image And Do IFT:

convert \( lena_F_Q16.tiff -coalesce \) null: \
gauss16neg.png -compose multiply -layers Composite \
lena_mask_gauss16neg_F_Q16_filtfft.tiff

path-to-imfft/trunk/demo i lena_mask_gauss16neg_F_Q16_filtfft.tiff

Gaussian High Pass Filter Mask

Normalize Filtered Image To Enhance Edges:

convert lena_mask_gauss16neg_F_Q16_filtfft.tiff \
-normalize lena_mask_gauss16neg_F_Q16_filtfft_norm.tiff

Blend Image With Gaussian High Pass Edge Image To Sharpen:

composite -blend 100x100 lena.png \
lena_mask_gauss16neg_F_Q16_filtfft_rt_Q16.tiff \
lena_sharp16_blend100x100.png

Original Image



Image With A Third "Noise" Pattern - Demo Pattern Filtering By Notch and Low Pass Masking FFT
original noisy image

(image from http://home.planet.nl/~ber03728/4N6site/improc/fftplugin/howto.htm)

Perform FFT to produce tiff stack of frames:

path-to-imfft/trunk/demo f fingerprint.png

Then create magnitude and phase (below):

convert fingerprint_F_Q16.tiff[0] fingerprint_F_Q16.tiff[2] \
fingerprint_F_Q16.tiff[4] fingerprint_F_Q16_mag.png

convert fingerprint_F_Q16.tiff[1] fingerprint_F_Q16.tiff[3] \
fingerprint_F_Q16.tiff[5] fingerprint_F_Q16_phase.png

Generate Spectrum From Magnitude (scale=100000):

convert fingerprint_F_Q16_mag.png -contrast-stretch 0% \
-fx "log(99999*u+1)/log(100000)" -fill "rgb(1,1,1)" \
-opaque black fingerprint_F_Q16_spec100000.png

Phase

Mask Spectrum in GIMP or Photoshop
To Remove Low Frequency Stripes

Threshhold To Create Binary Notch Mask
From Masked Spectrum

convert fingerprint_F_Q16_spec100000_masked1.png \
-threshold 1 fingerprint_F_Q16_mask1.png

Apply Mask To FFT Stack To Remove Stripes:

convert \( fingerprint_F_Q16.tiff -coalesce \) null: \
fingerprint_F_Q16_mask1.png -compose multiply \
-layers Composite fingerprint_F_Q16_masked1.tiff

Then Perform IFT On Masked FFT Stack:

path-to-imfft/trunk/demo i fingerprint_F_Q16_masked1.tiff

Original Noisy Image

Apply Low Pass Filter To Remove Dotted Noise:

convert -size 400x400 xc:black -fill white -stroke white \
-draw "circle 199,200 279,200" circle80.png
convert fingerprint_F_Q16_spec100000_masked1.png \
circle80.png -compose multiply -composite \
fingerprint_F_Q16_spec100000_masked2.png

Threshhold To Create Binary Low Pass And
Notch Mask From Masked Spectrum

convert fingerprint_F_Q16_spec100000_masked2.png \
-threshold 1 fingerprint_F_Q16_mask2.png

Apply Mask To FFT Stack To Remove Dotted Noise:

convert \( fingerprint_F_Q16.tiff -coalesce \) null: \
fingerprint_F_Q16_mask2.png -compose multiply \
-layers Composite fingerprint_F_Q16_masked2.tiff

Then Perform IFT On Masked FFT Stack:

path-to-imfft/trunk/demo i fingerprint_F_Q16_masked1.tiff

Original Noisy Image



Stay Tuned - More To Come

Fred Weinhaus

fmw at alink.net