Интерактивная обрезка
Работа с фиксированными координатами не всегда удобна, особенно когда требуется гибкий и точный выбор области на изображении. В таких случаях необходима интерактивная обрезка, которая позволяет пользователю самостоятельно определять границы выделяемой области.
Для решения этой задачи мы разработаем интерактивный скрипт на основе 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 = []