【问题标题】:Filter bank taking too long for kernels to be created创建内核所需的过滤器库时间过长
【发布时间】:2023-03-19 13:29:01
【问题描述】:

看看this question

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        Bitmap image = DataConverter2d.ReadGray(StandardImage.LenaGray);
        Array2d<double> dImage = DataConverter2d.ToDouble(image);            

        int newWidth = Tools.ToNextPowerOfTwo(dImage.Width) * 2;
        int newHeight = Tools.ToNextPowerOfTwo(dImage.Height) * 2;

        double n = 6;
        double f0 = 0.5;
        double theta = 60;
        double a = 0.4;

        Array2d<Complex> kernel2d = CustomFft(newWidth, newHeight, n, f0, theta, a);

        dImage.PadTo(newWidth, newHeight);
        Array2d<Complex> cImage = DataConverter2d.ToComplex(dImage);
        Array2d<Complex> fImage = FourierTransform.ForwardFft(cImage);

        // FFT convolution .................................................
        Array2d<Complex> fOutput = new Array2d<Complex>(newWidth, newHeight);
        for (int x = 0; x < newWidth; x++)
        {
            for (int y = 0; y < newHeight; y++)
            {
                fOutput[x, y] = fImage[x, y] * kernel2d[x, y];
            }
        }

        Array2d<Complex> cOutput = FourierTransform.InverseFft(fOutput);
        Array2d<double> dOutput = Rescale2d.Rescale(DataConverter2d.ToDouble(cOutput));

        dOutput.CropBy((newWidth - image.Width) / 2, (newHeight - image.Height) / 2);

        Bitmap output = DataConverter2d.ToBitmap(dOutput, image.PixelFormat);

        Array2d<Complex> cKernel = FourierTransform.InverseFft(kernel2d);
        cKernel = FourierTransform.RemoveFFTShift(cKernel);
        Array2d<double> dKernel = DataConverter2d.ToDouble(cKernel);
        Array2d<double> dLimitedKernel = Rescale2d.Limit(dKernel);

        Bitmap kernel = DataConverter2d.ToBitmap(dLimitedKernel, image.PixelFormat);

        pictureBox1.Image = image;
        pictureBox2.Image = kernel;
        pictureBox3.Image = output;
    }

    private double Basic(double u, double v, double n, double f0, double rad, double a, double b)
    {
        double ua = u + f0 * Math.Cos(rad);
        double va = v + f0 * Math.Sin(rad);

        double ut = ua * Math.Cos(rad) + va * Math.Sin(rad);
        double vt = (-1) * ua * Math.Sin(rad) + va * Math.Cos(rad);

        double p = ut/a;
        double q = vt/b;

        double sqrt = Math.Sqrt(p*p + q*q);

        return 1.0 / (1.0+ 0.414 * Math.Pow(sqrt, 2*n));
    }

    private double Custom(double u, double v, double n, double f0, double theta, double a)
    {
        double rad1 = (Math.PI / 180) * (90-theta);
        double rad2 = rad1 + Math.PI;
        double b = (a / 5.0) / (2*n);

        double ka = Basic(u, v, n, f0, rad1, a, b);
        double kb = Basic(u, v, n, f0, rad2, a, b);

        return Math.Max(ka, kb);
    }

    private Array2d<Complex> CustomFft(double sizeX, double sizeY, double n, double f0, double theta, double a)
    {
        double halfX = sizeX / 2;
        double halfY = sizeY / 2;

        Array2d<Complex> kernel = new Array2d<Complex>((int)sizeX, (int)sizeY);

        for (double y = 0; y < sizeY; y++)
        {
            double v = y / sizeY;

            for (double x = 0; x < sizeX; x++)
            {
                double u = x / sizeX;

                double kw = Custom(u, v, n, f0, theta, a);

                kernel[(int)x, (int)y] = new Complex(kw, 0);
            }
        }

        return kernel;
    }
}

