Autorem wpisu jest Michał Hałoń - kierownik Zakładu Przetwarzania Obrazu w NASK PIB. Prezentuje w nim metodę sortowania według koloru, pomocną w procesie analizy zawartości zbioru obrazów - na przykładzie zbioru zawierającego emoji z flagami.
Praca ze zbiorami zdjęć (na przykład w zagadnieniu wizji komputerowej) na wstępnym etapie często wymaga szczegółowego przyjrzenia się ich zawartości, w celu zweryfikowania występowania potencjalnych powtórzeń, pomyłek w oznaczeniach, a także dostrzeżenia zależności lub charakterystyk pomocnych w dalszej pracy z danymi.
W tym wpisie pokażę jak z wykorzystaniem programu MATLAB do takiej analizy zawartości zbioru zdjęć można wykorzystać sortowanie według koloru. Inspiracją będzie grafika przedstawiająca to zagadnienie na przykładzie flag. Obrazki przedstawiające flagi krajów oraz organizacji wyodrębnimy ze zbioru OpenMoji, który zawiera ponad 4000 emotikonek. Zbiór ten w rozszerzonej wersji zostanie przeze mnie wykorzystany także w jednym z kolejnych wpisów.
Pobranie i wczytanie danych
W pierwszym kroku pobieramy wspomniany zbiór ze strony internetowej (wersja PNG Color 72x72) i rozpakowujemy w tej samej lokalizacji co MATLABowy skrypt zawierający poniższe linie kodu.
Następnie sprawdzimy, czy możemy załadować oraz wyświetlić rozpakowany zbiór emotikonek – wybierzemy 42 losowe obrazki i wyświetlimy je z wykorzystaniem funkcji montage.
% OpenMoji folder path emojiPath = 'OpenMoji'; % get filenames of all emoji images emojiData = dir(fullfile(emojiPath,'*.png')); emojiPaths = fullfile(emojiPath, {emojiData.name}); % set the seed value for the random number generator rng(100) % select random emoji list emojiToDisplay = emojiPaths(randperm(numel(emojiPaths), 42)); % display randomly selected emoji montage(emojiToDisplay)
Na wizualizacji znajdują się obrazki o zdecydowanie zróżnicowanej tematyce – od osób i zwierząt, po flagi. Jako że w tym wpisie skupimy się na tej ostatniej kategorii, potrzebujemy wyodrębnić je z całego zbioru. Do tego zadania przygotowałem wcześniej listę zawierającą wyłącznie emoji z flagami (w pliku z rozszerzeniem MAT), którą wykorzystałem w poniższym kodzie:
% load flagPaths variable with list of selected emoji flags load('flagPaths.mat'); % display selected emoji flags montage(flagPaths)
Zobaczmy, jak wygląda pojedynczy obrazek ze zbioru na przykładzie flagi polskiej.
imshow(flag_paths{186})
Możemy zauważyć, że wokół flagi znajduje się całkiem szeroki obszar czarnego tła (który był także widoczny na wcześniejszym montażu). Przed kolorystycznym posortowaniem całego zbioru warto usunąć wspomniane tło – zwiększy to czytelność wizualizacji (większe flagi), a sam kolor tła nie zakłóci procesu sortowania.
Usunięcie tła
Do tego zadania wykorzystamy fakt, że wszystkie emoji to obrazy zapisane w formacie PNG, dla których dostępne są informacje na temat przezroczystości obrazka (tzw. kanał alpha). W kanale alpha każdy piksel z przypisaną wartością 0 stanowi tło, a dowolna inna wartość wskazuje na niezerową przezroczystość. Zwizualizujmy kanał alpha na przykładzie polskiej flagi (kolor czarny – tło, kolor biały – emotikon).
% read emoji as indexed image [img, map, alpha] = imread(flagPaths{186}); % display emoji and its transparency data subplot(1,2,1); imshow(img,map); title('Flaga emoji','FontSize',24); subplot(1,2,2); imshow(alpha); title('Kanał alpha','FontSize',24);
W jaki sposób wyodrębnić flagę z całego obrazka? Możemy na kanale alpha znaleźć krańcowe współrzędne flagi (najbardziej na lewo, prawo, na górze oraz na dole), a następnie z oryginalnego obrazka wyciąć prostokąt zawierający flagę.
% use function cropEmoji to remove background from image croppedImage = cropEmoji(flagPaths{186}); % display results subplot(1,3,1); imshow(img,map); title('Flaga emoji','FontSize',22); subplot(1,3,2); imshow(alpha); title('Kanał alpha','FontSize',22); subplot(1,3,3); imshow(croppedImage); title('Po usunięciu tła','FontSize',22);
Przeprowadzimy ten proces dla wszystkich flag i zapiszemy je w osobnej lokalizacji – katalogu „OpenMoji_flags”.
% name of new directory for cropped images folderName = 'OpenMoji_flags'; % create new folder if doesn't exist if ~exist(folderName, 'dir') mkdir(folderName); end % iterate over every image and remove background for i = 1:size(flagPaths,2) % remove flag background croppedImage = cropEmoji(flagPaths{i}); % get current file name [filepath,name,ext] = fileparts(flagPaths{i}); imageName = strcat(name,ext); % write cropped image in new directory imwrite(croppedImage,fullfile(folderName, imageName)); end % get filenames of all cropped emoji images flagData = dir(fullfile(folderName,'*.png')); flagCroppedPaths = fullfile(folderName, {flagData.name});
Porównajmy teraz flagi przycięte pokazanym wyżej sposobem, z tymi oryginalnymi.
subplot(1,2,1); montage(flagPaths,'BackgroundColor','white') title('Flagi oryginalne','FontSize',22) subplot(1,2,2); montage(flagCroppedPaths,'BackgroundColor','white') title('Flagi bez tła','FontSize',22)
Można zauważyć, że przycięte flagi są większe i dzięki temu widać na nich więcej szczegółów.
Demonstracja sortowania
Z tak przekształconym zestawem obrazków możemy przejść do kolejnego etapu. Sam proces sortowania według koloru warto zwizualizować na przykładowym obrazku składającym się z losowych pikseli w tradycyjnej przestrzeni kolorów RGB. Obraz posortujemy malejąco według wartości kanału R (odpowiadającego za zawartość koloru czerwonego na obrazie).
% create RGB image with random colors imgRand = randi([0, 255], 50, 50, 3, 'uint8'); % sort pixels by color imgSortedRGB = sortPixels(imgRand); % display results subplot(1,2,1); imshow(imgRand) title('Losowy obraz','FontSize',22) subplot(1,2,2); imshow(imgSortedRGB) title('Sortowanie wg. kanału R','FontSize',22)
Możemy zauważyć, że na posortowanym obrazku koloru czerwonego najwięcej jest na górze, a jego natężenie maleje wraz z kolejnymi rzędami pikseli. Choć efekt ten wygląda (w mojej opinii) estetycznie i przyjemnie dla oka, ciężko jest na nim wyróżnić poszczególne grupy kolorów.
Spróbujmy zatem przekształcić oryginalny losowy obraz z przestrzeni barw RGB do przestrzeni HSV, która w dziedzinie przetwarzania obrazów jest często wykorzystywana z uwagi na własności, które częściowo uwidocznią się na poniższym przykładzie. Obraz posortujemy malejąco według wartości kanału H (ang. hue), który reprezentuje barwę koloru.
% convert RGB image to HSV color space imgHSV = rgb2hsv(imgRand); % sort pixels by color imgSortedHSV = sortPixels(imgHSV); % conver HSV image back to RGB imgSortedHSV = hsv2rgb(imgSortedHSV); % display results subplot(1,3,1); imshow(imgRand) title('Losowy obraz','FontSize',21) subplot(1,3,2); imshow(imgSortedRGB) title('Sortowanie - kanał R','FontSize',21) subplot(1,3,3); imshow(imgSortedHSV) title('Sortowanie - kanał H','FontSize',21)
Na obrazie posortowanym w przestrzeni barw HSV w łatwy sposób możemy pogrupować piksele według znanych nam kolorów. Choć sortowanie nie przebiegło w sposób idealny (widoczne są na przykład piksele o różnym stopniu szarości rozrzucone po całym obrazie), na nasze potrzeby powinno to wystarczyć – dlatego wykorzystamy je w kolejnym etapie.
Sortowanie flag według koloru
W celu wykorzystania opisanej powyżej metody, dla każdej flagi obliczymy jej średni kolor, co zostało zwizualizowane na poniższej animacji.
Następnie pozostaje średnie kolory odpowiednio posortować (w przestrzeni barw HSV) oraz zwizualizować.
% get mean color of each emoji flag meanColors = []; % iterave over every emoji for i = 1:size(flagCroppedPaths,2) % load HSV image from path imgHSV = loadEmojiForSorting(flagCroppedPaths{i}); % calculate mean value for every channel hMean = mean(imgHSV(:,:,1),'all'); sMean = mean(imgHSV(:,:,2),'all'); vMean = mean(imgHSV(:,:,3),'all'); % save values to meanColors list meanColors = [meanColors; hMean sMean vMean]; end % get indexes of list sorted by mean color [~, idx] = sortrows(meanColors, [1, 2]); % get paths to emojis in sorted direction sortedPaths = flagCroppedPaths(idx); % display results subplot(1,2,1); montage(flagCroppedPaths) subplot(1,2,2); montage(sortedPaths)
Jak można zauważyć, udało nam się posortować kolorystycznie emoji z flagami, co szczególnie dobrze widać na przykładzie flag na których przeważa kolor niebieski. Większość biało-czerwonych flag została pogrupowana w jednym miejscu, a po sprawdzeniu ich przynależności możemy stwierdzić duże podobieństwo flag Polski oraz Grenlandii (po prawej stronie od naszej flagi), a także odwrócenie kolorów na flagach Indonezji oraz Monako (po lewej) w porównaniu do polskiej.
Analizując posortowane zestawienie można także dostrzec wiele powtarzających się wyglądem flag. W przypadku Norwegii można dostrzec trzy takie flagi – po zweryfikowaniu na stronie OpenMoji okazuje się, że poza Norwegią flaga ta jest wykorzystywana przez Svalbard i Jan Mayen oraz uważaną za „najbardziej samotną wyspę świata”, bezludną Wyspę Bouveta.
Modyfikacja algorytmu sortowania
Na podstawie analizy przedstawionej powyżej animacji GIF można wywnioskować, że obliczenie średniego koloru nie zawsze dobrze oddaje kolorystykę obrazka. Dzieje się tak z uwagi na specyficzne własności algorytmu liczenia średniego koloru – często dla obrazów zawierających wiele zróżnicowanych kolorów, ich uśrednienie może przyjąć nieintuicyjną barwę.
Spróbujmy zatem posortować obrazy nie po średnim, a po kolorach najczęściej występujących na obrazie. Dla ułatwienia porównania z poprzednią metodą, poniżej umieściłem animację GIF prezentującą znaleziony w przestrzeni barw HSV najpopularniejszy kolor dla każdej flagi.
% get mean color of each emoji flag mostCommonColors = []; % iterave over every emoji for i = 1:size(flagCroppedPaths,2) % load HSV image from path imgHSV = loadEmojiForSorting(flagCroppedPaths{i}); % round hue channel values hueChannel = imgHSV(:,:,1); hueChannel = round(hueChannel * 100) / 100; % transform matrix into a vector hueVector = hueChannel(:); % find unique values and count them [uniqueHues, ~, idx] = unique(hueVector); hueCounts = accumarray(idx, 1); % sort unique hue values by number of occurences [~, sortedIdx] = sort(hueCounts, 'descend'); % find most common hue values firstHue = uniqueHues(sortedIdx(1)); if length(sortedIdx) == 1 secondHue = firstHue; else secondHue = uniqueHues(sortedIdx(2)); end % save values to mostCommonColors list mostCommonColors = [mostCommonColors; firstHue secondHue]; end % get indexes of list sorted by most common color [~, idx] = sortrows(mostCommonColors, [1, 2]); % get paths to emojis in sorted direction sortedPaths = flagCroppedPaths(idx); % display results subplot(1,2,1); montage(flagCroppedPaths) subplot(1,2,2); montage(sortedPaths)
W przypadku tej metody pogrupowanie według kolorów widać jeszcze wyraźniej, szczególnie w przypadku flag zawierających dużo białego koloru.
Sortowanie na bazie zdjęć
Opisany powyżej algorytm, poza obrazkami w stylu emoji, można także wykorzystać na bazach zawierających zdjęcia. Poniżej prezentuję przykład zastosowania sortowania według koloru na bazie zdjęć kwiatów. Baza ta znajduje się na liście zbiorów danych przydatnych we wdrażaniu się w zastosowanie technik Deep Learningu.
W celu uzyskania poniższego wyniku sortowania dokonałem drobnych modyfikacji samego algorytmu, z którymi można zapoznać się analizując zawartość funkcji sortFlowers (dostępna w kodzie załączonym na końcu wpisu).
sortFlowers();
Opisany w tym wpisie algorytm kolorystycznego sortowania obrazów często wykorzystuję w pracy ze zbiorami zdjęć, co zdecydowanie przyspiesza i usprawnia eksplorację analizowanych danych. Zachęcam do wypróbowania tej metody oraz podzielenia się Waszymi doświadczeniami dotyczącymi wykorzystania oprogramowania MATLAB w analizie obrazów.