convolution with gaussian kernel using fft
古いコメントを表示
Hey,
I'm really no pro in Matlab so I've got a few difficulties with the following task.
I'm trying to implement diffusion of a circle through convolution with the 2d gaussian kernel. The convolution is between the Gaussian kernel an the function u, which helps describe the circle by being +1 inside the circle and -1 outside. The Gaussian kernel is
. I've tried not to use fftshift but to do the shift by hand. Also I know that the Fourier transform of the Gaussian is
with coefficients depending on the length of the interval.
with coefficients depending on the length of the interval. But with my code, there happens no diffusion at all and I don't understand what I've done wrong. I would relly appreciate some help.
I have the follwoing code with meshsize nxn:
function gaussian(n)
length = 1; %length of the interval
x = (length/n)*(0:n-1);
[X1,X2] = meshgrid(x,x); %grid
K = [0:n/2-1,-n/2:-1]; [K1,K2] = meshgrid(K,K); %fftshift by hand
A = K1.^2 + K2.^2; %coefficients for the Fourier transform of the Gaussian kernel
dt = 0.01;
R0 = 0.4; %radius of the circle
%initial condition
u = u_0(X1,X2,n,length,R0);
show(X1,X2,u,length);
for i=0:dt:1
Gaussian = (length/n)^2*exp(-dt*A); %pre-factor of the discrete fourier transform
convolution = sign(real((length/n)^(-2)*ifft2( (length/n)^2*fft2(u) .* Gaussian ))); %here I'm solving the convolution with fft2
show(X1,X2,convolution,length); drawnow
end
function show(X1,X2,u,length)
surf(X1,X2,u);
shading flat; axis([0 length 0 length -1 1 -1 1]);
%inital condition
function val = u_0(X1,X2,n,length,r)
u = zeros(n,n);
A = (X1-length/2).^2 + (X2-length/2).^2;
for i=1:n
for j=1:n
if A(i,j) < r^2
u(i,j) = 1;
else
u(i,j) = -1;
end
end
end
val = u;
採用された回答
その他の回答 (1 件)
Paul Safier
2021 年 4 月 14 日
編集済み: Paul Safier
2021 年 4 月 14 日
Can you point me to where you found the 3.53 value?
All this discussion has led me to conclude that since in my problem of interest, the unknown is within the convolution integral, when I manipulate the terms, I may be able to just multiply the kernel (in Fourier space) with the other unknown and remove the singularity. I seem to be off by a scale values (at least), do you know where?
As a test example, I would like to recover f in this expression (the equation we've been using), since I know g and c:
c(r) = f(r-s)*g(s) d2s
My strategy would be to form this expression (in essence):
f = ifft(fft(c)/fft(g))
But, since the analytical form of the fft of g is known, I believe:
g = 1/sqrt(x.^2 + y.^2);
fft(g) = 1/sqrt(kF.^2 + qF.^2); % According to Mathematica...
Then the quotient fft(c)/fft(g) is div1 below.
Here's the code:
N = 401; % Make odd
nov2 = (N-1)/2;
delx = .25;
a = 0.2;
arr = (-nov2:nov2)*delx;
kk = (-nov2:nov2)*(1/delx)*2*pi/(N);
rx1 = arr; ry1 = arr; sx1 = arr; sy1 = arr;
[rx, ry] = meshgrid(rx1,ry1);
[sx, sy] = meshgrid(sx1,sy1);
[kF,qF] = meshgrid(kk,kk); % FFT space
f = exp(-a*(rx.^2 +ry.^2));
cA = (pi^(3/2)/sqrt(a))*besseli(0,(a/2)*(rx.^2+ry.^2),1);
cAF = fftshift(fft2(ifftshift(cA))); % FFT of analytical solution (RHS)
div1 = cAF.*sqrt(kF.^2 +qF.^2); % Here's the key part. This is fft(c)/fft(g). If I do this product, I can avoid the singularity. This needs a scalar multiplying it though??
ftest2 = 1/(delx*delx)*fftshift(ifft2(ifftshift(div1)));
% Compare
m = 201;
figure(5)
plot(arr,f(m,:),'k',arr,ftest2(m,:),'*');
grid on
Pretty sure somewhere I need some scale factors to toggle back and forth from Fourier space... Does this seem sensible??
14 件のコメント
David Goodmanson
2021 年 4 月 14 日
編集済み: David Goodmanson
2021 年 4 月 14 日
Hi Paul,
< I think you have mistakenly posted the stuff above as an answer (easy to do, from personal experience) so if you wish to repost it as yet another comment to the comments I will save this and do the same. >
Dividing in the frequency domain is a bit trickier than multiplying, so I would recommmend as a first step, finding f(k) and g(k), then transforming their product to see if you can come up with cA. Making things harder for finding f by transforming cA/g is the fact that f(x) becomes impossibly small for larger values of x. It is going to become impossible to get an accurate f(x) for the full range of x.
For f(k)*g(k), again there will be a g singularity to deal with at kx = ky = 0.
The Mathematica result you quoted for the fft, let's say it is correct, but it leaves questions about intended normalization. For the fourier transform of g(x), a previous post of mine says, essentially,
Int e^(-i*k.x)/|x| d2x = 2*pi/|k| (dot product)
Since fft is being used to approximate an integral, I think it is safer to write down the integral and adjust the fft by multiplying by delx and so forth.
3.53: tbd
Paul Safier
2021 年 4 月 14 日
編集済み: Paul Safier
2021 年 4 月 14 日
@David Goodmanson : Oops, yes, meant to be a comment--must have hit the wrong button. Shall I delete and start this over?
Sorry, I'm having trouble understanding the notation:
Int e^(-i*k.x)/|x| d2x = 2*pi/|k| (dot product)
Is this saying the g(k) is 2*pi/|k|? Then is k = sqrt(kF.^2 + qF.^2)? Is the integral a convolution? Why are k (Fourier space) and x (normal space) both on the lhs?
Since g(x) is a fraction, the division cA/g is like a multiplicaton, no? Do you think this product is doomed?:
div1 = cAF.*sqrt(kF.^2 +qF.^2);
Paul Safier
2021 年 4 月 14 日
編集済み: Paul Safier
2021 年 4 月 14 日
Oh, I see, that's just the Fourier Transform integral... Sorry.
Another thing, in my problem, the scaling is such that x and y should never be greater than 10 and most likely less than 1...
David Goodmanson
2021 年 4 月 14 日
編集済み: David Goodmanson
2021 年 4 月 14 日
since deleting an answer deletes all the comments to it (at least that is how it used to work, I don't know if that has changed), it may be too late to transfer over. But if you want to copy your answer over to the end of the original comment thread, then I copy over my reply, then you copy over your reply to that, etc then you delete the 'answer' and its comments, that could work.
Paul Safier
2021 年 4 月 15 日
編集済み: Paul Safier
2021 年 4 月 15 日
@David Goodmanson : Here's a test of a couple of possible ways of solving the problem I'm interested in. Can you let me know your thoughts? In my problem the unknown is inside the convolution integrand and this code uses your suggested test equation (with all three functions being known) and tries to recover f. Method1 evaluates the kernel with its singularity and then fixes it as we've been discussing. I look at a few values of the fix's numerator to gauge the sensitivity. Method2 multiplies the two functions (cA and g) to avoid dealing with the singularity. You mentioned issues with this approach, but I'm concerned with weighing those with the issue of using the "perfect" numerator for the fix. Which method is most susceptable to failing in a problem without a known analytical solution?
Running this will generate a plot comparing Methods 1 and 2 with the exact value of f.
% Test of methods of recovering f from within the convolution integral of the expression:
% g * f = cA
% f = exp(-a*(rx.^2 +ry.^2)) and g = 1./sqrt(sx.^2 +sy.^2) and cA = (pi^(3/2)/sqrt(a))*besseli(0,(a/2)*(rx.^2+ry.^2),1)
N = 401; % Make odd
nov2 = (N-1)/2;
delx = .25;
arr = (-nov2:nov2)*delx;
xx = (-nov2:nov2)*delx;
kk = (-nov2:nov2)*(1/delx)*2*pi/(N);
rx1 = arr; ry1 = arr; sx1 = arr; sy1 = arr;
[rx, ry] = meshgrid(rx1,ry1);
[sx, sy] = meshgrid(sx1,sy1);
[kF,qF] = meshgrid(kk,kk); % FFT space
a = .5;
f = exp(-a*(rx.^2 +ry.^2)); % The function we are trying to recover
g = 1./sqrt(sx.^2 +sy.^2); % The kernel that has a singularity
g(isinf(g)) = 3.85/delx; % Replace the singularity
gF = fftshift(fft2(ifftshift(g))); % FFT of kernel
g1 = 1./sqrt(sx.^2 +sy.^2); % The kernel that has a singularity
g1(isinf(g1)) = 3.4/delx; % Replace the singularity
g1F = fftshift(fft2(ifftshift(g1))); % FFT of kernel
g2 = 1./sqrt(sx.^2 +sy.^2); % The kernel that has a singularity
g2(isinf(g2)) = 4.3/delx; % Replace the singularity
g2F = fftshift(fft2(ifftshift(g2))); % FFT of kernel
cA = (pi^(3/2)/sqrt(a))*besseli(0,(a/2)*(rx.^2+ry.^2),1);
cAF = fftshift(fft2(ifftshift(cA))); % FFT of analytical solution (RHS)
div1 = cAF.*sqrt(kF.^2 +qF.^2)*delx*delx/(2*pi);
ftestAlt = 1/(delx*delx)*fftshift(ifft2(ifftshift(div1)));
ftest = 1/(delx*delx)*fftshift(ifft2(ifftshift(cAF./gF)));
ftest1 = 1/(delx*delx)*fftshift(ifft2(ifftshift(cAF./g1F)));
ftest2 = 1/(delx*delx)*fftshift(ifft2(ifftshift(cAF./g2F)));
m = nov2 + 1;
figure(1)
plot(arr,f(m,:),'k',arr,ftest1(m,:),'g',arr,ftest(m,:),'r',arr,ftest2(m,:),'b',arr,ftestAlt(m,:),'*');
xlim([-4 4]);
legend('Exact','Method 1 (num = 3.4)','Method 1 (num = 3.85)','Method 1 (num = 4.3)','Method 2')
grid on
David Goodmanson
2021 年 4 月 15 日
Hi Paul,
I have not looked at your code yet, but when you say cA*g I assume what is meant is really cA/g.
When I said that division has some issues, consider convolving f and g to get h, with no assumptions yet about f,g,h. When you multiply f(k)*g(k) to get h(k) and transform to get h(t), that tends to work pretty well. But when you know g and h and want f, then of course f(k) = h(k)/g(k). Often g goes to 0 fairly quickly for large k. To get a good f, most of the time you need h to go to 0 about as quickly as g does. But if h is experimental data or something, it does not necessarily go to 0 as fast as it should. Then h/g gets large for large k, which messes up f.
David Goodmanson
2021 年 4 月 18 日
Hi Paul,
I ran your code, and things look basically all right. I added the following
figure(6)
semilogy(arr,f(m,:),arr,ftest2(m,:));
grid on
figure(7)
semilogy(arr,f(m,:),arr,ftest2(m,:)/100);
xlim([-5.5 5.5])
grid on
Fig 6 shows that aside from being off by a normalization factor of approximately 100, the two agree pretty well for -5 <= k <= 5. Fig 7 is a blowup of fig. 6 wiith a factor of 100 thrown in. The agreement is good for about two decades of function value.
Although I am not anxious to do it, you can get the correct normalization by writing out the actual fourier integrals and then putting factors of delx^2 or delk^2 into the fft2 or ifft2. Be aware that for a 1d N-point ifft, the ifft does the sum and divides by N (that means divide by N^2 for a 2d ifft), so you have to correct for that.
As you can see the division f(k) = h(k)/g(k) has some problems when k gets too large and cA(k) gets too small.
Paul Safier
2021 年 4 月 19 日
I am confused as to the location of a factor of ~100 error you mention. I compared the fft/ifft Matlab summation formulae with the continuous integrals from the link below. It seems to suggest to convert the expressions from the commented to the uncommented shown below. The multiplying constant in front turns out to be numerically equivalent in both cases. However, I do not understand why it needed a second 1/(2 pi). I added that, willy-nilly, when I saw that the result was 2pi off of the exact solution! :(
cA = (pi^(3/2)/sqrt(a))*besseli(0,(a/2)*(rx.^2+ry.^2),1);
cAF = fftshift(fft2(ifftshift(cA))); % FFT of analytical solution (RHS)
div1 = cAF.*sqrt(kF.^2 +qF.^2)*delx*delx/(2*pi);
% ftestAlt = 1/(delx*delx)*fftshift(ifft2(ifftshift(div1)));
% ftest = 1/(delx*delx)*fftshift(ifft2(ifftshift(cAF./gF)));
% ftest1 = 1/(delx*delx)*fftshift(ifft2(ifftshift(cAF./g1F)));
% ftest2 = 1/(delx*delx)*fftshift(ifft2(ifftshift(cAF./g2F)));
dk = kk(2)-kk(1);
ftestAlt = N*N*dk*dk/(2*pi*2*pi)*fftshift(ifft2(ifftshift(div1))); % Method 2
ftest = N*N*dk*dk/(2*pi*2*pi)*fftshift(ifft2(ifftshift(cAF./gF))); % Method 1
ftest1 = N*N*dk*dk/(2*pi*2*pi)*fftshift(ifft2(ifftshift(cAF./g1F))); % Method 1
ftest2 = N*N*dk*dk/(2*pi*2*pi)*fftshift(ifft2(ifftshift(cAF./g2F))); % Method 1
Bottom line is: I must solve for f somehow--my problem requires it. After I get to the bottom of the scaling issue, the question becomes, do I solve it via method 1 (replace the singularity) or method 2 (do the mult/div before evaluating the kernel (and its singularity)). I want to think method 2 is better since it doesn't rely on picking the appropriate constant to replace infinity with (since it seems arbitrary at least until I find theory discussing it). However, in messing with delx, N and such in the code above, I see that method 2 is less accurate in many instances. I don't see why that would be the case since both methods involve a division of two transformed terms. Why do div1 and cAF./gF lead to different final results??
Thanks for all your help and patience in working through this.
David Goodmanson
2021 年 4 月 19 日
編集済み: David Goodmanson
2021 年 4 月 20 日
Hi Paul
kind of like a puzzle, not bad to work on. ok for the convolution we have
Int f(r')g(r-r') d2r' = h(r) % convolution
f(k) = (1/(2*pi)) Int f(r)e^(-ik.r) d2r % transform pair
f(r) = (1/(2*pi)) Int f(k)e^( ik.r) d2k
g(k) = (1/(2*pi)) Int g(s)e^(-ik.s) d2s % transform pair
g(r) = (1/(2*pi)) Int g(k)e^( ik.s) d2k
h(r) = Int h(k)*e^( ik.r) d2k % transform pair
h(k) = (1/(2*pi))^2 Int h(r)*e^(-ik.r) d2r
where h(k) = f(k)*g(k)
so f(r) = (1/(2*pi))*Int (h(k)/g(k))*e^( ik.r) d2k
For a 2d transform pair, you can pick any normalization constant c1 you want going into k space, but for the normalization constant c2 coming back the other direction, their product has to equal c1*c2 = (1/(2*pi))^2. Here I arbitrarily picked symmetric transform pairs for f and g so you don't have to remember anything extra. Then for the way the normalization works out, and defining h(k) = f(k)*g(k), the h transform pair is not symmetric.
If you define
delk = (1/delx)*2*pi/(N);
with
kk = (-nov2:nov2)*delk;
and insert the appropriate factors (1/(2*pi)), delx^2 and delk^2 and N into the fft and ifft expressions, you should get agreement between the new f(r) and the original one. Let me know how that goes. You don't literally have to fourier transform g(s) since there is the analytic expression
g(k) = (1/(2*pi)) Int (1/|s|) e^(-i*k.s) d2s = 1/|k|
and you don't have to do anything about g(k) at the origin since in the process |k| appears in the numerator so a zero is all right.
Paul Safier
2021 年 4 月 20 日
After running through that exercise, I keep getting an offset with the two methods. Method 1 replaces the singularity with 3.85/delx and method 2 avoids the singularity by multiplying cA(k)*|k|. Method 1 seems about dead-on with the known analytical solution for f(r). Method 2 comes up a bit short regardless of the mesh size. Do you know why?
This code compares the two methods against the analytical solution for f(r):
N = 801; % Make odd
xymax = 10.25;
nov2 = (N-1)/2;
delx = xymax/nov2;
arr = (-nov2:nov2)*delx;
xx = (-nov2:nov2)*delx;
kk = (-nov2:nov2)*(1/delx)*2*pi/(N); dk = kk(2)-kk(1);
rx1 = arr; ry1 = arr; sx1 = arr; sy1 = arr;
[rx, ry] = meshgrid(rx1,ry1);
[sx, sy] = meshgrid(sx1,sy1);
[kF,qF] = meshgrid(kk,kk); % FFT space
a = .5;
f = exp(-a*(rx.^2 +ry.^2)); % The function we are trying to recover.
cA = (pi^(3/2)/sqrt(a))*besseli(0,(a/2)*(rx.^2+ry.^2),1);
cAF = fftshift(fft2(ifftshift(cA)));
% Method 1
g = 1./sqrt(sx.^2 +sy.^2); % The kernel that has a singularity
g(isinf(g)) = 3.85/delx; % Replace the singularity
gF = fftshift(fft2(ifftshift(g))); % FFT of kernel
meth1 = N^2*dk^2/((2*pi)^2)*fftshift(ifft2(ifftshift(cAF./gF)));
% Method 2 (uses analytical solution for fft of kernel)
meth2 = delx^2*N^2*dk^2/((2*pi)^3)*fftshift(ifft2(ifftshift(cAF.*sqrt(kF.^2 +qF.^2)))); % Avoid dealing with singularity
% Compare via a plot
m = nov2 + 1;
figure(1)
plot(arr,f(m,:),'*k',arr,meth1(m,:),'g',arr,meth2(m,:),'r');
xlim([-0.4 0.4]);
legend('Exact','Method 1','Method 2')
grid on
% The ratio of the two methods is alway off!
meth1(m,m)/meth2(m,m)
Paul Safier
2021 年 4 月 20 日
Here's a way I compared the arguments of both method's inversion:
% Test two arguments of methods 1 and 2
figure(3)
tstA = cAF./gF; % Method 1
tst1A = cAF.*sqrt(kF.^2 +qF.^2)*delx^2/(2*pi); % Method 2
plot(kk,abs(tstA(m,:)),'k',kk,abs(tst1A(m,:)),'g')
xlim([kk(m-50) kk(m+50)])
legend('Method 1','Method 2')
Looks like the origin (that was the location of the singularity) is the big difference. Does the origin need to be "fixed" in method 2??
David Goodmanson
2021 年 4 月 21 日
編集済み: David Goodmanson
2021 年 4 月 21 日
Hi Paul,
[1] myself, I don't particularly like the line
cAF = fftshift(fft2(ifftshift(cA)))
because I start to lose track of normalization. The method I used is
cAF = (1/(2*pi))^2*delx^2*fftshift(fft2(ifftshift(cA))); % fourier trans of analytical solution (RHS)
% analytical result for fourier transform of g(r):
gF = (1/2*pi) *Int e^(-ikr) / |r| d2r = 1/|k|
cAFdivgF = cAF.*sqrt(kF.^2 +qF.^2);
% fourier transform of the ratio
% meth3 = (1/(2*pi))*delk^2*N^2*fftshift(ifft2(ifftshift(cAFdivgF)));
(which gives the same comparison, and peaks out at .99).
But certainly any normalization you are good with is up to you. And it is true that if your immediate attention is not on frequency domain normalization, then a lot of the constants associated with mimicking integrals cancel out. If you define
cAF4 = fftshift(fft2(ifftshift(cA))); (1)
cAFdivgF4 = cAF4.*sqrt(kF.^2 +qF.^2); % cA(k)/g(k)
then
meth4 = [(1/(2*pi))*delk^2*N^2]*[(1/(2*pi))^2*delx^2]*fftshift(ifft2(ifftshift(cAFdivgF4)))
and since delx*delk = 2*pi/N,
meth4 = (1/(2*pi))*fftshift(ifft2(ifftshift(cAFdivgF))) (2)
so (1) and (2) get the job done with a minimum of constants.
[2] as you recall, when I suggested replacing the 0 in g, it was for the convolution
Int f(r-s)*g(s) d2s.
Since there is a 0 of g(s) = 1/|s| in the denominator, replacing that 0 with something else is a necessity. For notation, suppose g0(s) stands for g without replacing the 0, and ge(s) is g, only having replaced the 0 with something else. So ge is well behaved, whereas g0 has a zero in the denominator and 1/g0 has a zero in the numerator. Since g0(s) = 1/|s| and g0(k) = 1\|k|, let the same notation apply in k space as well.
For the original convolution, you found ge replaces the 0 with is 3.85/delx, experimentally. So far so good,
For the deconvolution, with cA(k)/g(k), since there is a zero in the numerator the integral does not diverge and is well behaved, so there is no compelling reason to change the zero to anything else. I was quite surprised to find out that ge(k) works better than g0(k). Good catch by you. I have no idea at all why that might be the case, especially since you are replacing the zero of g0(k) -- in the k domain -- with 3.85/delx, which involves a spacing in the spacial domain.
(info on 3.53 to follow)
David Goodmanson
2021 年 4 月 22 日
編集済み: David Goodmanson
2021 年 4 月 22 日
The 3.53 came about in the 2d convolution
h(r) = Int f(r-s)*g(s) d2s = Int f(r-s)*1/|s| d2s
Assume a 2d lattice of points for sx, sy, each with spacing delx. Draw a checkerboard of squares of side delx, with each point in the center of a square. Each point will represent the function within its square. For the square centered at the origin, assume that the square is small enough that f(r-s) is essentially constant. Going to polar coordinates u and phi, the integral over the square is
Int 1/|u| udu dphi = Int du dphi
and the singularity is gone. Assume phi = 0 is the x axis. The distance from the origin to the side is delx/2, and denote this by b. If the square were an inscribed circle of radius b, then the integral would just be 2*pi*b. But the square is a bit larger. Draw a 45 degree line from the origin to the upper right corner to make a triangle. There are eight such triangles. For this triangle, any ray from the origin to an intersection with the right hand side has length such that u*cos(phi) = b, so u = b/cos(phi). The u integration is
Int {0,b/cos(phi)} du
which is just b/cos(phi) and the remaining integral is
Int {0,pi/4} b/cos(phi) dphi
Taking b outside temporarily,
syms phi
int(1/cos(phi))
ans = log(1/cos(phi)) + log(sin(phi) + 1)
int(1/cos(phi),0,pi/4)
ans = log(2^(1/2) + 1)
8*double(int(1/cos(phi),0,pi/4)) % 8 triangles
ans = 7.0510
The final result, 7.0510*b, is a bit larger than 2*pi*b.
Since b = delx/2, the final result is 3.53*delx which gets changed to 3.53/delx for the reason discussed before.
Paul Safier
2021 年 4 月 22 日
Thanks for this explanation. I'm slightly confused still though since it seems it should be more straightforward as the area of a circle inscribed by that square would be pi/4*delx*delx and the area of the square would be delx*delx--both are different from the result obtained. It seems like the integration ought to provide an area, but its dimension is that of length and thus will shrink/grow as dx not dx^2. Curious. I'll think more about this. I imagine this issue must come up quite a bit in numerical analysis but I have not been able to find and literature regarding it. Do you know of a text, paper, web link or something else?
The difference between the methods is definitely perplexing. In my real problem that I'm dealing with, the results of the two methods are significantly different and it's definitely bothersome. However, on the other hand, by running that last code snippet I sent (that compares the arguments of both methods) it's obvious they are different and thus should NOT provide the exact same answer. But, why the "approximate" method (that replaces the singularity) is the one that's correct is the mystery...
Perhaps there's another analytical test case to look at. Did you find this example we've been looking at in a text/paper or did you derive it via analysis or symbolic convolution, etc?
カテゴリ
ヘルプ センター および File Exchange で Bartlett についてさらに検索
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!
