wtorek, 15 listopada 2011

Matlab - LZW - kompresja i dekompresja.

Napiszemy w Matlabie kompresje i dekompresje obrazka algorytmem LZW. Takie bylo zadanie na zajeciach. ;)

Wszystkie pliki zrodlowe, troche zabalaganione, znajduja sie tutaj:
http://dl.dropbox.com/u/2545858/lzw.zip

Cel.
Ogolna idea naszej dzialalnosci, napiszmy funkcje im2fd(), ktora musi:
-wczytac obrazek w odcieniach szarosci z pliku i zapamietac jego wymiary,
-przeksztalcic z macierzy odcieni szarosci na wektor,
-przeksztalcic z wektora odcieni szarosci na wektor znakow 8-bit char,
-skompresowac wektor znakow char algorytmem lzw,
-utworzyc plik do ktorego zapisze rozmiary obrazka i otrzymany kod z kompresji lzw.

Skoro bedziemy mieli funkcje im2fd, przyda sie tez fd2im(), ktora musi:
-wczytac z pliku ktory jest efektem dzialania funkcji im2fd rozmiar obrazka i kodo z kompresji lzw,
-zdekodowac kod z kompresji lzw na wektor znakow char,
-przeksztalcic z wektora znakow char na wektor odcieni szarosci,
-przeksztalcic z wektora odcieni szarosci na macierz dwuwymiarowa korzystajac z wczytanych wymiarow obrazka.
-zapisac otrzymana macierz odcienia szarosci jako obraz do pliku.

Cel jest jasny i prosty.

Plan.

Zaprojektujmy w takim razie funkcje im2fd:

void im2fd(string imagefile, string output) {
IMAGE= imageread(imagefile); %wczytac obrazek
SIZE= size(IMAGE); %wczytac jego rozmiary
IMAGE= m2v(IMAGE); %macierz -> wektor
IMAGE= int2char(IMAGE); %konwesja na wetkor char
CODE= lzw_encode(IMAGE); %kompresja
TOFILE= vector_concatenate(SIZE,CODE); %przygotowanie danych do zapisu: polaczenie wektora rozmiaru obrazka z wektorem kodu z kompresji
filewrite(output,TOFILE); %zapisanie do pliku output
}

Rozbilismy im2fd na kroki postepowania i kazdemu przypisalismy funkcje. Przerobienie takiego ideowego pseudokodu na dzialajacy kod w Matlabie, jest raczej banalne:

%pcygan 09.11.2011
% in -> out: str, str -> void
% purpose: compress imagefile using lzw algorithm and write generated code to output text file

function im2fd(imagefile,output)
disp(['Loading image: ' imagefile]);
IMAGE= imread(imagefile);  %wczytac obrazek
PREIMAGE= IMAGE;
disp('Reading image size.');
[rows_ cols_]= size(IMAGE); %wczytac jego rozmiary
disp(['Image size: ' num2str(rows_) 'x' num2str(cols_)]);
disp('Image vectorization.');
IMAGE= IMAGE(:)'; %macierz -> wektor
[o p]= size(IMAGE);
disp(['Data vector size is: ' num2str(o) 'x' num2str(p)]);
disp(['Max value: ' num2str(double(max(IMAGE))) ', min value: ' num2str(double(min(IMAGE)))]);
disp('Converting int2char');
IMAGE= char(IMAGE); %konwesja na wetkor char
disp('Coding image.'); 
CODE= lzw_encode(IMAGE); %kompresja
disp(['Code size' num2str(size(CODE))]);
disp('Code generated.');
disp(['Saving to file ' output '.']);
FILE= fopen(output,'w');
%przygotowanie danych do zapisu: polaczenie wektora rozmiaru obrazka z wektorem kodu z kompresji + zapisanie do pliku
fprintf(FILE,num2str([rows_ cols_ double(CODE)]));  
fclose(FILE);
disp('Save ok!');
imshow(PREIMAGE);
end

Bedziemy schludni i dodamy odrobine komentarzy, zeby po czasie gdy wrocimy do kodu, bylo latwiej go ogarnac. Ponadto dopisalismy troche wypisywania wiadomosci na konsole. :) Wszystkie funkcje dostepne sa w Matlabie, oprocz lzw_encode, ktora napiszemy sami.

lzw_encode
Jak mozemy sie dowiedziec z Wikipedii, pseudokod algorytmu kodowania LZW przedstawia sie nastepujaco:
1. Wypełnij słownik alfabetem źródła informacji.
2. c := pierwszy symbol wejściowy
3. Dopóki są dane na wejściu:
   Wczytaj znak s.
   Jeżeli ciąg c + s znajduje się w słowniku, 
      przedłuż ciąg c, tj. c := c + s
   Jeśli ciągu c + s nie ma w słowniku, wówczas:
      wypisz kod dla c (c znajduje się w słowniku)
      dodaj ciąg c + s do słownika
      przypisz c := s.
4. Na końcu wypisz na wyjście kod związany c.

W prosty sposob zaimplementujemy to w Matlabie, jako zapisanie (niemal) wprost pseudokodu w kod:

%pcygan 11.2011
%in->out: char vector -> int vector
%purpose: compress string(char vector) DATA using lzw algorithm and save compressed code in CODE (int vector)
%example:
%octave:33> lzw_encode('abrakadabra!')
%ans =
%
%    98    99   115    98   108    98   101   257   259    34
%

function CODE= lzw_encode(DATA)
    DICT= lzw_initDict();
    m= length(DATA);
    c= DATA(1); %char
    CODE= [];
    %TODO HANDLE 1-ELEMENT OR 2-ELEMENT DATA
    for i= 2:m
        %get s char
        s= DATA(i); %char
        %if c + s is in dict
        if lzw_findInDict(DICT,[c s])
            c= [c s];
        else
            CODE= [CODE lzw_findInDict(DICT,c)];
            c= [c s];
            DICT= [DICT {c}];
            c= s;
        end
    end
    CODE= [CODE lzw_findInDict(DICT,c)];
end

Pozostaja dwie funkcje do zdefiniowania:
-lzw_initDict(), odpowiedzialna za inicjalizacje slownika.
-lzw_findInDict(), odpowiedzialna za wyszukiwanie stringu w slowniku i zwracanie jego indeksu (badz 0 w przypadku gdy nie ma w slowniku).

Inicjalizacja slownika polega na wypelnieniu go alfabetem danych. W naszym przypadku, alfabetem danych jest zasieg 8-bitowej zmiennej char, poniewaz taki jest nasz Cel. Daje nam to 256 mozliwych znakow. Kod w Matlabie jest krotki i prosty, z jednym malym 'ale':

%pcygan 11.2011
%in->out: void->char vector
%purpose: create 1:256 char table containing all possible char values

function DICT= lzw_initDict()
    DICT= {0:255};
    for i=1:256
        DICT(i)= {char(i-1)};
    end
    %disp(DICT)
end

Ale polega na sposobie w jakim przechowujemy slownik. Przechowujemy jakby wskazniki na dane, zamiast samych danych - dlatego ciagi znakow w slowniku moga miec rozna dlugosc, co jest konieczne zeby algorytm zadzialal. Zwroc uwage na klamry '{' i '}' zamiast '[' i ']' w DICT.

Chemy, zeby lzw_findInDict(dict,str) zwrocila indeks wystapienia stringu str w slowniku dict lub 0 w przypadku, gdy nie wystepuje. Szukanie najprosciej zrobic jako porownywanie kazdego elementu slownika z szukanym stringiem i zwroceniem wartosci indeksu w razie sukcesu:

%pcygan 11.2011
%in->out: vector of strings, string->integer
%purpose: find whether string STR(char vector) is in dictionary(strings vector) DICT, if is, return it's index, if is not, return 0

function INDEX= lzw_findInDict(DICT,STR)
    INDEX= 0;
    [n m]= size(DICT);
    for i=1:m
        if strcmp(STR,DICT(i))
            INDEX= i;
            return;
        end
    end
end

Mamy teraz wszystko co jest potrzebne zeby nasza funkcja im2fd zadzialala. :)

fd2im
Teraz zrealizujemy drugi cel, jakim jest funkcja odwrotna do im2fd. Postepujemy analogicznie jak poprzednio - najpierw plan (ktory pomine), potem implementacja glownej funkcji fd2im, nastepnie implementacja funkcji 'skladowych'.

%pcygan 09.11.2011
% in -> out: str -> void
% purpose: read output lzw_file generated using im2fd function and create image

function fd2im(lzw_file)
disp(['Opening lzw .txt file: ' lzw_file '.']);
FILE= fopen(lzw_file);
disp('Copying data to memory.');
code= str2num(fread(FILE, '*char')');
disp('Closing file.');
fclose(FILE);
disp('File closed.');
rows_= code(1);
cols_= code(2);
disp(['Image size is: ' num2str(rows_) 'x' num2str(cols_)]);
[m n]= size(code);
code= code(3:n);
[m n]= size(code);
disp(['Code size is: ' num2str(m) 'x' num2str(n)]);
data= double(lzw_decode(code));
[o p]= size(data);
disp(['Data vector size is: ' num2str(o) 'x' num2str(p)]);
disp('Recovering matrix from vector by image size.');
data= v2m(data,rows_,cols_);
[r s]= size(data);
disp(['Data size is: ' num2str(r) 'x' num2str(s)]);
imshow(data,[0,255]);
disp(['Decompressed image size is: ' num2str(o*p) ', should be: ' num2str(rows_*cols_) '.']);
end

fd2im dziala odwrotnie jak im2fd, wiec nie ma sie tu nad czym zbytnio rozwodzic. Wyjasnienia wymagaja tylko dwie funkcje: v2m i lzw_decode. v2m jest banalne, poniewaz zamienia wektor w macierz:

%%
%AUTHOR 
%%
% pcygan 2011.10.28

%%
%DECLARATION    INPUT -> OUTPUT
%%
% VECTOR, ROWS_, COLS_ -> MATRIX[ROWS_,COLS_]

%%
%PURPOSE
%%
% CREATE MATRIX X EQUAL TO MATRIX M FROM VECTOR V, 
% WHEN VECTOR V WAS CREATED FROM MATRIX M: V= M(:)
% THIS FUNCTION REVERSE OPERATION M(:)
% V= M(:), X= v2m(V,M_rows,M_columns); 
% X == M -> true, after MATRIX X IS EQUAL TO MATRIX M.

%%
%EXAMPLE
%%
% $in$ F= [1 2 3 4; 5 6 7 8]
% $out$
% F =
%
%   1   2   3   4
%   5   6   7   8
% $in$ [rows_ cols_]= size(F)
% $out$
%   rows_ =  2
%   cols_ =  4
% $in$ F= F(:)
% $out$
%
% F =
%
%   1
%   5
%   2
%   6
%   3
%   7
%   4
%   8
% $in$ v2m(F,2,4)
% $out$
% ans =
%
%   1   2   3   4
%   5   6   7   8

function MATRIX= v2m(VECTOR,r,c)
    MATRIX= ones(r,c); %preallocate memory for better performance
    m= length(VECTOR); %remember VECTOR's number of rows
    i= 0; %set starting variables
    j= 1;
    for it=1:m %iterate through VECTOR
        i= i+1; %increment row pointer
        if i > r %if row pointer is greater restored matrix* then number of rows
            i= 1; %then reset row pointer to first row
        end
        %copy data from VECTOR(it) to proper position in MATRIX
        MATRIX(i,j)= VECTOR(it);
        if mod(it,r) == 0 %this mean we went through restored matrix column
            j= j+1; %so increment column pointer
        end
    end
end

% *restored matrix is M in %EXAMPLE

Troche duzo kodu, a dowiedzialem sie ostatnio, ze nie musialem deklarowac nowej funkcji, bo wystarczylo zamiast niej napisac (:

MATRIX(:)= VECTOR;

Natomiast lzw_decode to juz inna bajka:

%pcygan 11.2011
%in->out: int vector->char vector
%purpose: decode char string(char vector) DATA from CODE (int vector). we assume CODE was generated using LZW compression algorithm
%example:
%octave:34> lzw_decode(lzw_encode('abrakadabra!'))
%ans = 
%
% abrakadabra!
%

function DATA= lzw_decode(CODE)
    DICT= lzw_initDict();
    pk= CODE(1);
    k= 1;
    [n m]= size(CODE);
    DATA= DICT{pk};
    for i= 2:m
        %wczytaj kod k
        k= CODE(i);
        %ciag skojarzony z poprzednim kodem
        pc= DICT{pk};
        %jezeli slowo o kodzie k jest w slowniku
        [o p]= size(DICT);
        if k <= p
            tmp= DICT{1,k};
            pc= [pc tmp(1)]; %dodaj do slownika pc + slownik[k][0]
            DICT= [DICT {pc}];
            DATA= [DATA tmp]; %na wyjsciu wypisz slownik[k]
        else
            pc= [pc pc(1)];
            DICT= [DICT {pc}];
  DATA= [DATA pc];
        end
    pk= k;
    end
end

wtorek, 1 listopada 2011

OpenCV - niezly kombajn!

Odnoszac sie do poprzedniego postu, poszperalem w sieci troche w poszukiwaniu cross-platform API do obslugi kamerki i natknalem sie na OpenCV. Obsluguje ona wiekszosc, jezeli nie wszystkie ficzery jakie potrzebuje w swoim programie do sledzenia twarzy. Co znaczy, ze jezeli bym sie uparl, nie musialbym samemu nic napisac. :D W kazdym razie - rzeczy wymaga glebszej analizy, haha! :)

poniedziałek, 31 października 2011

GPU RGB2HSV

Maly programik, konwertujacy obrazek .TGA z przestrzeni kolorow RGB do HSV z pomoca GPU. Jest to wstep do wiekszego projektu - sledzenia twarzy przez kamere. Najpierw taka twarz trzeba znalezc. :) Mozna to osiagnac po przejsciu na przestrzen HSV i znalezieniu pikseli mieszczaczych sie w odpowiednich wartosciach:

