Wklej obraz do innego obrazu przy dwóch podanych współrzędnych ze zmienionym kryciem za pomocą PIL lub OpenCV w Pythonie

Dec 10 2020

Mam dwa obrazy z określonymi punktami, po jednym punkcie na każdym obrazie, które należy wyrównać, aby obraz wynikowy był sumą obu obrazów, podczas gdy obraz 2 jest wklejany na obrazie 1 z kryciem 40%. Brałem to pytanie pod uwagę, ale w naszym przypadku nie odpowiada dokładnie jak obraz koordynować jest dostarczane przez użytkownika i obrazów może mieć szeroki zakres rozmiarów.

Obraz 1:

Obraz2:

Wynik końcowy (pożądana wydajność):

W tym celu wypróbowałem img.paste()funkcję PIL i zamianę wartości w numpy tablicy obrazów w cv2, oba dające wyniki, które są dalekie od pożądanych.

Odpowiedzi

1 MarkSetchell Dec 11 2020 at 19:07

Zrobiłem dwa obrazy wejściowe w ImageMagick w ten sposób:

magick -size 300x400 xc:"rgb(1,204,255)" -fill red -draw "point 280,250" 1.png
magick -size 250x80  xc:"rgb(150,203,0)" -fill red -draw "point 12,25"   2.png

Następnie uruchomiono następujący kod:

#!/usr/bin/env python3
"""
Paste one image on top of another such that given points in each are coincident.
"""

from PIL import Image

# Open images and ensure RGB
im1 = Image.open('1.png').convert('RGB')
im2 = Image.open('2.png').convert('RGB')

# x,y coordinates of point in each image
p1x, p1y = 280, 250
p2x, p2y = 12, 25

# Work out how many pixels of space we need left, right, above, below common point in new image
pL = max(p1x, p2x)
pR = max(im1.width-p1x,  im2.width-p2x)
pT = max(p1y, p2y)
pB = max(im1.height-p1y, im2.height-p2y)

# Create background in solid white
bg = Image.new('RGB', (pL+pR, pT+pB),'white')
bg.save('DEBUG-bg.png')

# Paste im1 onto background
bg.paste(im1, (pL-p1x, pT-p1y))
bg.save('DEBUG-bg+im1.png')

# Make 40% opacity mask for im2
alpha = Image.new('L', (im2.width,im2.height), int(40*255/100))
alpha.save('DEBUG-alpha.png')

# Paste im2 over background with alpha
bg.paste(im2, (pL-p2x, pT-p2y), alpha)
bg.save('result.png')

Wynik jest taki:

Wiersze, które zapisują obrazy z nazwami zaczynającymi się, "DEBUG-xxx.png"służą tylko do łatwego debugowania i można je usunąć. Mogę łatwo wyświetlić je wszystkie, aby zobaczyć, co się dzieje z kodem, i mogę je łatwo usunąć, usuwając "DEBUG*png".

Thymen Dec 11 2020 at 05:04

Bez dalszych szczegółów postaram się odpowiedzieć na to pytanie najlepiej, jak potrafię i wymienię wszystkie dodatkowe założenia, które poczyniłem (i jak sobie z nimi poradzić, jeśli nie możesz ich zrobić).

Ponieważ nie było dostarczonych obrazów, utworzyłem niebiesko-zielony obraz z czarną kropką jako współrzędną scalającą, używając następującego kodu:

import numpy as np

from PIL import Image, ImageDraw


def create_image_with_point(name, color, x, y, width=3):
    image = np.full((400, 400, 3), color, dtype=np.uint8)
    image[y - width:y + width, x - width:x + width] = (0, 0, 0)
    image = Image.fromarray(image, mode='RGB')
    ImageDraw.Draw(image).text((x - 15, y - 20), 'Point', (0, 0, 0))
    image.save(name)
    return image


blue = create_image_with_point('blue.png', color=(50, 50, 255), x=300, y=100)
green = create_image_with_point('green.png', color=(50, 255, 50), x=50, y=50)

W rezultacie powstają następujące obrazy:

Teraz założę, że obrazy nie zawierają jeszcze warstwy alfa (tak jak je stworzyłem bez). Dlatego załaduję obraz i dodam do nich warstwę alfa:

import numpy as np

from PIL import Image

blue = Image.open('blue.png')
blue.putalpha(255)

green = Image.open('green.png')
green.putalpha(255)

Moje założenie jest takie, że znasz wcześniej współrzędne scalania:

# Assuming x, y coordinates.
point_blue = (300, 100)
point_green = (50, 50)

Następnie możesz utworzyć pusty obraz, który z łatwością pomieści oba obrazy:

new_image = np.zeros((1000, 1000, 4), dtype=np.uint8)

Jest to bardzo rozciągliwe założenie, jeśli wcześniej nie znasz rozmiaru obrazu, a jeśli tego nie wiesz, będziesz musiał obliczyć łączny rozmiar dwóch obrazów.

Następnie możesz umieścić kropkę obrazów w środku nowo utworzonych obrazów (w moim przypadku (500, 500). W tym celu używasz punktów łączenia jako przesunięć. I możesz wykonać mieszanie alfa (w każdym przypadku np.uint8(img_1*alpha + img_2*(1-alpha)):), aby scalić obrazy z różnym kryciem.

Który jest w kodzie:

def place_image(image: Image, point_xy: tuple[int, int], dest: np.ndarray, alpha: float = 1.) -> np.ndarray:

    # Place the merging dot on (500, 500).
    offset_x, offset_y = 500 - point_xy[0], 500 - point_xy[1]

    # Calculate the location of the image and perform alpha blending.
    destination = dest[offset_y:offset_y + image.height, offset_x:offset_x + image.width]
    destination = np.uint8(destination * (1 - alpha) + np.array(image) * alpha)

    # Copy the 'merged' imaged to the destination location.
    dest[offset_y:offset_y + image.height, offset_x:offset_x + image.width] = destination
    return dest

# Add the background image blue with alpha 1
new_image = place_image(blue, point_blue, dest=new_image, alpha=1)

# Add the second image with 40% opacity
new_image = place_image(green, point_green, dest=new_image, alpha=0.4)

# Store the resulting image.
image = Image.fromarray(new_image)
image.save('result.png')

Ostatecznym rezultatem będzie większy obraz połączonych obrazów, ponownie możesz obliczyć poprawną ramkę ograniczającą, aby nie wystawały ogromne obszary „niczego”. Ostateczny wynik będzie wyglądał następująco: