Алгоритмы совмещения изображений - Реализация обратного композиционного алгоритма совмещения изображений

ОГЛАВЛЕНИЕ

Реализация обратного композиционного алгоритма совмещения изображений

В обратном композиционном алгоритме шаблон T и изображение I меняются ролями.

Предварительно вычислить:

1.    Вычислить градиент  изображения T(x).
2.    Вычислить якобиан   в (x;0).
3.    Вычислить изображения быстрейшего спуска,  .
4.    Вычислить матрицу Гессе,

Повторять:

1.    Деформировать I с помощью W(x;p) для вычисления I(W(x,p)).
2.    Вычислить изображение ошибки I(W(x;p))-T(x).
3.    Деформировать градиент  с помощью W(x;p).
4.    Вычислить  
5.    Вычислить  
6.    Обновить параметры

до тех пор, пока  .

Код этого метода находится в invcomp.h и invcomp.cpp. Ниже показано, что содержит invcomp.cpp.

Включить все требуемые заголовки:

#include <span class="code-keyword"><stdio.h></span>
#include <span class="code-keyword"><time.h></span>
#include <span class="code-keyword"><cv.h>        // Включить заголовок для части машинного зрения OpenCV.</span>
#include <span class="code-keyword"><highgui.h>    // Включить заголовок для части GUI OpenCV.</span>
#include <span class="code-string">"auxfunc.h"    // Заголовок для функций деформации.</span>

Реализация алгоритма Бейкера-Деллаерта-Мэтьюса (обратного композиционного) приведена ниже:

// Обратный композиционный метод Бейкера-Деллаерта-Мэтьюса
// @param[in] pImgT   Изображение шаблона T
// @param[in] omega   Прямоугольный участок шаблона
// @param[in] pImgI   Другое изображение I

void align_image_inverse_compositional(IplImage* pImgT, CvRect omega, IplImage* pImgI)
{
    // Несколько констант для процесса итеративного уменьшения.
    const float EPS = 1E-5f; // Пороговое значение для условия завершения.
    const int MAX_ITER = 100;  // Максимальное число итераций.

Создать все используемые внутри матрицы и изображения:

    // Здесь будут храниться используемые внутри изображения.
    IplImage* pGradTx = 0;       // Градиент I в направлении X.
    IplImage* pGradTy = 0;       // Градиент I в направлении Y.
    IplImage* pStDesc = 0;        // Изображения быстрейшего спуска.

    // Здесь будут храниться матрицы.
    CvMat* W = 0;  // Текущее значение деформации W(x,p)
    CvMat* dW = 0;  // Обновление деформации.
    CvMat* idW = 0; // Обратное обновление деформации.
    CvMat* X = 0;  // Точка в системе координат T.
    CvMat* Z = 0;  // Точка в системе координат I.

    CvMat* H = 0;  // Гессиан.
    CvMat* iH = 0; // Обратный гессиан.
    CvMat* b = 0;  // Вектор в правой части системы линейных уравнений.
    CvMat* delta_p = 0; // Значение обновления параметра.
    
    // Создать матрицы.
    W = cvCreateMat(3, 3, CV_32F);
    dW = cvCreateMat(3, 3, CV_32F);
    idW = cvCreateMat(3, 3, CV_32F);
    X = cvCreateMat(3, 1, CV_32F);
    Z = cvCreateMat(3, 1, CV_32F);

    H = cvCreateMat(3, 3, CV_32F);
    iH = cvCreateMat(3, 3, CV_32F);
    b = cvCreateMat(3, 1, CV_32F);
    delta_p = cvCreateMat(3, 1, CV_32F);

IPL_DEPTH_16S использует короткий тип в качестве элемента изображения градиента. Здесь используется короткий тип, потому что cvSobel выдает не нормализованное изображение, и может произойти переполнение uchar. Изображение градиента нормализуется посредством cvConvertScale().

    // Создать изображения.
    CvSize image_size = cvSize(pImgI->width, pImgI->height);
    pGradTx = cvCreateImage(image_size, IPL_DEPTH_16S, 1);
    pGradTy = cvCreateImage(image_size, IPL_DEPTH_16S, 1);
    pStDesc = cvCreateImage(image_size, IPL_DEPTH_32F, 3);

    // Получить текущее время. Оно используется позже для получения полного времени расчета.
    clock_t start_time = clock();

    /*
     *  Стадия предварительных вычислений.
     */

    // Вычислить градиент T.
    cvSobel(pImgT, pGradTx, 1, 0); // Градиент в направлении X
    cvConvertScale(pGradTx, pGradTx, 0.125); // Нормализовать
    cvSobel(pImgT, pGradTy, 0, 1); // Градиент в направлении Y
    cvConvertScale(pGradTy, pGradTy, 0.125); // Нормализовать
    

    // Вычислить изображения быстрейшего спуска и гессиан

    cvSet(H, cvScalar(0)); // Установить гессиан в ноль

Обойти пиксели изображения шаблона pImgT и собрать матрицу Гессе H и матрицу b:

    int u, v;    // (u,v) – координаты пикселя в системе координат T.
    float u2, v2; // (u2,v2) - координаты пикселя в системе координат I.
    
    // Обойти пиксели в шаблоне T.
    int i, j;
    for(i=0; i<omega.width; i++)
    {
        u = i + omega.x;

        for(j=0; j<omega.height; j++)
        {
            v = j + omega.y;

            // вычислить градиент T.
            short Tx = CV_IMAGE_ELEM(pGradTx, short, v, u);    
            short Ty = CV_IMAGE_ELEM(pGradTy, short, v, u);    
            
            // Вычислить элемент изображения быстрейшего спуска.
            // элемент изображения быстрейшего спуска
            float* stdesc = &CV_IMAGE_ELEM(pStDesc, float, v,u*3);
            stdesc[0] = (float)(-v*Tx+u*Ty);
            stdesc[1] = (float)Tx;
            stdesc[2] = (float)Ty;

            // Добавить член в гессиан.
            int l,m;
            for(l=0;l<3;l++)
            {
                for(m=0;m<3;m++)
                {
                    CV_MAT_ELEM(*H, float, l, m) += stdesc[l]*stdesc[m];
                }
            }
        }    
    }

    // Инвертировать гессиан.
    double inv_res = cvInvert(H, iH);
    if(inv_res==0)
    {
        printf("Error: Hessian is singular.\n");
        return;
    }

Войти в цикл итераций для минимизации значения средней ошибки.

    /*
     *   Стадия итераций.
     */

    // Установить деформацию в единицу.
    cvSetIdentity(W);

    // Здесь будет храниться текущее значение средней ошибки.
    float mean_error=0;

    // Повторять
    int iter=0; // номер текущей итерации
    while(iter < MAX_ITER)
    {
        iter++; // Увеличить счетчик итераций на единицу

        mean_error = 0; // Установить значение средней ошибки в нуль

        int pixel_count=0; // Число обработанных пикселей
        
        cvSet(b, cvScalar(0)); // Установить матрицу b в ноль
            
        // Обойти пиксели в шаблоне T.
        int i, j;
        for(i=0; i<omega.width; i++)
        {
            int u = i + omega.x;

            for(j=0; j<omega.height; j++)
            {
                int v = j + omega.y;

                // Установить вектор X в координаты пикселя (u,v,1)
                SET_VECTOR(X, u, v);

                // Деформировать Z=W*X
                cvGEMM(W, X, 1, 0, 0, Z);

                // Получить координаты деформированного пикселя в системе координат I.
                GET_VECTOR(Z, u2, v2);

                // Получить ближайшие целые координаты пикселя (u2i;v2i).
                int u2i = cvFloor(u2);
                int v2i = cvFloor(v2);

                if(u2i>=0 && u2i<pImgI->width && // проверить, находится ли пиксель внутри I.
                    v2i>=0 && v2i<pImgI->height)
                {
                    pixel_count++;

                    // Вычислить интенсивность преобразованного пикселя с подпиксельной точностью
                    // с использованием двухлинейной интерполяции.
                    float I2 = interpolate<uchar>(pImgI, u2, v2);

                    // Вычислить разность изображений D = I(W(x,p))-T(x).
                    float D = I2 - CV_IMAGE_ELEM(pImgT, uchar, v, u);

                    // Обновить значение средней ошибки.
                    mean_error += fabs(D);

                    // Добавить член в матрицу b.
                    float* stdesc = &CV_IMAGE_ELEM(pStDesc, float, v, u*3);
                    float* pb = &CV_MAT_ELEM(*b, float, 0, 0);
                    pb[0] += stdesc[0] * D;
                    pb[1] += stdesc[1] * D;
                    pb[2] += stdesc[2] * D;                    
                }    
            }
        }

        // Наконец, вычислить итоговую среднюю ошибку.
        if(pixel_count!=0)
            mean_error /= pixel_count;

        // Найти увеличение параметров.
        cvGEMM(iH, b, 1, 0, 0, delta_p);
        float delta_wz = CV_MAT_ELEM(*delta_p, float, 0, 0);
        float delta_tx = CV_MAT_ELEM(*delta_p, float, 1, 0);
        float delta_ty = CV_MAT_ELEM(*delta_p, float, 2, 0);

        init_warp(dW, delta_wz, delta_tx, delta_ty);
        // Инвертировать деформацию.
        inv_res = cvInvert(dW, idW);
        if(inv_res==0)
        {
            printf("Error: Warp matrix is singular.\n");
            return;
        }

        cvGEMM(idW, W, 1, 0, 0, dW);
        cvCopy(dW, W);

        // Вывести диагностическую информацию на экран.
        printf("iter=%d mean_error=%f\n", iter, mean_error);

        // Проверить условие завершения.
        if(fabs(delta_wz)<=EPS && fabs(delta_tx)<=EPS && fabs(delta_ty)<=EPS) break;
    }

    // Получить текущее время и получить полное время расчета.
    clock_t finish_time = clock();
    double total_time = (double)(finish_time-start_time)/CLOCKS_PER_SEC;

Распечатать информационную сводку в stdout:

    // Распечатать сводку.
    printf("===============================================\n");
    printf("Algorithm: inverse compositional.\n");
    printf("Caclulation time: %g sec.\n", total_time);
    printf("Iteration count: %d\n", iter);
    printf("Epsilon: %f\n", EPS);
    printf("Resulting mean error: %f\n", mean_error);
    printf("===============================================\n");

    // Показать результат совмещения изображений.
    draw_warped_rect(pImgI, omega, W);
    cvSetImageROI(pImgT, omega);
    cvShowImage("template",pImgT);
    cvShowImage("image",pImgI);
    cvResetImageROI(pImgT);

    // Освободить используемые ресурсы и завершить работу.
    cvReleaseMat(&W);
    cvReleaseMat(&dW);
    cvReleaseMat(&idW);
    cvReleaseMat(&H);
    cvReleaseMat(&iH);
    cvReleaseMat(&b);
    cvReleaseMat(&delta_p);
    cvReleaseMat(&X);
    cvReleaseMat(&Z);
    
}