168 lines
6.4 KiB
Python
168 lines
6.4 KiB
Python
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')
|
||
|
||
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)
|
||
|
||
T = np.array([
|
||
[1, 0, 0.2],
|
||
[0, 1, 0.1],
|
||
[0, 0, 2],
|
||
])
|
||
|
||
# Создаем матрицу трансформации
|
||
H = camera_matrix @ R @ T @ 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()
|