feat/pitch-roll #1
183
examples/transform_app.py
Normal file
183
examples/transform_app.py
Normal file
@@ -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()
|
||||
Reference in New Issue
Block a user