Скачиваний:
0
Добавлен:
06.10.2025
Размер:
16.59 Кб
Скачать
import cv2
import numpy as np
from tkinter import filedialog, Tk, Button, Label, Frame, Listbox, Scrollbar, Menu, messagebox, Canvas, NW, HORIZONTAL, VERTICAL, Scale, StringVar
from tkinter import ttk
from PIL import Image, ImageTk
import os

# Глобальные переменные
loaded_images = []
image_paths = []
processed_image = None
thumbnail_labels = []
brightness_threshold = 200  # Порог яркости
pixel_brightness_var = None  # Переменная для хранения яркости пикселя
click_points = []  # Список для хранения координат кликов
select_mode = False  # Флаг для режима выбора области
current_poly_id = None  # Идентификатор текущего полигона
display_text_mode = True  # Флаг для режима отображения текста (по умолчанию включён)
polygon_counter = 1  # Счётчик для нумерации полигонов
polygon_info_labels = []  # Список для хранения текстовых меток о полигонах (отображаемых вне изображения)

# Функция для загрузки изображений
def load_images():
    global loaded_images, image_paths, thumbnail_labels
    file_paths = filedialog.askopenfilenames(title="Выберите изображения", 
                                             filetypes=[("Image files", "*.png;*.jpg;*.jpeg")])
    if file_paths:
        for file in file_paths:
            image_paths.append(file)
            img = cv2.imread(file)
            loaded_images.append(img)
            # Отображение миниатюр
            display_thumbnail(img, os.path.basename(file))

# Функция для удаления выбранного изображения
def delete_image():
    global thumbnail_labels
    selected = images_listbox.curselection()
    if selected:
        index = selected[0]
        images_listbox.delete(index)
        del loaded_images[index]
        del image_paths[index]
        # Удаляем оба элемента кортежа: label и text_label
        thumbnail_labels[index][0].destroy()  # Удаление label
        thumbnail_labels[index][1].destroy()  # Удаление text_label
        del thumbnail_labels[index]

