diff --git a/doc/tutorials/core/discrete_fourier_transform/discrete_fourier_transform.markdown b/doc/tutorials/core/discrete_fourier_transform/discrete_fourier_transform.markdown index 22bbc877d8..32536a5632 100644 --- a/doc/tutorials/core/discrete_fourier_transform/discrete_fourier_transform.markdown +++ b/doc/tutorials/core/discrete_fourier_transform/discrete_fourier_transform.markdown @@ -1,6 +1,9 @@ Discrete Fourier Transform {#tutorial_discrete_fourier_transform} ========================== +@prev_tutorial{tutorial_random_generator_and_text} +@next_tutorial{tutorial_file_input_output_with_xml_yml} + Goal ---- @@ -8,21 +11,49 @@ We'll seek answers for the following questions: - What is a Fourier transform and why use it? - How to do it in OpenCV? -- Usage of functions such as: @ref cv::copyMakeBorder() , @ref cv::merge() , @ref cv::dft() , @ref - cv::getOptimalDFTSize() , @ref cv::log() and @ref cv::normalize() . +- Usage of functions such as: **copyMakeBorder()** , **merge()** , **dft()** , + **getOptimalDFTSize()** , **log()** and **normalize()** . Source code ----------- +@add_toggle_cpp You can [download this from here -](https://github.com/opencv/opencv/tree/master/samples/cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp) or +](https://raw.githubusercontent.com/opencv/opencv/master/samples/cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp) or find it in the `samples/cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp` of the OpenCV source code library. +@end_toggle + +@add_toggle_java +You can [download this from here +](https://raw.githubusercontent.com/opencv/opencv/master/samples/java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java) or +find it in the +`samples/java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java` of the +OpenCV source code library. +@end_toggle + +@add_toggle_python +You can [download this from here +](https://raw.githubusercontent.com/opencv/opencv/master/samples/python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py) or +find it in the +`samples/python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py` of the +OpenCV source code library. +@end_toggle + +Here's a sample usage of **dft()** : + +@add_toggle_cpp +@include cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp +@end_toggle -Here's a sample usage of @ref cv::dft() : +@add_toggle_java +@include java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java +@end_toggle -@includelineno cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp +@add_toggle_python +@include python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py +@end_toggle Explanation ----------- @@ -49,89 +80,140 @@ Fourier Transform too needs to be of a discrete type resulting in a Discrete Fou (*DFT*). You'll want to use this whenever you need to determine the structure of an image from a geometrical point of view. Here are the steps to follow (in case of a gray scale input image *I*): --# **Expand the image to an optimal size**. The performance of a DFT is dependent of the image - size. It tends to be the fastest for image sizes that are multiple of the numbers two, three and - five. Therefore, to achieve maximal performance it is generally a good idea to pad border values - to the image to get a size with such traits. The @ref cv::getOptimalDFTSize() returns this - optimal size and we can use the @ref cv::copyMakeBorder() function to expand the borders of an - image: - @code{.cpp} - Mat padded; //expand input image to optimal size - int m = getOptimalDFTSize( I.rows ); - int n = getOptimalDFTSize( I.cols ); // on the border add zero pixels - copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0)); - @endcode - The appended pixels are initialized with zero. - --# **Make place for both the complex and the real values**. The result of a Fourier Transform is - complex. This implies that for each image value the result is two image values (one per - component). Moreover, the frequency domains range is much larger than its spatial counterpart. - Therefore, we store these usually at least in a *float* format. Therefore we'll convert our - input image to this type and expand it with another channel to hold the complex values: - @code{.cpp} - Mat planes[] = {Mat_(padded), Mat::zeros(padded.size(), CV_32F)}; - Mat complexI; - merge(planes, 2, complexI); // Add to the expanded another plane with zeros - @endcode --# **Make the Discrete Fourier Transform**. It's possible an in-place calculation (same input as - output): - @code{.cpp} - dft(complexI, complexI); // this way the result may fit in the source matrix - @endcode --# **Transform the real and complex values to magnitude**. A complex number has a real (*Re*) and a - complex (imaginary - *Im*) part. The results of a DFT are complex numbers. The magnitude of a - DFT is: - - \f[M = \sqrt[2]{ {Re(DFT(I))}^2 + {Im(DFT(I))}^2}\f] - - Translated to OpenCV code: - @code{.cpp} - split(complexI, planes); // planes[0] = Re(DFT(I), planes[1] = Im(DFT(I)) - magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude - Mat magI = planes[0]; - @endcode --# **Switch to a logarithmic scale**. It turns out that the dynamic range of the Fourier - coefficients is too large to be displayed on the screen. We have some small and some high - changing values that we can't observe like this. Therefore the high values will all turn out as - white points, while the small ones as black. To use the gray scale values to for visualization - we can transform our linear scale to a logarithmic one: - - \f[M_1 = \log{(1 + M)}\f] - - Translated to OpenCV code: - @code{.cpp} - magI += Scalar::all(1); // switch to logarithmic scale - log(magI, magI); - @endcode --# **Crop and rearrange**. Remember, that at the first step, we expanded the image? Well, it's time - to throw away the newly introduced values. For visualization purposes we may also rearrange the - quadrants of the result, so that the origin (zero, zero) corresponds with the image center. - @code{.cpp} - magI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2)); - int cx = magI.cols/2; - int cy = magI.rows/2; - - Mat q0(magI, Rect(0, 0, cx, cy)); // Top-Left - Create a ROI per quadrant - Mat q1(magI, Rect(cx, 0, cx, cy)); // Top-Right - Mat q2(magI, Rect(0, cy, cx, cy)); // Bottom-Left - Mat q3(magI, Rect(cx, cy, cx, cy)); // Bottom-Right - - Mat tmp; // swap quadrants (Top-Left with Bottom-Right) - q0.copyTo(tmp); - q3.copyTo(q0); - tmp.copyTo(q3); - - q1.copyTo(tmp); // swap quadrant (Top-Right with Bottom-Left) - q2.copyTo(q1); - tmp.copyTo(q2); - @endcode --# **Normalize**. This is done again for visualization purposes. We now have the magnitudes, - however this are still out of our image display range of zero to one. We normalize our values to - this range using the @ref cv::normalize() function. -@code{.cpp} -normalize(magI, magI, 0, 1, NORM_MINMAX); // Transform the matrix with float values into a - // viewable image form (float between values 0 and 1). -@endcode +#### Expand the image to an optimal size + +The performance of a DFT is dependent of the image +size. It tends to be the fastest for image sizes that are multiple of the numbers two, three and +five. Therefore, to achieve maximal performance it is generally a good idea to pad border values +to the image to get a size with such traits. The **getOptimalDFTSize()** returns this +optimal size and we can use the **copyMakeBorder()** function to expand the borders of an +image (the appended pixels are initialized with zero): + +@add_toggle_cpp +@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp expand +@end_toggle + +@add_toggle_java +@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java expand +@end_toggle + +@add_toggle_python +@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py expand +@end_toggle + +#### Make place for both the complex and the real values + +The result of a Fourier Transform is +complex. This implies that for each image value the result is two image values (one per +component). Moreover, the frequency domains range is much larger than its spatial counterpart. +Therefore, we store these usually at least in a *float* format. Therefore we'll convert our +input image to this type and expand it with another channel to hold the complex values: + +@add_toggle_cpp +@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp complex_and_real +@end_toggle + +@add_toggle_java +@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java complex_and_real +@end_toggle + +@add_toggle_python +@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py complex_and_real +@end_toggle + +#### Make the Discrete Fourier Transform +It's possible an in-place calculation (same input as +output): + +@add_toggle_cpp +@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp dft +@end_toggle + +@add_toggle_java +@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java dft +@end_toggle + +@add_toggle_python +@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py dft +@end_toggle + +#### Transform the real and complex values to magnitude +A complex number has a real (*Re*) and a +complex (imaginary - *Im*) part. The results of a DFT are complex numbers. The magnitude of a +DFT is: + +\f[M = \sqrt[2]{ {Re(DFT(I))}^2 + {Im(DFT(I))}^2}\f] + +Translated to OpenCV code: + +@add_toggle_cpp +@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp magnitude +@end_toggle + +@add_toggle_java +@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java magnitude +@end_toggle + +@add_toggle_python +@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py magnitude +@end_toggle + +#### Switch to a logarithmic scale +It turns out that the dynamic range of the Fourier +coefficients is too large to be displayed on the screen. We have some small and some high +changing values that we can't observe like this. Therefore the high values will all turn out as +white points, while the small ones as black. To use the gray scale values to for visualization +we can transform our linear scale to a logarithmic one: + +\f[M_1 = \log{(1 + M)}\f] + +Translated to OpenCV code: + +@add_toggle_cpp +@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp log +@end_toggle + +@add_toggle_java +@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java log +@end_toggle + +@add_toggle_python +@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py log +@end_toggle + +#### Crop and rearrange +Remember, that at the first step, we expanded the image? Well, it's time +to throw away the newly introduced values. For visualization purposes we may also rearrange the +quadrants of the result, so that the origin (zero, zero) corresponds with the image center. + +@add_toggle_cpp +@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp crop_rearrange +@end_toggle + +@add_toggle_java +@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java crop_rearrange +@end_toggle + +@add_toggle_python +@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py crop_rearrange +@end_toggle + +#### Normalize +This is done again for visualization purposes. We now have the magnitudes, +however this are still out of our image display range of zero to one. We normalize our values to +this range using the @ref cv::normalize() function. + +@add_toggle_cpp +@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp normalize +@end_toggle + +@add_toggle_java +@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java normalize +@end_toggle + +@add_toggle_python +@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py normalize +@end_toggle Result ------ @@ -140,7 +222,7 @@ An application idea would be to determine the geometrical orientation present in example, let us find out if a text is horizontal or not? Looking at some text you'll notice that the text lines sort of form also horizontal lines and the letters form sort of vertical lines. These two main components of a text snippet may be also seen in case of the Fourier transform. Let us use -[this horizontal ](https://github.com/opencv/opencv/tree/master/samples/data/imageTextN.png) and [this rotated](https://github.com/opencv/opencv/tree/master/samples/data/imageTextR.png) +[this horizontal ](https://raw.githubusercontent.com/opencv/opencv/master/samples/data/imageTextN.png) and [this rotated](https://raw.githubusercontent.com/opencv/opencv/master/samples/data/imageTextR.png) image about a text. In case of the horizontal text: diff --git a/doc/tutorials/core/table_of_content_core.markdown b/doc/tutorials/core/table_of_content_core.markdown index af040b9145..0a1fb5614c 100644 --- a/doc/tutorials/core/table_of_content_core.markdown +++ b/doc/tutorials/core/table_of_content_core.markdown @@ -76,6 +76,8 @@ understanding how to manipulate the images on a pixel level. - @subpage tutorial_discrete_fourier_transform + *Languages:* C++, Java, Python + *Compatibility:* \> OpenCV 2.0 *Author:* Bernát Gábor diff --git a/samples/cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp b/samples/cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp index e23ab1c326..7121b16b35 100644 --- a/samples/cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp +++ b/samples/cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp @@ -8,45 +8,58 @@ using namespace cv; using namespace std; -static void help(char* progName) +static void help(void) { cout << endl << "This program demonstrated the use of the discrete Fourier transform (DFT). " << endl << "The dft of an image is taken and it's power spectrum is displayed." << endl << "Usage:" << endl - << progName << " [image_name -- default ../data/lena.jpg] " << endl << endl; + << "./discrete_fourier_transform [image_name -- default ../data/lena.jpg]" << endl; } int main(int argc, char ** argv) { - help(argv[0]); + help(); const char* filename = argc >=2 ? argv[1] : "../data/lena.jpg"; Mat I = imread(filename, IMREAD_GRAYSCALE); - if( I.empty()) + if( I.empty()){ + cout << "Error opening image" << endl; return -1; + } +//! [expand] Mat padded; //expand input image to optimal size int m = getOptimalDFTSize( I.rows ); int n = getOptimalDFTSize( I.cols ); // on the border add zero values copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0)); +//! [expand] +//! [complex_and_real] Mat planes[] = {Mat_(padded), Mat::zeros(padded.size(), CV_32F)}; Mat complexI; merge(planes, 2, complexI); // Add to the expanded another plane with zeros +//! [complex_and_real] +//! [dft] dft(complexI, complexI); // this way the result may fit in the source matrix +//! [dft] // compute the magnitude and switch to logarithmic scale // => log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2)) +//! [magnitude] split(complexI, planes); // planes[0] = Re(DFT(I), planes[1] = Im(DFT(I)) magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude Mat magI = planes[0]; +//! [magnitude] +//! [log] magI += Scalar::all(1); // switch to logarithmic scale log(magI, magI); +//! [log] +//! [crop_rearrange] // crop the spectrum, if it has an odd number of rows or columns magI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2)); @@ -67,9 +80,12 @@ int main(int argc, char ** argv) q1.copyTo(tmp); // swap quadrant (Top-Right with Bottom-Left) q2.copyTo(q1); tmp.copyTo(q2); +//! [crop_rearrange] +//! [normalize] normalize(magI, magI, 0, 1, NORM_MINMAX); // Transform the matrix with float values into a // viewable image form (float between values 0 and 1). +//! [normalize] imshow("Input Image" , I ); // Show the result imshow("spectrum magnitude", magI); diff --git a/samples/java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java b/samples/java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java new file mode 100644 index 0000000000..ca2560be35 --- /dev/null +++ b/samples/java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java @@ -0,0 +1,109 @@ +import org.opencv.core.*; +import org.opencv.highgui.HighGui; +import org.opencv.imgcodecs.Imgcodecs; + +import java.util.List; +import java.util.*; + +class DiscreteFourierTransformRun{ + private void help() { + System.out.println("" + + "This program demonstrated the use of the discrete Fourier transform (DFT). \n" + + "The dft of an image is taken and it's power spectrum is displayed.\n" + + "Usage:\n" + + "./DiscreteFourierTransform [image_name -- default ../data/lena.jpg]"); + } + + public void run(String[] args){ + + help(); + + String filename = ((args.length > 0) ? args[0] : "../data/lena.jpg"); + + Mat I = Imgcodecs.imread(filename, Imgcodecs.IMREAD_GRAYSCALE); + if( I.empty() ) { + System.out.println("Error opening image"); + System.exit(-1); + } + + //! [expand] + Mat padded = new Mat(); //expand input image to optimal size + int m = Core.getOptimalDFTSize( I.rows() ); + int n = Core.getOptimalDFTSize( I.cols() ); // on the border add zero values + Core.copyMakeBorder(I, padded, 0, m - I.rows(), 0, n - I.cols(), Core.BORDER_CONSTANT, Scalar.all(0)); + //! [expand] + + //! [complex_and_real] + List planes = new ArrayList(); + padded.convertTo(padded, CvType.CV_32F); + planes.add(padded); + planes.add(Mat.zeros(padded.size(), CvType.CV_32F)); + Mat complexI = new Mat(); + Core.merge(planes, complexI); // Add to the expanded another plane with zeros + //! [complex_and_real] + + //! [dft] + Core.dft(complexI, complexI); // this way the result may fit in the source matrix + //! [dft] + + // compute the magnitude and switch to logarithmic scale + // => log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2)) + //! [magnitude] + Core.split(complexI, planes); // planes.get(0) = Re(DFT(I) + // planes.get(1) = Im(DFT(I)) + Core.magnitude(planes.get(0), planes.get(1), planes.get(0));// planes.get(0) = magnitude + Mat magI = planes.get(0); + //! [magnitude] + + //! [log] + Mat matOfOnes = Mat.ones(magI.size(), magI.type()); + Core.add(matOfOnes, magI, magI); // switch to logarithmic scale + Core.log(magI, magI); + //! [log] + + //! [crop_rearrange] + // crop the spectrum, if it has an odd number of rows or columns + magI = magI.submat(new Rect(0, 0, magI.cols() & -2, magI.rows() & -2)); + + // rearrange the quadrants of Fourier image so that the origin is at the image center + int cx = magI.cols()/2; + int cy = magI.rows()/2; + + Mat q0 = new Mat(magI, new Rect(0, 0, cx, cy)); // Top-Left - Create a ROI per quadrant + Mat q1 = new Mat(magI, new Rect(cx, 0, cx, cy)); // Top-Right + Mat q2 = new Mat(magI, new Rect(0, cy, cx, cy)); // Bottom-Left + Mat q3 = new Mat(magI, new Rect(cx, cy, cx, cy)); // Bottom-Right + + Mat tmp = new Mat(); // swap quadrants (Top-Left with Bottom-Right) + q0.copyTo(tmp); + q3.copyTo(q0); + tmp.copyTo(q3); + + q1.copyTo(tmp); // swap quadrant (Top-Right with Bottom-Left) + q2.copyTo(q1); + tmp.copyTo(q2); + //! [crop_rearrange] + + magI.convertTo(magI, CvType.CV_8UC1); + //! [normalize] + Core.normalize(magI, magI, 0, 255, Core.NORM_MINMAX, CvType.CV_8UC1); // Transform the matrix with float values + // into a viewable image form (float between + // values 0 and 255). + //! [normalize] + + HighGui.imshow("Input Image" , I ); // Show the result + HighGui.imshow("Spectrum Magnitude", magI); + HighGui.waitKey(); + + System.exit(0); + } +} + + +public class DiscreteFourierTransform { + public static void main(String[] args) { + // Load the native library. + System.loadLibrary(Core.NATIVE_LIBRARY_NAME); + new DiscreteFourierTransformRun().run(args); + } +} diff --git a/samples/python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py b/samples/python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py new file mode 100644 index 0000000000..a16c9bead8 --- /dev/null +++ b/samples/python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py @@ -0,0 +1,80 @@ +from __future__ import print_function +import sys + +import cv2 +import numpy as np + + +def print_help(): + print(''' + This program demonstrated the use of the discrete Fourier transform (DFT). + The dft of an image is taken and it's power spectrum is displayed. + Usage: + discrete_fourier_transform.py [image_name -- default ../../../../data/lena.jpg]''') + + +def main(argv): + + print_help() + + filename = argv[0] if len(argv) > 0 else "../../../../data/lena.jpg" + + I = cv2.imread(filename, cv2.IMREAD_GRAYSCALE) + if I is None: + print('Error opening image') + return -1 + ## [expand] + rows, cols = I.shape + m = cv2.getOptimalDFTSize( rows ) + n = cv2.getOptimalDFTSize( cols ) + padded = cv2.copyMakeBorder(I, 0, m - rows, 0, n - cols, cv2.BORDER_CONSTANT, value=[0, 0, 0]) + ## [expand] + ## [complex_and_real] + planes = [np.float32(padded), np.zeros(padded.shape, np.float32)] + complexI = cv2.merge(planes) # Add to the expanded another plane with zeros + ## [complex_and_real] + ## [dft] + cv2.dft(complexI, complexI) # this way the result may fit in the source matrix + ## [dft] + # compute the magnitude and switch to logarithmic scale + # = > log(1 + sqrt(Re(DFT(I)) ^ 2 + Im(DFT(I)) ^ 2)) + ## [magnitude] + cv2.split(complexI, planes) # planes[0] = Re(DFT(I), planes[1] = Im(DFT(I)) + cv2.magnitude(planes[0], planes[1], planes[0])# planes[0] = magnitude + magI = planes[0] + ## [magnitude] + ## [log] + matOfOnes = np.ones(magI.shape, dtype=magI.dtype) + cv2.add(matOfOnes, magI, magI) # switch to logarithmic scale + cv2.log(magI, magI) + ## [log] + ## [crop_rearrange] + magI_rows, magI_cols = magI.shape + # crop the spectrum, if it has an odd number of rows or columns + magI = magI[0:(magI_rows & -2), 0:(magI_cols & -2)] + cx = int(magI_rows/2) + cy = int(magI_cols/2) + + q0 = magI[0:cx, 0:cy] # Top-Left - Create a ROI per quadrant + q1 = magI[cx:cx+cx, 0:cy] # Top-Right + q2 = magI[0:cx, cy:cy+cy] # Bottom-Left + q3 = magI[cx:cx+cx, cy:cy+cy] # Bottom-Right + + tmp = np.copy(q0) # swap quadrants (Top-Left with Bottom-Right) + magI[0:cx, 0:cy] = q3 + magI[cx:cx + cx, cy:cy + cy] = tmp + + tmp = np.copy(q1) # swap quadrant (Top-Right with Bottom-Left) + magI[cx:cx + cx, 0:cy] = q2 + magI[0:cx, cy:cy + cy] = tmp + ## [crop_rearrange] + ## [normalize] + cv2.normalize(magI, magI, 0, 1, cv2.NORM_MINMAX) # Transform the matrix with float values into a + ## viewable image form(float between values 0 and 1). + ## [normalize] + cv2.imshow("Input Image" , I ) # Show the result + cv2.imshow("spectrum magnitude", magI) + cv2.waitKey() + +if __name__ == "__main__": + main(sys.argv[1:])