4

ガウスぼかしを周波数領域の画像に適用することに問題があります。理由は不明ですが (おそらく何か間違ったことはしていません)、ぼやけた画像ではなく有線の画像を受け取ります。

私が段階的に行うことは次のとおりです。

  1. 画像を読み込みます。
  2. 画像を別々のチャネルに分割します。

    private static Bitmap[] separateColorChannels(Bitmap source, int channelCount)
    {
        if (channelCount != 3 && channelCount != 4)
        {
            throw new NotSupportedException("Bitmap[] FFTServices.separateColorChannels(Bitmap, int): Only 3 and 4 channels are supported.");
        }
    
        Bitmap[] result = new Bitmap[channelCount];
        LockBitmap[] locks = new LockBitmap[channelCount];
        LockBitmap sourceLock = new LockBitmap(source);
        sourceLock.LockBits();
    
        for (int i = 0; i < channelCount; ++i)
        {
            result[i] = new Bitmap(source.Width, source.Height, PixelFormat.Format8bppIndexed);
            locks[i] = new LockBitmap(result[i]);
            locks[i].LockBits();
        }
    
        for (int x = 0; x < source.Width; x++)
        {
            for (int y = 0; y < source.Height; y++)
            {
                switch (channelCount)
                {
                    case 3:
                        locks[0].SetPixel(x, y, Color.FromArgb(sourceLock.GetPixel(x, y).R));
                        locks[1].SetPixel(x, y, Color.FromArgb(sourceLock.GetPixel(x, y).G));
                        locks[2].SetPixel(x, y, Color.FromArgb(sourceLock.GetPixel(x, y).B));
    
                        break;
                    case 4:
                        locks[0].SetPixel(x, y, Color.FromArgb(sourceLock.GetPixel(x, y).A));
                        locks[1].SetPixel(x, y, Color.FromArgb(sourceLock.GetPixel(x, y).R));
                        locks[2].SetPixel(x, y, Color.FromArgb(sourceLock.GetPixel(x, y).G));
                        locks[3].SetPixel(x, y, Color.FromArgb(sourceLock.GetPixel(x, y).B));
    
                        break;
                    default:
                        break;
                }
            }
        }
    
        for (int i = 0; i < channelCount; ++i)
        {
            locks[i].UnlockBits();
        }
    
        sourceLock.UnlockBits();
    }
    
  3. すべてのチャンネルを複雑な画像に変換します (AForge.NET を使用)。

    public static AForge.Imaging.ComplexImage[] convertColorChannelsToComplex(Emgu.CV.Image<Emgu.CV.Structure.Gray, Byte>[] channels)
    {
        AForge.Imaging.ComplexImage[] result = new AForge.Imaging.ComplexImage[channels.Length];
    
        for (int i = 0; i < channels.Length; ++i)
        {
            result[i] = AForge.Imaging.ComplexImage.FromBitmap(channels[i].Bitmap);
        }
    
        return result;
    }
    
  4. ガウスぼかしを適用します。

    1. 最初にカーネルを作成します(テスト目的で、カーネルのサイズは画像サイズと同じです。中央部分のみがガウス関数で計算され、残りのカーネルはre = 1 im = 0に等しくなります)。

      private ComplexImage makeGaussKernel(int side, double min, double max, double step, double std)
      {
          // get value at top left corner
          double _0x0 = gauss2d(min, min, std);
      
          // top left corner should be 1, so making scaler for rest of the values
          double scaler = 1 / _0x0;
      
          int pow2 = SizeServices.getNextNearestPowerOf2(side);
      
          Bitmap bitmap = new Bitmap(pow2, pow2, PixelFormat.Format8bppIndexed);
      
          var result = AForge.Imaging.ComplexImage.FromBitmap(bitmap);
      
          // For test purposes my kernel is size of image, so first, filling with 1 only.
          for (int i = 0; i < result.Data.GetLength(0); ++i)
          {
              for (int j = 0; j < result.Data.GetLength(0); ++j)
              {
                  result.Data[i, j].Re = 1;
                  result.Data[i, j].Im = 0;
              }
          }
      
          // The real kernel's size.
          int count = (int)((Math.Abs(max) + Math.Abs(min)) / step);
      
          double h = min;
          // Calculating kernel's values and storing them somewhere in the center of kernel.
          for (int i = result.Data.GetLength(0) / 2 - count / 2; i < result.Data.GetLength(0) / 2 + count / 2; ++i)
          {
              double w = min;
              for (int j = result.Data.GetLength(1) / 2 - count / 2; j < result.Data.GetLength(1) / 2 + count / 2; ++j)
              {
                  result.Data[i, j].Re = (scaler * gauss2d(w, h, std)) * 255;
                  w += step;
              }
              h += step;
          }
      
          return result;
      }
      
      // The gauss function
      private double gauss2d(double x, double y, double std)
      {
          return ((1.0 / (2 * Math.PI * std * std)) * Math.Exp(-((x * x + y * y) / (2 * std * std))));
      }
      
    2. すべてのチャネルとカーネルに FFT を適用します。

    3. すべてのチャネルの中央部分にカーネルを掛けます。

      void applyFilter(/*shortened*/)
      {
          // Image's size is 512x512 that's why 512 is hardcoded here
          // min = -2.0; max = 2.0; step = 0.33; std = 11
          ComplexImage filter = makeGaussKernel(512, min, max, step, std);
      
          // Applies FFT (with AForge.NET) to every channel and filter
          applyFFT(complexImage);
          applyFFT(filter);
      
          for (int i = 0; i < 3; ++i)
          {
              applyGauss(complexImage[i], filter, side);
          }
      
          // Applies IFFT to every channel
          applyIFFT(complexImage);
      }
      
      private void applyGauss(ComplexImage complexImage, ComplexImage filter, int side)
      {
          int width = complexImage.Data.GetLength(1);
          int height = complexImage.Data.GetLength(0);
      
          for(int i = 0; i < height; ++i)
          {
              for(int j = 0; j < width; ++j)
              {
                  complexImage.Data[i, j] = AForge.Math.Complex.Multiply(complexImage.Data[i, j], filter.Data[i, j]);
              }
          }
      }
      
  5. すべてのチャネルに IFFT を適用します。
  6. すべてのチャネルをビットマップに戻します (AForge.NET を使用)。

    public static System.Drawing.Bitmap[] convertComplexColorChannelsToBitmap(AForge.Imaging.ComplexImage[] channels)
    {
        System.Drawing.Bitmap[] result = new System.Drawing.Bitmap[channels.Length];
    
        for (int i = 0; i < channels.Length; ++i)
        {
            result[i] = channels[i].ToBitmap();
        }
    
        return result;
    }
    
  7. ビットマップを単一のビットマップにマージ

    public static Bitmap mergeColorChannels(Bitmap[] channels)
    {
        Bitmap result = null;
    
        switch (channels.Length)
        {
            case 1:
                return channels[0];
            case 3:
                result = new Bitmap(channels[0].Width, channels[0].Height, PixelFormat.Format24bppRgb);
                break;
            case 4:
                result = new Bitmap(channels[0].Width, channels[0].Height, PixelFormat.Format32bppArgb);
                break;
            default:
                throw new NotSupportedException("Bitmap FFTServices.mergeColorChannels(Bitmap[]): Only 1, 3 and 4 channels are supported.");
        }
    
        LockBitmap resultLock = new LockBitmap(result);
        resultLock.LockBits();
    
        LockBitmap red = new LockBitmap(channels[0]);
        LockBitmap green = new LockBitmap(channels[1]);
        LockBitmap blue = new LockBitmap(channels[2]);
    
        red.LockBits();
        green.LockBits();
        blue.LockBits();
    
        for (int y = 0; y < result.Height; y++)
        {
            for (int x = 0; x < result.Width; x++)
            {
                resultLock.SetPixel(x, y, Color.FromArgb((int)red.GetPixel(x, y).R, (int)green.GetPixel(x, y).G, (int)blue.GetPixel(x, y).B));
            }
        }
    
        red.UnlockBits();
        green.UnlockBits();
        blue.UnlockBits();
    
        resultLock.UnlockBits();
    
        return result;
    }
    

