# -*- coding: utf-8 -*-
import sys
import numpy # pip install numpy
import cv2 # pip install opencv-python
def loadImg(fname : str) -> numpy.ndarray: # грузит файл
data = numpy.fromfile(fname, dtype=numpy.uint8)
img = cv2.imdecode(data, cv2.IMREAD_COLOR)
if img is None:
raise IOError("Not an image file")
return img
class Clicker: # класс для выбора точек на экране
def __init__(self, name: str, image: numpy.ndarray):
self.wnd = name
self.image = image
self.clicks = []
self.markersize = 5
self.markercolor = (255,0,255)
cv2.namedWindow(self.wnd, cv2.WINDOW_AUTOSIZE)
cv2.setMouseCallback(self.wnd, self._click)
def draw(self): # рисует точки на изображении и выводит их на экран
copy = self.image.copy()
color = self.markercolor
radius = self.markersize
for x,y in self.clicks:
cv2.circle(copy, (x,y), radius, color, 1)
cv2.line(copy, (x-radius,y), (x+radius,y), color, 1)
cv2.line(copy, (x,y-radius), (x,y+radius), color, 1)
cv2.imshow(self.wnd, copy)
def _click(self, event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDOWN: # левый клик - поставить точку
self.clicks.append((x,y))
elif event == cv2.EVENT_RBUTTONDOWN: # правый клик - сбросить последнюю точку
if self.clicks:
del self.clicks[-1]
else:
return
self.draw()
def close(self):
cv2.destroyWindow(self.wnd)
def __enter__(self):
self.draw()
return self
def __exit__(self, exctype, excvalue, traceback):
self.close()
try:
image = loadImg('times-square.jpg') # изображение, внутрь которого вписываем другое
poster = loadImg('lena.png') # изображение, которое вписываем в первое
except IOError:
print('Ошибка загрузки файла.')
sys.exit(1)
# эта часть только для ручного ввода координат
# если они уже есть, то это не нужно.
with Clicker('Select area', image) as clicker:
# четыре точки ставятся строго по часовой, начиная слева-сверху
while len(clicker.clicks) < 4: # пока не получили четыре точки - угла
if cv2.waitKey(100) == 27:
print('Отменено')
sys.exit(0)
pts = numpy.array(clicker.clicks, dtype=numpy.float32) # координаты углов тут
# вписываем изображение
height, width = poster.shape[:2]
srcpoints = numpy.array([ # углы вставляемого изображения в том же порядке по часовой
(0,0),
(width-1, 0),
(width-1, height-1),
(0, height-1),
], dtype=numpy.float32)
# матрица преобразования сопоставляет четыре точки второго изображения с точками первого
# по сути, она позволяет перейти от второго изображения к первому
matrix = cv2.getPerspectiveTransform(srcpoints, pts) # порядок аргументов важен, иначе переход будет наоборот
# применяем матрицу ко второму изображению. Но теперь надо убрать чёрные поля.
warped = cv2.warpPerspective(poster, matrix, (image.shape[1], image.shape[0]))
# делаем маску для переноса пикселей с warped на image
# мы хотим перенести только пиксели, на которые пришлись пиксели второго изображения
mask = numpy.zeros(image.shape, dtype=numpy.uint8) # рисовать можно только на обычном изображении
# закрашиваем пиксели внутри выбранного ранее четырёхугольника
cv2.fillPoly(mask, pts.reshape(1, -1, 2).astype(numpy.int32), (1,1,1))
mask.dtype = bool # а для переноса нам нужна логическая маска
# маска готова, переносим. numpy рулит, правда ведь?
image[mask] = warped[mask]
# показываем результат
cv2.imshow('Result', image)
cv2.waitKey()