Tenho milhares de imagens cujas principais características são as da imagem anexa: todas as imagens estão em uma moldura (quase) preta, enquanto o conteúdo real da imagem quase sempre está em um fundo branco.
Agora eu gostaria de girar o conteúdo real da imagem para que a borda esquerda desse conteúdo fique vertical. Então eu gostaria de cortar (aparar) a imagem para que a borda preta seja jogada fora, mas mantendo o conteúdo real completamente. Ou seja, mesmo que a área que contém o conteúdo real não seja perfeitamente retangular, tudo dessa área deve ser mantido, o que significa que pequenos restos do quadro preto ainda podem ser visíveis depois.
Dado o número de imagens que eu gostaria de tratar dessa forma, acho que terei que fazer isso usando ferramentas de linha de comando. No passado, usei o ImageMagick para esse tipo de trabalho (para transformações que são muito mais fáceis), mas eu realmente não me importaria em combinar várias ferramentas diferentes para realizar a tarefa.
O que eu já tentei:
Eu pesquisei como desnivelar imagens, e isso funciona na maioria das vezes. No entanto, os métodos de desnivelamento que eu encontrei funcionam deixando as linhas de texto horizontais. Isso é legal porque facilita a leitura, mas é claro que na maioria dos casos as bordas da área que segura o texto não ficam verticais ou horizontais depois, respectivamente. Não é isso que eu estou procurando.
Para explicar com mais detalhes, gostaria de:
- Detecte a borda esquerda entre o quadro preto e a área que contém o conteúdo real da imagem.
- Ajuste uma linha reta (invisível) ao longo dessa borda.
- Determine o ângulo entre essa linha e uma linha vertical.
- Gire a imagem inteira pelo (negativo) ângulo para que a borda da etapa 1 fique vertical.
- Corte a imagem, descartando o máximo possível do quadro preto, mas mantendo assim a área completa que contém o conteúdo real (e, portanto, aceitando que o restante do quadro preto seja mantido se essa área não for perfeitamente retangular).
Alguém poderia explicar como fazer isso, de preferência usando ferramentas de linha de comando?
Para seu exemplo simples e limpo, você pode fazer o seguinte; usar o imagemagick
trim
para remover a maior parte da borda externa, então tentar girar em 1 grau (adicionando um fundo preto correspondente aos pixels recém-adicionados da imagem agora maior), aparar novamente e ver se o tamanho da imagem foi reduzido. Pegue o melhor tamanho reduzido.Para a imagem de teste obtive:
Se o quadro real for um pouco menos monocromático que o do exemplo, você pode tentar prefixar a
-trim
opção com, digamos,-fuzz 15%
para obter uma comparação de cores de pixel mais aproximada.Esta resposta tenta ser rápida usando um recurso
-trim
pelo qual deixa o deslocamento da nova imagem dentro da tela antiga dentro das informações da nova imagem. Primeiro, fazemos um corte global para obter uma imagem onde: o canto superior do espaço em branco desejado deve encostar no topo da imagem, e o canto mais à esquerda do espaço em branco desejado deve encostar no lado esquerdo da imagem. Este é o retângulo amarelo pontilhado na imagem abaixo.Repaginamos esta imagem para que a tela tenha o mesmo tamanho da nova imagem.
Uma fatia horizontal fina do topo é tirada (de altura
$stripwidth
no script abaixo) no arquivoline1.png
, que é o retângulo vermelho. Isso é aparado (no arquivoout4.png
), como mostrado pelo retângulo azul. O deslocamento com seta%X
da imagem resultante deve ser onde o canto superior foi encontrado. Para obter uma estimativa melhor, metade da largura da imagem aparada (%w/2
) é adicionada a isso (exagerada na imagem abaixo).Similarmente, uma fatia vertical fina é tomada e aparada. O deslocamento
%Y
é onde o canto mais à esquerda foi encontrado, com metade da altura%h/2
adicionada.Por exemplo, se o resultado aparado da faixa vermelha
line1.png
for azulout4.png
, entãoidentify out4.png
pode ser geradoque diz que a imagem tem 14x2 pixels, de um
line1.png
tamanho de tela ( ) de 387x432, com um deslocamento de 36 pixels em x e 0 em y.Agora temos os comprimentos dos lados opostos e adjacentes do triângulo superior esquerdo. Se calcularmos os graus usando arctan, essa é a rotação necessária para fazer esse triângulo desaparecer, e assim alinhar o canto superior com a borda esquerda.
Note que
a()
é arctan (em radianos) embc
. Oidentify -format
usa%w %h %X %Y
para obter a largura, altura, deslocamento x, deslocamento y da imagem aparada na tela original. O extra0
é porque o imagemagick adiciona um+
sinal de liderança ebc
não lida com isso.O resultado foi 5,76 graus, como visto nesta
sh -x
saída:A solução a seguir é baseada nas ideias de @meuh. Por favor, vote positivamente nas respostas dele se achar minha solução útil. Primeiro, postarei meu código e, depois, explicarei o que alterei, mencionando apenas as coisas não óbvias.
O código é baseado em algumas suposições que são seguras para as imagens que tenho que processar:
A ideia aqui é que na verdade não precisamos encontrar os cantos da área branca. Isso é importante, porque encontrar os cantos de forma confiável era impossível para mim, mesmo quando brincava com valores diferentes para
stripwidth
.Minhas imagens do mundo real vêm de um scanner, o que significa que as bordas da área branca não são muito nítidas. Isso torna bem difícil encontrar o canto superior. Normalmente, poderíamos encontrar a borda superior da seguinte maneira: após a primeira
convert
operação (aparar), vá para a primeira linha de pixels (no topo), caminhe ao longo dela da esquerda para a direita e pare no primeiro pixel branco; então você encontrou o canto.Isso é praticamente impossível se a área branca já for horizontal, ou quase horizontal, e fica mais difícil quanto menos "nítida" for a borda superior.
Portanto, movi a área (a "linha") que é cortada pela segunda
convert
operação para longe da borda superior em um décimo da altura da imagem. Isso garante que temos uma transição horizontal clara e relativamente nítida do preto para o branco naquela linha (nome do arquivoline1.png
).Eu queria evitar ter duas transições horizontais na linha 1 (por exemplo, preto -> branco no lado esquerdo e branco -> preto no lado direito). Isso é simplesmente porque eu não tenho ideia
imagemagick
e nenhuma pista sobre o que aconteceria na próxima etapa se houvesse mais de uma transição na linha. Portanto, ao cortar a linha, eu uso apenas metade da largura da imagem. [Nota lateral: ainda pode haver várias transições na linha, dependendo do conteúdo da área branca (por exemplo, texto preto), mas isso não impôs problemas em meus primeiros testes.]A terceira
convert
operação gera algo como0x10+45+0
. A parte0x10
denota as dimensões da transição mencionada acima,+45
é aX
coordenada onde a transição começa. Este é o valor que buscamos; aread
construção o salva emx1
.O mesmo método é aplicado para a segunda "linha" (nome do arquivo
line2.png
), que é cortada um décimo da altura da imagem a partir da parte inferior da imagem (quartaconvert
operação). Na próxima linha (quintaconvert
operação), aX
coordenada da transição do preto para o branco na linha 2 é salva emx2
.Agora podemos calcular a distância horizontal e vertical entre as duas transições na linha 1 e na linha 2 e, a partir dessas distâncias, podemos calcular o ângulo de rotação.
Usei Perl para executar esse cálculo porque
bc
ele não fornece aatan2()
função, que eu claramente prefiro nesses casos por sua capacidade de lidar com situações em que a distância horizontal ou vertical (ou mesmo ambas) é0
.O resto deve ser autoexplicativo.