PIL не обнаруживает идентичные изображения

Мне нужно определить, будет ли слой изображения (mainLayer) видимым в конечном изображении, в котором он будет находиться под слоями, слоями 2 и слоями 3. Мне нужно сделать это, не зная, что это за другие слои. Все эти слои изображения являются RGBA; они также имеют одинаковые размеры (2048x2048) и формат файла (.png).

Вот код, который у меня есть до сих пор:

# Create compImage1, which is all of the layers that go on top of mainLayer
compImage1 = layer.copy()
compImage1.paste(layer2, (0, 0), layer2)
compImage1.paste(layer3, (0, 0), layer3)

# Create compImage2, which is mainLayer with all of the other layers added
compImage2 = mainLayer.copy() # I want to determine this layer's visibility in the final image
compImage1Copy = compImage1.copy()
compImage2.paste(compImage1Copy, (0, 0), compImage1Copy)

# Compare compImage1 and compImage2; if layer, layer2, and/or layer 3 completely obscure
# mainLayer, then there should be no difference between the two images    
difference = ImageChops.difference(
    compImage1.convert("RGB"), compImage2.convert("RGB")
).getbbox()

if not difference:
    print("mainLayer will not be visible")
else:
    print("mainLayer will be visible")

Этот код печатает «mainLayer будет виден», когда mainLayer не перекрывается Layer, Layer2 и/или Layer3. Однако он по-прежнему печатает «mainLayer будет виден», когда mainLayer покрыт одним из этих слоев.

Почему этот код работает неправильно? Есть ли лучший/быстрый способ сделать это? Спасибо за любую помощь, которую вы можете предоставить.

Обновлено: Меня попросили уточнить, о чем именно я спрашиваю:

У меня есть 4 слоя изображения: mainLayer, layer, layer2 и layer3. Я соединяю их в один образ. Я не знаю положения или размеров какой-либо из видимых частей этих слоев; все они являются слоями изображения 2048x2048, и большая часть каждого слоя изображения прозрачна.

Мне нужно знать, будет ли mainLayer полностью покрыт какой-либо комбинацией слоев layer, layer2 и layer3 (случай 2) или нет (случай 1). Мой код работает для случая 1, но не для случая 2. Вот диаграмма, изображающая случаи 1 и 2.

Image.getbbox всегда будет возвращать 4-кортеж, если изображение не является полностью пустым и не имеет размеров, и в этом случае оно вернет None. Четверка всегда истинна, поэтому условие not difference никогда не будет истинным. Как вы ожидали, что difference будет выглядеть?
Paul M. 07.04.2022 22:28

Вы не показываете изображения. И ваш код неполный и не может быть запущен. И ваш вопрос не очень прост для понимания. Можете ли вы нажать редактировать, пожалуйста, и попытаться улучшить его, чтобы люди могли помочь вам лучше? Вас как будто интересует, будет ли что-то видно как-то через 2 слоя чего-то еще, пока не уточненного, может быть? Спасибо.

Mark Setchell 07.04.2022 22:33

@ПолМ. Я ожидал, что он вернет None, если ImageChops.difference() вернет Image, где значение каждого пикселя равно 0. Глядя на документацию, я понимаю, что неправильно понял, что именно делает Image.getbbox. Я только что попытался преобразовать Image, возвращаемый ImageChops.difference(), в пустой массив, а затем перебрать его, чтобы найти любые ненулевые значения. Однако и это не сработало.

Andrew 07.04.2022 23:02

@MarkSetchell Мне не разрешено делиться какими-либо изображениями, которые я использую; Я также не могу показать остальную часть кода. Однако я отредактировал свой пост и попытался уточнить свой вопрос. Спасибо вам за помощь.

Andrew 07.04.2022 23:56
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
4
58
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Это немного глупо, но вы можете создать функцию, которая по заданному изображению возвращает все пиксельные координаты (в виде xy-кортежей), которые являются непрозрачными (непрозрачными) как набор. Затем переберите все комбинации (набор мощности?) слоев, объедините текущую комбинацию слоев в одно изображение и возьмите разницу между набором непрозрачных пиксельных координат в основном слое и набором непрозрачных пиксельных координат в текущем объединенном изображение слоя. Если какая-либо из комбинаций слоев полностью закрывает основной слой, мы преждевременно завершаем цикл. Псевдокод:

def get_opaque_coords(image):
    # Return all pixel coords that are not completely transparent
    return set((pixel.x, pixel.y) for pixel in image.get_pixels() if pixel.color.alpha != 0)


main_layer = Image.open(...)

layers = [
    Image.open(...),
    Image.open(...),
    Image.open(...)
]

def powerset(iterable):
    "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
    from itertools import chain, combinations
    s = list(iterable)
    return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))