我创建了一个 40 内核 filter bank,其中每个内核的大小相同但参数不同。

这组过滤器大约需要 55000 毫秒 来生成内核。

我怎样才能减少这个时间?

注意:以下行花费的时间最多:

Array2d<Complex> kernel2d = CustomFft(newWidth, newHeight, n, f0, theta, a);

创建内核库的示例源代码:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        Stopwatch sw = new Stopwatch();
        sw.Start();

        Bitmap image = DataConverter2d.Read(StandardImage.Scratch1);

        int newWidth = Tools.ToNextPowerOfTwo(image.Width);
        int newHeight = Tools.ToNextPowerOfTwo(image.Height);

        List<FrequencyDomainKernel> list = new List<FrequencyDomainKernel>();
        for (int i = 0; i < 40; i++)
        {          
            FrequencyDomainKernel k= new FrequencyDomainKernel();
            k.Size = new System.Drawing.Size(newWidth, newHeight);
            k.theta = i;
            k.Compute();

            list.Add(k);
        }

        sw.Stop();

        MessageBox.Show(sw.ElapsedMilliseconds.ToString());
    }
}

注意:这不是可执行代码。这段代码只是为了说明我生成内核的方法。

【问题讨论】:

  • 什么电话占用了时间?
  • @LeonardoSeccia,嵌套的 for 循环。
  • 究竟是哪一行?是k.Compute() 吗?如果是这样,最好的办法是减小图像的宽度和高度...如果将DataConverter2d.Read(StandardImage.Scratch1); 替换为new Bitmap(64,64),性能会提高吗?
  • @LeonardoSeccia, If you replace DataConverter2d.Read(StandardImage.Scratch1); with new Bitmap(64,64) does the performance improve? --- 是的。严重。
  • 另见Parallel.ForParallel.ForEach。也可以使用那些额外的核心。 docs.microsoft.com/en-us/dotnet/standard/parallel-programming/…

标签: c# image-processing filter-bank


【解决方案1】:

作为起点,试试这个:

private Array2d<Complex> CustomFft( double sizeX, double sizeY, double n, double f0, double theta,
                                    double a ) {
    double halfX = sizeX / 2;
    double halfY = sizeY / 2;

    //pre-calculated values
    double rad1, rad2, b, f1cos, f1sin, f2cos, f2sin, cosrad1, sinrad1, cosrad2, sinrad2;
    rad1 = ( Math.PI / 180 ) * ( 90 - theta );
    rad2 = rad1 + Math.PI;
    cosrad1 = Math.Cos( rad1 );
    sinrad1 = Math.Sin( rad1 );
    cosrad2 = Math.Cos( rad2 );
    sinrad2 = Math.Sin( rad2 );
    f1cos = f0 * Math.Cos( rad1 );
    f1sin = f0 * Math.Sin( rad1 );
    f2cos = f0 * Math.Cos( rad2 );
    f2sin = f0 * Math.Sin( rad2 );
    b = ( a / 5.0 ) / ( 2 * n );

    Array2d<Complex> kernel = new Array2d<Complex>((int)sizeX, (int)sizeY);

    /*
    for( double y = 0; y < sizeY; y++ ) {
        double v = y / sizeY;

        for( double x = 0; x < sizeX; x++ ) {
            double u = x / sizeX;

            double kw = Custom2( u, v, n, f0, theta, a, rad1, rad2, b, f1cos, f1sin, f2cos, f2sin,
                                 cosrad1, sinrad1 , cosrad2, sinrad2 );

            kernel[ (int)x, (int)y ] = new Complex( kw, 0 );
        }
    }*/

    //reverse the loops
    for( double x = 0; x < sizeX; x++ ) {    
        double u = x / sizeX;

        for( double y = 0; y < sizeY; y++ ) {
            double v = y / sizeY;

            double kw = Custom2( u, v, n, f0, theta, a, rad1, rad2, b, f1cos, f1sin, f2cos, f2sin,
                                  cosrad1, sinrad1, cosrad2, sinrad2 );

            kernel[ (int)x, (int)y ] = new Complex( kw, 0 );
        }
    }

    return kernel;
}

