Помогите с реализацией виннеровского фильтра

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

Собственно сама формула фильтра F(u,v)=conj(H(u,v)) / ( |H(u,v)|^2 + K ) * G(u,v)
где G — двухмерное преобразование Фурье исходной картинки
H — искажающая функция.
en.wikipedia.org/...​wiki/Wiener_deconvolution

        public static Complex[,] WienerFilter(Complex[,] Gx, Complex[,] Hx)
        {
            int w = Gx.GetLength(0);
            int h = Gx.GetLength(1);
            Complex[,] lRes = new Complex[w, h];
            //F(u,v)=conj(H(u,v)) / ( |H(u,v)|^2 + K ) * G(u,v)            
            for (int ly = 0; ly < h; ly++)
                for (int lx = 0; lx < w; lx++)
                {
                    Complex lH = Hx[lx, ly];
                    Complex lG = Gx[lx, ly];
                    double lHmod = lH.Re * lH.Re + lH.Im * lH.Im;
                    Complex lHConj = lH;
                    lHConj.Im = -lHConj.Im;
                    double lDiv = (lHmod + 0.003f);

                    Complex lFilt = lHConj;
                    lFilt.Im = lFilt.Im / lDiv;
                    lFilt.Re = lFilt.Re / lDiv;
                    lFilt.Re = Math.Abs(lFilt.Re);
                    lFilt.Im = Math.Abs(lFilt.Im);

                    Complex lFiltRes = lG * lFilt;

                    lRes[lx, ly] = lFiltRes;                
                }

            return lRes;
        }

0.003f — это параметр K отношение шума к сигналу
Gx — оригинальная картинка после преобразования фурье.
Hx — двухмерная гаусова свертка после преобразования Фурье
Нормализация на количество отсчетов 1/N производится при прямом преобразовании Фурье.

Если не делать
lFilt.Re = Math.Abs(lFilt.Re);
lFilt.Im = Math.Abs(lFilt.Im);
То исходная картинка отражается по 4 квадрантам изображения.

Вспомогательные функции

        public static ByteBitmap GetGausianByteBitmapImage(Size OutImageSize, int HalfWindow, float aPower, float aSigma)
        {
            ByteBitmap lRes = null;

            int lMinHalf = Math.Min(OutImageSize.Width, OutImageSize.Height) / 2;
            HalfWindow = Math.Min(HalfWindow, lMinHalf);

            ByteBitmap Gausian = new ByteBitmap(2 * HalfWindow, 2 * HalfWindow);
            float lCenterX = (float)HalfWindow - 0.5f;
            float lCenterY = (float)HalfWindow - 0.5f;
            //
            //-1/(2 * aSigma ^ 2);
            float larg = -1 / (2 * aSigma * aSigma);
            for (int ly = 0; ly < Gausian.Height; ly++)
                for (int lx = 0; lx < Gausian.Width; lx++)
                {
                    float xdiff = (float)lx - lCenterX;
                    float ydiff = (float)ly - lCenterY;
                    Gausian[lx, ly] = (byte)(aPower * Math.Exp(larg * (xdiff * xdiff + ydiff * ydiff)));
                }

            //fit for res
            lRes = new ByteBitmap(OutImageSize.Width, OutImageSize.Height);
            int lNewShiftX = (OutImageSize.Width - Gausian.Width) / 2;
            int lNewShiftY = (OutImageSize.Height - Gausian.Height) / 2;
            for (int ly = 0; ly < Gausian.Height; ly++)
                for (int lx = 0; lx < Gausian.Width; lx++)
                {
                    lRes[lx + lNewShiftX, ly + lNewShiftY] = Gausian[lx, ly];

                }

            return lRes;
        }

        public static ByteBitmap FitByteBitmapForFFT2D(ByteBitmap aBitmap, int Shift, out Point StartPoint, byte DefVal)
        {
            StartPoint = new Point(0, 0);
            ByteBitmap lRes = null;
            //calculate new shift
            int lNewWidth = aBitmap.Width + 2 * Shift;
            int lNewHeight = aBitmap.Height + 2 * Shift;
            lNewWidth += AdditionToPowerOfTwo(lNewWidth);
            lNewHeight += AdditionToPowerOfTwo(lNewHeight);
            int lNewShiftX = (lNewWidth - aBitmap.Width) / 2;
            int lNewShiftY = (lNewHeight - aBitmap.Height) / 2;


            if (0 != DefVal)
                lRes = new ByteBitmap(lNewWidth, lNewHeight, DefVal);
            else
                lRes = new ByteBitmap(lNewWidth, lNewHeight);

            for (int ly = 0; ly < aBitmap.Height; ly++)
                for (int lx = 0; lx < aBitmap.Width; lx++)
                    lRes[lx + lNewShiftX, ly + lNewShiftY] = aBitmap[lx, ly];
            //
            StartPoint.X = lNewShiftX;
            StartPoint.Y = lNewShiftY;

            return lRes;
        }

        public static ByteBitmap TestWienerFilter(ByteBitmap aBitmap)
        {
            Point lStart;
            ByteBitmap lLoc = FFTHelpers.FitByteBitmapForFFT2D(aBitmap, 10, out lStart, 255);
            Complex[,] lGx = FFTHelpers.ByteBitmapToComplex(lLoc);
            FourierTransform.FFT2(lGx, FourierTransform.Direction.Forward);

            //convolution function
            //gauss distribution
            ByteBitmap lBt = GetGausianByteBitmapImage(new Size(lGx.GetLength(0), lGx.GetLength(1)), 5, 255, 1.0f);
            Complex[,] lHx = FFTHelpers.ByteBitmapToComplex(lBt);
            FourierTransform.FFT2(lHx, FourierTransform.Direction.Forward);

            //make wiener
            Complex[,] lWR = WienerFilter(lGx, lHx);
            
            //backward result
            FourierTransform.FFT2(lWR, FourierTransform.Direction.Backward);


            //convert to bytebitmap
            ByteBitmap lRes = FFTHelpers.ComplexToByteBitmap(lWR);
            lRes = FFTHelpers.GetFittedByteBitmapFromFFT2D(lRes, lStart, new Size(aBitmap.Width, aBitmap.Height));
            return lRes;
        }

