К оглавлению

движения плоскости

С.И.Хашин


vTransf.h - заголовочный файл
vTransf.cpp - реализация.

Рассматриваем движения плоскости следующих видов:

Функция vError вывода сообщений об ошибке описывается, но не реализуется. Должна быть реализована где-то в другом месте.

void    vError(const char *format, ...);        // show error function

Тип данных, описывающий допустимые типы преобразований определён как

enum tTransf 
{   
    tTransf_none,       // невозможное преобразование
    tTransf_shift,      // параллельный перенос
    tTransf_rotate,     // поворот + параллельный перенос
    tTransf_affine,     // полное аффинное преобразование
    tTransf_project     // полное проективное преобразование, пока не реализовано
};

Формулы, задающе преобраование каждого типа:


    tTransf_shift,      // параллельный перенос
    x0 = x + a[0];
    y0 = y + a[1];

    tTransf_rotate,     // поворот + параллельный перенос
    x0 = a[0] + cos(a[2])*x + sin(a[2])*y;
    y0 = a[1] - sin(a[2])*x + cos(a[2])*y;

    tTransf_affine,     // полное аффинное преобразование
    x0 = a[0] + a[1]*x + a[2]*y;
    y0 = a[3] + a[4]*x + a[5]*y;

Конструкторы:

CvTransf();     // строит невозможное отображение
CvTransf(double dx, double dy);                 // строит сдвиг
CvTransf(double dx, double dy, double angle);   // строит поворот
CvTransf(double a0, double a1, double a2, double a3, double a4, double a5);
                // строит полное аффинное преобразование

Задать преобразование можно одним из следующих способов:

void set(double dx, double dy);                 // строит сдвиг
void set(double dx, double dy, double angle);   // строит поворот
void set(double a0, double a1, double a2, double a3, double a4, double a5);
        // строит полное аффинное

double get(int i);                              // коэффициент номер i

void inc(int i, double v);                      // увеличивает коэффициент номер i

// строит поворот с центром (x0,y0)
void setRot(double x0, double y0, double angle);

// строим поворот+растяжение такое, что P0->P2, P1->P3
void setSRot(double x0, double y0, double x1, double y1, 
        double x2, double y2, double x3, double y3);

// строит аффинное с центром (x0,y0)
void setAff(double x0, double y0, double a1, 
        double a2, double a4, double a5);

// добавляет сдвиг к this
void addShift(double vx, double vy);

Найти аффинное преобразование, переводящее три данные точки в три другие можно с помощью метода setAffine:

// строит аффинное такое, что 
//  A(x1, y1) = (x1s, y1s), 
//  A(x2, y2) = (x2s, y2s), 
//  A(x3, y3) = (x3s, y3s), 
void setAffine
    (double x1,  double y1,   double x2,  double y2,   double x3,  double y3,
    double x1s, double y1s,  double x2s, double y2s,  double x3s, double y3s);

Применение преобразования к точке:

void apply( double x, double y,  double &x0, double &y0 );  
        // (x0,y0) := Применить преобразование к точке (x,y) 

Для записи коэффициентов преобразования в файл используются функции:

void save( const char *fname );  // append CvTransf to file
void save( FILE *f );            // записать CvTransf в открытый файл

Несколько служебных функций:

void mult( double k );                  // увеличить масштаб в k раз
void mult( CvTransf &a, CvTransf &b);   // this = a*b. Если надо, повышаем тип. a*b(P) = a(b(P))
void divide( CvTransf &a, CvTransf &b); // Находим this такое, что this*a = b
void revers( CvTransf &b);              // this = обратное(b)
void power( CvTransf &b, int k);        // this = b^k;

Эффективное кодирование аффинных преобразований

Аффинное преобразование задается 6-ю коэффициентами типа double и, следовательно, занимает 48 байтов. Запоминать их все, конечно же неэффективно. Для применений в компьютеной графике можно ввести более экономную запись.

