NCrunch – Jakie to, kurwa, dobre

Cytatem Youtubowego kucharza (Food Emperor) polecam wszystkim narzędzie wniesione do zespołu, w którym pracuję, przez nowego kolegę Sebastiana.

Wiem że o NCrunchu pisało już wielu. Jednak nikomu nie udało się mnie do niego przekonać, więc jest pewnie wielu innych sceptyków tego narzędzia i do nich właśnie jest ten artykuł.
Jest to narzędzie pełniące rolę sumienia dla osób stosujących TDD. Każda linijka kodu C#-owego jest oznaczona kropą: białą gdy brakuje testów ją pokrywajacych, czerwoną gdy jakiś test nie przechodzi i zieloną gdy wszystko jest OK.
Działanie NCruncha

Czytaj dalej NCrunch – Jakie to, kurwa, dobre

Aktualizacja ASP.Net Identity do wesji 2

Ostatnimi czasy w jednym z projektów pociągnąłem sobie aktualizacje bibliotek przez Nuget’a. A że byłem leniwy to pociągnąłem je jak leci, nie patrząc co aktualizuje o change logu poszczególnych bibliotek nie mówiąc.

Nie róbcie tego w domu!
Jedną z aktualizacji była nowa wersja mechanizmu ASP.Net Identity. Aktualizacja zmieniła encje wykorzystywane przez mechanizm, nie zmieniła jednak bazy danych (być może ze względu na fakt iż nie korzystam z mechanizmu migracji).
Fakt ten można dość łatwo przeoczyć, wystarczy, że podczas testowania ręcznego nie wykona się żadnej operacji związanej z autoryzacją. Ponieważ miałem aktywną sesję sprzed aktualizacji nie musiałem się ani logować, ani rejestrować, wyjątek został więc zgłoszony tuż po wylogowaniu.

Czytaj dalej Aktualizacja ASP.Net Identity do wesji 2

Gdy HtmlHelper to za mało

Czytając ostatnio o rozszerzeniach do klasy HtmlHelper, stwierdziłem że w wraz z rozwojem projektu ich różnorodność może stać się trudna do ogarnięcia i przydałoby się je pogrupować, zorganizować.
Zacząłem się więc zastanawiać jak takie rozwiązanie mogłoby wyglądać. Zaznaczam przy tym że sam tego jeszcze nie doświadczyłem, a koncepcje dalej przedstawione są raczej propozycjami niż wskazówkami.

Czytaj dalej Gdy HtmlHelper to za mało

Amatorskie spojrzenie na EmguCV – rysowanie

Zagadnieniem do którego dotarłem w ramach moich eksploracji EmguCV jest rysowanie. 
Podstawowym narzędziem rysującym jest metoda Draw. Metoda ta ma 11 przeciążeń, które osobiście podzieliłbym na 3 grupy: Rysowanie kształtów, Wyświetlanie tekstu, Rysowanie sekwencji.

Rysowanie kształtów:

Metoda Draw posiada 7 dedykowanych metod przeznaczonych do rysowania podstawowych kształtów geometrycznych. Metody te jako pierwszy parametr przyjmują strukturę opisującą kształt oraz 2 dodatkowe parametry: kolor lini, oraz jej grubość. Przy czym grubość 0 lub mniej oznacza wypełnienie kształtu kolorem.

	var rect = new System.Drawing.Rectangle(x, y, width, height);
	image.Draw(rect, color, thickness);

I w zasadzie jedyne co mogę stwierdzić to fakt, że działa.

Przypadkiem w którym nie jest już tak różowo jest koło/okrąg ponieważ to coś 
Okrąg generowany metodą Draw
co powstaje w wyniku wywołania tego kodu:

	CircleF c = new CircleF(new PointF(centerX, centerY), radius);
	image.Draw(c, color, thickness);

ciężko nazwać okręgiem. Przydałoby się tutaj trochę antialiasingu, niestety klasa Image nie posiada żadnej metody ani właściwości gwarantującej taką funkcjonalność, a więc trzeba kombinować.
Pierwszym obejściem jakie znalazłem było wykorzystanie właściwości Bitmap która jest typu System.Drawing.Bitmap. Właściwość ta dla kolorów Brg, Brga oraz Gray zwraca wewnętrzną Bitmapę synchronizowaną dwustronnie z zawartością obrazu, dla pozostałych sposobów opisu koloru zwracana jest nowa/niesynchronizowana Bitmapa. Podejrzewam że ta właśnie synchronizacja jest przyczyną przegranej Emgu z metodą BitmapData w teście wydajności.  

Mając obiekt typu Bitmap możemy wykorzystać całe bogactwo biblioteki GDI+ w tym klasę Graphics. Narysowanie okręgu z antialiasingiem można wykonać takim kodem:

	using (var graphics = Graphics.FromImage(image.Bitmap))
	{
		graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
		using (var pen = new Pen(color, thickness))
		{
			graphics.DrawEllipse(pen, x, y, diameter, diameter);
		}
	}

a efekt jego działania prezentuje się następująco
Okrąg generowany z wykorzystaniem GDI+

