带通滤波器组
Bandpass filter bank
我已经实现了一组定向带通滤波器 described in this article。
参见名为“2.1 预处理”部分的最后一段。
We selected 12 not overlapping filters, to analyze 12 different directions, rotated with respect to 15° each other.
我遇到了以下问题,
过滤器组应该生成 12 个过滤图像。但是,实际上,我只有 03 个输出,如下面的快照所示,
源代码:
Here is the complete VS2013 solution as a zipped file.
这是源代码中最相关的部分,
public class KassWitkinFunction
{
/*
* tx = centerX * cos
* ty = centerY * sin
*
* u* = cos . (u + tx) + sin . (v + ty)
* v* = - sin . (u + tx) + cos . (v + ty)
*
*/
//#region MyRegion
public static double tx(int centerX, double theta)
{
double costheta = Math.Cos(theta);
double txx = centerX * costheta;
return txx;
}
public static double ty(int centerY, double theta)
{
double sintheta = Math.Sin(theta);
double tyy = centerY * sintheta;
return tyy;
}
public static double uStar(double u, double v, int centerX, int centerY, double theta)
{
double txx = tx(centerX, theta);
double tyy = ty(centerY, theta);
double sintheta = Math.Sin(theta);
double costheta = Math.Cos(theta);
double cosThetaUTx = costheta * (u + txx);
double sinThetaVTy = sintheta * (v + tyy);
double returns = cosThetaUTx + sinThetaVTy;
return returns;
}
//#endregion
public static double vStar(double u, double v, int centerX, int centerY, double theta)
{
double txx = tx(centerX, theta);
double tyy = ty(centerY, theta);
double sintheta = Math.Sin(theta);
double costheta = Math.Cos(theta);
double sinThetaUTx = (-1) * sintheta * (u + txx);
double cosThetaVTy = costheta * (v + tyy);
double returns = sinThetaUTx + cosThetaVTy;
return returns;
}
public static double ApplyFilterOnOneCoord(double u, double v, double Du, double Dv, int CenterX, int CenterY, double Theta, int N)
{
double uStar = KassWitkinFunction.uStar(u, v, CenterX, CenterY, Theta);
double vStar = KassWitkinFunction.vStar(u, v, CenterX, CenterY, Theta);
double uStarDu = uStar / Du;
double vStarDv = vStar / Dv;
double sqrt = Math.Sqrt(uStarDu + vStarDv);
double _2n = 2 * N;
double pow = Math.Pow(sqrt, _2n);
double div = 1 + 0.414 * pow;
double returns = 1/div;
return returns;
}
}
public class KassWitkinKernel
{
public readonly int N = 4;
public int Width { get; set; }
public int Height { get; set; }
public double[,] Kernel { get; private set; }
public double[,] PaddedKernel { get; private set; }
public double Du { get; set; }
public double Dv { get; set; }
public int CenterX { get; set; }
public int CenterY { get; set; }
public double ThetaInRadian { get; set; }
public void SetKernel(double[,] value)
{
Kernel = value;
Width = Kernel.GetLength(0);
Height = Kernel.GetLength(1);
}
public void Pad(int newWidth, int newHeight)
{
double[,] temp = (double[,])Kernel.Clone();
PaddedKernel = ImagePadder.Pad(temp, newWidth, newHeight);
}
public Bitmap ToBitmap()
{
return ImageDataConverter.ToBitmap(Kernel);
}
public Bitmap ToBitmapPadded()
{
return ImageDataConverter.ToBitmap(PaddedKernel);
}
public Complex[,] ToComplex()
{
return ImageDataConverter.ToComplex(Kernel);
}
public Complex[,] ToComplexPadded()
{
return ImageDataConverter.ToComplex(PaddedKernel);
}
public void Compute()
{
Kernel = new double[Width, Height];
for (int i = 0; i < Width; i++)
{
for (int j = 0; j < Height; j++)
{
Kernel[i, j] = (double)KassWitkinFunction.ApplyFilterOnOneCoord(i, j,
Du,
Dv,
CenterX,
CenterY,
ThetaInRadian,
N);
//Data[i, j] = r.NextDouble();
}
}
string str = string.Empty;
}
}
public class KassWitkinBandpassFilter
{
public Bitmap Apply(Bitmap image, KassWitkinKernel kernel)
{
Complex[,] cImagePadded = ImageDataConverter.ToComplex(image);
Complex[,] cKernelPadded = kernel.ToComplexPadded();
Complex[,] convolved = Convolution.Convolve(cImagePadded, cKernelPadded);
return ImageDataConverter.ToBitmap(convolved);
}
}
public class KassWitkinFilterBank
{
private List<KassWitkinKernel> Kernels;
public int NoOfFilters { get; set; }
public double FilterAngle { get; set; }
public int WidthWithPadding { get; set; }
public int HeightWithPadding { get; set; }
public int KernelDimension { get; set; }
public KassWitkinFilterBank()
{}
public List<Bitmap> Apply(Bitmap bitmap)
{
Kernels = new List<KassWitkinKernel>();
double degrees = FilterAngle;
KassWitkinKernel kernel;
for (int i = 0; i < NoOfFilters; i++)
{
kernel = new KassWitkinKernel();
kernel.Width = KernelDimension;
kernel.Height = KernelDimension;
kernel.CenterX = (kernel.Width) / 2;
kernel.CenterY = (kernel.Height) / 2;
kernel.Du = 2;
kernel.Dv = 2;
kernel.ThetaInRadian = Tools.DegreeToRadian(degrees);
kernel.Compute();
kernel.Pad(WidthWithPadding, HeightWithPadding);
Kernels.Add(kernel);
degrees += degrees;
}
List<Bitmap> list = new List<Bitmap>();
foreach (KassWitkinKernel k in Kernels)
{
Bitmap image = (Bitmap)bitmap.Clone();
Complex[,] cImagePadded = ImageDataConverter.ToComplex(image);
Complex[,] cKernelPadded = k.ToComplexPadded();
Complex[,] convolved = Convolution.Convolve(cImagePadded, cKernelPadded);
Bitmap temp = ImageDataConverter.ToBitmap(convolved);
list.Add(temp);
}
return list;
}
}
问题在这里:
public static double ApplyFilterOnOneCoord(double u, double v, double Du, double Dv, int CenterX, int CenterY, double Theta, int N)
{
double uStar = KassWitkinFunction.uStar(u, v, CenterX, CenterY, Theta);
double vStar = KassWitkinFunction.vStar(u, v, CenterX, CenterY, Theta);
double uStarDu = uStar / Du;
double vStarDv = vStar / Dv;
double sqrt = Math.Sqrt(uStarDu + vStarDv);
double _2n = 2 * N;
double pow = Math.Pow(sqrt, _2n);
if (!double.IsNaN(sqrt) && Math.Abs(pow - Math.Pow(uStarDu + vStarDv, N)) > 1e-7)
{
//execution will never reach here!!
}
pow = Math.Pow(uStarDu + vStarDv, N);
double div = 1 + 0.414 * pow;
double returns = 1 / div;
return returns;
}
我不明白的是,为什么我们要在计算 Math.Pow 之前取平方根,尤其是当我们知道幂是偶数时。它唯一做的事情(除了使代码更复杂和更慢之外)是为负值生成 NaN。
我不确定整个计算是否正确,但现在所有 12 个过滤图像都出现了!
这是在预处理中使用的,据称来自Kass和Within的论文。我尝试阅读原始论文,但质量很低且难以阅读。您碰巧有 link 对他们的 [15] 参考进行质量更好的扫描吗?
正如我之前在评论中指出的那样,大多数过滤器输出都是空白的,因为它们包含 NaN
s。这些是由
等式(1)和(2)的实现
your reference article。
与原始作者取得联系可能最有可能重现原始结果,但至少您可以确保不会产生 NaN
s:
double arg = uStarDu + vStarDv;
double div = 1 + 0.414 * Math.Pow(Math.Abs(arg), N);
另一方面,给定方程的一般形式让人联想到 Butterworth filter
(连同带通滤波的提及),
和看似不必要的平方根后跟指数(这表明要么错过了明显的简化,要么在我看来更可能是错误
在方程式的呈现中),我建议改为使用以下方程式:
其中 是图像的中心。这可以通过以下方式实现:
public static double uStar(double u, double v, int centerX, int centerY, double theta)
{
double sintheta = Math.Sin(theta);
double costheta = Math.Cos(theta);
return costheta * (u - centerX) + sintheta * (v - centerY);
}
public static double vStar(double u, double v, int centerX, int centerY, double theta)
{
double sintheta = Math.Sin(theta);
double costheta = Math.Cos(theta);
return (-1) * sintheta * (u - centerX) + costheta * (v - centerY);
}
public static double ApplyFilterOnOneCoord(double u, double v, double Du, double Dv, int CenterX, int CenterY, double Theta, int N)
{
double uStarDu = KassWitkinFunction.uStar(u, v, CenterX, CenterY, Theta) / Du;
double vStarDv = KassWitkinFunction.vStar(u, v, CenterX, CenterY, Theta) / Dv;
double arg = uStarDu + vStarDv;
double div = Math.Sqrt(1 + Math.Pow(arg, 2*N));;
return 1/div;
}
现在您必须意识到,这些方程式是针对频域中的滤波器表示给出的,而您的 Convolution.Convolve
希望在空间域中提供滤波器内核(尽管计算的核心是在频域中完成的)。
应用这些过滤器(并且仍然在空间域中获得正确的填充)的最简单方法是:
- 将频域内核大小设置为填充大小以在频域中计算内核
- 将频域核变换到空间域
- 将本应添加填充的内核清零
- 将内核变换回频域
这可以通过以下 KassWitkinKernel.Pad
的修改版本来实现:
private Complex[,] cPaddedKernel;
public void Pad(int unpaddedWidth, int unpaddedHeight, int newWidth, int newHeight)
{
Complex[,] unpaddedKernelFrequencyDomain = ImageDataConverter.ToComplex((double[,])Kernel.Clone());
FourierTransform ftInverse = new FourierTransform();
ftInverse.InverseFFT(FourierShifter.RemoveFFTShift(unpaddedKernelFrequencyDomain));
Complex[,] cKernel = FourierShifter.FFTShift(ftInverse.GrayscaleImageComplex);
int startPointX = (int)Math.Ceiling((double)(newWidth - unpaddedWidth) / (double)2) - 1;
int startPointY = (int)Math.Ceiling((double)(newHeight - unpaddedHeight) / (double)2) - 1;
for (int j = 0; j < newHeight; j++)
{
for (int i=0; i<startPointX; i++)
{
cKernel[i, j] = 0;
}
for (int i = startPointX + unpaddedWidth; i < newWidth; i++)
{
cKernel[i, j] = 0;
}
}
for (int i = startPointX; i < startPointX + unpaddedWidth; i++)
{
for (int j = 0; j < startPointY; j++)
{
cKernel[i, j] = 0;
}
for (int j = startPointY + unpaddedHeight; j < newHeight; j++)
{
cKernel[i, j] = 0;
}
}
FourierTransform ftForward = new FourierTransform(cKernel); ftForward.ForwardFFT();
cPaddedKernel = ftForward.FourierImageComplex;
}
public Complex[,] ToComplexPadded()
{
return cPaddedKernel;
}
稍后在计算卷积时,您将跳过卷积核的 FFT。
请注意,您可以类似地避免为滤波器组中的每个滤波器重新计算图像的 FFT。
如果预先计算图像的 FFT,则剩余的计算需要得到卷积
降为频域乘法和最后的逆变换:
public partial class Convolution
{
public static Complex[,] ConvolveInFrequencyDomain(Complex[,] fftImage, Complex[,] fftKernel)
{
Complex[,] convolve = null;
int imageWidth = fftImage.GetLength(0);
int imageHeight = fftImage.GetLength(1);
int maskWidth = fftKernel.GetLength(0);
int maskHeight = fftKernel.GetLength(1);
if (imageWidth == maskWidth && imageHeight == maskHeight)
{
Complex[,] fftConvolved = new Complex[imageWidth, imageHeight];
for (int j = 0; j < imageHeight; j++)
{
for (int i = 0; i < imageWidth; i++)
{
fftConvolved[i, j] = fftImage[i, j] * fftKernel[i, j];
}
}
FourierTransform ftForConv = new FourierTransform();
ftForConv.InverseFFT(fftConvolved);
convolve = FourierShifter.FFTShift(ftForConv.GrayscaleImageComplex);
Rescale(convolve);
}
else
{
throw new Exception("padding needed");
}
return convolve;
}
}
将在 KassWitkinFilterBank.Apply
中使用如下:
Bitmap image = (Bitmap)bitmap.Clone();
Complex[,] cImagePadded = ImageDataConverter.ToComplex(image);
FourierTransform ftForImage = new FourierTransform(cImagePadded); ftForImage.ForwardFFT();
Complex[,] fftImage = ftForImage.FourierImageComplex;
foreach (KassWitkinKernel k in Kernels)
{
Complex[,] cKernelPadded = k.ToComplexPadded();
Complex[,] convolved = Convolution.ConvolveInFrequencyDomain(fftImage, cKernelPadded);
Bitmap temp = ImageDataConverter.ToBitmap(convolved);
list.Add(temp);
}
因此,这应该可以帮助您克服问题中指出的障碍。
当然,如果目的是重现论文的结果,您还有其他障碍需要克服。
第一个是实际使用锐化图像作为滤波器组的输入。
在执行此操作时,您可能希望首先平滑图像的边缘以避免产生强烈的边缘
图像周围,这会扭曲线检测算法的结果。
我已经实现了一组定向带通滤波器 described in this article。
参见名为“2.1 预处理”部分的最后一段。
We selected 12 not overlapping filters, to analyze 12 different directions, rotated with respect to 15° each other.
我遇到了以下问题,
过滤器组应该生成 12 个过滤图像。但是,实际上,我只有 03 个输出,如下面的快照所示,
源代码:
Here is the complete VS2013 solution as a zipped file.
这是源代码中最相关的部分,
public class KassWitkinFunction
{
/*
* tx = centerX * cos
* ty = centerY * sin
*
* u* = cos . (u + tx) + sin . (v + ty)
* v* = - sin . (u + tx) + cos . (v + ty)
*
*/
//#region MyRegion
public static double tx(int centerX, double theta)
{
double costheta = Math.Cos(theta);
double txx = centerX * costheta;
return txx;
}
public static double ty(int centerY, double theta)
{
double sintheta = Math.Sin(theta);
double tyy = centerY * sintheta;
return tyy;
}
public static double uStar(double u, double v, int centerX, int centerY, double theta)
{
double txx = tx(centerX, theta);
double tyy = ty(centerY, theta);
double sintheta = Math.Sin(theta);
double costheta = Math.Cos(theta);
double cosThetaUTx = costheta * (u + txx);
double sinThetaVTy = sintheta * (v + tyy);
double returns = cosThetaUTx + sinThetaVTy;
return returns;
}
//#endregion
public static double vStar(double u, double v, int centerX, int centerY, double theta)
{
double txx = tx(centerX, theta);
double tyy = ty(centerY, theta);
double sintheta = Math.Sin(theta);
double costheta = Math.Cos(theta);
double sinThetaUTx = (-1) * sintheta * (u + txx);
double cosThetaVTy = costheta * (v + tyy);
double returns = sinThetaUTx + cosThetaVTy;
return returns;
}
public static double ApplyFilterOnOneCoord(double u, double v, double Du, double Dv, int CenterX, int CenterY, double Theta, int N)
{
double uStar = KassWitkinFunction.uStar(u, v, CenterX, CenterY, Theta);
double vStar = KassWitkinFunction.vStar(u, v, CenterX, CenterY, Theta);
double uStarDu = uStar / Du;
double vStarDv = vStar / Dv;
double sqrt = Math.Sqrt(uStarDu + vStarDv);
double _2n = 2 * N;
double pow = Math.Pow(sqrt, _2n);
double div = 1 + 0.414 * pow;
double returns = 1/div;
return returns;
}
}
public class KassWitkinKernel
{
public readonly int N = 4;
public int Width { get; set; }
public int Height { get; set; }
public double[,] Kernel { get; private set; }
public double[,] PaddedKernel { get; private set; }
public double Du { get; set; }
public double Dv { get; set; }
public int CenterX { get; set; }
public int CenterY { get; set; }
public double ThetaInRadian { get; set; }
public void SetKernel(double[,] value)
{
Kernel = value;
Width = Kernel.GetLength(0);
Height = Kernel.GetLength(1);
}
public void Pad(int newWidth, int newHeight)
{
double[,] temp = (double[,])Kernel.Clone();
PaddedKernel = ImagePadder.Pad(temp, newWidth, newHeight);
}
public Bitmap ToBitmap()
{
return ImageDataConverter.ToBitmap(Kernel);
}
public Bitmap ToBitmapPadded()
{
return ImageDataConverter.ToBitmap(PaddedKernel);
}
public Complex[,] ToComplex()
{
return ImageDataConverter.ToComplex(Kernel);
}
public Complex[,] ToComplexPadded()
{
return ImageDataConverter.ToComplex(PaddedKernel);
}
public void Compute()
{
Kernel = new double[Width, Height];
for (int i = 0; i < Width; i++)
{
for (int j = 0; j < Height; j++)
{
Kernel[i, j] = (double)KassWitkinFunction.ApplyFilterOnOneCoord(i, j,
Du,
Dv,
CenterX,
CenterY,
ThetaInRadian,
N);
//Data[i, j] = r.NextDouble();
}
}
string str = string.Empty;
}
}
public class KassWitkinBandpassFilter
{
public Bitmap Apply(Bitmap image, KassWitkinKernel kernel)
{
Complex[,] cImagePadded = ImageDataConverter.ToComplex(image);
Complex[,] cKernelPadded = kernel.ToComplexPadded();
Complex[,] convolved = Convolution.Convolve(cImagePadded, cKernelPadded);
return ImageDataConverter.ToBitmap(convolved);
}
}
public class KassWitkinFilterBank
{
private List<KassWitkinKernel> Kernels;
public int NoOfFilters { get; set; }
public double FilterAngle { get; set; }
public int WidthWithPadding { get; set; }
public int HeightWithPadding { get; set; }
public int KernelDimension { get; set; }
public KassWitkinFilterBank()
{}
public List<Bitmap> Apply(Bitmap bitmap)
{
Kernels = new List<KassWitkinKernel>();
double degrees = FilterAngle;
KassWitkinKernel kernel;
for (int i = 0; i < NoOfFilters; i++)
{
kernel = new KassWitkinKernel();
kernel.Width = KernelDimension;
kernel.Height = KernelDimension;
kernel.CenterX = (kernel.Width) / 2;
kernel.CenterY = (kernel.Height) / 2;
kernel.Du = 2;
kernel.Dv = 2;
kernel.ThetaInRadian = Tools.DegreeToRadian(degrees);
kernel.Compute();
kernel.Pad(WidthWithPadding, HeightWithPadding);
Kernels.Add(kernel);
degrees += degrees;
}
List<Bitmap> list = new List<Bitmap>();
foreach (KassWitkinKernel k in Kernels)
{
Bitmap image = (Bitmap)bitmap.Clone();
Complex[,] cImagePadded = ImageDataConverter.ToComplex(image);
Complex[,] cKernelPadded = k.ToComplexPadded();
Complex[,] convolved = Convolution.Convolve(cImagePadded, cKernelPadded);
Bitmap temp = ImageDataConverter.ToBitmap(convolved);
list.Add(temp);
}
return list;
}
}
问题在这里:
public static double ApplyFilterOnOneCoord(double u, double v, double Du, double Dv, int CenterX, int CenterY, double Theta, int N)
{
double uStar = KassWitkinFunction.uStar(u, v, CenterX, CenterY, Theta);
double vStar = KassWitkinFunction.vStar(u, v, CenterX, CenterY, Theta);
double uStarDu = uStar / Du;
double vStarDv = vStar / Dv;
double sqrt = Math.Sqrt(uStarDu + vStarDv);
double _2n = 2 * N;
double pow = Math.Pow(sqrt, _2n);
if (!double.IsNaN(sqrt) && Math.Abs(pow - Math.Pow(uStarDu + vStarDv, N)) > 1e-7)
{
//execution will never reach here!!
}
pow = Math.Pow(uStarDu + vStarDv, N);
double div = 1 + 0.414 * pow;
double returns = 1 / div;
return returns;
}
我不明白的是,为什么我们要在计算 Math.Pow 之前取平方根,尤其是当我们知道幂是偶数时。它唯一做的事情(除了使代码更复杂和更慢之外)是为负值生成 NaN。
我不确定整个计算是否正确,但现在所有 12 个过滤图像都出现了!
这是在预处理中使用的,据称来自Kass和Within的论文。我尝试阅读原始论文,但质量很低且难以阅读。您碰巧有 link 对他们的 [15] 参考进行质量更好的扫描吗?
正如我之前在评论中指出的那样,大多数过滤器输出都是空白的,因为它们包含 NaN
s。这些是由
等式(1)和(2)的实现
your reference article。
与原始作者取得联系可能最有可能重现原始结果,但至少您可以确保不会产生 NaN
s:
double arg = uStarDu + vStarDv;
double div = 1 + 0.414 * Math.Pow(Math.Abs(arg), N);
另一方面,给定方程的一般形式让人联想到 Butterworth filter (连同带通滤波的提及), 和看似不必要的平方根后跟指数(这表明要么错过了明显的简化,要么在我看来更可能是错误 在方程式的呈现中),我建议改为使用以下方程式:
其中
public static double uStar(double u, double v, int centerX, int centerY, double theta)
{
double sintheta = Math.Sin(theta);
double costheta = Math.Cos(theta);
return costheta * (u - centerX) + sintheta * (v - centerY);
}
public static double vStar(double u, double v, int centerX, int centerY, double theta)
{
double sintheta = Math.Sin(theta);
double costheta = Math.Cos(theta);
return (-1) * sintheta * (u - centerX) + costheta * (v - centerY);
}
public static double ApplyFilterOnOneCoord(double u, double v, double Du, double Dv, int CenterX, int CenterY, double Theta, int N)
{
double uStarDu = KassWitkinFunction.uStar(u, v, CenterX, CenterY, Theta) / Du;
double vStarDv = KassWitkinFunction.vStar(u, v, CenterX, CenterY, Theta) / Dv;
double arg = uStarDu + vStarDv;
double div = Math.Sqrt(1 + Math.Pow(arg, 2*N));;
return 1/div;
}
现在您必须意识到,这些方程式是针对频域中的滤波器表示给出的,而您的 Convolution.Convolve
希望在空间域中提供滤波器内核(尽管计算的核心是在频域中完成的)。
应用这些过滤器(并且仍然在空间域中获得正确的填充)的最简单方法是:
- 将频域内核大小设置为填充大小以在频域中计算内核
- 将频域核变换到空间域
- 将本应添加填充的内核清零
- 将内核变换回频域
这可以通过以下 KassWitkinKernel.Pad
的修改版本来实现:
private Complex[,] cPaddedKernel;
public void Pad(int unpaddedWidth, int unpaddedHeight, int newWidth, int newHeight)
{
Complex[,] unpaddedKernelFrequencyDomain = ImageDataConverter.ToComplex((double[,])Kernel.Clone());
FourierTransform ftInverse = new FourierTransform();
ftInverse.InverseFFT(FourierShifter.RemoveFFTShift(unpaddedKernelFrequencyDomain));
Complex[,] cKernel = FourierShifter.FFTShift(ftInverse.GrayscaleImageComplex);
int startPointX = (int)Math.Ceiling((double)(newWidth - unpaddedWidth) / (double)2) - 1;
int startPointY = (int)Math.Ceiling((double)(newHeight - unpaddedHeight) / (double)2) - 1;
for (int j = 0; j < newHeight; j++)
{
for (int i=0; i<startPointX; i++)
{
cKernel[i, j] = 0;
}
for (int i = startPointX + unpaddedWidth; i < newWidth; i++)
{
cKernel[i, j] = 0;
}
}
for (int i = startPointX; i < startPointX + unpaddedWidth; i++)
{
for (int j = 0; j < startPointY; j++)
{
cKernel[i, j] = 0;
}
for (int j = startPointY + unpaddedHeight; j < newHeight; j++)
{
cKernel[i, j] = 0;
}
}
FourierTransform ftForward = new FourierTransform(cKernel); ftForward.ForwardFFT();
cPaddedKernel = ftForward.FourierImageComplex;
}
public Complex[,] ToComplexPadded()
{
return cPaddedKernel;
}
稍后在计算卷积时,您将跳过卷积核的 FFT。 请注意,您可以类似地避免为滤波器组中的每个滤波器重新计算图像的 FFT。 如果预先计算图像的 FFT,则剩余的计算需要得到卷积 降为频域乘法和最后的逆变换:
public partial class Convolution
{
public static Complex[,] ConvolveInFrequencyDomain(Complex[,] fftImage, Complex[,] fftKernel)
{
Complex[,] convolve = null;
int imageWidth = fftImage.GetLength(0);
int imageHeight = fftImage.GetLength(1);
int maskWidth = fftKernel.GetLength(0);
int maskHeight = fftKernel.GetLength(1);
if (imageWidth == maskWidth && imageHeight == maskHeight)
{
Complex[,] fftConvolved = new Complex[imageWidth, imageHeight];
for (int j = 0; j < imageHeight; j++)
{
for (int i = 0; i < imageWidth; i++)
{
fftConvolved[i, j] = fftImage[i, j] * fftKernel[i, j];
}
}
FourierTransform ftForConv = new FourierTransform();
ftForConv.InverseFFT(fftConvolved);
convolve = FourierShifter.FFTShift(ftForConv.GrayscaleImageComplex);
Rescale(convolve);
}
else
{
throw new Exception("padding needed");
}
return convolve;
}
}
将在 KassWitkinFilterBank.Apply
中使用如下:
Bitmap image = (Bitmap)bitmap.Clone();
Complex[,] cImagePadded = ImageDataConverter.ToComplex(image);
FourierTransform ftForImage = new FourierTransform(cImagePadded); ftForImage.ForwardFFT();
Complex[,] fftImage = ftForImage.FourierImageComplex;
foreach (KassWitkinKernel k in Kernels)
{
Complex[,] cKernelPadded = k.ToComplexPadded();
Complex[,] convolved = Convolution.ConvolveInFrequencyDomain(fftImage, cKernelPadded);
Bitmap temp = ImageDataConverter.ToBitmap(convolved);
list.Add(temp);
}
因此,这应该可以帮助您克服问题中指出的障碍。 当然,如果目的是重现论文的结果,您还有其他障碍需要克服。 第一个是实际使用锐化图像作为滤波器组的输入。 在执行此操作时,您可能希望首先平滑图像的边缘以避免产生强烈的边缘 图像周围,这会扭曲线检测算法的结果。