С.И.Хашин
Рассматриваем движения плоскости следующих видов:
Функция 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). Введем три пары чисел:
При заданном центре (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);