Статья Автор: Деникина Наталья Владимировна

Интерактивная обрезка

Интерактивная обрезка

Работа с фиксированными координатами не всегда удобна, особенно когда требуется гибкий и точный выбор области на изображении. В таких случаях необходима интерактивная обрезка, которая позволяет пользователю самостоятельно определять границы выделяемой области.

Для решения этой задачи мы разработаем интерактивный скрипт на основе OpenCV, который позволяет вручную выделять области изображения с помощью мыши и сохранении файла PNG с альфа-каналом.

   → 
 
import cv2
import numpy as np

# Глобальные переменные
points = []
cursor_position = (0, 0)

def mouse_callback(event, x, y, flags, param):
    global points, cursor_position
    cursor_position = (x, y)
    if event == cv2.EVENT_LBUTTONDOWN:
        # Добавление новой точки
        points.append((x, y))
    elif event == cv2.EVENT_RBUTTONDOWN and points:
        # Удаление последней точки
        points.pop()

# Загрузка изображения
img = cv2.imread('image.png')
img_original = img.copy()  # Сохраняем оригинал изображения для обрезки
cv2.imshow('image', img)

# Установка функции обратного вызова
cv2.setMouseCallback('image', mouse_callback)

while True:
    # Создание копии изображения для обновления
    img_copy = img.copy()
    # Вывод координат курсора на изображение
    cursor_text = f"Положение курсора: {cursor_position}"
    cv2.putText(img_copy, cursor_text, (10, 30), cv2.FONT_HERSHEY_COMPLEX, 0.9, (255, 255, 255), 2, cv2.LINE_AA)
    # Отрисовка точек
    for point in points:
        cv2.circle(img_copy, point, 5, (0, 255, 0), -1)
    
    # Если точек больше 3, отрисовываем полигон с прозрачностью
    if len(points) > 2:
        pts = np.array(points, np.int32)
        overlay = img_copy.copy()
        cv2.fillPoly(overlay, [pts], (0, 255, 0))
        alpha = 0.2  # Прозрачность
        img_copy = cv2.addWeighted(overlay, alpha, img_copy, 1 - alpha, 0)
    
    # Отображение изображения с точками и полигоном
    cv2.imshow('image', img_copy)
    
    key = cv2.waitKey(20)
    if key == 27:  # Выход из цикла при нажатии клавиши 'Esc'
        break
    elif key == 32 and len(points) > 2:  # Сохранение обрезанного изображения при нажатии пробела
        # Создание маски для полигона
        mask = np.zeros_like(img_original, dtype=np.uint8)
        cv2.fillPoly(mask, [pts], (255, 255, 255, 255))
        
        # Применение маски к изображению и сохранение
        cropped_image = cv2.bitwise_and(img_original, mask)
        
        # Убираем черный фон, делая его прозрачным
        b, g, r = cv2.split(cropped_image)
        alpha_channel = np.where((b + g + r) > 0, 255, 0).astype(np.uint8)
        cropped_image = cv2.merge((b, g, r, alpha_channel))
        
        cv2.imwrite('cropped_image.png', cropped_image)
        print("Обрезанное изображение сохранено как 'cropped_image.png'")

cv2.destroyAllWindows()

Рассмотрим основные функциональные части кода, чтобы разобраться, как работает этот скрипт:

Обработка событий мыши:

  • С помощью функции cv2.setMouseCallback устанавливается обработчик событий мыши. При нажатии левой кнопки мыши добавляется новая точка в список, которая будет вершиной полигона.
  • Если пользователь нажимает правую кнопку мыши, последняя добавленная точка удаляется. Это позволяет корректировать выбранный полигон до завершения процесса обрезки.
def mouse_callback(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDOWN:
        points.append((x, y))
    elif event == cv2.EVENT_RBUTTONDOWN and points:
        points.pop()

Отображение точек и полигона:

  • В цикле while изображение обновляется в режиме реального времени, отображая текущие точки полигона. Если точек больше трёх, формируется и отображается полигон с полупрозрачным фоном.
for point in points:
    cv2.circle(img_copy, point, 5, (0, 255, 0), -1)

if len(points) > 2:
    pts = np.array(points, np.int32)
    overlay = img_copy.copy()
    cv2.fillPoly(overlay, [pts], (0, 255, 0))
    img_copy = cv2.addWeighted(overlay, 0.2, img_copy, 0.8, 0)

Сохранение вырезанной области:

  • Когда пользователь завершает выбор полигона (количество точек больше трёх) и нажимает клавишу "пробел", программа создает маску на основе выбранного полигона и применяет её к изображению.
  • Вырезанная область сохраняется в формате PNG с альфа-каналом, что позволяет сохранить прозрачный фон для областей, находящихся вне полигона.
if key == 32 and len(points) > 2:
    mask = np.zeros_like(img_original, dtype=np.uint8)
    cv2.fillPoly(mask, [pts], (255, 255, 255, 255))
    cropped_image = cv2.bitwise_and(img_original, mask)
    
    b, g, r = cv2.split(cropped_image)
    alpha_channel = np.where((b + g + r) > 0, 255, 0).astype(np.uint8)
    cropped_image = cv2.merge((b, g, r, alpha_channel))
   
    cv2.imwrite('cropped_image.png', cropped_image)

Давайте теперь модифицируем скрипт, чтобы он поддерживал рисование полигона при удержании и перемещении мыши. Так как точек теперь будет много, по нажатию правой кнопки мыши, будем просто очищать список точек!

  → 

 

Для такого изменения скрипта достаточно внести изменения в функцию mouse_callback() и добавить глобальную переменную drawing

points = []
drawing = False
cursor_position = (0, 0)

def mouse_callback(event, x, y, flags, param):
    global points, drawing, cursor_position
    cursor_position = (x, y)
    if event == cv2.EVENT_LBUTTONDOWN:
        # Начало рисования полигона
        drawing = True
        points = [(x, y)]  # Начинаем новый полигон
    elif event == cv2.EVENT_MOUSEMOVE and drawing:
        # Добавляем точки при движении мыши, если зажата левая кнопка
        points.append((x, y))
    elif event == cv2.EVENT_LBUTTONUP:
        # Завершение рисования полигона
        drawing = False
    elif event == cv2.EVENT_RBUTTONDOWN:
        # Очистка полигона по нажатию правой кнопки мыши
        points = []
Пропустить Навигационные Ссылки.
Чтобы оставить комментарий нужна авторизация
Печать