У меня уже есть перспективная деформация, которая работает правильно, однако я могу Мне не удалось понять, как добиться такого рода деформации деформации сетки, мне не повезло.
Текущий вид: сетка прямо сейчас не будет оставаться на месте и не будет оставаться на месте. t деформирует форму и, следовательно, изображение, как вы можете видеть здесь:
Желаемый результат:
[img]https://i.sstatic. net/tCKQ661y.gif[/img]
Вот текущий код:
import tkinter as tk
from tkinter import ttk, filedialog
import cv2
import numpy as np
from PIL import Image, ImageTk
import os
from matplotlib.path import Path
class AdvancedSmartObjectGUI:
def __init__(self, master):
self.master = master
self.master.title("Gui")
self.canvas = tk.Canvas(self.master, width=800, height=600, bg='white')
self.canvas.pack(fill=tk.BOTH, expand=True)
self.background_image = None
self.overlay_image = None
self.warped_overlay = None
self.shape_points = []
self.grid_points = []
self.original_shape_points = []
self.dragging = None
self.mode = 'create'
self.opacity = 1.0
self.create_buttons()
self.canvas.bind("", self.on_click)
self.canvas.bind("", self.on_drag)
self.canvas.bind("", self.on_release)
self.master.bind("", self.undo)
self.history = []
self.load_background_from_folder()
def create_buttons(self):
button_frame = ttk.Frame(self.master)
button_frame.pack(fill=tk.X)
ttk.Button(button_frame, text="Load Overlay", command=self.load_overlay).pack(side=tk.LEFT)
ttk.Button(button_frame, text="Create Shape", command=lambda: self.set_mode('create')).pack(side=tk.LEFT)
ttk.Button(button_frame, text="Move Shape", command=lambda: self.set_mode('move')).pack(side=tk.LEFT)
ttk.Button(button_frame, text="Edit Shape", command=lambda: self.set_mode('edit')).pack(side=tk.LEFT)
ttk.Button(button_frame, text="Grid", command=lambda: self.set_mode('grid')).pack(side=tk.LEFT)
ttk.Button(button_frame, text="Reset", command=self.reset).pack(side=tk.LEFT)
ttk.Button(button_frame, text="Undo", command=self.undo).pack(side=tk.LEFT)
self.opacity_scale = ttk.Scale(button_frame, from_=0, to=1, orient=tk.HORIZONTAL, command=self.update_opacity)
self.opacity_scale.set(1.0)
self.opacity_scale.pack(side=tk.LEFT)
def set_mode(self, mode):
self.mode = mode
if mode == 'grid' and len(self.shape_points) == 4:
self.create_grid_points()
self.original_shape_points = self.shape_points.copy()
self.display_image()
def create_grid_points(self):
rows, cols = 4, 4
self.grid_points = []
for i in range(rows + 1):
for j in range(cols + 1):
t = i / rows
s = j / cols
x = (1-t)*(1-s)*self.shape_points[0][0] + (1-t)*s*self.shape_points[1][0] + t*(1-s)*self.shape_points[3][0] + t*s*self.shape_points[2][0]
y = (1-t)*(1-s)*self.shape_points[0][1] + (1-t)*s*self.shape_points[1][1] + t*(1-s)*self.shape_points[3][1] + t*s*self.shape_points[2][1]
self.grid_points.append((x, y))
self.original_grid_points = self.grid_points.copy()
def load_background_from_folder(self):
folder_path = r'C:\Users\Utente\Desktop\0a\2'
image_files = [f for f in os.listdir(folder_path) if f.lower().endswith(('.png', '.jpg', '.jpeg'))]
if image_files:
file_path = os.path.join(folder_path, image_files[0])
self.background_image = cv2.imread(file_path)
self.background_image = cv2.cvtColor(self.background_image, cv2.COLOR_BGR2RGB)
self.background_image = cv2.resize(self.background_image, (800, 600))
self.display_image()
else:
print("No image files found in the specified folder.")
def load_overlay(self):
file_path = filedialog.askopenfilename(filetypes=[("Image files", "*.png *.jpg *.jpeg")])
if file_path:
self.overlay_image = cv2.imread(file_path, cv2.IMREAD_UNCHANGED)
if self.overlay_image.shape[2] == 3:
self.overlay_image = cv2.cvtColor(self.overlay_image, cv2.COLOR_BGR2RGBA)
else:
self.overlay_image = cv2.cvtColor(self.overlay_image, cv2.COLOR_BGRA2RGBA)
if len(self.shape_points) < 4:
self.shape_points = [(0, 0), (800, 0), (800, 600), (0, 600)]
self.create_grid_points()
self.warp_overlay()
self.display_image()
def warp_overlay(self):
if self.overlay_image is None or len(self.grid_points) < 25:
return
min_x = min(p[0] for p in self.shape_points)
max_x = max(p[0] for p in self.shape_points)
min_y = min(p[1] for p in self.shape_points)
max_y = max(p[1] for p in self.shape_points)
shape_height = max_y - min_y
overlay_aspect = self.overlay_image.shape[1] / self.overlay_image.shape[0]
new_width = int(shape_height * overlay_aspect)
resized_overlay = cv2.resize(self.overlay_image, (new_width, int(shape_height)))
src_points = np.array([(0, 0), (new_width, 0), (new_width, shape_height), (0, shape_height)], dtype=np.float32)
dst_points = np.array(self.shape_points, dtype=np.float32)
matrix = cv2.getPerspectiveTransform(src_points, dst_points)
self.warped_overlay = cv2.warpPerspective(resized_overlay, matrix, (800, 600), borderMode=cv2.BORDER_TRANSPARENT)
def display_image(self):
if self.background_image is None:
self.canvas.delete("all")
return
img = self.background_image.copy()
if self.warped_overlay is not None:
img = self.overlay_images(img, self.warped_overlay)
self.photo = ImageTk.PhotoImage(image=Image.fromarray(img))
self.canvas.delete("all")
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.photo)
if self.mode == 'grid':
self.draw_grid()
else:
self.draw_shape()
def overlay_images(self, background, overlay):
mask = overlay[:, :, 3] / 255.0 * self.opacity
for c in range(3):
background[:, :, c] = background[:, :, c] * (1 - mask) + overlay[:, :, c] * mask
return background.astype(np.uint8)
def draw_shape(self):
if len(self.shape_points) > 1:
self.canvas.create_polygon(self.shape_points, outline='red', fill='', tags='shape')
for i, point in enumerate(self.shape_points):
self.canvas.create_oval(point[0]-5, point[1]-5, point[0]+5, point[1]+5, fill='red', tags=f'handle{i}')
def draw_grid(self):
rows, cols = 4, 4
for i in range(rows + 1):
points = [self.grid_points[j] for j in range(i*(cols+1), (i+1)*(cols+1))]
self.canvas.create_line(points, fill='blue')
for j in range(cols + 1):
points = [self.grid_points[i*(cols+1)+j] for i in range(rows+1)]
self.canvas.create_line(points, fill='blue')
for point in self.grid_points:
self.canvas.create_oval(point[0]-3, point[1]-3, point[0]+3, point[1]+3, fill='blue')
def on_click(self, event):
if self.mode == 'create':
self.shape_points.append((event.x, event.y))
elif self.mode == 'move':
if self.point_inside_shape(event.x, event.y):
self.dragging = 'all'
self.last_x, self.last_y = event.x, event.y
elif self.mode == 'edit':
for i, point in enumerate(self.shape_points):
if abs(event.x - point[0]) < 5 and abs(event.y - point[1]) < 5:
self.dragging = i
self.last_x, self.last_y = event.x, event.y
return
elif self.mode == 'grid':
for i, point in enumerate(self.grid_points):
if abs(event.x - point[0]) < 5 and abs(event.y - point[1]) < 5:
self.dragging = i
return
self.display_image()
def on_drag(self, event):
if self.dragging is not None:
if self.mode == 'move':
dx, dy = event.x - self.last_x, event.y - self.last_y
self.shape_points = [(x+dx, y+dy) for x, y in self.shape_points]
self.grid_points = [(x+dx, y+dy) for x, y in self.grid_points]
self.last_x, self.last_y = event.x, event.y
elif self.mode == 'edit':
self.shape_points[self.dragging] = (event.x, event.y)
self.create_grid_points()
elif self.mode == 'grid':
self.update_grid(self.dragging, event.x, event.y)
self.warp_overlay()
self.display_image()
def update_grid(self, index, new_x, new_y):
old_x, old_y = self.grid_points[index]
dx, dy = new_x - old_x, new_y - old_y
self.grid_points[index] = (new_x, new_y)
for i, point in enumerate(self.grid_points):
if i != index:
dist = np.sqrt((point[0] - old_x)**2 + (point[1] - old_y)**2)
max_dist = 200
if dist < max_dist:
influence = (max_dist - dist) / max_dist
self.grid_points = (point[0] + dx * influence, point[1] + dy * influence)
grid_to_shape = {0: 0, 4: 1, -1: 2, -5: 3}
for grid_index, shape_index in grid_to_shape.items():
self.shape_points[shape_index] = (
self.original_shape_points[shape_index][0] + (self.grid_points[grid_index][0] - self.original_grid_points[grid_index][0]),
self.original_shape_points[shape_index][1] + (self.grid_points[grid_index][1] - self.original_grid_points[grid_index][1])
)
def on_release(self, event):
if self.dragging is not None:
self.add_to_history()
self.dragging = None
def point_inside_shape(self, x, y):
path = Path(self.shape_points)
return path.contains_point((x, y))
def reset(self):
self.shape_points = []
self.grid_points = []
self.original_shape_points = []
self.overlay_image = None
self.warped_overlay = None
self.display_image()
def update_opacity(self, value):
self.opacity = float(value)
self.display_image()
def add_to_history(self):
state = {
'shape_points': self.shape_points.copy(),
'grid_points': self.grid_points.copy(),
'original_grid_points': self.original_grid_points.copy(),
'warped_overlay': self.warped_overlay.copy() if self.warped_overlay is not None else None,
'opacity': self.opacity
}
self.history.append(state)
if len(self.history) > 10:
self.history.pop(0)
def undo(self, event=None):
if len(self.history) > 1:
self.history.pop()
previous_state = self.history[-1]
self.shape_points = previous_state['shape_points']
self.grid_points = previous_state['grid_points']
self.original_grid_points = previous_state['original_grid_points']
self.warped_overlay = previous_state['warped_overlay']
self.opacity = previous_state['opacity']
self.opacity_scale.set(self.opacity)
self.display_image()
if __name__ == "__main__":
root = tk.Tk()
app = AdvancedSmartObjectGUI(root)
root.mainloop()
Подробнее здесь: https://stackoverflow.com/questions/786 ... -in-python