Considérez ces 10 images de différentes quantités de grains de riz blanc non cuits.
CE SONT SEULEMENT DES PATTES. Cliquez sur une image pour la voir en taille réelle.
Nombre de grains: A: 3, B: 5, C: 12, D: 25, E: 50, F: 83, G: 120, H:150, I: 151, J: 200
Remarquerez que...
- Les grains peuvent se toucher mais ils ne se chevauchent jamais. La disposition des grains n’est jamais supérieure à un grain.
- Les images ont des dimensions différentes, mais l’échelle du riz est cohérente car la caméra et le fond étaient immobiles.
- Les grains ne sortent jamais des limites et ne touchent pas les limites de l'image.
- Le fond est toujours la même nuance consistante de blanc jaunâtre.
- Les petits et les gros grains sont comptés de la même manière comme un grain chacun.
Ces 5 points sont des garanties pour toutes les images de ce type.
Défi
Ecrivez un programme qui prend de telles images et, le plus précisément possible, compte le nombre de grains de riz.
Votre programme devrait prendre le nom de fichier de l'image et imprimer le nombre de grains qu'il calcule. Votre programme doit fonctionner pour au moins un des formats de fichier image suivants: JPEG, Bitmap, PNG, GIF, TIFF (pour l’instant, les images sont toutes au format JPEG).
Vous pouvez utiliser des bibliothèques de traitement d'images et de vision par ordinateur.
Vous ne pouvez pas coder en dur les sorties des 10 exemples d’images. Votre algorithme devrait être applicable à toutes les images de grain de riz similaires. Il devrait pouvoir fonctionner en moins de 5 minutes sur un ordinateur moderne convenable si la zone d'image est inférieure à 2 000 * 2 000 pixels et s'il y a moins de 300 grains de riz.
Notation
Pour chacune des 10 images, prenez la valeur absolue du nombre réel de grains moins le nombre de grains prédit par votre programme. Faites la somme de ces valeurs absolues pour obtenir votre score. Le score le plus bas gagne. Un score de 0 est parfait.
En cas d'égalité, la réponse la plus votée l'emporte. Je peux tester votre programme sur des images supplémentaires pour vérifier sa validité et son exactitude.
la source
Réponses:
Mathematica, score: 7
Je pense que les noms des fonctions sont assez descriptifs:
Traitement de toutes les images à la fois:
Le score est:
Vous pouvez voir ici la sensibilité de la partition par rapport à la taille de grain utilisée:
la source
EdgeDetect[]
,DeleteSmallComponents[]
etDilation[]
sont mises en œuvre ailleurs)Python, Score:
2416Cette solution, à l'instar de celle de Falko, consiste à mesurer la surface "au premier plan" et à la diviser par la surface moyenne des grains.
En fait, ce que ce programme essaie de détecter, c’est l’arrière-plan et non le premier plan. En utilisant le fait que les grains de riz ne touchent jamais la limite de l'image, le programme commence par un remplissage blanc dans le coin supérieur gauche. L'algorithme de remplissage par inondation peint les pixels adjacents si la différence entre la leur et la luminosité du pixel actuel est inférieure à un certain seuil, s'adaptant ainsi au changement progressif de la couleur d'arrière-plan. À la fin de cette étape, l'image pourrait ressembler à ceci:
Comme vous pouvez le constater, il détecte très bien l’arrière-plan, mais il ne tient pas compte des zones "piégées" entre les grains. Nous traitons ces zones en estimant la luminosité de l'arrière-plan à chaque pixel et en détectant tous les pixels égaux ou plus lumineux. Cette estimation fonctionne comme suit: pendant la phase de remplissage, nous calculons la luminosité moyenne de l'arrière-plan pour chaque ligne et chaque colonne. La luminosité de fond estimée à chaque pixel est la moyenne de la luminosité des lignes et des colonnes à ce pixel. Cela produit quelque chose comme ceci:
EDIT: Enfin, la surface de chaque région de premier plan continue (c’est-à-dire non blanche) est divisée par la surface moyenne, pré-calculée, des grains, ce qui nous donne une estimation du nombre de grains dans ladite région. La somme de ces quantités est le résultat. Au départ, nous avons fait la même chose pour l’ensemble du premier plan, mais cette approche est littéralement plus fine.
Prend le nom du fichier d'entrée à travers la ligne de commande.
Résultats
la source
avg_grain_area = 3038.38;
vient-il?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.
Il s'agit simplement d'une valeur qui représente cette règle. Le résultat, cependant, change en fonction de l'entrée. Si vous modifiez la règle, cette valeur changera, mais le résultat sera le même - en fonction de l'entrée.Python + OpenCV: Score 27
Balayage de ligne horizontale
Idée: numérisez l'image, une ligne à la fois. Pour chaque ligne, comptez le nombre de grains de riz rencontrés (en vérifiant si le pixel passe du noir au blanc ou l'inverse). Si le nombre de grains pour la ligne augmente (par rapport à la ligne précédente), cela signifie que nous avons rencontré un nouveau grain. Si ce nombre diminue, cela signifie que nous sommes passés au-dessus d'un grain. Dans ce cas, ajoutez +1 au résultat total.
En raison du fonctionnement de l’algorithme, il est important d’avoir une image nette en noir et blanc. Beaucoup de bruit produit de mauvais résultats. Le premier fond principal est nettoyé à l’aide de la méthode Floodfill (solution similaire à Ell answer), puis le seuil est appliqué pour produire un résultat noir et blanc.
C'est loin d'être parfait, mais cela donne de bons résultats en matière de simplicité. Il y a probablement plusieurs façons de l'améliorer (en fournissant une meilleure image en noir et blanc, en effectuant un balayage dans d'autres directions (par exemple: verticale, diagonale) en prenant la moyenne, etc.).
Les erreurs par image: 0, 0, 0, 3, 0, 12, 4, 0, 7, 1
la source
Python + OpenCV: Score 84
Voici une première tentative naïve. Il applique un seuil adaptatif avec des paramètres réglés manuellement, ferme certains trous avec érosion et dilution ultérieures et déduit le nombre de grains de la zone de premier plan.
Ici vous pouvez voir les images binaires intermédiaires (le noir est au premier plan):
Les erreurs par image sont 0, 0, 2, 2, 4, 0, 27, 42, 0 et 7 grains.
la source
C # + OpenCvSharp, Score: 2
C'est ma deuxième tentative. C'est assez différent de ma première tentative , qui est beaucoup plus simple, alors je la publie comme une solution séparée.
L'idée de base est d'identifier et d'étiqueter chaque grain individuellement par un ajustement ellipse itératif. Supprimez ensuite les pixels pour ce grain de la source et essayez de trouver le grain suivant, jusqu'à ce que chaque pixel soit étiqueté.
Ce n'est pas la plus jolie solution. C'est un porc géant avec 600 lignes de code. Il faut 1,5 minute pour la plus grande image. Et je m'excuse vraiment pour le code en désordre.
Il y a tellement de paramètres et de façons de penser en cette matière que j'ai bien peur de trop adapter mon programme pour les 10 exemples d'images. Le résultat final de 2 est presque définitivement un cas de surajustement: j'ai deux paramètres
average grain size in pixel
etminimum ratio of pixel / elipse_area
, à la fin, j'ai simplement épuisé toutes les combinaisons de ces deux paramètres jusqu'à obtenir le score le plus bas. Je ne suis pas sûr que ce soit tout ce qui casher avec les règles de ce défi.average_grain_size_in_pixel = 2530
pixel / elipse_area >= 0.73
Mais même sans ces embrayages trop lourds, les résultats sont plutôt bons. Sans une taille de grain ou un ratio de pixels fixe, simplement en estimant la taille de grain moyenne à partir des images d'apprentissage, le score est toujours de 27.
Et je reçois en sortie non seulement le nombre, mais la position réelle, l'orientation et la forme de chaque grain. il y a un petit nombre de grains mal étiquetés, mais dans l'ensemble, la plupart des étiquettes correspondent exactement aux grains réels:
A B C D E
F G H I J
(cliquez sur chaque image pour la version agrandie)
Après cette étape d’étiquetage, mon programme examine chaque grain et évalue chaque estimation en fonction du nombre de pixels et du rapport pixels / ellipse.
Les scores d'erreur pour chaque image sont
A:0; B:0; C:0; D:0; E:2; F:0; G:0 ; H:0; I:0, J:0
Cependant, l'erreur réelle est probablement un peu plus élevée. Certaines erreurs dans la même image s’annulent. L'image H en particulier présente des grains mal étiquetés, alors que dans l'image E les étiquettes sont généralement correctes
Le concept est un peu artificiel:
D'abord, le premier plan est séparé via un otsu-seuillage sur le canal de saturation (voir ma réponse précédente pour plus de détails)
répétez jusqu'à ce qu'il ne reste plus de pixels:
choisissez 10 pixels de bord aléatoires sur cette goutte en tant que positions de départ pour un grain
pour chaque point de départ
supposons un grain avec une hauteur et une largeur de 10 pixels à cette position.
répéter jusqu'à convergence
allez radialement vers l'extérieur à partir de ce point, sous différents angles, jusqu'à rencontrer un pixel de bord (blanc à noir)
les pixels trouvés devraient, espérons-le, être les pixels de bord d'un seul grain. Essayez de séparer les éléments internes des valeurs éloignées, en éliminant les pixels qui sont plus éloignés de l’ellipse supposée que les autres.
essayez à plusieurs reprises de faire passer une ellipse dans un sous-ensemble d'inliers, conservez la meilleure ellipse (RANSACK)
mettre à jour la position du grain, l'orientation, la largeur et la hauteur avec l'élipse trouvé
si la position du grain ne change pas de manière significative, arrêtez
parmi les 10 grains ajustés, choisissez le meilleur grain en fonction de la forme, du nombre de pixels de bord. Jeter les autres
supprimer tous les pixels pour ce grain de l'image source, puis répétez
enfin, parcourez la liste des grains trouvés et comptez chaque grain comme 1 grain, 0 grain (trop petit) ou 2 grains (trop gros)
L'un de mes principaux problèmes était que je ne souhaitais pas mettre en œuvre une métrique de distance point-ellipse complète, car le calcul de cette distance est en soi un processus itératif complexe. J'ai donc utilisé diverses solutions de contournement à l'aide des fonctions OpenCV Ellipse2Poly et FitEllipse, et les résultats ne sont pas très beaux.
Apparemment, j'ai également dépassé la limite de taille pour codegolf.
Une réponse est limitée à 30000 caractères, je suis actuellement à 34000. Je vais donc devoir raccourcir un peu le code ci-dessous.
Le code complet peut être vu à http://pastebin.com/RgM7hMxq
Désolé, je ne savais pas qu'il y avait une limite de taille.
Je suis un peu gêné par cette solution car a) je ne suis pas sûr que cela soit dans l’esprit de ce défi et b) il est trop gros pour une réponse codegolf et manque de l’élégance des autres solutions.
D'autre part, je suis assez satisfait des progrès que j'ai accomplis dans l' étiquetage des grains, pas seulement en les comptant, alors c'est tout.
la source
C ++, OpenCV, score: 9
L’idée de base de ma méthode est assez simple - essayez d’effacer les grains simples (et les «grains doubles» - 2 grains (mais pas plus!), Proches les uns des autres) de l’image puis de compter le repos en utilisant une méthode basée sur la surface (comme Falko, Ell et Bélisarius). L'utilisation de cette approche est un peu meilleure que la "méthode d'aire" standard, car il est plus facile de trouver une bonne valeur moyennePixelsPerObject.
(1ère étape) Avant tout, nous devons utiliser la binarisation Otsu sur le canal S de l’image en HSV. L'étape suivante consiste à utiliser l'opérateur dilate pour améliorer la qualité de l'avant-plan extrait. Que nous devons trouver des contours. Bien sûr, certains contours ne sont pas des grains de riz - nous devons supprimer les contours trop petits (avec une surface inférieure à averagePixelsPerObject / 4. AveragePixelsPerObject est de 2855 dans mon cas). Enfin, nous pouvons enfin commencer à compter les grains :) (2ème étape) La recherche de grains simples et doubles est assez simple - il suffit de regarder dans la liste des contours les contours avec une zone dans des plages spécifiques - si la zone de contour est dans la plage, supprimez-la de la liste et ajoutez 1 (ou 2 si c'était "double" grain) au compteur de grains. (3ème étape) La dernière étape est bien sûr de diviser la surface des contours restants par la valeur moyennePixelsPerObject et d'ajouter le résultat au compteur de grains.
Les images (pour l'image F.jpg) devraient montrer cette idée mieux que les mots:
1ère étape (sans petits contours (bruit)):
2ème étape - uniquement des contours simples:
3ème étape - contours restants:
Voici le code, c'est plutôt moche, mais devrait fonctionner sans problème. Bien sûr, OpenCV est requis.
Si vous souhaitez voir les résultats de toutes les étapes, supprimez la mise en commentaire de tous les appels de fonction imshow (.., ..) et définissez la variable fastProcessing sur false. Les images (A.jpg, B.jpg, ...) doivent être dans des images de répertoire. Vous pouvez également choisir le nom d’une image en tant que paramètre à partir de la ligne de commande.
Bien sûr, si quelque chose n'est pas clair, je peux l'expliquer et / ou fournir des images / informations.
la source
C # + OpenCvSharp, score: 71
C’est très frustrant, j’ai essayé de trouver une solution permettant d’identifier chaque grain à l’aide d’un bassin versant , mais j’ai juste. ne peut pas. obtenir. il. à. travail.
J'ai opté pour une solution qui sépare au moins quelques grains individuels, puis utilise ces grains pour estimer la taille moyenne des grains. Cependant, jusqu'à présent, je ne peux pas battre les solutions avec une taille de grain codée en dur.
Donc, le point fort de cette solution: elle ne suppose pas une taille de pixel fixe pour les grains et devrait fonctionner même si la caméra est déplacée ou si le type de riz est modifié.
Ma solution fonctionne comme ceci:
Séparez le premier plan en transformant l'image en HSV et en appliquant un seuillage Otsu sur le canal de saturation. Ceci est très simplement, fonctionne extrêmement bien, et je le recommanderais à tous ceux qui veulent essayer ce défi:
->
Cela supprimera proprement l'arrière-plan.
J'ai ensuite supprimé les ombres de grain du premier plan en appliquant un seuil fixe au canal de valeur. (Je ne sais pas si cela aide beaucoup, mais c'était assez simple à ajouter)
Ensuite, j'applique une transformation de distance sur l'image au premier plan.
et trouver tous les maxima locaux dans cette distance transformer.
C'est là que mon idée s'effondre. pour ne pas avoir plusieurs maxima locaux dans le même grain, je dois filtrer beaucoup. Actuellement, je ne garde que le maximum le plus fort dans un rayon de 45 pixels, ce qui signifie que tous les grains n'ont pas de maximum local. Et je n'ai pas vraiment de justification pour le rayon de 45 pixels, c'était juste une valeur qui a fonctionné.
(comme vous pouvez le constater, ce ne sont pas assez de semences pour tenir compte de chaque grain)
Ensuite, j'utilise ces maxima comme semences de l'algorithme de gestion des bassins versants:
Les résultats sont meh . J'espérais surtout des grains individuels, mais les touffes sont encore trop grosses.
Maintenant, j'identifie les plus petites gouttes, compte leur taille moyenne en pixels, puis en estime le nombre de grains. Ce n’était pas ce que j’avais prévu de faire au début, mais c’était le seul moyen de sauver cela.
Un petit test utilisant une taille de pixel par grain codée en dur de 2544,4 a montré une erreur totale de 36, ce qui est encore plus grand que la plupart des autres solutions.
la source
HTML + Javascript: Score 39
Les valeurs exactes sont:
Il se décompose (n'est pas précis) sur les plus grandes valeurs.
Explication: En gros, compte le nombre de pixels du riz et le divise par le nombre moyen de pixels par grain.
la source
Une tentative avec php, pas la réponse la plus basse mais son code assez simple
SCORE: 31
Auto-marquant
95 est une valeur bleue qui semblait fonctionner lorsque les tests avec GIMP 2966 étaient une taille de grain moyenne
la source