Drugim rozwiązaniem jakie udało mi się znaleźć jest użycie klasy cvInvoke. O ile klasa Image jest dobrze przemyślanym proxy do wielu funkcjonalności z OpenCV tak klasa cvInvoke ma na celu zapewnić dostęp do jak największej grupy metod, niekoniecznie trzymając się najlepszych wzorców. Większość (jak nie wszystkie) z metod tej klasy korzysta bezpośrednio z PInvoke, jest pośród nich także metoda cvImage.

	var center = new Point(centerX, centerY);
	int radius = dlg.Radius;
	var color = new Bgr(); // Jakiś kolor
	var lineType = Emgu.CV.CvEnum.LINE_TYPE.CV_AA;
	
	CvInvoke.cvCircle(image.Ptr, center, radius, color.MCvScalar, thickness, lineType, 0);

Metoda jako pierwszy parametr pobiera wskaźnik, można go znaleźć w klasie Image<,> pod właściwością Ptr, drugim parametrem są współrzędne środka okręgu, a kolejnym jego promień.
Czwartym parametrem jest kolor, nie jest to jednak ani liczba opisująca kolor, ani obiekt z grupy kolorów tylko coś nazwane MCvScalar, jak widać w powyższym listingu wartość tą można uzyskać z koloru przy pomocy właściwości MCvScalar. 
Piątym parametrem jest grubość linii, interpretowana tak samo jak w metodzie Draw, a szóstym jej typ. Są 3 typy linii 4-połączeniowa, 8-połączeniowa (używana przez metodę Draw) oraz AntiAliasing. Wyniki metody cvCircle z różną wartością tego parametru (w powiększeniu) przedstawia grafika poniżej.
Okręgi generowane metodą z klasy CvInvoke

Organoleptycznie stwierdzam, w tym miejscu błąd w OpenCV ponieważ przy wybraniu opcji 4-połączeniowej wyraźnie widać połączenia po ukosie, których nie powinno być. 
Ostatnim parametrem jest przesunięcie służące do skalowania wynikowego okręgu jeżeli 1 piksel ma być równy 1 jednostce we współrzędnych środka i długości promienia należy ustawić ten parametr na 0.

Rysowanie napisów

Jak każda biblioteka graficzna EmguCV umożliwia umieszczanie napisów na grafice. Sama klasa Image posiada przeciążenie metody Draw która jako parametry pobiera:

  • napis który ma zostać narysowany
  • opis czcionki – struktura zawierająca dane o czcionce
  • pozycję tekstu
  • kolor

Rysowanie sekwencji

Sekwencja (Typ Seq<T>) jest kolekcją pozwalającym przechowywać elementy w sposób uporządkowany. Elementy można dodawać na końcu (Metoda Push) albo na początku (PushFront) sekwencji. Sekwencja wymaga wskazania Magazynu (klasa MemStorage).

	using (var memStor = new MemStorage())
	{
		var seq = new Seq(memStor);
		var rand = new Random();
		for (int i = 0; i < 100; i++)
		{
			var point = new Point(rand.Next(100), rand.Next(100));
			seq.Push(point);
		}
	}

Metoda Draw posiada 3 metody do rysowania sekwencji punktów. W najprostrzym przypadku przyjmuje ona 3 parametry: wspomnianą sekwencję oraz grubość i kolor linii. Dwa ostatnie parametry są interpretowane analogicznie jak we wczesniejszych przeciążeniach. Co ciekawe przekazanie obiektu Seq<Point> wypełnionymi punktami sprawi, że grafika nie ulegnie zmianie (WTF?).
Po wczytaniu się w dokumentację znalazłem przyczynę. Sekwencja sama w sobie stanowi tylko abstrakcję dla innych implementacji. Jednak jako taka nie jest abstrakcyjna można ją tworzyć, można do niej dodawać elementy, jednak nie da się jej narysować. Kolejną ciekawostką jest fakt iż jedyną klasą dziedziczącą po sekwencji jest Contour<T>, a próba dziedziczenia i nadpisania jedynej wirtualnej metody InContour nic nie zmienia (podczas rysowania ta metoda nie jest nawet wywoływana). Na szczęście  użycie klasy Contour<T> daje efekt w postaci narysowania wielokąta. Którego wierzchołkami są punkty sekwencji. 

DrawPolyline

Jak sama nazwa wskazuje służy do rysowania wielu linii, czyli łamanej. Posiada 2 przeciążenia. Pierwsze z nich pobiera jako parametr tablicę punktów, flagę określającą czy domknąć łamaną (czyli zrobić z niej wielokąt), oraz kolor i grubość linii interpretowane tak samo jak przez metodę Draw. Z punktu widzenia programisty można tą metodę traktować jak uproszczoną wersję rysowania konturu.
Drugie przeciążenie różni się pierwszym parametrem jako który przyjmuje tablicę tablic punktów  i służy do rysowania wielu łamanych.

FillConvexPolygon

Metoda pozwala wypełnić kolorem wielokąt wypukły nie należy jednak używać tej metody do rysowania wielokątów wklęsłych albo figur z przecinającymi się krawędziami, metoda ta prawdopodobnie wykorzystuje algorytm skanowania linii (o ile dobrze pamiętam nazwę) wydajny ale ograniczony funkcjonalnie. Moim zdaniem metoda ta ma wąskie zastosowanie.

Przykładowe listingi prezentujące wykorzystanie metod jak  zawsze znajdują się w projekcie EmguExperiments

Amatorskie spojrzenie na EmguCV – operacja na pikselach oraz wydajność