Так вот в результате TestWienerFilter фильтр почему-то делает не восстановление сигнала после применения функции, а наоборот размазывает сигнал Hx — гаусовым блюром.

Проект для сборки(VS2008 но легко конвертируется и в 2010) можно найти здесь:
yadi.sk/d/lJ_sQbss4clws

PS
Оригинальный фильтр F = G (1/H) * (|H|^2/(|H|^2 + SNR)) вообще даёт полностью черную картинку.

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

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

1. Ты ядро размытия подаешь точно в частотной области?
2. Ты как ядро размытия преобразуешь в частотный вид? Дополняешь по измерениям и сдвигаешь дял центрирования?
3. Ты проверял, у тебя PSF вообще валидный? Там не мусор? ПРи свертке PSF тоже в частотный домен преобразуешь с необходимыми сдвигами? Или у тебя гаусово ядро?
4. Правильно ли обрабатываешь граничные случаи?
5. Глянул на формулу, которую ты привел, как базовую. Ты уверен, что она верна? Не помню точно, но по-моему она таки такая была

F(u,v)=conj(H(u,v)) * |H(u,v)|^2 / ( |H(u,v)|^2 + N(u, v) / S(u, v) ) * G(u,v), где n-шум, s — сигнал, G — фурье размытого иозбражения, а H — фурье отцентрированного psf
Инверс фильтрует, остальное душит шумы в частотной области, фактически, винер и есть синтез обычного инверсного фильтра + шумодавилка. Иными слвоами, для начала выкинь винеровскую часть и просто проверь, отфильтрует ли оно с инверсом, чтобы просто локализовать ошибку. Под инверсом имею в виду чистую свертку с conj(psf)/(abs(psf)^2 + маленькое рандомное число) ядра размытия.
6. У тебя снр не зависит от частоты и вообще константа, винер такое, скорее всего усилит, либо пойдут эффекты призраки, либо артифакты от усиления высоких частот.