# Функция для отображения миниатюры изображения в Tkinter
def display_thumbnail(image, name):
    thumbnail = cv2.resize(image, (int(image.shape[1] // 4), int(image.shape[0] // 4)))
    img_rgb = cv2.cvtColor(thumbnail, cv2.COLOR_BGR2RGB)
    img_pil = Image.fromarray(img_rgb)
    img_tk = ImageTk.PhotoImage(img_pil)

    label = Label(thumbnail_frame, image=img_tk)
    label.image = img_tk
    label.pack(side="top", padx=5, pady=5)

    text_label = Label(thumbnail_frame, text=name)
    text_label.pack(side="top", padx=5)

    thumbnail_labels.append((label, text_label))
    images_listbox.insert('end', name)

# Функция для удаления пикселей, которые ярче порога
def remove_bright_pixels(image, threshold):
    gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    mask = gray_image > threshold  # Пиксели, яркость которых выше порога
    result = image.copy()
    result[mask] = [0, 0, 0]  # Убираем (делаем черными) все яркие пиксели
    return result

# Функция для усреднения пикселей внутри произвольного полигона
def smooth_polygon(image, points):
    mask = np.zeros(image.shape[:2], dtype=np.uint8)
    points_array = np.array(points, dtype=np.int32)
    cv2.fillPoly(mask, [points_array], 1)
    
    # Извлекаем только пиксели внутри полигона
    roi = cv2.bitwise_and(image, image, mask=mask)
    avg_color = cv2.mean(roi, mask=mask)[:3]
    
    # Заполняем полигон усреднённым цветом
    image[mask == 1] = avg_color
    return image, avg_color

# Функция для отображения яркости на полигоне
def display_polygon_info(brightness):
    global polygon_counter, polygon_info_labels

    brightness_info = f"Полигон {polygon_counter}: Яркость {int(brightness)}"
    label = Label(polygon_info_frame, text=brightness_info, fg="green", font=("Helvetica", 12))
    label.pack(side="top", anchor="w", padx=5, pady=2)
    
    polygon_info_labels.append(label)
    polygon_counter += 1

# Функция для обработки изображений
def process_images():
    global processed_image
    if not loaded_images:
        messagebox.showwarning("Ошибка", "Не загружено ни одного изображения для обработки")
        return

    # Приведение всех изображений к размеру первого
    base_size = loaded_images[0].shape[:2]  # (height, width)
    resized_images = [cv2.resize(img, (base_size[1], base_size[0])) for img in loaded_images]

    # Усреднение изображений и нахождение объекта
    accumulated_image = np.zeros_like(resized_images[0], dtype=np.float32)

    for img in resized_images:
        processed_img = remove_bright_pixels(img, brightness_threshold)
        accumulated_image += processed_img.astype(np.float32)

    averaged_image = accumulated_image / len(resized_images)
    processed_image = averaged_image.astype(np.uint8)

    # Отображение усреднённого изображения в правом окне
    display_result(processed_image)

# Функция для включения режима выбора области
def enable_select_mode():
    global select_mode
    select_mode = True
    click_points.clear()
    messagebox.showinfo("Информация", "Режим выбора включен. Кликните 4 раза, чтобы задать область.")

# Функция для усреднения выбранной области (полигона)
def smooth_selected_area():
    global processed_image, click_points, select_mode, current_poly_id
    if processed_image is None:
        messagebox.showwarning("Ошибка", "Нет изображения для усреднения")
        return

    if len(click_points) < 4:
        messagebox.showwarning("Ошибка", "Выберите 4 точки, чтобы задать полигон")
        return

    smoothed_image, avg_color = smooth_polygon(processed_image, click_points)
    display_result(smoothed_image)
    processed_image = smoothed_image

    if display_text_mode:
        avg_brightness = 0.299 * avg_color[2] + 0.587 * avg_color[1] + 0.114 * avg_color[0]  # Рассчитать яркость по формуле
        display_polygon_info(avg_brightness)

    click_points.clear()

    # Сбрасываем режим выбора
    select_mode = False
    current_poly_id = None

# Функция для отображения обработанного изображения с возможностью зума
def display_result(image):
    img_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    img_pil = Image.fromarray(img_rgb)
    img_tk = ImageTk.PhotoImage(img_pil)

    processed_image_canvas.create_image(0, 0, anchor=NW, image=img_tk)
    processed_image_canvas.image = img_tk
    processed_image_canvas.config(scrollregion=processed_image_canvas.bbox("all"))

    # Добавляем обработчик движения мыши для получения яркости пикселя
    processed_image_canvas.bind('<Motion>', lambda event: update_pixel_brightness(event, img_rgb))
    # Добавляем обработчик кликов для выбора точек
    processed_image_canvas.bind('<Button-1>', lambda event: select_points(event, img_rgb))

# Функция для выбора точек на изображении
def select_points(event, img_rgb):
    global click_points, select_mode, current_poly_id

    if not select_mode:
        return

    canvas_x = int(processed_image_canvas.canvasx(event.x))
    canvas_y = int(processed_image_canvas.canvasy(event.y))

    # Проверяем, находится ли курсор внутри изображения
    if 0 <= canvas_x < img_rgb.shape[1] and 0 <= canvas_y < img_rgb.shape[0]:
        click_points.append((canvas_x, canvas_y))

        # Обновляем отображение текущего полигона
        if len(click_points) >= 2:
            if current_poly_id is not None:
                processed_image_canvas.delete(current_poly_id)
            points = click_points + [(canvas_x, canvas_y)]
            current_poly_id = processed_image_canvas.create_polygon(points, outline="red", width=2, fill="")

        # Если выбраны 4 точки, фиксируем полигон
        if len(click_points) == 4:
            current_poly_id = processed_image_canvas.create_polygon(click_points, outline="green", width=2, fill="")

    else:
        messagebox.showwarning("Ошибка", "Клик за пределами изображения.")

# Функция для обновления значения яркости пикселя
def update_pixel_brightness(event, img_rgb):
    canvas_x = processed_image_canvas.canvasx(event.x)
    canvas_y = processed_image_canvas.canvasy(event.y)

    # Проверяем, находится ли курсор внутри изображения
    if 0 <= canvas_x < img_rgb.shape[1] and 0 <= canvas_y < img_rgb.shape[0]:
        r, g, b = img_rgb[int(canvas_y), int(canvas_x)]
        brightness = int(0.299 * r + 0.587 * g + 0.114 * b)  # Рассчитываем яркость по формуле
        pixel_brightness_var.set(f"Яркость пикселя: {brightness}")
    else:
        pixel_brightness_var.set("Яркость пикселя: -")

# Функция для сохранения результата
def save_result():
    global processed_image
    if processed_image is None:
        messagebox.showwarning("Ошибка", "Нет обработанного изображения для сохранения")
        return
    
    save_path = filedialog.asksaveasfilename(defaultextension=".png", 
                                             filetypes=[("PNG files", "*.png"), ("JPEG files", "*.jpg")])
    if save_path:
        cv2.imwrite(save_path, processed_image)
        messagebox.showinfo("Успех", f"Изображение сохранено как {save_path}")

# Функция для очистки проекта
def new_project():
    global loaded_images, image_paths, processed_image, thumbnail_labels, click_points, current_poly_id, select_mode, polygon_info_labels
    loaded_images.clear()
    image_paths.clear()
    thumbnail_labels.clear()
    click_points.clear()
    images_listbox.delete(0, 'end')
    processed_image_canvas.delete("all")
    current_poly_id = None
    select_mode = False
    # Удаляем все метки о полигонах
    for label in polygon_info_labels:
        label.destroy()
    polygon_info_labels.clear()

# Функция для обновления порога яркости из слайдера
def update_brightness_threshold(value):
    global brightness_threshold
    brightness_threshold = int(value)

# Создание основного окна приложения
root = Tk()
root.title("Image Processor")

# Инициализация переменной яркости пикселя после создания окна
pixel_brightness_var = StringVar()  # Переменная для хранения яркости пикселя

# Создание меню
menu = Menu(root)
root.config(menu=menu)

# Меню "Файл"
file_menu = Menu(menu, tearoff=0)
menu.add_cascade(label="Файл", menu=file_menu)
file_menu.add_command(label="Открыть изображения", command=load_images)
file_menu.add_command(label="Сохранить результат", command=save_result)
file_menu.add_separator()
file_menu.add_command(label="Новый проект", command=new_project)

# Создание панелей для динамического изменения размеров
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.grid_columnconfigure(1, weight=3)

# Фрейм для списка изображений и кнопок управления
left_frame = Frame(root)
left_frame.grid(row=0, column=0, sticky="nswe", padx=10, pady=10)

# Лейбл для списка изображений
images_listbox = Listbox(left_frame, selectmode="single", height=7, width=40)
images_listbox.pack(side="top", padx=10, pady=10, fill="both", expand=True)

# Кнопки управления изображениями
button_frame = Frame(left_frame)
button_frame.pack(side="bottom", pady=5)

add_button = Button(button_frame, text="Добавить изображения", command=load_images)
add_button.pack(side="left", padx=5)

delete_button = Button(button_frame, text="Удалить изображение", command=delete_image)
delete_button.pack(side="left", padx=5)

# Слайдер для настройки порога яркости
brightness_slider_label = Label(left_frame, text="Порог яркости")
brightness_slider_label.pack(side="top", padx=5, pady=5)
brightness_slider = Scale(left_frame, from_=0, to=255, orient=HORIZONTAL, command=update_brightness_threshold)
brightness_slider.set(brightness_threshold)  # Начальное значение
brightness_slider.pack(side="top", padx=5, pady=5)

# Кнопка для включения режима выбора области
select_button = Button(left_frame, text="Включить выбор области", command=enable_select_mode)
select_button.pack(side="bottom", padx=10, pady=10)

# Кнопка для усреднения выбранных областей (полигонов)
smooth_button = Button(left_frame, text="Усреднить выбранную область", command=smooth_selected_area)
smooth_button.pack(side="bottom", padx=10, pady=10)

# Кнопка для начала обработки изображений
start_button = Button(left_frame, text="Начать обработку", command=process_images)
start_button.pack(side="bottom", padx=10, pady=10)

# Поле для отображения яркости пикселя
pixel_brightness_label = Label(left_frame, textvariable=pixel_brightness_var)
pixel_brightness_label.pack(side="bottom", padx=5, pady=5)

# Фрейм для отображения обработанного изображения с возможностью прокрутки и зума
right_frame = Frame(root)
right_frame.grid(row=0, column=1, rowspan=2, sticky="nswe", padx=10, pady=10)

# Canvas для отображения обработанного изображения
processed_image_canvas = Canvas(right_frame, bg="white")
processed_image_canvas.pack(side="left", padx=10, pady=10, fill="both", expand=True)

# Скроллбары для Canvas
scroll_x = Scrollbar(right_frame, orient=HORIZONTAL, command=processed_image_canvas.xview)
scroll_x.pack(side="bottom", fill="x")
scroll_y = Scrollbar(right_frame, orient=VERTICAL, command=processed_image_canvas.yview)
scroll_y.pack(side="right", fill="y")

processed_image_canvas.config(xscrollcommand=scroll_x.set, yscrollcommand=scroll_y.set)

# Фрейм для миниатюр изображений (расположены горизонтально)
thumbnail_frame = Frame(left_frame)
thumbnail_frame.pack(side="top", fill="x", padx=5, pady=5)
thumbnail_canvas = Canvas(thumbnail_frame, bg="white", height=100)
thumbnail_canvas.pack(side="top", fill="x", expand=True)

# Фрейм для отображения информации о полигонах (яркость)
polygon_info_frame = Frame(left_frame)
polygon_info_frame.pack(side="bottom", fill="both", expand=True)

# Запуск приложения
root.mainloop()
Соседние файлы в папке лаб 6