diff --git a/examples/transform_app.py b/examples/transform_app.py new file mode 100644 index 0000000..0959c08 --- /dev/null +++ b/examples/transform_app.py @@ -0,0 +1,183 @@ +from pathlib import Path + +from kivy.app import App +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.slider import Slider +from kivy.uix.label import Label +from kivy.uix.image import Image as KivyImage +from kivy.graphics.texture import Texture +from kivy.core.window import Window +import numpy as np +from PIL import Image +import cv2 +import io + +class CameraTransformApp(App): + def build(self): + Window.size = (900, 700) + + # Главный контейнер + main_layout = BoxLayout(orientation='vertical', padding=10, spacing=10) + + # Контейнер для изображения + self.image_widget = KivyImage(size_hint=(1, 0.7)) + main_layout.add_widget(self.image_widget) + + # Контейнер для ползунков + controls_layout = BoxLayout(orientation='vertical', size_hint=(1, 0.3), spacing=5) + + # Yaw (рыскание) - вращение вокруг вертикальной оси + yaw_layout = BoxLayout(orientation='horizontal', spacing=10, size_hint=(1, None), height=40) + yaw_layout.add_widget(Label(text='Рыскание (Yaw):', size_hint=(0.2, 1))) + self.yaw_slider = Slider(min=-45, max=45, value=0, size_hint=(0.6, 1)) + self.yaw_slider.bind(value=self.on_slider_change) + self.yaw_label = Label(text='0°', size_hint=(0.2, 1)) + yaw_layout.add_widget(self.yaw_slider) + yaw_layout.add_widget(self.yaw_label) + controls_layout.add_widget(yaw_layout) + + # Pitch (тангаж) - наклон вперед/назад + pitch_layout = BoxLayout(orientation='horizontal', spacing=10, size_hint=(1, None), height=40) + pitch_layout.add_widget(Label(text='Тангаж (Pitch):', size_hint=(0.2, 1))) + self.pitch_slider = Slider(min=-45, max=45, value=0, size_hint=(0.6, 1)) + self.pitch_slider.bind(value=self.on_slider_change) + self.pitch_label = Label(text='0°', size_hint=(0.2, 1)) + pitch_layout.add_widget(self.pitch_slider) + pitch_layout.add_widget(self.pitch_label) + controls_layout.add_widget(pitch_layout) + + # Roll (крен) - наклон влево/вправо + roll_layout = BoxLayout(orientation='horizontal', spacing=10, size_hint=(1, None), height=40) + roll_layout.add_widget(Label(text='Крен (Roll):', size_hint=(0.2, 1))) + self.roll_slider = Slider(min=-45, max=45, value=0, size_hint=(0.6, 1)) + self.roll_slider.bind(value=self.on_slider_change) + self.roll_label = Label(text='0°', size_hint=(0.2, 1)) + roll_layout.add_widget(self.roll_slider) + roll_layout.add_widget(self.roll_label) + controls_layout.add_widget(roll_layout) + + main_layout.add_widget(controls_layout) + + # Создаем тестовое изображение с сеткой + self.original_image = self.create_test_image() + self.update_image() + + return main_layout + + def create_test_image(self): + """Создает тестовое изображение с сеткой и текстом""" + img = cv2.imread(Path('images') / 'photo_610.png') + # img = np.ones((400, 600, 3), dtype=np.uint8) * 255 + + # # Рисуем сетку + # for i in range(0, 600, 50): + # cv2.line(img, (i, 0), (i, 400), (200, 200, 200), 1) + # for i in range(0, 400, 50): + # cv2.line(img, (0, i), (600, i), (200, 200, 200), 1) + + # # Рисуем оси координат + # cv2.line(img, (300, 200), (450, 200), (255, 0, 0), 3) # X - красная + # cv2.line(img, (300, 200), (300, 50), (0, 255, 0), 3) # Y - зеленая + + # # Добавляем текст + # cv2.putText(img, 'X', (460, 210), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2) + # cv2.putText(img, 'Y', (310, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + # cv2.putText(img, 'Perspective Transform', (150, 30), + # cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) + + # # Рисуем прямоугольник + # cv2.rectangle(img, (200, 150), (400, 250), (255, 0, 255), 2) + + return img + + def get_rotation_matrix(self, yaw, pitch, roll): + """Вычисляет матрицу поворота 3D""" + # Конвертируем в радианы + yaw = np.radians(yaw) + pitch = np.radians(pitch) + roll = np.radians(roll) + + # Матрица поворота вокруг Z (yaw) + Rz = np.array([ + [np.cos(yaw), -np.sin(yaw), 0], + [np.sin(yaw), np.cos(yaw), 0], + [0, 0, 1] + ]) + + # Матрица поворота вокруг Y (pitch) + Ry = np.array([ + [np.cos(pitch), 0, -np.sin(pitch)], + [0, 1, 0], + [np.sin(pitch), 0, np.cos(pitch)] + ]) + + # Матрица поворота вокруг X (roll) + Rx = np.array([ + [1, 0, 0], + [0, np.cos(roll), -np.sin(roll)], + [0, np.sin(roll), np.cos(roll)] + ]) + + # Комбинируем повороты: R = Rz * Ry * Rx + R = Rx @ Ry @ Rz + return R + + def apply_perspective_transform(self, img, yaw, pitch, roll): + """Применяет перспективное преобразование к изображению""" + h, w = img.shape[:2] + + # Параметры камеры + focal_length = w + distance = w + + # Матрица камеры + camera_matrix = np.array([ + [focal_length / 2, 0, w / 2], + [0, focal_length / 2, h / 2], + [0, 0, 1] + ]) + + # Получаем матрицу поворота + R = self.get_rotation_matrix(yaw, pitch, roll) + Z = np.eye(3) + Z[2, 2] = 2 + + # Создаем матрицу трансформации + H = camera_matrix @ Z @ R @ np.linalg.inv(camera_matrix) + + # Применяем преобразование + try: + result = cv2.warpPerspective(img, H, (w, h), + borderMode=cv2.BORDER_CONSTANT, + borderValue=(50, 50, 50)) + return result + except: + return img + + def update_image(self): + """Обновляет изображение с текущими параметрами""" + yaw = self.yaw_slider.value + pitch = self.pitch_slider.value + roll = self.roll_slider.value + + # Применяем трансформацию + transformed = self.apply_perspective_transform( + self.original_image, yaw, pitch, roll + ) + + # Конвертируем в текстуру Kivy + buf = cv2.flip(transformed, 0).tobytes() + texture = Texture.create(size=(transformed.shape[1], transformed.shape[0]), + colorfmt='bgr') + texture.blit_buffer(buf, colorfmt='bgr', bufferfmt='ubyte') + self.image_widget.texture = texture + + def on_slider_change(self, instance, value): + """Обработчик изменения ползунков""" + self.yaw_label.text = f'{int(self.yaw_slider.value)}°' + self.pitch_label.text = f'{int(self.pitch_slider.value)}°' + self.roll_label.text = f'{int(self.roll_slider.value)}°' + self.update_image() + +if __name__ == '__main__': + CameraTransformApp().run()