User 6976447 Ответов: 2

Потеря данных во время обратного БПФ изображения.


Я использую следующий код для преобразования растрового изображения в сложное и наоборот.

Даже несмотря на то, что они были непосредственно скопированы с аккорда.NET framework, тестируя эти статические методы, я обнаружил, что повторное использование этих статических методов приводит к "потере данных".



В результате конечный результат искажается.[^]

public partial class ImageDataConverter
{
    #region private static Complex[,] FromBitmapData(BitmapData bmpData)
    private static Complex[,] ToComplex(BitmapData bmpData)
    {
        Complex[,] comp = null;

        if (bmpData.PixelFormat == PixelFormat.Format8bppIndexed)
        {
            int width = bmpData.Width;
            int height = bmpData.Height;
            int offset = bmpData.Stride - (width * 1);//1 === 1 byte per pixel.

            if ((!Tools.IsPowerOf2(width)) || (!Tools.IsPowerOf2(height)))
            {
                throw new Exception("Imager width and height should be n of 2.");
            }

            comp = new Complex[width, height];

            unsafe
            {
                byte* src = (byte*)bmpData.Scan0.ToPointer();

                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++, src++)
                    {
                        comp[y, x] = new Complex((float)*src / 255,
                                                    comp[y, x].Imaginary);
                    }
                    src += offset;
                }
            }
        }
        else
        {
            throw new Exception("EightBppIndexedImageRequired");
        }

        return comp;
    }
    #endregion

    public static Complex[,] ToComplex(Bitmap bmp)
    {
        Complex[,] comp = null;

        if (bmp.PixelFormat == PixelFormat.Format8bppIndexed)
        {
            BitmapData bmpData = bmp.LockBits(  new Rectangle(0, 0, bmp.Width, bmp.Height),
                                                ImageLockMode.ReadOnly,
                                                PixelFormat.Format8bppIndexed);
            try
            {
                comp = ToComplex(bmpData);
            }
            finally
            {
                bmp.UnlockBits(bmpData);
            }
        }
        else
        {
            throw new Exception("EightBppIndexedImageRequired");
        }

        return comp;
    }

    public static Bitmap ToBitmap(Complex[,] image, bool fourierTransformed)
    {
        int width = image.GetLength(0);
        int height = image.GetLength(1);

        Bitmap bmp = Imager.CreateGrayscaleImage(width, height);

        BitmapData bmpData = bmp.LockBits(
            new Rectangle(0, 0, width, height),
            ImageLockMode.ReadWrite,
            PixelFormat.Format8bppIndexed);

        int offset = bmpData.Stride - width;
        double scale = (fourierTransformed) ? Math.Sqrt(width * height) : 1;

        unsafe
        {
            byte* address = (byte*)bmpData.Scan0.ToPointer();

            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++, address++)
                {
                    double min = System.Math.Min(255, image[y, x].Magnitude * scale * 255);

                    *address = (byte)System.Math.Max(0, min);
                }
                address += offset;
            }
        }

        bmp.UnlockBits(bmpData);

        return bmp;
    }
}


Как вы можете видеть, FFT работает правильно, но I-FFT-нет.

Это происходит потому, что bitmap to complex и наоборот работают не так, как ожидалось.

Что можно сделать, чтобы исправить функции ToComplex() и ToBitmap (), чтобы они не теряли данные?

Что я уже пробовал:

public partial class ImageDataConverter
{
    #region private static Complex[,] FromBitmapData(BitmapData bmpData)
    private static Complex[,] ToComplex(BitmapData bmpData)
    {
        Complex[,] comp = null;

        if (bmpData.PixelFormat == PixelFormat.Format8bppIndexed)
        {
            int width = bmpData.Width;
            int height = bmpData.Height;
            int offset = bmpData.Stride - (width * 1);//1 === 1 byte per pixel.

            if ((!Tools.IsPowerOf2(width)) || (!Tools.IsPowerOf2(height)))
            {
                throw new Exception("Imager width and height should be n of 2.");
            }

            comp = new Complex[width, height];

            unsafe
            {
                byte* src = (byte*)bmpData.Scan0.ToPointer();

                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++, src++)
                    {
                        comp[y, x] = new Complex((float)*src / 255,
                                                    comp[y, x].Imaginary);
                    }
                    src += offset;
                }
            }
        }
        else
        {
            throw new Exception("EightBppIndexedImageRequired");
        }

        return comp;
    }
    #endregion

    public static Complex[,] ToComplex(Bitmap bmp)
    {
        Complex[,] comp = null;

        if (bmp.PixelFormat == PixelFormat.Format8bppIndexed)
        {
            BitmapData bmpData = bmp.LockBits(  new Rectangle(0, 0, bmp.Width, bmp.Height),
                                                ImageLockMode.ReadOnly,
                                                PixelFormat.Format8bppIndexed);
            try
            {
                comp = ToComplex(bmpData);
            }
            finally
            {
                bmp.UnlockBits(bmpData);
            }
        }
        else
        {
            throw new Exception("EightBppIndexedImageRequired");
        }

        return comp;
    }

    public static Bitmap ToBitmap(Complex[,] image, bool fourierTransformed)
    {
        int width = image.GetLength(0);
        int height = image.GetLength(1);

        Bitmap bmp = Imager.CreateGrayscaleImage(width, height);

        BitmapData bmpData = bmp.LockBits(
            new Rectangle(0, 0, width, height),
            ImageLockMode.ReadWrite,
            PixelFormat.Format8bppIndexed);

        int offset = bmpData.Stride - width;
        double scale = (fourierTransformed) ? Math.Sqrt(width * height) : 1;

        unsafe
        {
            byte* address = (byte*)bmpData.Scan0.ToPointer();

            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++, address++)
                {
                    double min = System.Math.Min(255, image[y, x].Magnitude * scale * 255);

                    *address = (byte)System.Math.Max(0, min);
                }
                address += offset;
            }
        }

        bmp.UnlockBits(bmpData);

        return bmp;
    }
}

