给定一个像这样的精灵表:
我想编写一个算法,可以循环遍历像素数据并确定每个谨慎精灵的边界矩形。
如果我们假设对于每个像素 X、Y,我可以拉出真(像素不完全透明)或假(像素完全透明),我将如何为每个精灵自动生成边界矩形?
结果数据应该是具有 {x, y, width, height} 的矩形对象数组。
这是相同的图像,但前四个精灵的边界用浅蓝色标记:
任何人都可以逐步说明如何检测上述界限吗?
给定一个像这样的精灵表:
我想编写一个算法,可以循环遍历像素数据并确定每个谨慎精灵的边界矩形。
如果我们假设对于每个像素 X、Y,我可以拉出真(像素不完全透明)或假(像素完全透明),我将如何为每个精灵自动生成边界矩形?
结果数据应该是具有 {x, y, width, height} 的矩形对象数组。
这是相同的图像,但前四个精灵的边界用浅蓝色标记:
任何人都可以逐步说明如何检测上述界限吗?
这是一种方法
转换为灰度后,我们用Otsu的阈值得到二值图像
接下来我们执行形态变换,将每个精灵合并成一个轮廓
从这里我们找到轮廓,遍历每个轮廓,绘制边界矩形,并提取每个 ROI。这是结果
这是每个保存的精灵 ROI
我已经使用 OpenCV 和 Python 实现了这个方法,但是您可以将该策略调整到任何语言
import cv2
image = cv2.imread('1.jpg')
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2)
dilate = cv2.dilate(close, kernel, iterations=1)
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
sprite_number = 0
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
ROI = image[y:y+h, x:x+w]
cv2.imwrite('sprite_{}.png'.format(sprite_number), ROI)
cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
sprite_number += 1
cv2.imshow('thresh', thresh)
cv2.imshow('dilate', dilate)
cv2.imshow('image', image)
cv2.waitKey()
这个怎么样?唯一的缺点是您需要图像的可写版本来标记访问过的像素,否则填充将永远不会终止。
Process each* scan line in turn
For each scanline, walk from left to right, until you find a non-transparent pixel P.
If the location of P is already inside a known bounded box
Continue to the right of the bounded box
Else
BBox = ExploreBoundedBox(P)
Add BBox to the collection of known bounded boxes
Function ExploreBoundedBox(pStart)
Q = new Queue(pStart)
B = new BoundingBox(pStart)
While Q is not empty
Dequeue the front element as P
Expand B to include P
For each of the four neighbouring pixels N
If N is not transparent and N is not marked
Mark N
Enqueue N at the back of Q
return B
您不需要处理每条扫描线,您可以每 10 条或每 30 条扫描线处理一次。只要它不超过最小精灵高度。
使用 Pillow 在 Python 上附加实现:
网址:https ://gist.github.com/tuaplicacionpropia/f5bd6b0f69a11141767387eb789f5093
#!/usr/bin/env python
#coding:utf-8
from __future__ import print_function
from PIL import Image
class Sprite:
def __init__(self):
self.start_x = -1
self.start_y = -1
self.end_x = -1
self.end_y = -1
def expand (self, point):
if (self.start_x < 0 and self.start_y < 0 and self.end_x < 0 and self.end_y < 0):
self.start_x = point[0]
self.start_y = point[1]
self.end_x = point[0]
self.end_y = point[1]
else:
if (point[0] < self.start_x):
self.start_x = point[0]
if (point[0] > self.end_x):
self.end_x = point[0]
if (point[1] < self.start_y):
self.start_y = point[1]
if (point[1] > self.end_y):
self.end_y = point[1]
def belongs (self, point):
result = False
result = True
result = result and point[0] >= self.start_x and point[0] <= self.end_x
result = result and point[1] >= self.start_y and point[1] <= self.end_y
return result
def __str__(self):
result = ""
result = result + "("
result = result + str(self.start_x)
result = result + ", "
result = result + str(self.start_y)
result = result + ", "
result = result + str(self.end_x)
result = result + ", "
result = result + str(self.end_y)
result = result + ")"
return result
def loadSprite (pos, sprites):
result = None
for sprite in sprites:
if sprite.belongs(pos):
result = sprite
break
return result
def exploreBoundedBox (pStart, img):
result = None
q = []
q.append(pStart)
result = Sprite()
result.expand(pStart)
marks = []
while (len(q) > 0):
p = q.pop(0)
result.expand(p)
neighbouring = loadEightNeighbouringPixels(p, img)
for n in neighbouring:
if img.getpixel(n)[3] > 0 and not n in marks:
marks.append(n)
q.append(n)
return result
def loadFourNeighbouringPixels (point, img):
result = None
result = []
newPoint = (point[0], point[1] - 1)
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0] - 1, point[1])
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0] + 1, point[1])
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0], point[1] + 1)
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
return result
def loadEightNeighbouringPixels (point, img):
result = None
result = []
newPoint = (point[0], point[1] - 1)
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0] - 1, point[1])
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0] + 1, point[1])
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0], point[1] + 1)
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0] - 1, point[1] - 1)
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0] + 1, point[1] - 1)
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0] - 1, point[1] + 1)
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0] + 1, point[1] + 1)
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
return result
im = Image.open("test2.png")
print(im.format, im.size, im.mode)
#PNG (640, 252) RGBA
#im.show()
print("width = " + str(im.width))
print("height = " + str(im.height))
sprites = []
for y in range(im.height):
for x in range(im.width):
pixel = im.getpixel((x, y))
haycolor = True if pixel[3] > 0 else False
if (haycolor):
pos = (x, y)
#print("(" + str(x) + ", " + str(y) + ") -> " + str(pixel))
pixelP = pixel
sprite = loadSprite(pos, sprites)
if (sprite != None):
x = sprite.end_x
else:
sprite = exploreBoundedBox(pos, im)
sprites.append(sprite)
print("sprites")
print(str(sprites))
idx = 1
for sprite in sprites:
print("sprite " + str(idx) + ". -> " + str(sprite))
imSprite = im.crop((sprite.start_x, sprite.start_y, sprite.end_x + 1, sprite.end_y + 1))
#imSprite.show()
imSprite.save("sprite" + str(idx) + ".png")
idx += 1
为了避免留下小部分精灵,我们必须添加以下改进:
MINIMUM_SPRITE = 8
def firstNonSprites (sprites):
result = None
for sprite in sprites:
if (sprite.end_x - sprite.start_x + 1) < MINIMUM_SPRITE or (sprite.end_y - sprite.start_y + 1) < MINIMUM_SPRITE:
result = sprite
break
return result
def mergeSprites (sprite1, sprite2):
result = None
if (sprite1 != None and sprite2 != None):
result = Sprite()
result.start_x = min(sprite1.start_x, sprite2.start_x)
result.start_y = min(sprite1.start_y, sprite2.start_y)
result.end_x = max(sprite1.end_x, sprite2.end_x)
result.end_y = max(sprite1.end_y, sprite2.end_y)
return result
def findNextSprite (pivot, sprites):
result = None
distance = 99999999
for sprite in sprites:
if sprite != pivot:
itemDistance = distanceSprites(pivot, sprite)
if (itemDistance < distance):
distance = itemDistance
result = sprite
return result
#Pitagoras
def distancePoints (point1, point2):
result = 99999999
if (point1 != None and point2 != None):
a = abs(point2[0] - point1[0])
b = abs(point2[1] - point1[1])
result = math.sqrt(math.pow(a, 2) + math.pow(b, 2))
return result
def distancePointSprite (point, sprite):
result = 99999999
if (point != None and sprite != None):
distance = distancePoints(point, (sprite.start_x, sprite.start_y))
if (distance < result):
result = distance
distance = distancePoints(point, (sprite.end_x, sprite.start_y))
if (distance < result):
result = distance
distance = distancePoints(point, (sprite.start_x, sprite.end_y))
if (distance < result):
result = distance
distance = distancePoints(point, (sprite.end_x, sprite.end_y))
if (distance < result):
result = distance
return result
def distanceSprites (sprite1, sprite2):
result = 99999999
if (sprite1 != None and sprite2 != None):
distance = distancePointSprite((sprite1.start_x, sprite1.start_y), sprite2)
if (distance < result):
result = distance
distance = distancePointSprite((sprite1.end_x, sprite1.start_y), sprite2)
if (distance < result):
result = distance
distance = distancePointSprite((sprite1.start_x, sprite1.end_y), sprite2)
if (distance < result):
result = distance
distance = distancePointSprite((sprite1.end_x, sprite1.end_y), sprite2)
if (distance < result):
result = distance
return result
def fixMergeSprites (sprites):
result = []
pivotNonSprite = firstNonSprites(sprites)
while (pivotNonSprite != None):
nextSprite = findNextSprite(pivotNonSprite, sprites)
if nextSprite == None:
break
mergeSprite = mergeSprites(pivotNonSprite, nextSprite)
sprites.remove(nextSprite)
sprites.remove(pivotNonSprite)
sprites.append(mergeSprite)
pivotNonSprite = firstNonSprites(sprites)
result = sprites
return result
#BEFORE CROP
sprites = fixMergeSprites(sprites)
完整代码:
#!/usr/bin/env python
#coding:utf-8
from __future__ import print_function
from PIL import Image
import math
#https://stackoverflow.com/questions/13584586/sprite-sheet-detect-individual-sprite-bounds-automatically?rq=1
'''
Process each* scan line in turn
For each scanline, walk from left to right, until you find a non-transparent pixel P.
If the location of P is already inside a known bounded box
Continue to the right of the bounded box
Else
BBox = ExploreBoundedBox(P)
Add BBox to the collection of known bounded boxes
Function ExploreBoundedBox(pStart)
Q = new Queue(pStart)
B = new BoundingBox(pStart)
While Q is not empty
Dequeue the front element as P
Expand B to include P
For each of the four neighbouring pixels N
If N is not transparent and N is not marked
Mark N
Enqueue N at the back of Q
return B
'''
class Sprite:
def __init__(self):
self.start_x = -1
self.start_y = -1
self.end_x = -1
self.end_y = -1
def expand (self, point):
if (self.start_x < 0 and self.start_y < 0 and self.end_x < 0 and self.end_y < 0):
self.start_x = point[0]
self.start_y = point[1]
self.end_x = point[0]
self.end_y = point[1]
else:
if (point[0] < self.start_x):
self.start_x = point[0]
if (point[0] > self.end_x):
self.end_x = point[0]
if (point[1] < self.start_y):
self.start_y = point[1]
if (point[1] > self.end_y):
self.end_y = point[1]
def belongs (self, point):
result = False
result = True
result = result and point[0] >= self.start_x and point[0] <= self.end_x
result = result and point[1] >= self.start_y and point[1] <= self.end_y
return result
def __str__(self):
result = ""
result = result + "("
result = result + str(self.start_x)
result = result + ", "
result = result + str(self.start_y)
result = result + ", "
result = result + str(self.end_x)
result = result + ", "
result = result + str(self.end_y)
result = result + ")"
return result
def loadSprite (pos, sprites):
result = None
for sprite in sprites:
if sprite.belongs(pos):
result = sprite
break
return result
'''
Function ExploreBoundedBox(pStart)
Q = new Queue(pStart)
B = new BoundingBox(pStart)
While Q is not empty
Dequeue the front element as P
Expand B to include P
For each of the four neighbouring pixels N
If N is not transparent and N is not marked
Mark N
Enqueue N at the back of Q
return B
'''
def exploreBoundedBox (pStart, img):
result = None
q = []
q.append(pStart)
result = Sprite()
result.expand(pStart)
marks = []
while (len(q) > 0):
p = q.pop(0)
result.expand(p)
neighbouring = loadEightNeighbouringPixels(p, img)
for n in neighbouring:
if img.getpixel(n)[3] > 0 and not n in marks:
marks.append(n)
q.append(n)
return result
def loadFourNeighbouringPixels (point, img):
result = None
result = []
newPoint = (point[0], point[1] - 1)
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0] - 1, point[1])
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0] + 1, point[1])
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0], point[1] + 1)
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
return result
def loadEightNeighbouringPixels (point, img):
result = None
result = []
newPoint = (point[0], point[1] - 1)
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0] - 1, point[1])
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0] + 1, point[1])
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0], point[1] + 1)
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0] - 1, point[1] - 1)
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0] + 1, point[1] - 1)
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0] - 1, point[1] + 1)
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
newPoint = (point[0] + 1, point[1] + 1)
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height):
result.append(newPoint)
return result
MINIMUM_SPRITE = 8
def firstNonSprites (sprites):
result = None
for sprite in sprites:
if (sprite.end_x - sprite.start_x + 1) < MINIMUM_SPRITE or (sprite.end_y - sprite.start_y + 1) < MINIMUM_SPRITE:
result = sprite
break
return result
def mergeSprites (sprite1, sprite2):
result = None
if (sprite1 != None and sprite2 != None):
result = Sprite()
result.start_x = min(sprite1.start_x, sprite2.start_x)
result.start_y = min(sprite1.start_y, sprite2.start_y)
result.end_x = max(sprite1.end_x, sprite2.end_x)
result.end_y = max(sprite1.end_y, sprite2.end_y)
return result
def findNextSprite (pivot, sprites):
result = None
distance = 99999999
for sprite in sprites:
if sprite != pivot:
itemDistance = distanceSprites(pivot, sprite)
if (itemDistance < distance):
distance = itemDistance
result = sprite
return result
#Pitagoras
def distancePoints (point1, point2):
result = 99999999
if (point1 != None and point2 != None):
a = abs(point2[0] - point1[0])
b = abs(point2[1] - point1[1])
result = math.sqrt(math.pow(a, 2) + math.pow(b, 2))
return result
def distancePointSprite (point, sprite):
result = 99999999
if (point != None and sprite != None):
distance = distancePoints(point, (sprite.start_x, sprite.start_y))
if (distance < result):
result = distance
distance = distancePoints(point, (sprite.end_x, sprite.start_y))
if (distance < result):
result = distance
distance = distancePoints(point, (sprite.start_x, sprite.end_y))
if (distance < result):
result = distance
distance = distancePoints(point, (sprite.end_x, sprite.end_y))
if (distance < result):
result = distance
return result
def distanceSprites (sprite1, sprite2):
result = 99999999
if (sprite1 != None and sprite2 != None):
distance = distancePointSprite((sprite1.start_x, sprite1.start_y), sprite2)
if (distance < result):
result = distance
distance = distancePointSprite((sprite1.end_x, sprite1.start_y), sprite2)
if (distance < result):
result = distance
distance = distancePointSprite((sprite1.start_x, sprite1.end_y), sprite2)
if (distance < result):
result = distance
distance = distancePointSprite((sprite1.end_x, sprite1.end_y), sprite2)
if (distance < result):
result = distance
return result
def fixMergeSprites (sprites):
result = []
pivotNonSprite = firstNonSprites(sprites)
while (pivotNonSprite != None):
nextSprite = findNextSprite(pivotNonSprite, sprites)
if nextSprite == None:
break
mergeSprite = mergeSprites(pivotNonSprite, nextSprite)
sprites.remove(nextSprite)
sprites.remove(pivotNonSprite)
sprites.append(mergeSprite)
pivotNonSprite = firstNonSprites(sprites)
result = sprites
return result
im = Image.open("test.png")
print(im.format, im.size, im.mode)
#PNG (640, 252) RGBA
#im.show()
print("width = " + str(im.width))
print("height = " + str(im.height))
sprites = []
for y in range(im.height):
for x in range(im.width):
pixel = im.getpixel((x, y))
haycolor = True if pixel[3] > 0 else False
if (haycolor):
pos = (x, y)
#print("(" + str(x) + ", " + str(y) + ") -> " + str(pixel))
pixelP = pixel
sprite = loadSprite(pos, sprites)
if (sprite != None):
x = sprite.end_x
else:
sprite = exploreBoundedBox(pos, im)
sprites.append(sprite)
sprites = fixMergeSprites(sprites)
print("sprites")
print(str(sprites))
idx = 1
for sprite in sprites:
print("sprite " + str(idx) + ". -> " + str(sprite))
imSprite = im.crop((sprite.start_x, sprite.start_y, sprite.end_x + 1, sprite.end_y + 1))
#imSprite.show()
imSprite.save("sprite" + str(idx) + ".png")
idx += 1