Будем считать, что преобразование А применяется не на всей плоскости, а лишь в некоторой не слишком большой области U. Рассмотрим на плоскости точку P0 – центр области U (округленную до целых) и обозначим через r среднеквадратичное расстояние от точек области до центра, округленное до целых. Построим точку P1 на расстоянии r от P0 вправо и P2 на расстоянии r от P0 вниз. То есть:

Любое аффинное преобразование A полностью определяется образами этих трех точек A(P0), A(P1), A(P2). Введем три пары чисел:

  1. Пара (w0, w1) – вектор A(P0)-P0, т.е. вектор сдвига центра области.
  2. Пара (w2, w3) – разность векторов сдвига точек P1 и P0.
  3. Третья пара. Пусть A' – преобразование типа (сдвиг + поворот +растяжение), переводящее P0 в A(P0) и P1 в A(P1). Такое всегда существует и единственно. В качестве третьей пары чисел (w4, w5) возьмем координаты вектора A(P2)-A'(P0),

При заданном центре (cx,cy) области и ее радиусе (r) кодируем преобразование с помощью 6-ти чисел в буфере (buff[0..5]), буфер должен быть уже готов.

Введем обозначения:

A         - рассматриваемое преобразование,
(v0x,v0y) - вектор сдвига точки P0, А(P0)-P0
(v1x,v1y) - разность: (вектор сдвига точки P1)-(вектор сдвига точки P0),
            (A(P1)-P1) - (A(P0)-P0) = A(P1)-A(P0)-P1+P0,

Рассмотрим преобразование (поворот+растяжение) A' такое, что

A'(P0) = A(P0),
A'(P1) = A(P1).

Такое преобразование всегда существует и единственно. Обозначим

    (v2x,v2y) - A'(P2) - A(P2)

Числа (v0x,v0y, v1x,v1y, v2x,v2y) однозначно описывают преобразование А при заданных (cx,cy,r).

Для кодирования эти действительные числа умножим на 16 и округлим до целых. Результат положим в buff[0..5]

void code6(double cx, double cy, double r, double *buff);   
    // закодировать преобразование в 6 дейстительных чисел

void code6(int cx, int cy, int r, int *buff);               
    // закодировать преобразование в 6 целых чисел (округление до 1/16)

void decode6(int cx, int cy, int r, int *buff);             
    // декодировать преобразование из 6 целых чисел (округленных до 1/16)

void decode6(double cx, double cy, double r, double *buff); 
    // декодировать преобразование из 6 чисел

char *code6Str( int cx, int cy, int r, bool xls=false);
    // строка: закодированное преобразование в 6 целых чисел

char *code6Sts( int cx, int cy, int r, bool xls=false);
    // code6Str без деления на 16

В области плоскости, заданной центром (px,py) и среднеквадратичным отклонением r для данного аффинного преобразования A можно найти наиболее подходящий сдвиг:

void Transf2shift(CvTransf  &sh, double px, double py);

и найти наиболее подходящее преобразование типа "сдвиг+поворот":

void Transf2rot(CvTransf &rot, double px, double py, double r);

Среднее расстояние между образами точек |this(P) - a(P)| области находит функция

double TransfDist(CvTransf &a2, double px, double py, double r);

Кроме того, имеется ещё функция вне класса, находящая аффинное преобразование, аналогичная CvTransf::setAffine(..).

// строит аффинное такое, что 
//  A(x1, y1) = (x1s, y1s), 
//  A(x2, y2) = (x2s, y2s), 
//  A(x3, y3) = (x3s, y3s), 
void affineTMove(double x1,  double y1,   double x2,  double y2,   double x3,  double y3,  
                 double x1s, double y1s,  double x2s, double y2s,  double x3s, double y3s,  
                 double &a0, double &a1,  double &a2, double &a3,  double &a4, double &a5);

К оглавлению


free counters