Richard MacCutchan

Без дополнительной информации об этом можно только догадываться. Вам нужно объяснить, где теряются данные.

2 Ответов

Рейтинг:
0

Jochen Arndt

Существуют некоторые проблемы при назначении и чтении комплексных значений.

Здесь вы преобразуете byte (в 8-битный пиксел, значение) в float, масштабируйте его и назначьте действительной части комплексного числа:

comp[y, x] = new Complex((float)*src / 255, comp[y, x].Imaginary);

Но мнимая часть инициализируется фактическим значением, которое в данный момент не определено! Не критично, но стоит отметить: почему вы бросаете на float вместо double и масштабировать на 255 вместо 256?

Здесь вы получаете масштабированную величину обратно и записываете ее на изображение.
double min = System.Math.Min(255, image[y, x].Magnitude * scale * 255);
*address = (byte)System.Math.Max(0, min);

С помощью max() здесь нет необходимости, потому что все значения из приведенного выше выражения всегда положительны.

Вы не сказали нам о масштабах потери точности. Но я предполагаю, что он в основном получен путем установки мнимой части в неопределенное значение.

Другим источником, вероятно, является приведение значений с плавающей запятой к byte Это делается без округления, так что цифры после десятичной точки отсекаются. Чтобы использовать ближайшее округление, добавьте 0,5 перед литьем (добавление работает здесь, потому что приведенное значение всегда положительное). Так что вы можете попробовать:
*address = (byte)System.Math.Min(255, 0.5 + image[y, x].Magnitude * scale * 255));


Наконец, всегда есть небольшая потеря точности при выполнении обширных операций с плавающей запятой.

В вашем случае вы вводите первую небольшую ошибку при масштабировании входного значения путем деления на 255. При использовании 256 ошибок не будет. Причина в том, что деление на 255 может привести к результату, который не может быть точно представлен числами с плавающей запятой. Деление на 256 не приведет к ошибке, потому что плавающая точка
числа внутренне используют показатель степени на основе 2, а деление на 256 просто сдвигает этот показатель.


0x01AA

Я могу проследить вашу аргументацию в конце ответа. Но математически правильное значение равно 255 или нет? И разве масштабирование до 256 вместо 255 не приводит в конечном итоге к большей ошибке?

Jochen Arndt

Это просто масштабирующий фактор. Используя 255, вы получите максимум. значение 1. Использование 256 шкал до максимального значения 0,99609375. Это зависит от того, как используются данные. Только тогда, когда есть требования, чтобы значение было масштабировано до макс. 1, необходимо использовать 255.

0x01AA

Ну ладно, спасибо. Я не нырял очень глубоко, я просто предположил, что "255" имеет какое-то отношение к окончательному RGB. Но да, для IFFT, конечно, имеет смысл работать с 256 :) +5

Jochen Arndt

Спасибо.

Рейтинг:
0

Patrice T

Вы должны научиться использовать отладчик как можно скорее. Вместо того чтобы гадать, что делает ваш код, пришло время увидеть, как он выполняется, и убедиться, что он делает то, что вы ожидаете.

Отладчик позволяет вам следить за выполнением строка за строкой, проверять переменные, и вы увидите, что есть точка, в которой он перестает делать то, что вы ожидаете.
Отладчик-Википедия, свободная энциклопедия[^]
Освоение отладки в Visual Studio 2010 - руководство для начинающих[^]

С помощью отладчика вы можете обнаружить, что некоторые переменные не очищены и что некоторые данные предыдущего использования все еще находятся здесь при следующем использовании.

Цитата:

Братан, я хотел бы смиренно сказать тебе, что речь идет не об отладке.
Речь идет об алгоритме.

Код-это перевод алгоритма, поэтому проблема в коде подразумевает проблему в алгоритме или в переводе, ваш выбор. А проблема в алгоритме подразумевает проблему в коде.
Цитата:

Я скопировал код из уже существующего и хорошо протестированного фреймворка.

Это предложение подразумевает, что нет никакой ошибки в алгоритме или в коде.


[no name]

Братан, я хотел бы смиренно сказать тебе, что речь идет не об отладке.

Речь идет об алгоритме.

Я скопировал код из уже существующего и хорошо протестированного фреймворка.

[no name]

> Это предложение подразумевает, что нет никакой ошибки в алгоритме или в коде.

Дорогой Братан,

У меня нет никакого интереса спорить с вами.

Это предложение подразумевает, что,

(1) этот код должен был работать идеально, но не работает.

(2) этот код должен быть изменен, но у меня нет понимания внутренней логики кода. Поэтому я не могу этого сделать.

(3) проблема гораздо больше, чем выглядит. Полученный код нуждается в тщательном пересмотре.



Patrice T

Почему бы не спросить автора фреймворка ? или форум пользователя ?
По крайней мере, они уже знакомы с ним.