Tempel gambar ke gambar lain pada dua koordinat yang diberikan dengan opasitas yang diubah menggunakan PIL atau OpenCV dengan Python

Dec 10 2020

Saya memiliki dua gambar dengan titik yang diberikan, satu titik untuk setiap gambar, yang perlu disejajarkan agar gambar hasil merupakan penjumlahan dari kedua gambar, sedangkan gambar 2 ditempelkan pada gambar 1 dengan opasitas 40%. Saya telah mempertimbangkan pertanyaan ini tetapi kasus kami tidak sama persis karena koordinat gambar disediakan oleh pengguna dan gambar dapat memiliki berbagai ukuran.

Gambar 1:

Image2:

Hasil akhir (keluaran yang diinginkan):

Untuk ini saya telah mencoba img.paste()fungsi PIL dan mengganti nilai dalam array numpy gambar di cv2, keduanya memberikan hasil yang jauh dari yang diinginkan.

Jawaban

1 MarkSetchell Dec 11 2020 at 19:07

Saya membuat dua gambar masukan dengan ImageMagick seperti ini:

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

Kemudian jalankan kode berikut:

#!/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')

Hasilnya begini:

Garis-garis yang menyimpan gambar dengan nama dimulai "DEBUG-xxx.png"hanya untuk debugging yang mudah dan dapat dihapus. Saya dapat dengan mudah melihat semuanya untuk melihat apa yang terjadi dengan kode dan saya dapat dengan mudah menghapus semuanya dengan menghapus "DEBUG*png".

Thymen Dec 11 2020 at 05:04

Tanpa rincian lebih lanjut, saya akan mencoba menjawab pertanyaan itu sebaik mungkin dan akan menyebutkan semua asumsi tambahan yang saya buat (dan bagaimana menanganinya jika Anda tidak dapat membuatnya).

Karena tidak ada gambar yang disediakan, saya membuat gambar biru dan hijau dengan titik hitam sebagai koordinat penggabungan, menggunakan kode berikut:

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)

Ini menghasilkan gambar berikut:

Sekarang saya akan membuat asumsi bahwa gambar belum berisi lapisan alfa (seperti yang saya buat tanpa lapisan alfa). Oleh karena itu saya akan memuat gambar dan menambahkan lapisan alfa ke dalamnya:

import numpy as np

from PIL import Image

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

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

Asumsi saya berikut ini adalah Anda mengetahui koordinat penggabungan sebelumnya:

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

Kemudian Anda dapat membuat gambar kosong, yang dapat menampung kedua gambar dengan mudah:

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

Ini adalah asumsi yang jauh lebih luas jika Anda tidak mengetahui ukuran gambar sebelumnya, dan jika Anda tidak mengetahuinya, Anda harus menghitung ukuran gabungan dari dua gambar.

Kemudian Anda dapat menempatkan titik gambar di tengah gambar yang baru dibuat (dalam kasus saya (500, 500). Untuk ini, Anda menggunakan titik penggabungan sebagai offset. Dan Anda dapat melakukan pencampuran alfa (dalam hal apa pun :) np.uint8(img_1*alpha + img_2*(1-alpha))) untuk menggabungkan gambar menggunakan opasitas berbeda.

Yang ada di kode:

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')

Hasil akhirnya akan menjadi gambar yang lebih besar, dari gambar gabungan, sekali lagi Anda dapat menghitung kotak pembatas yang benar, sehingga Anda tidak memiliki area besar 'tidak ada' yang menonjol. Hasil akhirnya akan terlihat seperti ini: