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