その結果、画像の赤い色のぼやけたバージョンがシフトされました: link

@edit - コードにいくつかの変更を加えて質問を更新しました。

4

1 に答える 1

0

私は DSP stackexchange の助けを借りてそれを理解しました...そしていくつかの不正行為ですが、うまくいきます。主な問題は、カーネルの生成とそれに FFT を適用することでした。また、重要なことは、AForge.NET が ComplexImage への変換中に画像ピクセルを 255 で割り、ComplexImage から Bitmap への変換中に 255 を乗算することです (Olli Niemitalo @ DSP SE に感謝します)。

これをどのように解決したか:

  1. FFT 後のカーネルがどのように見えるかを発見しました (以下を参照)。
  2. その画像の色を調べました。
  3. x = -2 に対して計算された gauss2d; y = -2; 標準 = 1。
  4. pt で計算された値からカラー値を受け取るプリスケーラーを計算しました。3 (ウルフラムを参照)。
  5. pt から perscaler でスケーリングされた値を持つ生成されたカーネル。4.

ただし、生成されたフィルターは既に FFT 後のフィルターのように見えるため、生成されたフィルターで FFT を使用することはできません。それは機能します-出力画像はアーティファクトなしでぼやけているので、それほど悪くはないと思います.

画像(2つ以上のリンクを投稿することはできず、画像は非常に大きいです):

最終的なコード:

private ComplexImage makeGaussKernel(double size, double std, int imgWidth, int imgHeight)
{
    double scale = 2000.0;
    double hsize = size / 2.0;

    Bitmap bmp = new Bitmap(imgWidth, imgHeight, PixelFormat.Format8bppIndexed);
    LockBitmap lbmp = new LockBitmap(bmp);

    lbmp.LockBits();

    double y = -hsize;
    double yStep = hsize / (lbmp.Height / 2.0);
    double xStep = hsize / (lbmp.Width / 2.0);

    for (int i = 0; i < lbmp.Height; ++i)
    {
        double x = -hsize;

        for (int j = 0; j < lbmp.Width; ++j)
        {
            double g = gauss2d(x, y, std) * scale;

            g = g < 0.0 ? 0.0 : g;
            g = g > 255.0 ? 255.0 : g;

            lbmp.SetPixel(j, i, Color.FromArgb((int)g));

            x += xStep;
        }

        y += yStep;
    }

    lbmp.UnlockBits();

    return ComplexImage.FromBitmap(bmp);
}

private double gauss2d(double x, double y, double std)
{
    return (1.0 / (2 * Math.PI * std * std)) * Math.Exp(-(((x * x) + (y * y)) / (2 * std * std)));
}

private void applyGaussToImage(ComplexImage complexImage, ComplexImage filter)
{
    for (int i = 0; i < complexImage.Height; ++i)
    {
        for (int j = 0; j < complexImage.Width; ++j)
        {
            complexImage.Data[i, j] = AForge.Math.Complex.Multiply(complexImage.Data[i, j], filter.Data[i, j]);
        }
    }
}
于 2015-04-12T16:46:55.737 に答える