for selected_layers in powerset(layers):
    merged_image = merge_into_single_image(*selected_layers)
    if not(get_opaque_coords(main_layer) - get_opaque_coords(merged_image)):
        print("The following selected layers entirely obscure the main layer!")
        print(selected_layers)
        break
else:
    print("None of the layer combinations entirely obscure the main layer.")
Ответ принят как подходящий

Мое первое наблюдение заключается в том, что решение зависит исключительно от альфа-каналов изображений — каналы RGB не имеют значения.

Мое второе наблюдение заключается в том, что решение зависит только от ограничивающей рамки альфа-каналов на базовом изображении — все, что находится за пределами этого, не имеет значения.

Имея это в виду, я начну так, отбросив каналы RGB и обрезав ограничивающую рамку базового изображения, чтобы сэкономить время и оперативную память:

#!/usr/bin/env python3

import numpy as np
from PIL import Image

# Load base image, extract alpha channel and bounding box and crop to it
base = Image.open('base.png').getchannel('A')
bbox = base.getbbox()
print(f'Base image bbox: {bbox}')
base = base.crop(bbox)
base.save('DEBUG-base.png')

# Load overlying layers, extracting alpha channel, cropping and making into Numpy arrays
layers = []
for layer in [0,1,2]:
   filename = f'layer{layer}.png'
   print(f'Loading {filename}')
   im = Image.open(filename).getchannel('A').crop(bbox)
   im.save(f'DEBUG-{filename}')
   layers.append(np.array(im))

Обратите внимание, что вы можете закомментировать любую строку, содержащую слово DEBUG, если хотите измерить время или понять более простой код.

Вы также можете очистить после запуска очень просто, выполнив:

rm DEBUG*.png

Обратите внимание: поскольку я сформировал изображения DEBUG, умножив логическое изображение True/False на 255, истинные области будут отображаться белыми, а ложные области — черными.

Следующим шагом является создание логических масок различных условий, чтобы мы могли легко думать только об одном аспекте проблемы за раз и комбинировать маски просто в конце. Операции с маской очень быстры в Numpy.

# Make PIL Image of base layer into Numpy array
base = np.array(base)

# So we now have base and all layers, just alpha channels, cropped to bounding box as Numpy arrays
# We want to find areas where:
# a) base is non-transparent - we call this "maskA" and store as Numpy array
# b) any layer fully opaque - we call this "maskB" and store as Numpy array
maskA = base > 0
Image.fromarray((maskA*255).astype(np.uint8)).save('DEBUG-maskA.png')
maskB = (layers[0] == 255) | (layers[1] == 255) | (layers[2] == 255)
Image.fromarray((maskB*255).astype(np.uint8)).save('DEBUG-maskB.png')

# result is where there IS something in base layer and there is NO overlay that is fully opaque
res = maskA & ~maskB
Image.fromarray((res*255).astype(np.uint8)).save('result.png')

# Reduce result to simple Yes/No by seeing if any pixel is True
print(f'Result: {np.any(res)}')

Итак, если я использую эти изображения как base и layer0-2:

Это работает следующим образом:

Base image bbox: (80, 800, 301, 951)
Loading layer0.png
Loading layer1.png
Loading layer2.png
Result: True

И если я использую эти изображения, ответ будет False.


Обратите внимание, что я сделал изображения с ImageMagick вот так:

Дело 1

#!/bin/bash
magick -size 2000x2000 xc:none -fill red  -draw "rectangle 80,800 300,950"    PNG32:base.png
magick -size 2000x2000 xc:none -fill blue -draw "rectangle 280,700 480,850"   PNG32:layer0.png  
magick -size 2000x2000 xc:none -fill lime -draw "rectangle 320,1000 460,1200" PNG32:layer1.png
magick -size 2000x2000 xc:none -fill orange -draw "rectangle 500,800 800,900" PNG32:layer2.png
magick base.png layer0.png -composite layer1.png -composite layer2.png -composite PNG32:case1.png

Случай 2

#!/bin/bash
magick -size 2000x2000 xc:none -fill red  -draw "rectangle 80,800 300,950"    PNG32:base.png
magick -size 2000x2000 xc:none -fill blue -draw "rectangle 70,700 350,900"    PNG32:layer0.png  
magick -size 2000x2000 xc:none -fill lime -draw "rectangle 50,900 250,1200" PNG32:layer1.png
magick -size 2000x2000 xc:none -fill orange -draw "rectangle 250,900 800,1200" PNG32:layer2.png
magick base.png layer0.png -composite layer1.png -composite layer2.png -composite PNG32:case2.png

Обратите внимание, что если ваша основа и/или слои содержат частичную прозрачность, и/или "дыры", и/или несмежные области прозрачности, и/или рваные края, вам нужно будет тщательно проверить изображения DEBUG, которые создает мой код, чтобы убедиться, что я покрыл все случаи.

Другие вопросы по теме