Pomimo bardzo bogatego zestawu metod klasy Image nie robi ona wszystkiego. Więc istnieje konieczność operacji bezpośrednio na punktach składających się na obraz. EmguCV oferuje dwie możliwości:

  • Czytelną w której zostaje użyty indekser zwracający obiekt TColor(w przykładzie poniżej Bgr)

    var pixel = image[row, column];
    pixel.B = value;
    image[row, column] = pixel;
    
  • Mniej czytelną ale wydajnieszą, odwołanie następuje do obiektu Data indekser na nim pozwala pominąć operacje na obiekcie i operować bezpośrednio na tablicy znajdującej się pod spodem.

    byte bluePartOfPixel = image.Data[row, column, 0];
    image.Data[row, column, 0] = (byte)78;
    
  • Najwyższa pora sprawdzić wydajność Emgu CV. Postanowiłem zaimplementować metodę generującą negatyw zdjęcia na kilka sposobów:

    • GetPixel/SetPixel – metoda z góry skazana na przegraną, jej jedynymi zaletami są: nie zła czytelność kodu, oraz fakt że dostępna w .Net z pudełka
    • BitmapData – wydajna metoda, główny konkurent Emgu CV pod tym względem, oprócz wydajności posiada same wady: wymaga używania sekcji unsafe, jest mało czytelna i błędogenna. Ze względu na ostatnią cechę zamiast się bawić w ręczną implementację wykorzystałem gotowca ze Stacka
    • ColorMatrix – stare dobre GDI+ umożliwia dokonywania przekształceń matrycowych zarówno na orientacji pikseli jak i ich kolorów (potrzebna jest do tego klasa Graphics) implementację znalazłem pod tym samym linkiem, co w przypadku BitmapData
    • Pixel Access pierwsza wspomniana w tym poście metoda dostępów do pikseli w EmguCV
    • Pixel Access Fast – druga metoda
    • Metoda Not – metoda w klasie Image dedykowana generowaniu negatywu obrazu

    Testy wykonywałem na swoim laptopie (8 GB RAM, i5, dwie karty grafiki NVidia NVS 5400M oraz jakiś Intel). Test podzieliłem na 2 kroki Otwarcie pliku – polegające na wywołaniu konstruktora klasy z odpowiednim parametrem, oraz Wygenerowanie negatywu – właściwa robota. Odrzucałem wynik pierwszego uruchomienia Testu (podczas niego mogą wykonywać się operacje które nie występują przy kolejnych uruchomieniach). Test wykonałem tylko na trzech plikach, oraz uruchamiając program na różnych kartach grafiki (mam możliwość wskazania która karta powinna być wykorzystana).
    Metoda badań nie jest może idealna jednak mimo to daje całkiem ciekawe wyniki:

    Obraz 3702×2304 jpeg 916KB

    NVidia NVS 5400M

    GDI+ Emgu CV
    Metoda GetPixel/SetPixel BitmapData ColorMatrix Pixel Access Pixel Access Fast Metoda Not
    Otwarcie pliku 00.037 00.038 00.039 00.107 00.110 00.112
    Wygenerowanie negatywu 10.404 00.054 00.157 02.892 02.088 00.114

    Intel(R) HD Graphics 4000

    GDI+ Emgu CV
    Metoda GetPixel/SetPixel BitmapData ColorMatrix Pixel Access Pixel Access Fast Metoda Not
    Otwarcie pliku 00.037 00.036 00.038 00.110 00.113 00.110
    Wygenerowanie negatywu 10.470 00.053 00.157 03.071 02.296 00.112

    Obraz 4320×3240 jpeg 3,21 MB

    NVidia NVS 5400M

    GDI+ Emgu CV
    Metoda GetPixel/SetPixel BitmapData ColorMatrix Pixel Access Pixel Access Fast Metoda Not
    Otwarcie pliku 00.037 00.038 00.039 00.107 00.110 00.112
    Wygenerowanie negatywu 20.764 00.118 00.340 06.079 04.302 00.252

    Intel(R) HD Graphics 4000

    GDI+ Emgu CV
    Metoda GetPixel/SetPixel BitmapData ColorMatrix Pixel Access Pixel Access Fast Metoda Not
    Otwarcie pliku 00.088 00.087 00.086 00.248 00.244 00.247
    Wygenerowanie negatywu 20.644 00.120 00.344 06.041 04.066 00.252

    Obraz 5184×3456 jpeg 4,87 MB

    NVidia NVS 5400M

    GDI+ Emgu CV
    Metoda GetPixel/SetPixel BitmapData ColorMatrix Pixel Access Pixel Access Fast Metoda Not
    Otwarcie pliku 00:00.112 00:00.117 00:00.115 00:00.345 00:00.353 00:00.355
    Wygenerowanie negatywu 00:00.112 00:00.117 00:00.115 00:00.345 00:00.353 00:00.355

    Intel(R) HD Graphics 4000

    GDI+ Emgu CV
    Metoda GetPixel/SetPixel BitmapData ColorMatrix Pixel Access Pixel Access Fast Metoda Not
    Otwarcie pliku 00:00.114 00:00.119 00:00.118 00:00.348 00:00.351 00:00.342
    Wygenerowanie negatywu 00:00.114 00:00.119 00:00.118 00:00.348 00:00.351 00:00.342

    Zgodnie z moimi oczekiwaniami BitmapData pokonał inne metody GDI+. Wywołanie dedykowanej metody Not detronizuje zabawę bezpośrednio na Pikselach, jak dla mnie jest to jednoznaczne ze stwierdzeniem "Jak coś jest już napisane nie pisz tego sam".
    Bardzo interesujący natomiast jest fakt iż metoda BitmapData jest dwukrotnie szybsza od dedykowanej metody z Emgu CV. Jestem jednak daleki od stwierdzenia iż mój test udowadnia słabość tej biblioteki. Wskazany wynik może mieć wiele przyczyn, metoda Not może być na tyle prosta że Emgu CV nie ma co optymalizować (na co wskazywałby brak istotnych różnic przy uruchamianiu programu na różnych kartach grafiki), jednak ambitniejszych przekształceń po prostu nie chce mi się implementować "z palca" więc nie dokonam takiego porównania.
    Warto też zwrócić uwagę na fakt iż przekształcenie matrycowe także nie wypadło źle w zestawieniu, a jego czytelność jest przyzwoita (co nie zmienia faktu iż kod pisany z jego wykorzystaniem zawsze powinien być komentowany).
    Ostatnim elementem układanki jest czas pobrania danych z dysku. W przypadku Emgu CV odczytanie danych stanowi dużą część pracochłonności całej operacji. W moim teście popełniłem jeden błąd i większe zdjęcia wczytywałem z dysku SSD, a małe z talerzowego więc nie może on być podstawą wnioskowania o skalowalności tego rozwiązania.
    Całość kodu testu znajduje się pod adresem: https://github.com/szogun1987/EmguExperriments/tree/master/EmguExperiments/Performance

    Amatorskie spojrzenie na EmguCV – podstawowe przekształcenia obrazów

    W trzeciej części serii chciałbym ukazać łatwość z jaką przy pomocy EmguCV można dokonywać przekształceń obrazu. W tym poście skoncentruję się na 3 przekształceniach: Odbicie lustrzane (Flip), Obrocie, Skalowaniu.
    Jednak zanim przejdę do właściwego tematu chciałbym wspomnieć o ręcznym zarządzaniu zasobami. Klasa Image<TColor, TDepth> implementuje interfejs IDisposable co wskazuje na konieczność zwalniania zasobów gdy tylko obiekt przestaje nam być potrzebny. Jednak dokumentacja Emgu mówi o braku takiej konieczności (prawdopodobnie jest to związane z faktem iż Emgu poprawnie implementuje ten interfejs a sama klasa posiada finalizer zwalniający zasoby gdy przyjdzie odpowiednia pora. Ja osobiście domyślnie wywołuję Dispose (czy to jawnie czy to przez konstrukcję using), chyba że mam wątpliwości czy zwalniany obiekt nie jest gdzieś jeszcze używany, dlatego w swoim projekciku raczej nie będę się stosował do zaleceń dostawcy.
     
    Aby zrobić odbicie lustrzane obrazu należy na nim wywołać metodę Flip (albo _Flip), metoda ta jako parametr pobiera wartość FLIP.HORIZONTAL albo FLIP.VERTICAL gdzie FLIP jest Enum'em.
    Przy okazji wrócę do tematu metod "podreślnikowych". Kilka moich eksperymentów potwierdziło że ich używanie zużywa mniej pamięci operacyjnej niż używanie ich odpowiedników bez podkreślnika na początku. Drugim spostrzeżeniem jakiego przy ich okazji dokonałem to fakt iż kontrolka ImageBox nie obserwuje zmian w obrazie i po wywołaniu metody z podkreślnikiem należy albo podstawić właściwość Image kontrolki ponownie albo wywołać na niej Invalidate. 
    Wadą API Emgu CV jaką widać na przykładzie metod podkreślnikowych i ww. enuma jest nie trzymanie się konwencji nazewniczych przyjętych w C#.
    Jednak odbicie lustrzane nie jest niczym nadzwyczajnym (nawet klasa System.Drawing.Bitmap posiada metodę RotateFlip), dlatego przechodzę do czegoś co w Windows Forms jest gorzej wspierane, a więc obrót. W Emgu CV dokonuje się go metodą Rotate (jakie zaskakujące), metoda ta nie posiada swojego "podkreślnikowego" odpowiednika prawdopodobnie ze względu na fakt iż zmienia ona rozmiar obrazu. Metoda ta posiada 3 przeciążenia w najprostrzym przypadku parametrami tej metody jest kąt obrotu w stopniach oraz kolor jakim zostaną wypełnione "dziury" w obrazie powstałe w wyniku niedopasowania, dodatkowo można zdefiniować czy wynikowy obraz ma być obcięty do oryginalnych rozmiarów (domyślnie obraz jest obcięty), środek obrotu – punkt niestety nie udało mi się ustalić sposobu jego interpretacji, a w dokumentacji którą znalazłem brakowało opisu tego przeciążenia metody Rotate. Ostatnim parametrem jest sposób interpolacji – pomijając obroty o wielokrotności 90 stopni jasnym jest że po obrocie piksele oryginalnego obrazu nie trafią idealnie w piksele obrazu wynikowego, więc kolory pikseli wynikowych należy obliczyć na podstawie kolorów pikseli które wylądowały w pobliżu. W zastosowaniach profesjonalnych może parametr ten może mieć znaczenie, jednak organoleptycznie nie udało mi się dostrzec różnic pomiędzy wynikami zastosowanymi z różnymi wynikami tego parametru.
    Skalowanie obrazu wykonuje się przy pomocy metody Resize. Posiada ona 3 przeciążenia, wszystkie 3 mają parametr interpolationType interpretowany analogicznie jak taki sam parametr w obrocie. Najprostrze przeciążenie przyjmuje oprócz tego parametru także skalę. Drugie przeciążenie zamiast skali przyjmuje docelową szerokość i wysokość obrazu – przy czym obraz może się skalować inaczej wzdłóż osi X, a inaczej wzdłóż osi Y. Ostatnie przeciążenie do drugiego przeciążenia dodaje parametr preserveScale ustawienie go na true sprawi że obraz zostanie zeskalowany tak aby się zmieścił w docelowym prostokącie, nic z obrazu nie zostało obcięte oraz bez naruszenia proporcji, to przeciążenie świetnie się nadaje do generowania miniaturek (ponieważ takie warunki muszą one zazwyczaj spełnić.
    Na koniec dodam że moim celem nie jest opisywanie na łamach tego bloga wszystkich elementów EmguCV – nie chcę powielać dokumentacji.
    Przykładowy kod wykorzystujący ww metody znajduje się Tutaj 

    Amatorskie spojrzenie na EmguCv – kontrolka Image Box

    W poprzednim wpisie wspomniałem o metodzie ToBitmap klasy Image pozwalającej na konwersję klasy Image na klasę System.Drawing.Bitmap, co pozwala na używanie wyniku obróbki obrazu w kontrolkach Windows Forms. Jednak taka konwersja nie należy do najszybszych operacji, po za tym powoduje zwiększenie zużycia pamięci, ponieważ obraz jest przechowywany w pamięci w dwóch postaciach. Ponieważ najczęstszym zastosowaniem obrazów jest ich wyświetlanie do EmguCV została dodana kontrolka ImageBox.

    Jest ona pod pewnymi względami podobna do PictureBox z WindowsForms, podstawową różnicą jest fakt iż jako źródła używa klasy IImage (po którym dziedziczy Image<TColor, TDepth>) , a nie Bitmap. Kontrolka ta dodaje też kilka funkcjonalności np. skalowanie obrazka myszką, obsługę rolki itp. jednak mam nadzieję że da się to wyłączyć Edit.

    Aby dodać kontrolkę ImageBox do projektu należy dodać referencję do EmguCV (operację tą opisałem w poprzednim poście) i to wystarczy aby używać tej kontrolki z poziomu kodu. Jednak aby ww. kontrolka pojawiła się także w toolboxie należy się jeszcze trochę wysilić.

    Po kliknięciu prawym przyciskiem myszy w wolny obszar na toolboxie należy wybrać“Add Tab” ja swoją zakładkę nazwałem “EmguControls”:

    AddTab

    Następnie należy kliknąć prawym przyciskiem myszy w nową zakładkę wybrać opcję “Choose Items”.

    Choose Items

    Na wyświetlonym oknie można należy wybrać przycisk “Browse”, wskazać ścieżkę do EmguCV.UI.dll i zatwierdzamy obydwa formularze.

    Browse

    Oprócz kontrolki ImageBox zostają też dodane też kontrolki HistogramBox, MatrixBox, PanAndZoomPictureBox ich eksploracją zajmę się w późniejszym czasie (o ile mi starczy chęci).

    Na koniec dodam iż eksplorację EmguCV będę dodatkowo dokumentował na GitHubie w projekcie https://github.com/szogun1987/EmguExperriments

    W projekcie brakuje bibliotek OpenCV ze względu na ich rozmiar, należy je sobie samemu podlinkować/skopiować.

    Ponieważ projekt ma za zadanie ukazać możliwości API w jak najprostrzy sposób większość kodu będzie się znajdowała w EventHandlerach (nie znam dobrego polskiego odpowiednika tego słowa), a sam kod będzie pełen powtórzeń. Więc nie uznaję krytyki tych aspektów. Natomiast chętnie zapoznam się z innymi uwagami.

    Edit:

    Właściwością kontrolki determinującą zachowanie kontrolki jest FunctionalMode. Właściwość ta może przyjmować 4 wartości:

    • Minimum – ogranicza się do wyświetlenia obrazka ta na której mi zależy.
    • RightClickMenu – wyświetla menu pozwalające na dokonywanie przekształceń na obrazie, ponieważ Menu pokrywa większość funkcjonalności projektu EmguExperiments wyłącze je w swoim projekcie
    • PanAndZoom – obsługa gestów myszy
    • Everything – jak sama nazwa wskazuje wszystkie funkcjonalności – wartość domyślna dla właściwości FunctionalMode

    Amatorskie spojrzenie na EmguCV

    EmguCV jest .Netowym wrapperem do OpenCV – potężnej biblioteki do przetwarzania i analizy obrazów (zarówno statycznych jak i ruchomych) oraz innych sygnałów cyfrowych.

    Ważną cechą biblioteki jest fakt iż stara się ona wykorzystać jak najlepiej zasoby dostępne na maszynie na której jest ona uruchomiona, ze szczególnym uwzględnieniem karty grafiki.

    Oprócz tego OpenCV wspiera machine learning, jednak tej części biblioteki jeszcze nie dotykałem.

    Co ma wspólnego programista .Net zajmujący się nudnymi dokumentami korporacyjnymi z przetwarzaniem obrazów? Niewiele. Po prostu od czasu do czasu trzeba spróbować czegoś nowego for-fun więc znalazłem pierwszy lepszy pretekst żeby pobawić się się EmguCV.

    Od razu zaznaczę, że nie czytałem o żadnych dobrych praktykach, ani nawet nie zagłębiałem się w tutoriale tylko postanowiłem rozpocząć naukę przez eksplorację wspartą przez google i stackoverflow. W dodatku skoncentrowałem się tylko na podstawowych przekształceniach obrazów statycznych.

    1. Instalacja

    Instalację należy zacząć od wejścia na stronę http://sourceforge.net/projects/emgucv/ i pobrania instalatora i klikania next, next, next. Już sam rozmiar paczki instalacyjnej robi wrażenie (200+MB), Natomiast po jej zainstalowaniu stracimy prawie 2GB dysku. Liczbę tą można zmniejszyć rezygnując z instalacji przykładowych solucji.

    2. Pierwsze podpięcie

    Podłączenie Emgu do solucji w celu przekształcania obrazów statycznych jest opisane na stronie http://www.emgu.com/wiki/index.php/Setting_up_EMGU_C_Sharp zgodnie z tym tutorialem należy do swojego projektu dodać referencje do dll’ek EmguCV.dll, EmguCV.UI.dll oraz EmguCV.Util.dll biblioteki te znajdują się w katalogu “<ścieżka_do_emgu>\bin”.

    Biblioteki te nie są jednak samodzielnymi bibliotekami tylko wrapperami do bibliotek napisanych w c++ które posiadają wersje 32 i 64 bitową co za tym idzie nasza aplikacja albo będzie miała 2 wersje albo nie będzie działać na niektórych stacjach roboczych.

    Powoduje to że powinniśmy ustawić właściwość “Target platform” na “x64” (Properties w naszym csproj, zakładka Build).

    Drugim problemem o którym wspomniany wcześniej tutorial nie mówi to fakt iż zależności między bibliotekami się trochę pozmieniały i dodanie do projektu tylko i wyłącznie opencv_core<ver>.dll i opencv_imgproc<ver>.dll (znajdujacych się w katalogu  “<ścieżka_do_emgu>\bin\x64”) sprawi że po wykorzystaniu jakiejkolwiek metody z biblioteki EmguCV dostaniemy w twarz błędem o mało mówiącym komunikacie “'System.TypeInitializationException' occurred in Emgu.CV.dll”. Grzebiąc po internecie znalazłem poradę aby po prostu przekopiować cały katalog “<ścieżka_do_emgu>\bin\x64” do swojej solucji tak aby lądował w katalogu bin\Debug wadą tego rozwiązania jest jednak fakt iż cały katalog waży 566MB. Jeżeli ktoś zna minimalny podzbiór dll’ek potrzebny do otworzenia obrazu z pliku i wyświetlenia go w Picturebox będę wdzięczny.

    3. Pokaż cy…kod

    Podstawową klasą z której korzystałem podczas moich zabaw jest Image<TColor, TDepth> jak widać jest to klasa generyczna pierwszy parametr oznacza w jaki sposób można operować na kolorach obrazu. Przykładami typów które można tam postawić są Bgr, Gray, Bgr556, Hsv i kilka innych. Parametr TDepth oznacza typ używany do określenia głębi kolorów (osobiście zawsze tam wrzucam byte).

    Klasa ta posiada kilka kontruktorów z których według mnie najważniejsze to:

    public Image(Bitmap bmp);

    Służy do konwesji bitmapy z przestrzeni nazw System.Drawing na obraz który nadaje się do przetworzenia przez Emgu.

    public Image(string fileName)

    Pozwala otworzyć obraz z pliku, metoda nie wspiera formatów Uri, jednak wnioskując po treści zgłaszanego wyjątku obsługa taka zostanie dodana

    public Image(int width, int height, TColor value);

    Tworzy obraz o zadanych wymiarach i kolorze

    Niestety pośród konstruktorów brakuje otwarcia obrazu z dowolnego strumienia, jednak można się posiłkować następującym obejściem:

    using (Stream stream = new SomeStream())
                {
                    using (var memoryStream = new MemoryStream())
                    {
                        stream.CopyTo(memoryStream);
                        memoryStream.Position = 0;
    
                        Image image = Image.FromRawImageData(memoryStream.ToArray());
                    }
                }
    
    

    Klasa Image zawiera szereg metod które można podzielić na dwie grupy:

    • zaczynające się od podkreślenia – zmieniają obiekt obrazu na którym są wywoływane

    • nazywające się jak pan Bóg przykazał – generują kopię obrazu z naniesionymi zmianami

    Część metod posiada wersję z podkreśleniem i bez. Intuicja mi podpowiada że metody “podkreślnikowe” są wydajniejsze ale nie są nie można ich używać bezpiecznie w środowisku wielowątkowym (nie szukałem jednak potwierdzenia tej tezy w dokumentacji).

    Pierwszą metodą w klasie Image, którą postanowiłem przybliżyć jest Convert<TColor, TDepth>  metoda da pozwala na stworzenie obrazu opisanego w inny sposób lub mającego inną głębię.

    // Dzięki temu możemy operować na głębi i nasyceniu kolorów
    var image2 = image.Convert<Hsv, byte>();
    // Konwersja na skalę szarości - nieodwracalna
    var image3 = image.Convert<Gray, byte>();
    

    Drugą ważną dla mnie metodą jest .ToBitmap pozwalająca konwertować obraz do klasy System.Drawing.Bitmap co pozwala wyświetlić wynik przekształceń w kontrolkach Windows Forms.

    Lazy Yield Problems

    Zanim przejdę do sedna sprawy nakreślę najpierw ciąg wydarzeń który mnie ku napisaniu tego posta skłonił.

    Ostatnimi czasy wykonywałem małą biblioteczkę "na własne potrzeby" której podstawą był interfejs który można przedstawić następująco:

    public interface IProcessor<T>
    {
    	IEnumerable<Result> Process(T input);
    }
    

    Interfejs ten ma wiele implementacji jednak z punktu widzenia tego postu kluczowy jest "AggregateProcessor" który łączy wyniki kilku procesorów.

    class AggregateProcessor<T> : IProcessor<T>
    {
    	private IProcessor<T>[] subProcessors;
    
    	public AggregateProcessor(params IProcessor<T>[] subProcessors)
    	{
    		this.subProcessors = subProcessors;
    	}
    
    	public IEnumerable<Result> Process(T input)
    	{
    		return subProcessors.SelectMany(p => p.Process(input));
    
    		/* ekwiwalent
    		foreach (var processor in subProcessors)
    		{
    			foreach (var result in processor.Process(input))
    			{
    				yield return result;
    			}
    		}
    		 */
    	}
    }
    

    Następnie dla tej klasy napisałem prosty test sprawdzający czy podprocesor zostanie wywołany po wywołaniu Process na AggregateProcessorze.

    Test jest napisany w NUnicie do Mockowania wykorzystałem FakeItEasy.

    [Test]
    public void CheckIfSubProcessorIsCalled()
    {
    	// Given
    	var parameter = new TestClass();
    
    	var fakeProcessor = A.Fake<IProcessor<TestClass>>();
    	A.CallTo(() => fakeProcessor.Process(parameter)).Returns(Enumerable.Empty<Result>());
    
    	var aggregate = new AggregateProcessor<TestClass>(fakeProcessor);
    
    	// When
    	aggregate.Process(parameter);
    
    	// Then
    	A.CallTo(() => fakeProcessor.Process(parameter)).MustHaveHappened();
    }
    

    Wielkie było moje zdziwienie gdy okazało się że Test nie przechodzi.

    Przyczyną tej sytuacji jest fakt iż zarówno operacje w zapytaniu LINQ jak i operacja yield wywoływane są w momencie iteracji po wyniku metody której w tym przykładzie nie ma. W większości przypadków fakt ten nie ma większego znaczenia ponieważ iteracja zazwyczaj w końcu następuje, chyba że tak jak w tym przypadku metody będącej funkcją używamy jak metody będącej procedurą, albo wykonujemy na wyniku operacje typu Any, First/FirstOrDefault które nie iterują po całym wyniku.

    Wracając do mojego przypadku ostatecznie stwierdziłem że fakt wywołania metody wewnętrznej nie jest dla mnie istotny, a bardziej obchodzi mnie zwrócenie wyniku procesora wewnętrznego.

    [Test]
    public void CheckIfSubProcessorIsCalled()
    {
    	// Given
    	var parameter = new TestClass();
    	var result = new Result();
    
    	var fakeProcessor = A.Fake<IProcessor<TestClass>>();
    	A.CallTo(() => fakeProcessor.Process(parameter)).Returns(new[] { result });
    
    	var aggregate = new AggregateProcessor<TestClass>(fakeProcessor);
    
    	// When
    	var resultCollection = aggregate.Process(parameter);
    
    	// Then
    	CollectionAssert.Contains(resultCollection, result);
    }
    

    Drugim zagrożeniem jakie wiąże się z yield i LINQ jest fakt iż wszystkie operacje wykonywane pod spodem wykonują się przy każdej iteracji po wynikowej kolekcji. Sam się na tym nigdy nie przejechałem ponieważ przed błędem tego typu skutecznie chroni mnie Resharper, jednak ludzi nie posiadających tego genialnego narzędzia warto ostrzec że poniższy kod:

    static void Main(string[] args)
    {
    	var sourceCollection = Enumerable.Range(1, 10);
    
    	var resultCollection = from number in sourceCollection
    						   select Compute(number);
    
    	foreach (var number in resultCollection)
    	{
    		Console.Write("{0}, ", number);
    	}
    
    	Console.WriteLine();
    
    	foreach (var number in resultCollection)
    	{
    		Console.Write("{0}, ", number);
    	}
    }
    
    private static int Compute(int parameter)
    {
    	// Some Heavy operation
    	Thread.Sleep(1000);
    	return parameter + 1;
    }
    

    będzie uruchamiał się 20 a nie 10 sekund. Jakimś obejściem tego problemu jest użycie metody ToList. Najlepiej w następujący sposób:

    static void Main(string[] args)
    {
    	var sourceCollection = Enumerable.Range(1, 10);
    
    	var resultCollection = from number in sourceCollection
    						   select Compute(number);
    
    	/*
    	 * Przed wywołaniem ToList upewniam się że kolekcja nie jest już listą
    	 * W tym przypadku nie ma to sensu ponieważ wiem że kolekcja nie jest listą
    	 */
    	var resultList = resultCollection as List<int> ?? resultCollection.ToList();
    
    	foreach (var number in resultList)
    	{
    		Console.Write("{0}, ", number);
    	}
    
    	Console.WriteLine();
    
    	foreach (var number in resultList)
    	{
    		Console.Write("{0}, ", number);
    	}
    }
    

    Podsumowując zarówno LINQ jak i yield są bardzo fajnymi narzędziami, przed ich użyciem należy jednak przynajmniej w minimalnym stopniu dowiedzieć się jak one działają.

    Świąteczne małpki

    W ostatnim czasie w pracy spotkałem się z problemem który w uproszczeniu wygląda następująco:

    Mamy listę obiektów typu Dział zawierających kolekcje obiektów typu Wydział zawierających kolekcję obiektów typu pracownik. Problemem do rozwiązania jest policzenie średniej Wieku wszystkich pracowników. Zakładamy dodatkowo że baza nie jest tuż za płotem tylko znajduje się za WebServicem do którego dokładanie metod jest z jakiś przyczyn problematyczne, więc nie możemy po prostu złożyć składnego selecta.

    Problem wydaje się być banalny wystarczy zmontować coś takiego:

    int ilosc = 0;
                int suma = 0;
    
                foreach (Dzial dzial in dzialy)
                {
                    foreach (Wydzial wydzial in dzial.Wydzialy)
                    {
                        foreach (Pracownik pracownik in wydzial.Pracownicy)
                        {
                            ilosc++;
                            suma += pracownik.Wiek;
                        }
                    }
                }
    
                double wynik = ((float)suma) / ilosc;
    

    Jest to jednak kod mało czytelny. A jeżeli operacja byłaby bardziej złożona niż średnia robi się jeszcze mniej ciekawie.

    Rozwiązaniem jakie narzuca się bardziej doświadczonym programistom .Net jest użycie LINQ. Rozwiązanie to ma jednak wadę: nie chodzi na kolekcjach kolekcji tylko na jednowarstwowych kolekcjach. Można ten problem rozwiązać przez wyliczenie sobie pomocniczej kolekcji i na niej wykonania działania:

               List<Pracownik> pracownicy = new List<Pracownik>();
    
                foreach (Dzial dzial in dzialy)
                {
                    foreach (Wydzial wydzial in dzial.Wydzialy)
                    {
                        foreach (Pracownik pracownik in wydzial.Pracownicy)
                        {
                            pracownicy.Add(pracownik);
                        }
                    }
                }
    
                double wynik = pracownicy.Average(p => p.Wiek);
    

    Jest to rozwiązanie dobre jednak tworzenie i napełnianie listy w przypadku dużych kolekcji może okazać się zasobożerne.

    Dlatego też postanowiłem zrobić świąteczny prezent sobie i innym programistom i stworzyć małpkę która za nas będzie biegała po drzewach i strukturach drzewo-podobnych. W ten sposób powstało rozszerzenie ExtractSubCollection biblioteki Szogun1987.Monkey. Po wykorzystaniu tej metody powyższy kod zostaje skrócony do 4 w miarę czytelnych linijek:

    double result = 
                    dzialy.ExtractSubCollection(d => d.Wydzialy)
                    .ExtractSubCollection(w => w.Pracownicy)
                    .Average(p => p.Wiek);
    

    W bibliotece znajdują się dodatkowo rozszerzenia GetAllSubNodes, GetLeafs i GetRoot ułatwiające przeglądanie zwykłych (tj. składających się z węzłów spójnych pod względem typu) Drzew.

    Najprostszą z nich jest GetRoot która jako parametr pobiera obiekt – węzeł drzewa i Funkcję zwracającą element nadrzędny węzła. Jako korzeń uznawany jest element dla którego powyższa funkcja zwróci null.

    Kolejną metodą jest GetAllSubNodes pozwala ona na przeiterowanie po wszystkich węzłach znajdujących się poniżej węzła na którym wywoływane jest rozszerzenie (łącznie z nim samym). Jako drugi parametr przyjmowana jest funkcja zwracająca kolekcję elementów podrzędnych.

    Ostatnią metodą w bibliotece jest GetLeafs pozwalająca przeiterować się po liściach drzewa z niego się wywodzących.

    Dodatkowo w bibliotece znajduje się interfejs ITreeNode którego zaimplementowanie sprawia że do funkcji GetAllSubNodes, GetLeafs i GetRoot nie trzeba przekazywać funkcji wyciągających "najbliższych członków rodziny" danego elementu.

    Dołożyłem starań aby biblioteka była napisana w sposób powodujący jak najniższe zużycie zasobów systemowych (pamięci i procesora). Podobnie jak w LINQ wszelkie funkcje i metody przekazywane do moich rozszerzeń są wywoływane w momencie iteracji.

    Na koniec kwestie prawne, (Co by było wszystko na papierze znaczy się monitorze):

    Zezwalam na nieodpłatne korzystanie ze skompilowanej wersji biblioteki. Biorę na siebie poprawki ewentualnie wykrytych błędów, przy czym nie naprawiam błędów wynikających ze współbieżnego dostępu do drzewa. Nie gwarantuję także wprowadzania ulepszeń.

    Kodu źródłowego nie udostępnię w tym roku. Zakładam taką możliwość w pierwszym kwartale roku przyszłego (2012).

    Biblioteki są do pobrania Tutaj