H= <0, 0.1> u <0.9, 1>
S= <0.2, 0.7>
V= <0.4, 1>

W tym celu chce uzyc karty graficznej. Jestem na etapie konwersji rgb2hsv:
-> wczytaj obrazek i zamien na teksture,
-> w oknie o rozmiarach obrazka wyswietl prostokat w rzucie ortogonalnym z uzyciem wyspecjalizowanego shadera, z nalozona tekstura,
-> zapisz okno do pliku.
Okazuje sie, ze taki sposob dzialania jest bledny, bo wystarczy ze obrazek bedzie wiekszy niz dostepny ekran. :) Da sie to obejsc, ale na razie jest ok. ;)

Prosty shader konwersji wyglada nastepujaco:

rgb2hsv.glsl.vp:
#version 130

in vec4 vVertex;
in vec2 vTexCoords;

// Transformation Matrix
uniform mat4 mvpMatrix;

smooth out vec2 vVaryingTexCoords;

void main(void) 
{ 
    vVaryingTexCoords = vTexCoords;
    gl_Position = mvpMatrix * vVertex;
}
    

rgb2hsv.glsl.fp
#version 130

uniform sampler2D colorMap;

smooth in vec2 vVaryingTexCoords;
out vec4 vFragColor;

void main()
{
//image RGB value
 vec4 t= texture2D( colorMap, vVaryingTexCoords.st );
//max, min and diff 
 float fmax= max(max(t.x,t.y),t.z);
 float fmin= min(min(t.x,t.y),t.z);
 float diff= fmax-fmin;

//set up vFragColor with max, vFragColor represent HSV
 vFragColor.xyzw= vec4(fmax); //x->H y->S z->V

//if max is not 0, set S value
 if (fmax != 0)
  vFragColor.y= diff/fmax;

//if max and min are same, H is 0
 if (fmax == fmin)
  vFragColor.x= 0;
 else { //otherwise
  if ( fmax == t.x ) { //if R is dominant
   float tmp= 0;
   if (t.y < t.z)
    tmp= 6;
   vFragColor.x= (t.y-t.z)/diff + tmp;
  } 
  else if ( fmax == t.y ) //if G is dominant
   vFragColor.x= (t.z-t.x)/diff + 2;
  else if ( fmax == t.z ) //if B is dominant
   vFragColor.x= (t.x-t.y)/diff + 4;
   
  vFragColor.x= vFragColor.x/6;
 }
}
Jak widac, vertex shader ma tylko przepuscic geometrie i wspolrzedne tekstury do fragment shadera, w ktorym odbywa sie wlasciwa konwersja. Po szczegoly konwersji odsylam do wikipedii lub ksiazki "Techniki Biometryczne, Czesc 1, Metody Rzopoznawania Twarzy" G.Kukharev, A.Kuzminski. Poniższy screenshot pokazuje, od lewej - obraz oryginalny, obraz w HSV po przepuszczeniu przez moj program i obraz kontrolny w HSV po przepuszczeniu przez MATLABowska funkcjce rgb2hsv:

Dla Leny w calej jej krasie i w rozdzielczosci 1084x2318 widac duza roznice w szybkosci dzialania pomiedzy MATLABem a napisanym programem, i o to chodzilo. :) Lena moze nie jest najszczesliwszym obrazkiem do testow, bo wiekszosc obrazka miesci sie w przedzialach HSV, ale to nic. :)