PySide: разделение спрайт-листа/разделение изображения на смежные области цвета

Я работаю над программой, в которой мне нужно разделить листы спрайтов или, другими словами, разделить изображение на смежные области цвета.

Я никогда раньше не занимался обработкой изображений, поэтому мне интересно, как бы я это сделал. Что бы я сделал после проверки цвета пикселей? Как лучше всего определить, какой пиксель соответствует каждому спрайту?

Все входные изображения имеют одинаковый фон, а альфа-канал, отличный от альфа-канала фона, считается цветом. Порядок выходных изображений должен быть левый-правый, вверх-вниз. Мой проект написан на PySide, поэтому я надеюсь использовать его и для этой задачи, но при необходимости я могу импортировать больше библиотек.

Спасибо ваши ответы!

PS: я не уверен, подходит ли тег PySide или нет, поскольку я использую PySide, но вопрос не касается его аспектов графического интерфейса. Если мод считает, что он не подходит, не стесняйтесь удалять его.


Например, у меня есть спрайт-лист, который выглядит так:

Входное изображение

Я хочу разделить его на эти:

Вывод 1Вывод 2 Вывод 3Вывод 4 Выход 5Вывод 6 Выход 7Вывод 8


person sorbet    schedule 21.01.2013    source источник
comment
Я нашел похожее решение в JavaScript: 2d-массив в несколько массивов с использованием разделителя"> stackoverflow.com/questions/14507440/   -  person Anderson Green    schedule 02.03.2013


Ответы (1)


Похоже, что это должно быть реализовано во всем, что имеет дело со спрайтами, но здесь мы реализуем наш собственный спрайт-сплиттер.

Первое, что нам нужно здесь, это извлечь отдельные объекты. В этой ситуации вопрос только в том, является ли пиксель фоновым или нет. Если мы предположим, что исходная точка является фоновым пикселем, то мы закончили:

from PIL import Image

def sprite_mask(img, bg_point=(0, 0)):
    width, height = img.size
    im = img.load()

    bg = im[bg_point]
    mask_img = Image.new('L', img.size)
    mask = mask_img.load()
    for x in xrange(width):
        for y in xrange(height):
            if im[x, y] != bg:
                mask[x, y] = 255
    return mask_img, bg

Если вы сохраните изображение mask, созданное выше, и откроете его, вот что вы увидите на нем (я добавил прямоугольник внутри вашего пустого окна):

введите здесь описание изображения

Следующее, что нам нужно сделать с изображением выше, это заполнить его отверстия, если мы хотим соединить спрайты, которые находятся внутри других (например, добавленный прямоугольник, см. рисунок выше). Это еще одно простое правило: если точка не может быть достигнута из точки [0, 0], то это дыра, и ее нужно заполнить. Все, что осталось, — это разделить каждый спрайт на отдельные изображения. Это делается путем маркировки подключенных компонентов. Для каждого компонента мы получаем его ограничительную рамку, выровненную по оси, чтобы определить размеры фрагмента, а затем копируем из исходного изображения точки, принадлежащие данному компоненту. Чтобы быть кратким, следующий код использует scipy для этих задач:

import sys
import numpy
from scipy.ndimage import label, morphology

def split_sprite(img, mask, bg, join_interior=True, basename='sprite_%d.png'):
    im = img.load()

    m = numpy.array(mask, dtype=numpy.uint8)
    if join_interior:
        m = morphology.binary_fill_holes(m)
    lbl, ncc = label(m, numpy.ones((3, 3)))

    for i in xrange(1, ncc + 1):
        px, py = numpy.nonzero(lbl == i)
        xmin, xmax, ymin, ymax = px.min(), px.max(), py.min(), py.max()

        sprite = Image.new(img.mode, (ymax - ymin + 1, xmax - xmin + 1), bg)
        sp = sprite.load()
        for x, y in zip(px, py):
            x, y = int(x), int(y)
            sp[y - int(ymin), x - int(xmin)] = im[y, x]

        name = basename % i
        sprite.save(name)
        print "Wrote %s" % name

sprite = Image.open(sys.argv[1])
mask, bg = sprite_mask(sprite)
split_sprite(sprite, mask, bg)

Теперь у вас есть все части (sprite_1.png, sprite_2.png, ..., sprite_8.png) именно так, как вы указали в вопросе.

person mmgp    schedule 21.01.2013
comment
Есть ли смысл создавать контур, а затем заполнять отверстия? Кажется, если я добавлю if im[x, y] != bg: mask[x, y] = 255, m = morphology.binary_fill_holes(m) не понадобится. - person sorbet; 23.01.2013
comment
@user1588857 user1588857 это упрощение, которое может работать и для других изображений, которые у вас могут быть. Но предположим, что в крайнем правом спрайте у вас есть шар внутри пустого окна, не связанного с окном, ваше предложение приведет к созданию спрайта для этого шара и другого для внешнего компонента. - person mmgp; 23.01.2013
comment
@user1588857 user1588857 Я принял ваше предложение в отредактированном ответе, проверьте, соответствует ли оно тому, что мы здесь обсуждали. - person mmgp; 23.01.2013
comment
Работает отлично, проблем с ним пока не было. Большое спасибо! - person sorbet; 24.01.2013
comment
А как вы это делаете на Java? О, подождите, это не по теме для этого вопроса и, следовательно, не является дубликатом как это сделать на Java? - person Andrew Thompson; 03.02.2013
comment
Просто примечание: вам нужно убедиться, что входные изображения не проиндексированы, иначе на выходе вы получите множество черных изображений. (Не уверен, есть ли способ изменить скрипт для загрузки преобразования входного изображения в RGBA при загрузке). - person Stuart Axon; 20.04.2013