Rozważ te 10 zdjęć różnych ilości niegotowanych ziaren białego ryżu.
TO TYLKO TRZEWIKI. Kliknij obraz, aby wyświetlić go w pełnym rozmiarze.
Ziarna: A: 3, B: 5, C: 12, D: 25, E: 50, F: 83, G: 120, H:150, I: 151, J: 200
Zauważ, że...
- Ziarna mogą się stykać, ale nigdy się nie nakładają. Układ ziaren nigdy nie przekracza wysokości jednego ziarna.
- Obrazy mają różne wymiary, ale skala ryżu we wszystkich jest spójna, ponieważ kamera i tło były nieruchome.
- Ziarna nigdy nie wychodzą poza granice i nie dotykają granic obrazu.
- Tło ma zawsze ten sam spójny odcień żółtawo-biały.
- Małe i duże ziarna są liczone podobnie jak jedno ziarno.
Te 5 punktów jest gwarancją dla wszystkich obrazów tego rodzaju.
Wyzwanie
Napisz program, który pobiera takie obrazy i, tak dokładnie, jak to możliwe, liczy liczbę ziaren ryżu.
Twój program powinien pobrać nazwę pliku obrazu i wydrukować obliczoną liczbę ziaren. Twój program musi działać z co najmniej jednym z następujących formatów plików graficznych: JPEG, Bitmap, PNG, GIF, TIFF (obecnie wszystkie obrazy to JPEG).
Państwo może korzystać z bibliotek przetwarzania obrazu i wizyjne komputer.
Nie można zakodować na stałe wyników 10 przykładowych obrazów. Twój algorytm powinien mieć zastosowanie do wszystkich podobnych obrazów ziaren ryżu. Powinien być w stanie działać w mniej niż 5 minut na przyzwoitym nowoczesnym komputerze jeśli obszar obrazu jest mniejszy niż 2000 * 2000 pikseli i jest mniej niż 300 ziaren ryżu.
Punktacja
Dla każdego z 10 zdjęć weź wartość bezwzględną rzeczywistej liczby ziaren minus liczbę ziaren przewidzianych przez Twój program. Zsumuj te wartości bezwzględne, aby uzyskać wynik. Najniższy wynik wygrywa. Wynik 0 jest idealny.
W przypadku remisu wygrywa najwyższa głosowana odpowiedź. Mogę przetestować Twój program na dodatkowych obrazach, aby zweryfikować jego ważność i dokładność.
źródło
Odpowiedzi:
Mathematica, wynik: 7
Myślę, że nazwy funkcji są wystarczająco opisowe:
Przetwarzanie wszystkich zdjęć jednocześnie:
Wynik to:
Tutaj możesz zobaczyć czułość punktową względem użytej wielkości ziarna:
źródło
EdgeDetect[]
,DeleteSmallComponents[]
iDilation[]
są realizowane w innym miejscu)Python, wynik:
2416To rozwiązanie, podobnie jak Falko, polega na pomiarze obszaru „pierwszego planu” i podzieleniu go przez średni obszar ziarna.
W rzeczywistości program próbuje wykryć tło, a nie pierwszy plan. Biorąc pod uwagę fakt, że ziarna ryżu nigdy nie dotykają granicy obrazu, program rozpoczyna się od wypełnienia zalewającego białego w lewym górnym rogu. Algorytm zalewania maluje sąsiednie piksele, jeśli różnica między ich jasnością a bieżącym pikselem mieści się w określonym progu, dostosowując się w ten sposób do stopniowej zmiany koloru tła. Pod koniec tego etapu obraz może wyglądać mniej więcej tak:
Jak widać, wykrywanie tła robi całkiem dobrą robotę, ale pomija obszary, które są „uwięzione” między ziarnami. Obsługujemy te obszary, szacując jasność tła na każdym pikselu i dopasowując wszystkie równe lub jaśniejsze piksele. Oszacowanie działa w ten sposób: podczas etapu wypełniania zalewamy obliczamy średnią jasność tła dla każdego wiersza i każdej kolumny. Szacowana jasność tła dla każdego piksela jest średnią jasnością wiersza i kolumny dla tego piksela. To powoduje coś takiego:
EDYCJA: W końcu obszar każdego ciągłego obszaru pierwszego planu (tj. Niebiałego) jest podzielony przez średnią, wstępnie obliczoną powierzchnię ziarna, dając nam oszacowanie liczby ziaren w tym regionie. Suma tych ilości jest wynikiem. Początkowo robiliśmy to samo dla całego obszaru pierwszego planu jako całości, ale to podejście jest dosłownie bardziej szczegółowe.
Wprowadza nazwę pliku wejściowego przez wiersz polecenia.
Wyniki
źródło
avg_grain_area = 3038.38;
pochodzi?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.
Jest to tylko wartość reprezentująca tę regułę. Wynik zmienia się jednak zgodnie z danymi wejściowymi. Jeśli zmienisz regułę, wartość ta ulegnie zmianie, ale wynik będzie taki sam - na podstawie danych wejściowych.Python + OpenCV: wynik 27
Skanowanie linii poziomej
Pomysł: zeskanuj obraz, jeden rząd na raz. Dla każdej linii policz liczbę napotkanych ziaren ryżu (sprawdzając, czy piksel zmienia kolor z czarnego na biały lub odwrotnie). Jeśli liczba ziaren dla linii wzrośnie (w porównaniu do poprzedniej linii), oznacza to, że napotkaliśmy nowe ziarno. Jeśli ta liczba spadnie, oznacza to, że przeszliśmy przez ziarno. W takim przypadku dodaj +1 do całkowitego wyniku.
Ze względu na sposób działania algorytmu ważne jest, aby mieć czysty, czarno-biały obraz. Dużo hałasu powoduje złe wyniki. Pierwsze główne tło jest czyszczone za pomocą wypełnienia zalewowego (rozwiązanie podobne do odpowiedzi Ell), a następnie stosuje się próg, aby uzyskać czarno-biały wynik.
Jest daleki od ideału, ale daje dobre wyniki w zakresie prostoty. Prawdopodobnie istnieje wiele sposobów na jego poprawę (poprzez zapewnienie lepszego obrazu czarno-białego, skanowanie w innych kierunkach (np .: pionowy, ukośny) przy średniej itp.)
Błędy na obraz: 0, 0, 0, 3, 0, 12, 4, 0, 7, 1
źródło
Python + OpenCV: Wynik 84
Oto pierwsza naiwna próba. Stosuje próg adaptacyjny z ręcznie dostrojonymi parametrami, zamyka niektóre otwory z późniejszą erozją i rozcieńczaniem oraz wyprowadza liczbę ziaren z obszaru pierwszego planu.
Tutaj możesz zobaczyć pośrednie obrazy binarne (czarny jest na pierwszym planie):
Błędy na obraz to 0, 0, 2, 2, 4, 0, 27, 42, 0 i 7 ziaren.
źródło
C # + OpenCvSharp, wynik: 2
To moja druga próba. Zupełnie różni się od mojej pierwszej próby , która jest o wiele prostsza, więc zamieszczam to jako osobne rozwiązanie.
Podstawową ideą jest identyfikacja i oznakowanie każdego pojedynczego ziarna za pomocą iteracyjnego dopasowania elipsy. Następnie usuń piksele tego ziarna ze źródła i spróbuj znaleźć następne ziarno, aż każdy piksel zostanie oznaczony.
To nie jest najpiękniejsze rozwiązanie. To gigantyczny wieprz z 600 liniami kodu. Największy obraz potrzebuje 1,5 minuty. I naprawdę przepraszam za ten niechlujny kod.
Jest tak wiele parametrów i sposobów myślenia w tej rzeczy, że obawiam się, że mój program nie pasuje do 10 przykładowych obrazów. Końcowy wynik 2 jest prawie na pewno przypadkiem przeregulowania: mam dwa parametry
average grain size in pixel
, iminimum ratio of pixel / elipse_area
, a na koniec po prostu wyczerpały wszystkie kombinacje tych dwóch parametrów, dopóki nie dostał najniższą ocenę. Nie jestem pewien, czy to wszystko koszerne z zasadami tego wyzwania.average_grain_size_in_pixel = 2530
pixel / elipse_area >= 0.73
Ale nawet bez tych zbyt mocnych sprzęgieł wyniki są całkiem niezłe. Bez ustalonego rozmiaru ziarna lub stosunku pikseli, po prostu poprzez oszacowanie średniego rozmiaru ziarna na podstawie obrazów treningowych, wynik nadal wynosi 27.
I otrzymuję jako wynik nie tylko liczbę, ale rzeczywistą pozycję, orientację i kształt każdego ziarna. istnieje niewielka liczba źle oznakowanych ziaren, ale ogólnie większość etykiet dokładnie odpowiada rzeczywistym ziarnom:
A B C D E
F G H I J
(kliknij na każde zdjęcie, aby wyświetlić wersję w pełnym rozmiarze)
Po tym etapie etykietowania mój program analizuje każde ziarno i ocenia na podstawie liczby pikseli i stosunku pikseli do pola elipsy, niezależnie od tego, czy jest to
Oceny błędów dla każdego obrazu wynoszą
A:0; B:0; C:0; D:0; E:2; F:0; G:0 ; H:0; I:0, J:0
Jednak rzeczywisty błąd jest prawdopodobnie nieco wyższy. Niektóre błędy w tym samym obrazie znoszą się nawzajem. W szczególności obraz H ma pewne źle oznakowane ziarna, podczas gdy na zdjęciu E etykiety są w większości prawidłowe
Pomysł jest trochę wymyślony:
Najpierw pierwszy plan jest oddzielany za pomocą progu otsu na kanale nasycenia (szczegóły w mojej poprzedniej odpowiedzi)
powtarzaj, aż nie pozostanie więcej pikseli:
wybierz 10 losowych pikseli krawędzi na tej kropli jako pozycje początkowe dla ziarna
dla każdego punktu początkowego
załóż ziarno o wysokości i szerokości 10 pikseli w tej pozycji.
powtarzaj aż do konwergencji
idź od tego punktu promieniowo na zewnątrz, pod różnymi kątami, aż napotkasz piksel krawędzi (biały do czarnego)
znalezione piksele powinny mieć nadzieję, że będą pikselami brzegowymi pojedynczego ziarna. Spróbuj oddzielić wartości odstające od wartości odstających, odrzucając piksele, które są bardziej odległe od zakładanej elipsy niż inne
wielokrotnie próbuj dopasować elipsę przez podzbiór wartości wewnętrznych, zachowaj najlepszą elipsę (RANSACK)
zaktualizuj położenie, orientację, szerokość i wysokość ziarna za pomocą znalezionej elipsy
jeśli pozycja ziarna nie zmienia się znacząco, zatrzymać
spośród 10 dopasowanych ziaren wybierz najlepsze ziarno według kształtu, liczby pikseli krawędzi. Odrzuć pozostałe
usuń wszystkie piksele tego ziarna z obrazu źródłowego, a następnie powtórz
na koniec przejrzyj listę znalezionych ziaren i policz każde ziarno jako 1 ziarno, 0 ziaren (za małe) lub 2 ziarna (za duże)
Jednym z moich głównych problemów było to, że nie chciałem implementować pełnej miary odległości w punkcie elipsy, ponieważ obliczenie, że samo w sobie jest skomplikowanym procesem iteracyjnym. Użyłem więc różnych obejść przy użyciu funkcji OpenCV Ellipse2Poly i FitEllipse, a wyniki nie są zbyt ładne.
Najwyraźniej przekroczyłem również limit rozmiaru dla codegolf.
Odpowiedź jest ograniczona do 30000 znaków, obecnie mam 34000. Więc będę musiał nieco skrócić poniższy kod.
Pełny kod można zobaczyć na stronie http://pastebin.com/RgM7hMxq
Przepraszam za to, nie wiedziałem, że istnieje limit rozmiaru.
Jestem trochę zawstydzony tym rozwiązaniem, ponieważ a) nie jestem pewien, czy jest to zgodne z duchem tego wyzwania, i b) jest zbyt duże, aby odpowiedzieć na codegolf i brakuje mu elegancji innych rozwiązań.
Z drugiej strony jestem całkiem zadowolony z postępu, jaki osiągnąłem w etykietowaniu ziaren, a nie tylko ich liczeniu, więc jest.
źródło
C ++, OpenCV, wynik: 9
Podstawowa idea mojej metody jest dość prosta - spróbuj usunąć pojedyncze ziarna (i „podwójne ziarna” - 2 (ale nie więcej!) Ziarna, blisko siebie) z obrazu, a następnie policz resztę za pomocą metody opartej na powierzchni (np. Falko, Ell i Belizariusz). Korzystanie z tego podejścia jest nieco lepsze niż standardowa „metoda obszarowa”, ponieważ łatwiej jest znaleźć dobrą średnią wartośćPixelsPerObject.
(1. krok) Przede wszystkim musimy zastosować binaryzację Otsu na kanale S obrazu w HSV. Kolejnym krokiem jest użycie operatora dylatacji w celu poprawy jakości wyodrębnionego pierwszego planu. Niż musimy znaleźć kontury. Oczywiście niektóre kontury nie są ziarnami ryżu - musimy usunąć zbyt małe kontury (o powierzchni mniejszej niż średniaPixelsPerObject / 4. W mojej sytuacji średniaPixelsPerObject to 2855). Teraz w końcu możemy zacząć zliczać ziarna :) (drugi krok) Znalezienie pojedynczych i podwójnych ziaren jest dość proste - wystarczy spojrzeć na liście konturów, aby uzyskać kontury o obszarze w określonych zakresach - jeśli obszar konturu jest w zasięgu, usuń go z listy i dodaj 1 (lub 2, jeśli było to „podwójne” ziarno) do licznika ziaren. (3. krok) Ostatnim krokiem jest oczywiście podzielenie obszaru pozostałych konturów przez wartość średniąPixelsPerObject i dodanie wyniku do licznika ziaren.
Obrazy (dla obrazu F.jpg) powinny lepiej przedstawiać ten pomysł niż słowa:
1. krok (bez małych konturów (szumów)):
2. krok - tylko proste kontury:
3. krok - pozostałe kontury:
Oto kod, jest raczej brzydki, ale powinien działać bez problemu. Oczywiście wymagany jest OpenCV.
Jeśli chcesz zobaczyć wyniki wszystkich kroków, odkomentuj wszystkie wywołania funkcji imshow (.., ..) i ustaw zmienną fastProcessing na wartość false. Obrazy (A.jpg, B.jpg, ...) powinny znajdować się w obrazach katalogowych. Alternatywnie możesz podać nazwę jednego obrazu jako parametr z wiersza poleceń.
Oczywiście, jeśli coś jest niejasne, mogę to wyjaśnić i / lub dostarczyć trochę zdjęć / informacji.
źródło
C # + OpenCvSharp, wynik: 71
To jest bardzo irytujące, próbowałem znaleźć rozwiązanie, które faktycznie identyfikuje każde ziarno za pomocą zlewu , ale po prostu. żargon. otrzymać. to. do. praca.
Postanowiłem znaleźć rozwiązanie, które przynajmniej rozdzieli niektóre pojedyncze ziarna, a następnie wykorzystuje je do oszacowania średniej wielkości ziarna. Jednak do tej pory nie jestem w stanie pokonać rozwiązań o twardej ziarnistości.
Główną zaletą tego rozwiązania jest to, że nie zakłada ono stałego rozmiaru piksela dla ziaren i powinno działać nawet po przesunięciu aparatu lub zmianie rodzaju ryżu.
Moje rozwiązanie działa w następujący sposób:
Rozdziel pierwszy plan, przekształcając obraz do HSV i stosując próg Otsu w kanale nasycenia. Jest to bardzo proste, działa bardzo dobrze i poleciłbym to wszystkim, którzy chcą spróbować tego wyzwania:
->
Spowoduje to czyste usunięcie tła.
Następnie dodatkowo usunąłem cienie ziarna z pierwszego planu, stosując stały próg do kanału wartości. (Nie jestem pewien, czy to naprawdę pomaga, ale wystarczyło dodać)
Następnie stosuję transformację odległości na obrazie na pierwszym planie.
i znajdź wszystkie lokalne maksima w tej transformacji odległości.
To tutaj rozpada się mój pomysł. aby uniknąć mnożenia lokalnych maksimów w obrębie tego samego ziarna, muszę dużo filtrować. Obecnie utrzymuję tylko najsilniejsze maksimum w promieniu 45 pikseli, co oznacza, że nie każde ziarno ma lokalne maksimum. I tak naprawdę nie mam uzasadnienia dla promienia 45 pikseli, to była tylko wartość, która działała.
(jak widać, nie jest to wystarczająco dużo nasion, aby uwzględnić każde ziarno)
Następnie używam tych maksimów jako nasion dla algorytmu zlewu:
Wyniki są meh . Miałem nadzieję na pojedyncze ziarna, ale kępy są wciąż zbyt duże.
Teraz identyfikuję najmniejsze plamy, liczę ich średni rozmiar w pikselach, a następnie szacuję z nich liczbę ziaren. To nie to, co planuje zrobić na początku, ale to był jedyny sposób, by uratować to.
Mały test wykorzystujący zakodowany na stałe rozmiar pikseli na ziarno wynoszący 2544,4 wykazał całkowity błąd 36, który jest nadal większy niż większość innych rozwiązań.
źródło
HTML + JavaScript: wynik 39
Dokładne wartości to:
Rozkłada się (nie jest dokładny) na większe wartości.
Objaśnienie: Zasadniczo zlicza liczbę pikseli ryżu i dzieli ją przez średnią liczbę pikseli na ziarno.
źródło
Próba z php. Nie odpowiedź o najniższej punktacji, ale dość prosty kod
WYNIK: 31
Samoocena
95 to niebieska wartość, która wydawała się działać, gdy testowanie za pomocą GIMP 2966 jest średnią wielkością ziarna
źródło