zjx
zjx
发布于 2025-10-22 / 6 阅读
0
0

二维离散傅里叶变换

数学原理

使用离散二维傅立叶变换(Discrete Fourier Transformation),将灰度化的imori.jpg表示为频谱图。然后用二维离散傅立叶逆变换将图像复原。

二维离散傅立叶变换是傅立叶变换在图像处理上的应用方法。通常傅立叶变换用于分离模拟信号或音频等连续一维信号的频率。但是,数字图像使用[0,255]范围内的离散值表示,并且图像使用H×W的二维矩阵表示,所以在这里使用二维离散傅立叶变换。

二维离散傅立叶变换使用下式计算,其中I表示输入图像:

G(k,l)=\frac{1}{HW}\sum_{y=0}^{H-1}\sum_{x=0}^{W-1}I(x,y)e^{-2{\pi}j(\frac{kx}{W}+\frac{ly}{H})}

在这里让图像灰度化后,再进行离散二维傅立叶变换。

频谱图为了能表示复数G,所以图上所画长度为G的绝对值。这回的图像表示时,请将频谱图缩放至[0,255]范围。

二维离散傅立叶逆变换从频率分量G按照下式复原图像:

I(x,y)=\frac{1}{HW}\sum_{l=0}^{H-1}\sum_{k=0}^{W-1}G(l,k)e^{2{\pi}j(\frac{kx}{W}+\frac{ly}{H})}

上式中exp(j)是个复数,实际编程的时候请务必使用下式中的绝对值形态1

如果只是简单地使用for语句的话,计算量达到1284,十分耗时。如果善用NumPy的化,则可以减少计算量(答案中已经减少到1282)。

代码实现

C++(非智能指针)

struct fourier_str 
{
    std::complex<double>** coef;
};

fourier_str dft(cv::Mat img, int height,int width)
{

    fourier_str fourier_s;
    fourier_s.coef = new std::complex<double>*[height];
    for (int i = 0; i < height; ++i) 
		{
        fourier_s.coef[i] = new std::complex<double>[width];
    }

    double I;
    double theta;
    std::complex<double> val;

    for (int l = 0; l < height; l++) 
		{
        for (int k = 0; k < width; k++) 
				{
            val.real(0);
            val.imag(0);
            for (int y = 0; y < height; y++) 
						{
                for (int x = 0; x < width; x++) 
								{
                    I = (double)img.at<uchar>(y, x);
                    theta = -2 * CV_PI * ((double)k * (double)x / (double)width + (double)l * (double)y / (double)height);
                    val += std::complex<double>(cos(theta), sin(theta)) * I;
                }
            }
            val /= sqrt(height * width);
            fourier_s.coef[l][k] = val;
        }
    }

    return fourier_s;
}

// Inverse Discrete Fourier transformation
cv::Mat idft(cv::Mat out, fourier_str fourier_s) 
{

    double theta;
    double g;
    std::complex<double> G;
    std::complex<double> val;

    for (int y = 0; y < height; y++) 
		{
        for (int x = 0; x < width; x++) 
				{
            val.real(0);
            val.imag(0);
            for (int l = 0; l < height; l++) 
						{
                for (int k = 0; k < width; k++) 
								{
                    G = fourier_s.coef[l][k];
                    theta = 2 * CV_PI * ((double)k * (double)x / (double)width + (double)l * (double)y / (double)height);
                    val += std::complex<double>(cos(theta), sin(theta)) * G;
                }
            }
            g = std::abs(val) / sqrt(height * width);
            out.at<uchar>(y, x) = (uchar)g;
        }
    }

    return out;
}

C++(智能指针)

struct fourier_str {
    std::unique_ptr<std::complex<double>[]>* coef;
};

// Discrete Fourier transformation
fourier_str dft(cv::Mat img, int height, int width)
{
    fourier_str fourier_s;
    fourier_s.coef = new std::unique_ptr<std::complex<double>[]>[height];

    for (int i = 0; i < height; ++i) {
        fourier_s.coef[i] = std::make_unique<std::complex<double>[]>(width);
    }

    double I;
    double theta;
    std::complex<double> val;

    for (int l = 0; l < height; l++) 
		{
        for (int k = 0; k < width; k++) 
				{
            val.real(0);
            val.imag(0);
            for (int y = 0; y < height; y++) 
						{
                for (int x = 0; x < width; x++) 
								{
                    I = (double)img.at<uchar>(y, x);
                    theta = -2 * CV_PI * ((double)k * (double)x / (double)width + (double)l * (double)y / (double)height);
                    val += std::complex<double>(cos(theta), sin(theta)) * I;
                }
            }
            val /= sqrt(height * width);
            fourier_s.coef[l][k] = val;
        }
    }

    return fourier_s;
}

 //Inverse Discrete Fourier transformation
cv::Mat idft(cv::Mat out, fourier_str fourier_s) {

    double theta;
    double g;
    std::complex<double> G;
    std::complex<double> val;

    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            val.real(0);
            val.imag(0);
            for (int l = 0; l < height; l++) {
                for (int k = 0; k < width; k++) {
                    G = fourier_s.coef[l][k];
                    theta = 2 * CV_PI * ((double)k * (double)x / (double)width + (double)l * (double)y / (double)height);
                    val += std::complex<double>(cos(theta), sin(theta)) * G;
                }
            }
            g = std::abs(val) / sqrt(height * width);
            out.at<uchar>(y, x) = (uchar)g;
        }
    }

    return out;
}

Python

def dft(img):
	H, W, _ = img.shape

	# Prepare DFT coefficient
	G = np.zeros((L, K, channel), dtype=np.complex)

	# prepare processed index corresponding to original image positions
	x = np.tile(np.arange(W), (H, 1))
	y = np.arange(H).repeat(W).reshape(H, -1)

	# dft
	for c in range(channel):
		for l in range(L):
			for k in range(K):
				#G[l, k, c] = np.sum(img[..., c] * np.exp(-2j * np.pi * (x * k / K + y * l / L))) / np.sqrt(K * L)
				G[l, k, c] = np.sum(img[..., c] * np.exp(-2j * np.pi * (x * k / K + y * l / L))) / np.sqrt(K * L)
				#for n in range(N):
				#    for m in range(M):
				#        v += gray[n, m] * np.exp(-2j * np.pi * (m * k / M + n * l / N))
				#G[l, k] = v / np.sqrt(M * N)

	return G

# IDFT
def idft(G):
	# prepare out image
	H, W, _ = G.shape
	out = np.zeros((H, W, channel), dtype=np.float32)

	# prepare processed index corresponding to original image positions
	x = np.tile(np.arange(W), (H, 1))
	y = np.arange(H).repeat(W).reshape(H, -1)

	# idft
	for c in range(channel):
		for l in range(H):
			for k in range(W):
				out[l, k, c] = np.abs(np.sum(G[..., c] * np.exp(2j * np.pi * (x * k / W + y * l / H)))) / np.sqrt(W * H)

	# clipping
	out = np.clip(out, 0, 255)
	out = out.astype(np.uint8)

	return out

参考链接

https://github.com/gzr2017/ImageProcessing100Wen/tree/master/Question_31_40#user-content-fnref-1-2aaa7787341026b9cbeadc0f4239b492

https://zhuanlan.zhihu.com/p/99605178

FAQ

1,上述代码除于sqrt(height * width)是为了保证能量统一,如果idft的不使用除于sqrt(height * width),则可以使用normalize将图像重新归一化


评论