private double Custom2( double u, double v, double n, double f0, double theta, double a,
                        double rad1, double rad2, double b, double f1cos, double f1sin, 
                        double f2cos, double f2sin, double cosrad1, double sinrad1,
                        double cosrad2, double sinrad2 ) {

    double ka = Basic2( u, v, n, f0, rad1, a, b, f1cos, f1sin, cosrad1, sinrad1 );
    double kb = Basic2( u, v, n, f0, rad2, a, b, f2cos, f2sin, cosrad2, sinrad2 );

    return ( ka >= kb ? ka : kb); //Math.Max( ka, kb );
}

private double Basic2( double u, double v, double n, double f0, double rad, double a, double b,
                       double fcos, double fsin, double cosrad, double sinrad ) {
    double ua = u + fcos;
    double va = v + fsin;

    double ut = ua * cosrad + va * sinrad;
    double vt = ( -1 ) * ua * sinrad + va * cosrad;

    double p = ut / a;
    double q = vt / b;

    double sqrt = Math.Sqrt( p * p + q * q );

    return 1.0 / ( 1.0 + 0.414 * Math.Pow( sqrt, 2 * n ) );
}

我会继续努力。

编辑 1

我的第一个观察结果是您使用了太多的类,并且在这些类中使用了太多的方法。 OOP 的问题在于,有时代码被分成太多的类,方法非常小,这反过来又会使代码难以理解(遍历所有类等),并且对于简单的事情会不必要地变慢。让我们使用一些数字:

一个简单的:

Array2d<Complex> cImage = DataConverter2d.ToComplex( dImage );

大约需要 65 毫秒!它只是将cImage 的实部替换为dImage 的值,而虚部设置为zero。这真的不应该花那么多,但正如我所说的 OOP 和类。让我们做一个简单的重新配置,而不是 Array2d&lt;Complex&gt; 使用一个简单的双数组:

double[] cImagefast = new double[ newWidth * newHeight * 2 ];

就是这样。它以线性方式存储real-imaginary 部分,例如

cImagefast[0] -> real part of cImage[0,0]
cImagefast[1] -> imag part of cImage[0,0]
cImagefast[2] -> real part of cImage[0,1]
cImagefast[3] -> imag part of cImage[0,1]
....

让我们重新编写代码并测试它:

Stopwatch sw = new Stopwatch();
Bitmap image = DataConverter2d.ReadGray( "Scratch1.jpg" );
Array2d<double> dImage = DataConverter2d.ToDouble( image );

int newWidth = Tools.ToNextPowerOfTwo( dImage.Width );
int newHeight = Tools.ToNextPowerOfTwo( dImage.Height );

dImage.PadTo( newWidth, newHeight ); //~60msec

//Array2d<Complex> cImage = DataConverter2d.ToComplex( dImage ); //~65msec

double[] dImagefast = new double[ newWidth * newHeight ];
double[] cImagefast = new double[ newWidth * newHeight * 2 ];
int count = newWidth * newHeight * 2, pos = 0, pos_ = 0, i;

// fill it with true values before testing
for( int y = 0; y < newHeight; y++ ) {
    for( int x = 0; x < newWidth; x++ ) {
        dImagefast[ pos ] = dImage[ x, y ];

        pos++;
    }
}

//Start the testing!
sw.Start();

for( pos = 0; pos < count; pos += 2 ){
    cImagefast[ pos ] = dImagefast[ pos_ ];

    pos_++;
}

sw.Stop();

MessageBox.Show( sw.ElapsedMilliseconds.ToString() );

所需时间 ~10 毫秒。快了 6 倍以上!!!!

