Алгоритм коррекции искажения ствола для коррекции объектива FishEye - невозможно реализовать с помощью Java

У меня есть большая масса фотографий, снятых объективом "рыбий глаз". Поскольку я хочу выполнить некоторую обработку изображений (например, обнаружение краев) на фотографиях, я хочу удалить искажение бочки, которое сильно влияет на мои результаты.

После некоторых исследований и множества прочитанных статей я нашел эту страницу: они описывают алгоритм (и некоторые формулы) для решения этой проблемы.

M = a * rcorr ^ 3 + b * rcorr ^ 2 + c * rcorr + d
rsrc = (a * rcorr ^ 3 + b * rcorr ^ 2 + c * rcorr + d) * rcorr

rsrc = расстояние пикселя от центра исходного изображения
rcorr = расстояние пикселя от центра в исправленном изображении
a,b,c = искажение изображения d = линейное масштабирование изображения

Я использовал эти формулы и попытался реализовать это в приложении Java. К сожалению, это не работает, и я не смог заставить его работать. "Исправленное" изображение не имеет ничего общего с оригинальной фотографией и вместо этого показывает какие-то загадочные круги в центре. Смотри сюда:

http://imageshack.us/f/844/barreldistortioncorrect.jpg/(раньше это была фотография белой коровы перед синей стеной)

Вот мой код:

protected int[] correction(int[] pixels) {

    //
    int[] pixelsCopy = pixels.clone();

    // parameters for correction
    double paramA = 0.0; // affects only the outermost pixels of the image
    double paramB = -0.02; // most cases only require b optimization
    double paramC = 0.0; // most uniform correction
    double paramD = 1.0 - paramA - paramB - paramC; // describes the linear scaling of the image

    //
    for(int x = 0; x < dstView.getImgWidth(); x++) {
        for(int y = 0; y < dstView.getImgHeight(); y++) {

            int dstX = x;
            int dstY = y;

            // center of dst image
            double centerX = (dstView.getImgWidth() - 1) / 2.0;
            double centerY = (dstView.getImgHeight() - 1) / 2.0;

            // difference between center and point
            double diffX = centerX - dstX;
            double diffY = centerY - dstY;
            // distance or radius of dst image
            double dstR = Math.sqrt(diffX * diffX + diffY * diffY);

            // distance or radius of src image (with formula)
            double srcR = (paramA * dstR * dstR * dstR + paramB * dstR * dstR + paramC * dstR + paramD) * dstR;

            // comparing old and new distance to get factor
            double factor = Math.abs(dstR / srcR);
            // coordinates in source image
            double srcXd = centerX + (diffX * factor);
            double srcYd = centerY + (diffX * factor);

            // no interpolation yet (just nearest point)
            int srcX = (int)srcXd;
            int srcY = (int)srcYd;

            if(srcX >= 0 && srcY >= 0 && srcX < dstView.getImgWidth() && srcY < dstView.getImgHeight()) {

                int dstPos = dstY * dstView.getImgWidth() + dstX;
                pixels[dstPos] = pixelsCopy[srcY * dstView.getImgWidth() + srcX];
            }
        }
    }

    return pixels;
}

Мои вопросы:
1) Правильна ли эта формула?
2) Я сделал ошибку, превратив эту формулу в программный продукт?
3) Существуют и другие алгоритмы (например, как имитировать эффект объектива "рыбий глаз" с помощью openCV? Или wiki/Distortion_(оптика)), они лучше?

Спасибо за вашу помощь!

4 ответа

Решение

Основная ошибка, которую вы имеете, состоит в том, что алгоритм указывает, что r_corr и r_src выражаются в единицах min((xDim-1)/2, (yDim-1)/2). Это необходимо сделать для нормализации расчета, чтобы значения параметров не зависели от размера исходного изображения. С таким кодом вам нужно будет использовать гораздо меньшие значения для paramB, например, для меня это работало нормально с paramB = 0.00000002 (для изображения с размерами 2272 x 1704).

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

Исправление обеих этих ошибок должно дать вам что-то вроде этого:

protected static int[] correction2(int[] pixels, int width, int height) {
    int[] pixelsCopy = pixels.clone();

    // parameters for correction
    double paramA = -0.007715; // affects only the outermost pixels of the image
    double paramB = 0.026731; // most cases only require b optimization
    double paramC = 0.0; // most uniform correction
    double paramD = 1.0 - paramA - paramB - paramC; // describes the linear scaling of the image

    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            int d = Math.min(width, height) / 2;    // radius of the circle

            // center of dst image
            double centerX = (width - 1) / 2.0;
            double centerY = (height - 1) / 2.0;

            // cartesian coordinates of the destination point (relative to the centre of the image)
            double deltaX = (x - centerX) / d;
            double deltaY = (y - centerY) / d;

            // distance or radius of dst image
            double dstR = Math.sqrt(deltaX * deltaX + deltaY * deltaY);

            // distance or radius of src image (with formula)
            double srcR = (paramA * dstR * dstR * dstR + paramB * dstR * dstR + paramC * dstR + paramD) * dstR;

            // comparing old and new distance to get factor
            double factor = Math.abs(dstR / srcR);

            // coordinates in source image
            double srcXd = centerX + (deltaX * factor * d);
            double srcYd = centerY + (deltaY * factor * d);

            // no interpolation yet (just nearest point)
            int srcX = (int) srcXd;
            int srcY = (int) srcYd;

            if (srcX >= 0 && srcY >= 0 && srcX < width && srcY < height) {
                int dstPos = y * width + x;
                pixels[dstPos] = pixelsCopy[srcY * width + srcX];
            }
        }
    }

    return pixels;
}

В этой версии вы можете использовать значения параметров из существующих баз данных объективов, таких как LensFun (хотя вам нужно будет поменять знак каждого параметра). Страницу, описывающую алгоритм, теперь можно найти по http://mipav.cit.nih.gov/pubwiki/index.php/Barrel_Distortion_Correction

Я думаю, что ваши круги вызваны этой строкой:

double srcYd = centerY + (diffX * factor);

что я думаю, должно быть:

double srcYd = centerY + (diffY * factor);

Возможно, ваши параметры радиального искажения слишком велики, и изображение стало упакованным в сферу. Попробуйте поместить меньшие значения в a,b,c а также d,

Ваши ценности очень экстремальные, поэтому вы видите экстремальные результаты.

Попробуйте a=0, b=0, c=1. Это не описывает исправления вообще, если ваша программа верна, вы должны увидеть исходное изображение. Затем постепенно меняйте c и b. Изменение с шагом 0,1 - хорошее начало.

Другие вопросы по тегам