Considere estas 10 imágenes de varias cantidades de granos crudos de arroz blanco.
ESTAS SON SOLO MINIATURAS. Haga clic en una imagen para verla a tamaño completo.
Recuentos de granos: A: 3, B: 5, C: 12, D: 25, E: 50, F: 83, G: 120, H:150, I: 151, J: 200
Darse cuenta de...
- Los granos pueden tocarse entre sí, pero nunca se superponen. El diseño de los granos nunca tiene más de un grano de alto.
- Las imágenes tienen diferentes dimensiones, pero la escala del arroz en todas ellas es consistente porque la cámara y el fondo eran estacionarios.
- Los granos nunca salen de los límites ni tocan los límites de la imagen.
- El fondo es siempre el mismo tono consistente de blanco amarillento.
- Los granos pequeños y grandes se cuentan por igual como un grano cada uno.
Estos 5 puntos son garantías para todas las imágenes de este tipo.
Desafío
Escriba un programa que tome esas imágenes y, con la mayor precisión posible, cuente la cantidad de granos de arroz.
Su programa debe tomar el nombre de archivo de la imagen e imprimir la cantidad de granos que calcula. Su programa debe funcionar al menos para uno de estos formatos de archivo de imagen: JPEG, mapa de bits, PNG, GIF, TIFF (en este momento las imágenes son todas JPEG).
Usted puede utilizar las bibliotecas de procesamiento de imágenes y visión por ordenador.
No puede codificar las salidas de las 10 imágenes de ejemplo. Su algoritmo debe ser aplicable a todas las imágenes similares de grano de arroz. Debería poder ejecutarse en menos de 5 minutos en una computadora moderna decente si el área de la imagen es inferior a 2000 * 2000 píxeles y hay menos de 300 granos de arroz.
Puntuación
Para cada una de las 10 imágenes, tome el valor absoluto de la cantidad real de granos menos la cantidad de granos que predice su programa. Suma estos valores absolutos para obtener tu puntaje. El puntaje más bajo gana. Un puntaje de 0 es perfecto.
En caso de empate, gana la respuesta más votada. Puedo probar su programa en imágenes adicionales para verificar su validez y precisión.
fuente
Respuestas:
Mathematica, puntuación: 7
Creo que los nombres de las funciones son lo suficientemente descriptivos:
Procesando todas las imágenes a la vez:
El puntaje es:
Aquí puede ver la sensibilidad de la puntuación con el tamaño de grano utilizado:
fuente
EdgeDetect[]
,DeleteSmallComponents[]
yDilation[]
se implementan en otro lugar)Python, Puntuación:
24dieciséisEsta solución, como la de Falko, se basa en medir el área de "primer plano" y dividirla por el área de grano promedio.
De hecho, lo que este programa intenta detectar es el fondo, no tanto como el primer plano. Usando el hecho de que los granos de arroz nunca tocan el límite de la imagen, el programa comienza rellenando con blanco en la esquina superior izquierda. El algoritmo de relleno de inundación pinta píxeles adyacentes si la diferencia entre ellos y el brillo del píxel actual se encuentra dentro de un cierto umbral, ajustándose así a un cambio gradual en el color de fondo. Al final de esta etapa, la imagen podría verse así:
Como puede ver, hace un buen trabajo al detectar el fondo, pero deja fuera cualquier área que esté "atrapada" entre los granos. Manejamos estas áreas al estimar el brillo del fondo en cada píxel y al paitizar todos los píxeles iguales o más brillantes. Esta estimación funciona así: durante la etapa de inundación, calculamos el brillo de fondo promedio para cada fila y cada columna. El brillo de fondo estimado en cada píxel es el promedio del brillo de la fila y la columna en ese píxel. Esto produce algo como esto:
EDITAR: Finalmente, el área de cada región de primer plano continuo (es decir, no blanca) se divide por el área de grano promedio, precalculada, lo que nos da una estimación del recuento de granos en dicha región. La suma de estas cantidades es el resultado. Inicialmente, hicimos lo mismo para toda el área de primer plano en su conjunto, pero este enfoque es, literalmente, más detallado.
Toma el nombre de archivo de entrada a través de la línea de comando.
Resultados
fuente
avg_grain_area = 3038.38;
vienehardcoding 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.
Esto es simplemente un valor que representa esa regla. El resultado, sin embargo, cambia según la entrada. Si cambia la regla, este valor cambiará, pero el resultado será el mismo, según la entrada.Python + OpenCV: puntaje 27
Escaneo de linea horizontal
Idea: escanee la imagen, una fila a la vez. Para cada línea, cuente el número de granos de arroz encontrados (verificando si el píxel se vuelve negro a blanco o lo contrario). Si el número de granos para la línea aumenta (en comparación con la línea anterior), significa que encontramos un nuevo grano. Si ese número disminuye, significa que pasamos por alto un grano. En este caso, agregue +1 al resultado total.
Debido a la forma en que funciona el algoritmo, es importante tener una imagen limpia en blanco y negro. Mucho ruido produce malos resultados. Primero se limpia el fondo principal usando el relleno de inundación (solución similar a la respuesta de Ell), luego se aplica el umbral para producir un resultado en blanco y negro.
Está lejos de ser perfecto, pero produce buenos resultados con respecto a la simplicidad. Probablemente haya muchas maneras de mejorarlo (proporcionando una mejor imagen en blanco y negro, escaneando en otras direcciones (por ejemplo: vertical, diagonal) tomando el promedio, etc.)
Los errores por imagen: 0, 0, 0, 3, 0, 12, 4, 0, 7, 1
fuente
Python + OpenCV: Puntuación 84
Aquí hay un primer intento ingenuo. Aplica un umbral adaptativo con parámetros ajustados manualmente, cierra algunos agujeros con posterior erosión y dilución y deriva el número de granos del área de primer plano.
Aquí puede ver las imágenes binarias intermedias (el negro es primer plano):
Los errores por imagen son 0, 0, 2, 2, 4, 0, 27, 42, 0 y 7 granos.
fuente
C # + OpenCvSharp, Puntuación: 2
Este es mi segundo intento. Es bastante diferente de mi primer intento , que es mucho más simple, por lo que lo publico como una solución separada.
La idea básica es identificar y etiquetar cada grano individual mediante un ajuste de elipse iterativo. Luego, elimine los píxeles de este grano de la fuente e intente encontrar el siguiente grano, hasta que se haya etiquetado cada píxel.
Esta no es la solución más bonita. Es un cerdo gigante con 600 líneas de código. Necesita 1,5 minutos para la imagen más grande. Y realmente me disculpo por el código desordenado.
Hay tantos parámetros y formas de pensar en esto que tengo mucho miedo de sobreajustar mi programa para las 10 imágenes de muestra. El puntaje final de 2 es casi definitivamente un caso de sobreajuste: tengo dos parámetros,
average grain size in pixel
yminimum ratio of pixel / elipse_area
, al final, simplemente agoté todas las combinaciones de estos dos parámetros hasta que obtuve el puntaje más bajo. No estoy seguro de si esto es tan kosher con las reglas de este desafío.average_grain_size_in_pixel = 2530
pixel / elipse_area >= 0.73
Pero incluso sin estos embragues sobreajustados, los resultados son bastante buenos. Sin un tamaño de grano fijo o una relación de píxeles, simplemente estimando el tamaño de grano promedio a partir de las imágenes de entrenamiento, el puntaje sigue siendo 27.
Y obtengo como salida no solo el número, sino la posición, orientación y forma reales de cada grano. hay una pequeña cantidad de granos mal etiquetados, pero en general la mayoría de las etiquetas coinciden con precisión con los granos reales:
A B C D E
F G H I J
(haga clic en cada imagen para la versión de tamaño completo)
Después de este paso de etiquetado, mi programa analiza cada grano individual y las estimaciones basadas en el número de píxeles y la relación píxel / área de elipse, ya sea
Los puntajes de error para cada imagen son
A:0; B:0; C:0; D:0; E:2; F:0; G:0 ; H:0; I:0, J:0
Sin embargo, el error real es probablemente un poco más alto. Algunos errores dentro de la misma imagen se cancelan mutuamente. La imagen H en particular tiene algunos granos mal etiquetados, mientras que en la imagen E las etiquetas son en su mayoría correctas
El concepto es un poco artificial:
Primero, el primer plano se separa a través del umbral de otsu en el canal de saturación (vea mi respuesta anterior para más detalles)
repita hasta que no queden más píxeles:
elija 10 píxeles de borde aleatorio en este blob como posiciones iniciales para un grano
para cada punto de partida
Asumir un grano con altura y ancho de 10 píxeles en esta posición.
repetir hasta la convergencia
vaya radialmente hacia afuera desde este punto, en diferentes ángulos, hasta que encuentre un píxel de borde (blanco a negro)
Con suerte, los píxeles encontrados deberían ser los píxeles de borde de un solo grano. Intente separar los datos internos de los valores atípicos, descartando píxeles que estén más alejados de la elipse supuesta que los demás.
trate repetidamente de ajustar una elipse a través de un subconjunto de los signos internos, mantenga la mejor elipse (RANSACK)
Actualice la posición del grano, la orientación, el ancho y la altura con la elipse encontrada
Si la posición del grano no cambia significativamente, deténgase
entre los 10 granos ajustados, elija el mejor grano de acuerdo con la forma, el número de píxeles de borde. Descarta a los demás
elimine todos los píxeles de este grano de la imagen de origen, luego repita
finalmente, revise la lista de granos encontrados y cuente cada grano como 1 grano, 0 granos (demasiado pequeño) o 2 granos (demasiado grande)
Uno de mis principales problemas fue que no quería implementar una métrica de distancia de punto de elipse completa, ya que calcular eso en sí mismo es un proceso iterativo complicado. Así que utilicé varias soluciones usando las funciones de OpenCV Ellipse2Poly y FitEllipse, y los resultados no son demasiado bonitos.
Aparentemente también rompí el límite de tamaño para codegolf.
Una respuesta está limitada a 30000 caracteres, actualmente estoy en 34000. Así que tendré que acortar un poco el código a continuación.
El código completo se puede ver en http://pastebin.com/RgM7hMxq
Lo siento, no sabía que había un límite de tamaño.
Estoy un poco avergonzado con esta solución porque a) no estoy seguro de si está dentro del espíritu de este desafío, yb) es demasiado grande para una respuesta de codegolf y carece de la elegancia de las otras soluciones.
Por otro lado, estoy bastante contento con el progreso que logré en el etiquetado de los granos, no solo en contarlos, así que eso es todo.
fuente
C ++, OpenCV, puntaje: 9
La idea básica de mi método es bastante simple: intente borrar granos individuales (y "granos dobles" - 2 (¡pero no más!) Granos, cerca uno del otro) de la imagen y luego cuente el descanso usando el método basado en el área (como Falko, Ell y Belisario). Usar este enfoque es un poco mejor que el "método de área" estándar, porque es más fácil encontrar un buen valor promedio de PixelesPerObjeto.
(1er paso) Primero que nada necesitamos usar la binarización de Otsu en el canal S de imagen en HSV. El siguiente paso es usar el operador de dilatación para mejorar la calidad del primer plano extraído. De lo que necesitamos encontrar contornos. Por supuesto, algunos contornos no son granos de arroz: necesitamos eliminar contornos demasiado pequeños (con un área más pequeña que el promedio de píxeles por objeto / 4. El promedio de píxeles por objeto es 2855 en mi situación). Ahora finalmente podemos comenzar a contar granos :) (2do paso) Encontrar granos simples y dobles es bastante simple: solo busque en la lista de contornos los contornos con área dentro de rangos específicos; si el área de contorno está dentro del rango, elimínelo de la lista y agregue 1 (o 2 si era "doble" de grano) al contador de granos. (3er paso) El último paso es, por supuesto, dividir el área de los contornos restantes por el valor promedio de PixelPerObject y agregar el resultado al contador de granos.
Las imágenes (para la imagen F.jpg) deberían mostrar esta idea mejor que las palabras:
primer paso (sin contornos pequeños (ruido)):
segundo paso - solo contornos simples:
tercer paso - contornos restantes:
Aquí está el código, es bastante feo, pero debería funcionar sin ningún problema. Por supuesto, se requiere OpenCV.
Si desea ver los resultados de todos los pasos, elimine el comentario de todas las llamadas a funciones imshow (.., ..) y establezca la variable fastProcessing en false. Las imágenes (A.jpg, B.jpg, ...) deben estar en las imágenes del directorio. Alternativamente, puede dar el nombre de una imagen como parámetro desde la línea de comandos.
Por supuesto, si algo no está claro, puedo explicarlo y / o proporcionar algunas imágenes / información.
fuente
C # + OpenCvSharp, puntuación: 71
Esto es muy irritante, traté de obtener una solución que realmente identifique cada grano usando la cuenca hidrográfica , pero simplemente. hipocresía. obtener. eso. a. trabajo.
Me decidí por una solución que al menos separa algunos granos individuales y luego usa esos granos para estimar el tamaño promedio de grano. Sin embargo, hasta ahora no puedo superar las soluciones con un tamaño de grano codificado.
Entonces, lo más destacado de esta solución: no supone un tamaño de píxel fijo para los granos, y debería funcionar incluso si se mueve la cámara o se cambia el tipo de arroz.
Mi solución funciona así:
Separe el primer plano transformando la imagen a HSV y aplicando el umbral de Otsu en el canal de saturación. Esto es muy simple, funciona extremadamente bien, y lo recomendaría a todos los que quieran probar este desafío:
->
Esto eliminará limpiamente el fondo.
Luego, además, eliminé las sombras de grano del primer plano, aplicando un umbral fijo al canal de valores. (No estoy seguro si eso realmente ayuda mucho, pero fue lo suficientemente simple como para agregar)
Luego aplico una transformación de distancia en la imagen de primer plano.
y encuentre todos los máximos locales en esta transformación de distancia.
Aquí es donde se rompe mi idea. para evitar obtener máximos locales múltiples dentro del mismo grano, tengo que filtrar mucho. Actualmente mantengo solo el máximo más fuerte dentro de un radio de 45 píxeles, lo que significa que no todos los granos tienen un máximo local. Y realmente no tengo una justificación para el radio de 45 píxeles, solo fue un valor que funcionó.
(Como puede ver, esas no son semillas suficientes para dar cuenta de cada grano)
Luego uso esos máximos como semillas para el algoritmo de cuenca:
Los resultados son meh . Esperaba principalmente granos individuales, pero los grupos todavía son demasiado grandes.
Ahora identifico los blobs más pequeños, cuento su tamaño de píxel promedio y luego calculo el número de granos a partir de eso. Esto no es lo que planeé hacer al principio, pero esta era la única forma de salvarlo.
Una pequeña prueba con un tamaño de píxel por grano codificado de 2544.4 mostró un error total de 36, que aún es más grande que la mayoría de las otras soluciones.
fuente
HTML + Javascript: Puntuación 39
Los valores exactos son:
Se descompone (no es preciso) en los valores más grandes.
Explicación: Básicamente, cuenta el número de píxeles de arroz y lo divide por el promedio de píxeles por grano.
fuente
Un intento con php, no la respuesta con la puntuación más baja, sino su código bastante simple
PUNTUACIÓN: 31
Auto puntuación
95 es un valor azul que parece funcionar cuando se prueba con GIMP 2966 es el tamaño de grano promedio
fuente