在傅立叶中应用高斯核时的图像阴影/旋转/翻转 space

Image shadowing / rotating / flipping when applying Gaussian kernel in Fourier space

免责声明: 我知道我可以使用 OpenCV smooth/blur 图像,但我的任务更深入,所以我可以理解这个过程。一般来说,我是图像处理和 DFT 的新手,所以这被证明是一个挑战。

描述:

我正在使用 FFTW 库和 OpenCV 进行一些图像处理,以将图像读入 OpenCV::Mat 对象和 灰度 颜色 space。我将数据转换为 double 类型,这样我就可以创建一个指向它的双指针,FFTW 的 FFT 函数需要它。这是在名为 imageDouble 的 Mat 对象中,指向它的指针是 pImageDouble.

std::string filename = "<path_to_your_image>";
Mat image = imread(filename, IMREAD_GRAYSCALE);

/***********************************************************************
*** creating Mat object of type *doube* and copying image contents to it
*** then creating a pointer to it.
***********************************************************************/
Mat imageDouble;
image.convertTo(imageDouble, CV_64FC1);
double* pImageDouble = imageDouble.ptr<double>(0);

我还有一个 高斯内核 ,我已将其读入 Mat 对象。我执行 循环移位 ,其中高斯内核的中心(和最高)值被移动到内核的左上角((0,0)位置),然后我归零- 将内核填充到我读入的原始图像的大小。结果(类型 double)存储在名为 paddedGKernel 的 Mat 对象中,指针名为 pPaddedGKernel.

Mat paddedGKernel = padGKernelWithZeros(nRows, nCols, rolledGKernel);
double* pPaddedGKernel = paddedGKernel.ptr<double>(0);

我初始化 fftw_complex 个对象以将 FFT 结果输出到 fftw_plan,然后我为 fftw_complex 个对象分配内存并执行计划。我使用 FFTW 的 fftw_plan_dft_r2c_2d() 函数对两个二维对象(imageshifted/padded Gaussian kernel)执行 FFT,然后在傅立叶中执行逐点乘法 space 以将高斯滤波器应用于原始图像。

fftw_complex* out;               //for result of FFT for original image
fftw_complex* outGaussian;       //for result of FFT for Gaussian kernel
fftw_plan p;

/*************************************************
*** Allocating memory for the fftw_complex objects
*************************************************/
out = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * nRows * nCols);
outGaussian = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * nRows * nCols);


/****************************************
*** FFT on imageDouble, outputting to out
****************************************/
p = fftw_plan_dft_r2c_2d(imageDouble.rows, imageDouble.cols, pImageDouble, out, FFTW_ESTIMATE);
fftw_execute(p);


/**************************************************
*** FFT on paddedGKernel, outputting to outGuassian
**************************************************/
p = fftw_plan_dft_r2c_2d(paddedGKernel.rows, paddedGKernel.cols, pPaddedGKernel, outGaussian, FFTW_ESTIMATE);
fftw_execute(p);


/****************************************************
*** Pointwise multiplication to apply Gaussian kernel
****************************************************/
for (size_t i = 0; i < nCCols * nRows; i++)
{
    out[i][0] = out[i][0] * (1 / outGaussian[i][0]);
}

然后我对逐点乘法的结果执行 逆 FFT fftw_plan_dft_c2r_2d() 并将其输出到 OpenCV::Mat 对象 imageCloneDouble 和将数据转换回 uchar,将数据放入 OpenCV::Mat imageBack.

/***********************************************************************
*** Making Mat object (type *double*) to put data into and pointer to it
***********************************************************************/
Mat imageCloneDouble = Mat::zeros(nRows, nCols, CV_64FC1);
double* pImageCloneDouble = imageCloneDouble.ptr<double>(0);

/*****************************************************************************
*** Making and executing plan for inverse FFT, outputting to imageCloneDouble,
*** then normalizing since this function puts out unnormalized values.
*****************************************************************************/
fftw_plan pp = fftw_plan_dft_c2r_2d(imageCloneDouble.rows, imageCloneDouble.cols, out, pImageCloneDouble, FFTW_BACKWARD | FFTW_ESTIMATE);
fftw_execute(pp);
imageCloneDouble = imageCloneDouble / (nCols * nRows *2);

