Considere estas 10 imagens de várias quantidades de grãos não cozidos de arroz branco.
ESTES SÃO SOMENTE POLEGARES. Clique na imagem para vê-la em tamanho real.
Contagem de grãos: A: 3, B: 5, C: 12, D: 25, E: 50, F: 83, G: 120, H:150, I: 151, J: 200
Notar que...
- Os grãos podem se tocar, mas nunca se sobrepõem. O layout dos grãos nunca tem mais do que um grão de altura.
- As imagens têm dimensões diferentes, mas a escala do arroz em todas elas é consistente porque a câmera e o fundo estavam estacionários.
- Os grãos nunca saem dos limites ou tocam nos limites da imagem.
- O fundo é sempre o mesmo tom consistente de branco amarelado.
- Grãos pequenos e grandes são contados como um grão cada.
Esses 5 pontos são garantias para todas as imagens desse tipo.
Desafio
Escreva um programa que capte essas imagens e, com a maior precisão possível, conte o número de grãos de arroz.
Seu programa deve pegar o nome do arquivo da imagem e imprimir o número de grãos que calcula. Seu programa deve funcionar para pelo menos um destes formatos de arquivo de imagem: JPEG, Bitmap, PNG, GIF, TIFF (agora as imagens são todas JPEGs).
Você pode usar as bibliotecas de processamento de imagem e visão computacional.
Você não pode codificar as saídas das 10 imagens de exemplo. Seu algoritmo deve ser aplicável a todas as imagens semelhantes de grãos de arroz. Ele deve ser executado em menos de 5 minutos em um computador moderno decente se a área da imagem for menor que 2000 * 2000 pixels e houver menos de 300 grãos de arroz.
Pontuação
Para cada uma das 10 imagens, pegue o valor absoluto do número real de grãos menos o número de grãos que seu programa prevê. Soma esses valores absolutos para obter sua pontuação. A pontuação mais baixa vence. Uma pontuação de 0 é perfeita.
Em caso de empate, a resposta mais votada vence. Posso testar seu programa em imagens adicionais para verificar sua validade e precisão.
fonte
Respostas:
Mathematica, pontuação: 7
Eu acho que os nomes das funções são bastante descritivos:
Processando todas as imagens de uma vez:
A pontuação é:
Aqui você pode ver a sensibilidade da pontuação no tamanho do grão usado:
fonte
EdgeDetect[]
,DeleteSmallComponents[]
eDilation[]
são implementadas em outros lugares)Python, Pontuação:
2416Essa solução, como a de Falko, baseia-se na medição da área "em primeiro plano" e na divisão pela área média de grãos.
De fato, o que este programa tenta detectar é o plano de fundo, não tanto quanto o primeiro plano. Usando o fato de que os grãos de arroz nunca tocam o limite da imagem, o programa começa preenchendo a inundação em branco no canto superior esquerdo. O algoritmo de preenchimento pinta pixels adjacentes se a diferença entre o brilho deles e o pixel atual estiver dentro de um determinado limite, ajustando-se assim à mudança gradual na cor de fundo. No final deste estágio, a imagem pode ser algo como isto:
Como você pode ver, ele faz um bom trabalho na detecção do fundo, mas deixa de fora quaisquer áreas "presas" entre os grãos. Lidamos com essas áreas estimando o brilho do plano de fundo em cada pixel e paitando todos os pixels iguais ou mais brilhantes. Essa estimativa funciona da seguinte maneira: durante o estágio de preenchimento, calculamos o brilho médio do plano de fundo para cada linha e cada coluna. O brilho estimado do plano de fundo em cada pixel é a média do brilho da linha e da coluna nesse pixel. Isso produz algo como isto:
EDIT: Por fim, a área de cada região contínua de primeiro plano (ou seja, não branca) é dividida pela área média pré-calculada de grãos, fornecendo uma estimativa da contagem de grãos nessa região. A soma dessas quantidades é o resultado. Inicialmente, fizemos o mesmo para toda a área de primeiro plano como um todo, mas essa abordagem é, literalmente, mais refinada.
Leva o nome do arquivo de entrada pela linha de comando.
Resultados
fonte
avg_grain_area = 3038.38;
vem?hardcoding the result
?The images have different dimensions but the scale of the rice in all of them is consistent because the camera and background were stationary.
Este é apenas um valor que representa essa regra. O resultado, no entanto, muda de acordo com a entrada. Se você alterar a regra, esse valor será alterado, mas o resultado será o mesmo - com base na entrada.Python + OpenCV: pontuação 27
Digitalização de linha horizontal
Idéia: digitalize a imagem, uma linha de cada vez. Para cada linha, conte o número de grãos de arroz encontrados (verificando se o pixel fica preto em branco ou o oposto). Se o número de grãos para a linha aumentar (em comparação com a linha anterior), significa que encontramos um novo grão. Se esse número diminuir, significa que passamos por cima de um grão. Nesse caso, adicione +1 ao resultado total.
Devido à maneira como o algoritmo funciona, é importante ter uma imagem limpa, em preto e branco. Muito barulho produz resultados ruins. O primeiro plano de fundo principal é limpo usando o aterro (solução semelhante à resposta de Ell) e o limiar é aplicado para produzir resultados em preto e branco.
Está longe de ser perfeito, mas produz bons resultados em relação à simplicidade. Provavelmente, há muitas maneiras de melhorá-lo (fornecendo uma melhor imagem em preto e branco, digitalizando em outras direções (por exemplo: vertical, diagonal), medindo a média etc ...)
Os erros por imagem: 0, 0, 0, 3, 0, 12, 4, 0, 7, 1
fonte
Python + OpenCV: pontuação 84
Aqui está uma primeira tentativa ingênua. Aplica um limiar adaptável com parâmetros ajustados manualmente, fecha alguns furos com subsequente erosão e diluição e deriva o número de grãos da área do primeiro plano.
Aqui você pode ver as imagens binárias intermediárias (o preto é o primeiro plano):
Os erros por imagem são 0, 0, 2, 2, 4, 0, 27, 42, 0 e 7 grãos.
fonte
C # + OpenCvSharp, Pontuação: 2
Esta é a minha segunda tentativa. É bem diferente da minha primeira tentativa , que é muito mais simples, por isso estou publicando como uma solução separada.
A idéia básica é identificar e rotular cada grão individual por um ajuste de elipse iterativo. Em seguida, remova os pixels desse grão da origem e tente encontrar o próximo grão, até que cada pixel tenha sido rotulado.
Esta não é a solução mais bonita. É um porco gigante com 600 linhas de código. Ele precisa de 1,5 minutos para a maior imagem. E realmente peço desculpas pelo código confuso.
Existem tantos parâmetros e maneiras de pensar nisso que tenho muito medo de ajustar meu programa para as 10 imagens de amostra. A pontuação final de 2 é quase definitivamente um caso de sobreajuste: eu tenho dois parâmetros,,
average grain size in pixel
eminimum ratio of pixel / elipse_area
, e no final simplesmente exaurei todas as combinações desses dois parâmetros até obter a pontuação mais baixa. Não tenho certeza se isso é tudo o que kosher com as regras deste desafio.average_grain_size_in_pixel = 2530
pixel / elipse_area >= 0.73
Mas mesmo sem essas embreagens, os resultados são bastante bons. Sem um tamanho fixo de grão ou proporção de pixels, simplesmente estimando o tamanho médio de grão a partir das imagens de treinamento, a pontuação ainda é 27.
E recebo como resultado não apenas o número, mas a posição, orientação e forma reais de cada grão. há um pequeno número de grãos com etiquetas incorretas, mas no geral a maioria dos rótulos corresponde exatamente aos grãos reais:
A B C D E
F G H I J
(clique em cada imagem para obter a versão em tamanho normal)
Após esta etapa de rotulagem, meu programa analisa cada granulação individual e estima com base no número de pixels e na proporção pixel / elipse-área, se isso é
As pontuações de erro para cada imagem são
A:0; B:0; C:0; D:0; E:2; F:0; G:0 ; H:0; I:0, J:0
No entanto, o erro real é provavelmente um pouco maior. Alguns erros na mesma imagem se cancelam. A imagem H, em particular, possui alguns grãos mal rotulados, enquanto na imagem E os rótulos estão na maioria corretos
O conceito é um pouco artificial:
Primeiro, o primeiro plano é separado via otsu-thresholding no canal de saturação (veja minha resposta anterior para detalhes)
repita até que não haja mais pixels:
escolha 10 pixels de borda aleatórios neste blob como posições iniciais de um grão
para cada ponto de partida
assuma um grão com altura e largura de 10 pixels nessa posição.
repita até a convergência
vá radialmente para fora a partir deste ponto, em diferentes ângulos, até encontrar um pixel de borda (branco para preto)
esperamos que os pixels encontrados sejam os pixels da borda de uma única granulação. Tente separar inliers de outliers, descartando pixels que estão mais distantes da elipse assumida do que os outros
tente repetidamente ajustar uma elipse através de um subconjunto dos inliers, mantenha a melhor elipse (RANSACK)
atualize a posição, orientação, largura e altura do grão com a elipse encontrada
se a posição do grão não mudar significativamente, pare
Entre os 10 grãos ajustados, escolha o melhor grão de acordo com a forma, número de pixels da aresta. Descartar os outros
remova todos os pixels desse grão da imagem de origem e repita
por fim, leia a lista de grãos encontrados e conte cada grão como 1 grão, 0 grão (muito pequeno) ou 2 grãos (muito grande)
Um dos meus principais problemas era que eu não queria implementar uma métrica de distância de ponto de elipse completa, pois calcular isso por si só é um processo iterativo complicado. Então, usei várias soluções alternativas usando as funções OpenCV Ellipse2Poly e FitEllipse, e os resultados não são muito bonitos.
Aparentemente, eu também quebrei o limite de tamanho para o codegolf.
Uma resposta é limitada a 30000 caracteres, atualmente estou em 34000. Portanto, vou ter que diminuir um pouco o código abaixo.
O código completo pode ser visto em http://pastebin.com/RgM7hMxq
Desculpe por isso, eu não sabia que havia um limite de tamanho.
Estou um pouco envergonhado com esta solução porque a) não tenho certeza se está dentro do espírito desse desafio eb) é muito grande para uma resposta do codegolf e não tem a elegância das outras soluções.
Por outro lado, estou muito feliz com o progresso que obtive na rotulagem dos grãos, não apenas contando-os, então é isso.
fonte
C ++, OpenCV, pontuação: 9
A idéia básica do meu método é bastante simples - tente apagar grãos únicos (e "grãos duplos" - 2 (mas não mais!) Grãos, próximos um do outro) da imagem e, em seguida, conte o resto usando o método baseado na área (como Falko, Ell e Belisarius). O uso dessa abordagem é um pouco melhor que o "método de área" padrão, porque é mais fácil encontrar um bom valor averagePixelsPerObject.
(1º passo) Antes de tudo, precisamos usar a binarização Otsu no canal S da imagem em HSV. O próximo passo é usar o operador dilatado para melhorar a qualidade do primeiro plano extraído. Do que precisamos encontrar contornos. É claro que alguns contornos não são grãos de arroz - precisamos excluir contornos muito pequenos (com área menor que a médiaPixelsPerObject / 4. averagePixelsPerObject é 2855 na minha situação). Agora, finalmente, podemos começar a contar grãos :) (2º passo) Encontrar grãos simples e duplos é bastante simples - basta procurar na lista de contornos contornos com área dentro de intervalos específicos - se a área de contorno estiver no intervalo, exclua-a da lista e adicione 1 (ou 2, se for grão "duplo") para o contador de grãos. (3º passo) É claro que o último passo é dividir a área dos contornos restantes pelo valor averagePixelsPerObject e adicionar resultado ao contador de grãos.
As imagens (na imagem F.jpg) devem mostrar essa ideia melhor do que as palavras:
1º passo (sem pequenos contornos (ruído)):
2º passo - apenas contornos simples:
3º passo - contornos restantes:
Aqui está o código, é bastante feio, mas deve funcionar sem nenhum problema. Claro que o OpenCV é necessário.
Se você quiser ver os resultados de todas as etapas, remova o comentário de todas as chamadas de função imshow (.., ..) e defina a variável fastProcessing como false. As imagens (A.jpg, B.jpg, ...) devem estar em imagens de diretório. Como alternativa, é possível dar o nome de uma imagem como parâmetro na linha de comando.
Claro que, se algo não estiver claro, eu posso explicar e / ou fornecer algumas imagens / informações.
fonte
C # + OpenCvSharp, pontuação: 71
Isso é muito irritante, tentei obter uma solução que realmente identificasse cada grão usando a bacia hidrográfica , mas eu apenas. não posso. pegue. isto. para. trabalhos.
Eu decidi por uma solução que pelo menos separa alguns grãos individuais e depois os usa para estimar o tamanho médio dos grãos. No entanto, até agora não posso superar as soluções com tamanho de grão codificado.
Portanto, o principal destaque desta solução: ela não pressupõe um tamanho fixo de pixel para grãos e deve funcionar mesmo se a câmera for movida ou se o tipo de arroz for alterado.
Minha solução funciona assim:
Separe o primeiro plano transformando a imagem em HSV e aplicando o limiar de Otsu no canal de saturação. Isso é muito simples, funciona extremamente bem e eu recomendaria isso para todos os outros que queiram experimentar este desafio:
->
Isso removerá o plano de fundo de maneira limpa.
Em seguida, removi adicionalmente as sombras de granulação do primeiro plano, aplicando um limite fixo ao canal de valor. (Não tenho certeza se isso realmente ajuda muito, mas foi simples o suficiente para adicionar)
Em seguida, aplico uma transformação de distância na imagem em primeiro plano.
e encontre todos os máximos locais nessa transformação de distância.
É aqui que minha ideia se desdobra. para evitar obter vários máximos locais dentro do mesmo grão, tenho que filtrar bastante. Atualmente, mantenho apenas o máximo mais forte em um raio de 45 pixels, o que significa que nem todo grão tem um máximo local. E eu realmente não tenho uma justificativa para o raio de 45 pixels, foi apenas um valor que funcionou.
(como você pode ver, essas sementes não são suficientes para dar conta de cada grão)
Então eu uso esses máximos como sementes para o algoritmo da bacia hidrográfica:
Os resultados são meh . Eu esperava principalmente grãos individuais, mas os pedaços ainda são grandes demais.
Agora identifico os menores blobs, conto seu tamanho médio de pixel e, em seguida, estimo o número de grãos a partir disso. Não era isso que eu planejava fazer no início, mas essa era a única maneira de salvar isso.
Um pequeno teste usando um tamanho de pixel por grão codificado de 2544,4 mostrou um erro total de 36, ainda maior do que a maioria das outras soluções.
fonte
HTML + Javascript: Pontuação 39
Os valores exatos são:
Ele divide (não é preciso) nos valores maiores.
Explicação: Basicamente, conta o número de pixels de arroz e o divide pela média de pixels por grão.
fonte
Uma tentativa com php, não é a resposta de pontuação mais baixa, mas seu código bastante simples
PONTUAÇÃO: 31
Auto-pontuação
95 é um valor azul que parecia funcionar ao testar com o GIMP 2966 o tamanho médio dos grãos
fonte