Algoritmo para reduzir uma máscara de bitmap a uma lista de retângulos?

7

Antes de passar uma tarde escrevendo isso sozinho, pensei em perguntar se já havia uma implementação disponível - mesmo que apenas como referência. A primeira imagem é um exemplo de máscara de bitmap que eu gostaria de transformar em uma lista de retângulos. Um algoritmo ruim retornaria cada pixel definido como um retângulo 1x1. Um bom algoritmo se pareceria com a segunda imagem, onde retorna as coordenadas dos retângulos laranja e vermelho. O fato de os retângulos se sobreporem não importa, apenas existem apenas dois retornados.

Para resumir, o resultado ideal seria estes dois retângulos (x, y, w, h): [ { 3, 1, 2, 6 }, { 1, 3, 6, 2 } ]

Moswald
fonte

Respostas:

6

Não conheço nenhum algoritmo para esse problema específico, mas aqui está uma idéia de como você pode resolver isso:

  1. Deixe Xe Yseja0
  2. Pesquisa e incremento (X, Y) seu bitmap e visite cada pixel da esquerda para a direita, de cima para baixo até atingir um pixel de primeiro plano não visitado.
  3. Pesquise da (X, Y)direita, até atingir um pixel de fundo ou um pixel já visitado. Mantenha a quantidade de pixels encontrados como WIDTH. Defina HEIGHTcomo1 .
  4. Inicie outra pesquisa em ( X, Y + HEIGHT)e pesquise WIDTHpixels. Se todos esses pixels pertencerem ao primeiro plano, aumente HEIGHTem 1.
  5. Repita a etapa 3 até atingir um pixel de fundo na pesquisa. Em seguida, adicione o retângulo (formado por X, Ye WIDTH, HEIGHT) à lista de retângulos encontrados e marque todos os pixels que ele inclui como visitados.
  6. Incremento Xpor WIDTHe continuar sua pesquisa (vá para o passo 2 ). Sua pesquisa termina quando você alcança o pixel inferior direito do seu bitmap.

O algoritmo produziria três retângulos para o seu exemplo específico. Para obter um resultado com apenas 2 retângulos, é necessário alterar a etapa 3 para que ela pare apenas em um pixel de fundo e ignore os pixels já visitados. No entanto, isso resultaria em muitos retângulos sobrepostos. Portanto, a etapa 3 pode ser otimizada ainda mais, acompanhando os pixels de primeiro plano não visitados e visitados. Se o último pixel (antes do pixel de fundo) for um pixel visitado, use o último pixel de primeiro plano não visitado para obter oWIDTH .

Atualização: fui adiante e implementei o algoritmo que propus. Aqui está uma demonstração interativa qual você pode experimentar (clique em "Reproduzir flash em tela cheia", se a exibição for muito pequena).

Controles: Clique e arraste na área preta para desenhar pixels. AguardeShift e desenhe para apagar. Clique duas vezes para limpar a tela.

Aqui está a fonte completa da demonstração em flash .

bummzack
fonte
Recentemente, essa pergunta foi esbarrada na minha história do SO, mas sinceramente não me lembro de fazer essa pergunta, muito menos por que eu precisava dela em primeiro lugar. :) Dito isto, seu algoritmo e sua fonte parecem adequados para o que quer que fosse. Obrigado.
moswald
3

Esse problema está subespecificado. Como você forneceu um exemplo simples e simplista, perdeu algumas das importantes possibilidades lógicas e casos extremos aqui (ou simplesmente não os excluiu explicitamente). Por exemplo, como você não mencionou o aplicativo proposto para isso, não especificou o que é mais importante: dividir o mapa inteiro em retângulos exclusivos com uma área tão grande possível (o Problema de Embalagem, que possui um código arbitrário). número de soluções) ou simplesmente obtendo retângulos correspondentes às arestas em cada um de x e y, como você sugere acima. Eu assumo o último.

Considere o caso em que você preenche cada um dos 4 blocos que ficam imediatamente nos cantos da máscara de bit em forma de "sinal de mais" que você desenhou acima ([2, 2] é o mais alto). Agora, quais são os retângulos ideais? Considerando apenas as colunas, você prefere estreitos, rects mais longos com os mais finos para os lados, ou o quadrado maior e mais largo que agora pode ser encontrado no meio da imagem, com quadrados de agachamento acima e abaixo?

De qualquer forma, assumindo o maior número possível de rects correspondentes à borda, considere isso como uma solução de primeira tentativa:

  • Divida a máscara de bits em colunas. Cada coluna começa com algum valor y0 e termina com outro valor y1. Armazene cada coluna como uma tupla (y0, y1) em uma lista.
  • Colunas: avalie a lista. Encontre onde as tuplas adjacentes têm o mesmo y0 AND y1 entre elas (por exemplo, a coluna A começa em y0 = 1 e termina em y1 = 4; o mesmo acontece com a coluna B). Contanto que cada coluna consecutiva tenha os mesmos, aumente a largura de um retângulo que você criou para representá-los, em um. Assim que NÃO, termine esse retângulo, armazene-o e crie um novo para os grupos de colunas subsequentes.

Repita as duas etapas acima para linhas, exatamente como para colunas (usando x0 e x1 em vez disso, e uma lista separada, é claro).

Agora você deve ter algumas listas de retângulos com arestas limpas. No entanto, eles podem não ser ideais para a sua aplicação; se for esse o caso, você precisará de um sistema de heurísticas que permita empacotamento mais eficiente. Veja o Problema de Embalagem para mais informações.

Engenheiro
fonte