让我们再做一个。让我们测试 FFT convolution 需要 ~105msec

// FFT convolution ................................................. ~105msec
Array2d<Complex> fOutput = new Array2d<Complex>( newWidth, newHeight );
for( int x = 0; x < newWidth; x++ ) {
    for( int y = 0; y < newHeight; y++ ) {
        fOutput[ x, y ] = fImage[ x, y ] * kernel2d[ x, y ];
    }
}

用简单的一维双精度数组重写:

Stopwatch sw = new Stopwatch();
Bitmap image = DataConverter2d.ReadGray( "Scratch1.jpg" );
Array2d<double> dImage = DataConverter2d.ToDouble( image );
int newWidth = Tools.ToNextPowerOfTwo( dImage.Width );
int newHeight = Tools.ToNextPowerOfTwo( dImage.Height );
double n = 6, f0 = 0.5, theta = 60, a = 0.4;

Array2d<Complex> kernel2d = CustomFft( newWidth, newHeight, n, f0, theta, a );

dImage.PadTo( newWidth, newHeight ); //~60msec
Array2d<Complex> cImage = DataConverter2d.ToComplex( dImage ); //~65msec
Array2d<Complex> fImage = FourierTransform.ForwardFft( cImage );

/*
// FFT convolution ............................................. ~105msec
Array2d<Complex> fOutput = new Array2d<Complex>( newWidth, newHeight );
for( int x = 0; x < newWidth; x++ ) {
    for( int y = 0; y < newHeight; y++ ) {
        fOutput[ x, y ] = fImage[ x, y ] * kernel2d[ x, y ];
    }
}
*/

// Fast FFT convolution ..................................... ~31msec!!!
int pos = 0, count = newWidth * newHeight * 2;
double[] fImagefast = new double[ newWidth * newHeight * 2 ];
double[] kernel2dfast = new double[ newWidth * newHeight * 2 ];

//lets fill the arrays with values
for( int y = 0; y < newHeight; y++ ) {
    for( int x = 0; x < newWidth; x++ ) {
        kernel2dfast[ pos ] = kernel2d[ x, y ].Real;
        kernel2dfast[ pos + 1 ] = kernel2d[ x, y ].Imaginary;

        pos += 2;
    }
}

pos = 0;
for( int y = 0; y < newHeight; y++ ) {
    for( int x = 0; x < newWidth; x++ ) {
        fImagefast[ pos ] = fImage[ x, y ].Real;
        fImagefast[ pos + 1 ] = fImage[ x, y ].Imaginary;

        pos += 2;
    }
}

//Test!
sw.Start();
double[] fOutputfast = new double[ newWidth * newHeight * 2 ];

for( pos = 0; pos < count; pos += 2 ) {
    //( left.m_real * right.m_real ) - ( left.m_imaginary * right.m_imaginary );
    fOutputfast[ pos ] = fImagefast[pos] * kernel2dfast[ pos ] - fImagefast[ pos + 1 ] * kernel2dfast[ pos + 1 ];
    //( left.m_imaginary * right.m_real ) + ( left.m_real * right.m_imaginary );
    fOutputfast[ pos + 1 ] = fImagefast[ pos + 1 ] * kernel2dfast[ pos ] + fImagefast[ pos ] * kernel2dfast[ pos + 1 ];
}

sw.Stop();

MessageBox.Show( sw.ElapsedMilliseconds.ToString() );

结果,~31 毫秒!!! 快了 3.5 倍以上。想象一下,如果只使用简单的一维双精度数组,整个代码会有多快。

这是我的第一个观察结果。稍后会有新的修改。

EDIT2

在本次编辑中,我们将进一步优化 CustomFFt。一些观察。在对方程式进行一些操作后,我得出了以下结论:

由以上CustomFft可以写成:

private void CustomFftFast( ref float[] kernel, int sizeX, int sizeY, double n, double f0, double theta, double a ) {
    int pos = 0;

    //pre-calculated values
    float rad1, b, cosrad1, sinrad1; //rad2, cosrad2, sinrad2;
    float Ax, Ay, Bx, By, Fo;
    float p1, p2, q1, q2, ka, kb, kw;

    rad1 = ( (float)Math.PI / 180.0f ) * ( 90.0f - (float)theta );
    //rad2 = rad1 + (float)Math.PI;
    cosrad1 = (float)Math.Cos( rad1 );
    sinrad1 = (float)Math.Sin( rad1 );
    //cosrad2 = (float)Math.Cos( rad2 );
    //sinrad2 = (float)Math.Sin( rad2 );
    b = ( (float)a / 5.0f ) / ( 2.0f * (float)n );

    Ax = cosrad1 / ( (float)sizeX * (float)a );
    Bx = sinrad1 / ( (float)sizeX * (float)b );

    Ay = sinrad1 / ( (float)sizeY * (float)a );
    By = cosrad1 / ( (float)sizeY * (float)b );

    Fo = (float)f0 / (float)a;

    kernel = new float[ sizeX * sizeY * 2 ];

    for( int x = 0; x < sizeX; x++ ) {
        for( int y = 0; y < sizeY; y++ ) {
            p1 = Ax * (float)x + Ay * (float)y + Fo;
            q1 = By * (float)y - Bx * (float)x;

            p2 = -Ax * (float)x - Ay * (float)y + Fo;
            q2 = -By * (float)y + Bx * (float)x;

            ka = p1 * p1 + q1 * q1;
            kb = p2 * p2 + q2 * q2;

            //Normal Pow
            ka = 1.0f / ( 1.0f + 0.414f * (float)Math.Pow( ka, n ) );
            kb = 1.0f / ( 1.0f + 0.414f * (float)Math.Pow( kb, n ) );

            /*//Faster Pow, loses in percision though. Ther is another one
              //with better percision but a little slower.
            unsafe
            {
                uint *c = (uint *)&ka;
                float y_ = (float)*c;
                y_ *= 1.0f / (float)( 1 << 23 );
                y_ -= 126.94269504f;

                uint a_ = (uint)( ( 1 << 23 ) * ( y_ * (float)n + 126.94269504f ) );
                float *b_ = (float *)&a_;
                ka = 1.0f / ( 1.0f + 0.414f * ( *b_ ) );

                c = (uint *)&kb;
                y_ = (float)*c;
                y_ *= 1.0f / (float)( 1 << 23 );
                y_ -= 126.94269504f;

                a_ = (uint)( ( 1 << 23 ) * ( y_ * (float)n + 126.94269504f ) );
                b_ = (float *)&a_;
                kb = 1.0f / ( 1.0f + 0.414f * ( *b_ ) );

            }*/

            kw = ka >= kb ? ka : kb;

            kernel[ pos ] = kw;;
            //kernel[ pos + 1 ] = 0.0f;

            pos += 2;
        }
    }
}

CustomFFt 的瓶颈是Pow 调用。次:

With Math.Pow(),    time = ~110msec
With faster Pow,    time = ~90msec, strange for such a small improvement
Without Pow at all, time = ~10msec! <-- 

【讨论】:

  • 平均 400 毫秒
  • @anonymous 您可以反转 2 个 for 循环。见编辑。有什么区别吗?
  • @anonymous 无论如何。使 FFT 更快并没有太大的区别,因为 400 毫秒与 55000 毫秒相比!没什么。你能发布一个完整的工作代码来自己测试一下,看看我是否可以改进它?
  • You can reverse the 2 for loops. See edit. Does it make any difference? --- 不。结果变得不可预测。它在 从 365 到 650 毫秒变化。
  • @anonymous 那么你给我的代码的哪一部分(InitializeComponent();之后)你想加快速度?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-04-01
  • 2014-12-10
  • 2020-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多