• Как произвольно трансформировать изображение в Python?

    Vindicar
    @Vindicar
    RTFM!
    Скажи спасибо, что я делал такую лабораторную работу X)
    Код
    # -*- 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()


    Если коротко: находишь точки, которым надо сопоставить углы "вставыша". Перечисляешь их в том же порядке, что и эти углы. Находишь матрицу перспективного преобразования. Применяешь матрицу к вставышу, получаешь чёрное изображение, на котором вставыш расположен в нужном месте. Переносишь пиксели с этого изображения на картинку с экраном.
    Ответ написан
    3 комментария