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