import cv2 import math import numpy as np from numpy.linalg import inv from typing import Optional import constants from scipy.spatial.transform import Rotation import utility class Position: """Класс позиции с полной ориентацией БПЛА в 3D пространстве""" x: float # Координата X y: float # Координата Y z: float # Масштаб yaw: float # Рыскание (rotation around Z-axis) pitch: float # Тангаж (rotation around Y-axis) roll: float # Крен (rotation around X-axis) def __init__( self, x: float = 0, y: float = 0, z: float = 1, yaw: float = 0, pitch: float = 0, roll: float = 0 ): self.x = x self.y = y self.z = z self.yaw = yaw self.pitch = pitch self.roll = roll def __str__(self) -> str: return ( f"Position(x={self.x:.2f}, y={self.y:.2f}, z={self.z:.2f}, " f"yaw={math.degrees(self.yaw):.1f}°, " f"pitch={math.degrees(self.pitch):.1f}°, " f"roll={math.degrees(self.roll):.1f}°)" ) def get_homography_matrix(self, K_in: np.ndarray = constants.K, K_out: np.ndarray | None = None, sliding: bool = True) -> np.ndarray: """ Возвращает матрицу гомографии """ R = self.get_rotation_matrix() T = self.get_translation_matrix() if not sliding: T[0, 2] = T[1, 2] = 0 if K_out is None: K_out = K_in return K_out @ R @ T @ np.linalg.inv(K_in) def copy(self) -> 'Position': """Создает полную копию объекта""" return Position(self.x, self.y, self.z, self.yaw, self.pitch, self.roll) def get_translation_matrix(self) -> np.ndarray: return np.array([ [1, 0, self.x / constants._K_FOCUS_DISTANCE], [0, 1, self.y / constants._K_FOCUS_DISTANCE], [0, 0, self.z] ]) def get_rotation_matrix(self) -> np.ndarray: """ Матрица вращения с порядком применения: yaw → pitch → roll Умножение: R = Rx(roll) * Ry(pitch) * Rz(yaw) """ cy, sy = math.cos(self.yaw), math.sin(self.yaw) cp, sp = math.cos(self.pitch), math.sin(self.pitch) cr, sr = math.cos(self.roll), math.sin(self.roll) Rz = np.array([ [cy, -sy, 0], [sy, cy, 0], [0, 0, 1], ]) Ry = np.array([ [cp, 0, sp], [0, 1, 0], [-sp, 0, cp], ]) Rx = np.array([ [1, 0, 0], [0, cr, -sr], [0, sr, cr], ]) return Rx @ Ry @ Rz def iapply(self, homography_matrix: np.ndarray, K = constants.K) -> 'Position': """Применяет матрицу трансформации для вычисления новой позиции и ориентации.""" np.set_printoptions(suppress=True) H = homography_matrix @ self.get_homography_matrix(sliding=False) # Decompose homography _, R, t, _ = cv2.decomposeHomographyMat(H, K) R = np.array(R) t = np.array(t) T = inv(R) @ inv(K) @ H @ K ind = np.array([A[2][0] ** 2 + A[2][1] ** 2 for A in T]) top_k = max(1, len(T) // 2) if (len(T) == 3): raise "len(T) == 3" ind = np.argpartition(ind, top_k - 1)[:top_k] T = T[ind[0]] T = T @ np.array([0, 0, 1]) / np.mean((T[0][0], T[1][1])) T[2] -= 1 R = R[ind] t = t[ind] best_id = ((t - T) ** 2).sum((1, 2)).argmin() R = R[best_id] rot = Rotation.from_matrix(R).as_euler('XYZ').flatten() self.roll = rot[0] self.pitch = rot[1] self.yaw = rot[2] t = t[best_id].flatten() self.x += -T[0] * constants._K_FOCUS_DISTANCE * self.z self.y += T[1] * constants._K_FOCUS_DISTANCE * self.z self.z = 1 + T[2] def apply(self, homography_matrix: np.ndarray, K = constants.K) -> 'Position': """Применяет матрицу трансформации для вычисления новой позиции и ориентации.""" pos = self.copy() pos.iapply(homography_matrix, K) return pos