И главный вопрос, нафига пользуешь эту старину, вместо того, чтобы использовать итеративные развертки? Они ж не дают таких призраков хотя бы таких, если неточная аапроксимация ядра свертки или есть шумок.

1. Точно, проверял, причем проверял реализацию Фурье и свою и Aforge
2. Ядро дополняю и сдвигаю в центр, по бокам 0, ядро изображения дополняю определенным сдвигом (правильнее будет потом сделать через функцию Хеминга) и по бокам 255.
3. PSF — проверял, классический гаус, и преобразую с нужными сдвигами
4. В фильтре через К должны убираться граничные случаи, примем там граничный случай — рябь, а здесь просто гаусово размытие вместо ряби.

6. У тебя снр не зависит от частоты и вообще константа, винер такое, скорее всего усилит, либо пойдут эффекты призраки, либо артифакты от усиления высоких частот.

Ну естимейт шума можно добавить как опцию и подавать потом в функцию.

Иными слвоами, для начала выкинь винеровскую часть и просто проверь, отфильтрует ли оно с инверсом, чтобы просто локализовать ошибку. Под инверсом имею в виду чистую свертку с conj(psf)/(abs(psf)^2 + маленькое рандомное число) ядра размытия

По сути это ж оно?
F(u,v)=(conj(H(u,v)) / ( |H(u,v)|^2 + K )) * G(u,v)

И главный вопрос, нафига пользуешь эту старину, вместо того, чтобы использовать итеративные развертки? Они ж не дают таких призраков хотя бы таких, если неточная аапроксимация ядра свертки или есть шумок.

Они редкостно медленные при большом размере ядра, Виннер же зависит от размера исходного изображения со сложностью:
3 * (Height * Width log(Width) + Width * Height * log(Height)). Если для итеративных использовать большое ядро где-то 11×11 то будет вообще беда.

Может я чего пропустил, но с каких пор задачи деконволюции стали таковыми, что требуют высокой скорости работы? Учитывая их сложность вычислительную?

В данном случае для распознавания баркодов. Хотя я решил задачу через комбинацию лапласиана с исходным изображением. И быстрее и улучшение качества приемлимое.

Вообще, если честно, так и не понял, чего у тебя ничего не работало. В матлабе проверил — педалит, аж свистит.

Наф велосипеды, еси есть опенсорс решения, у?

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

public static Complex[,] WienerFilter(Complex[,] Gx, Complex[,] Hx)

Это спецсимвол случайно попал при copy/paste или это синтаксические новшества какие-то?
Я про это: [,]

Ну это C# двумерный массив. Собственно что не так?

Ясно. Просто, мне при беглом просмотре померещилась Java. :-)

Ну вот программка с исходниками, поиск занял аж 5 минут. github.com/...mir/SmartDeblur

Я её видел там формула F = G * (H.Re / (|H|^2 + K)) которая совсем на виннеровскую не похожа.

Я тоже волновой алгоритм могу объявить алгоритмом Дейкстры и называть ламерами всех кто с этим не согласен.

Я тоже волновой алгоритм могу объявить алгоритмом Дейкстры и называть ламерами всех кто с этим не согласен.
Ты же как выяснилось именно так и делал?

Вопрос, комплексное сопряжение это же простое инвертирование знака у мнимой части?

Да, это так (Complex conjugate). Matlab, естественно, выполняет его в векторной форме (conj).

Да. Извините не сразу обратил внимание на

conj

FireBird не пробовали использовать? Хорошая БД, многие хвалят.

Если бы можно было решить с помощь БД, я бы решил. Firebird Top1 по совокупности параметров среди всех БД.

Возможно, вы просто не достаточно интенсивно пробовали решить. Firebird — ваш вариант.

+1

файрберд с таким должен справлятся на раз два: “Embedding and using sophisticated mathematics in Firebird: Fast Fourier transforms, data smoothing and high order derivatives” www.ibphoenix.com/...WTO-A201-R1.zip

Можно посмотреть функцию deconvwnr в Matlab. Там же есть и исходники. Если не лениво, можно разобраться, в чем не совпадает.

Да я это сам понял, придется разбираться с Матлабом, так как я всегда работал только с Маткадом.

Підписатись на коментарі