/***************************************************************************
*** Making Mat object (type *uchar*) and copying data inverse FFT data to it
***************************************************************************/
Mat imageBack;
imageCloneDouble.convertTo(imageBack, CV_8UC1);

当我显示转换后的图像时 imageBack 我希望得到应用了高斯核的原图,但它看起来像原图,可能有一些修改覆盖了原始图像的旋转版本,看起来有一些修改。我不明白这个 flip/rotation 发生的位置和原因,以及为什么它与看起来像原始图像的东西重叠,但我怀疑它发生在我在傅里叶 space 中并执行逐点运算时或逐元素乘法。或者我省略了我需要对逐点乘法的结果执行的过程。

我需要对这些傅里叶数据做些什么 space 才能恢复到原始图像?

这是你的问题:

I perform a circular shift where the center (and highest) value of the Gaussian kernel is shifted to the top left (the (0,0) position) of the kernel, and then I zero-pad the kernel to the size of the original image that I read in.

您需要将这两个操作颠倒过来:先填充到合适的大小,然后应用循环移位。您确实需要内核的中心位于 (0,0)。但是你需要内核在循环周期世界中是连续的(即复制图像你应该能够看到完整的高斯内核)。当你在循环移位之后填充时,你在这个循环周期的世界中将内核的四个象限分开。


第二个问题在这一行:
out[i][0] = out[i][0] * (1 / outGaussian[i][0]);

首先,为什么要除而不是乘?您正在尝试应用卷积,不是吗?

其次,您只是将复数的实部相乘,而虚部保持不变。您需要对两个复数进行完全复数乘法以产生一个新的复数。将 out 指针转换为 std::complex<double>*,然后使用编译器的复杂算术知识来执行您的命令!

std::size_t N = nRows * nCols;
fftw_complex* out = fftw_alloc_complex(N);
fftw_complex* outGaussian = fftw_alloc_complex(N);

// ...

auto* out_p = reinterpret_cast<std::complex<double>*>(out);
auto* outGaussian_p = reinterpret_cast<std::complex<double>*>(outGaussian);
for (std::size_t i = 0; i < N; i++)
{
    out_p[i] *= outGaussian_p[i];
}

请注意,std::complex<double>fftw_complex 具有相同的内存布局,由 std::complex 的 C++ 规范保证,目的是能够进行此类转换。实际上并没有重新解释,只是C++类型系统这么认为。

解决此问题的关键始于 Cris 指出的第二个问题。所以首先要做的就是实现那部分回复中提供的代码。

auto* out_p = reinterpret_cast<std::complex<double>*>(out);
auto* outGaussian_p = reinterpret_cast<std::complex<double>*>(outGaussian);
for (std::size_t i = 0; i < N; i++)
{
    out_p[i] *= outGaussian_p[i];
}

我用通常的方式对高斯核进行了标准化(除以所有值的总和)。从 Cris 那里进行上述更改后,图像应用了模糊,唯一的问题是像素值的范围发生了变化。

为了解决这个问题,我应用了对比度拉伸公式来匹配原始范围并解决了问题。

/* Finding the original image's minimum and maximum value */
double oMin, oMax;
minMaxLoc(image, &oMin, &oMax);

/* Finding min and max values for our 'imageBack' and rounding them. */
double min, max;
minMaxLoc(imageCloneDouble, &min, &max);
min = round(min);
max = round(max);

/* Code version of formula for contrast stretching */
if (oMin != min | oMax != max) {
    double numerator = oMax - oMin;
    double denominator = max - min;
    double scaledMin = -(min)*numerator;
    double scaledOMin = oMin * denominator;
    double innerTerm = scaledMin + scaledOMin;

    imageCloneDouble = ((numerator * imageCloneDouble) + innerTerm ) / denominator;
}