feat: add transform_app
This commit is contained in:
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