Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 47

Sztuczna Inteligencja

Identyfikacja osób z wykorzystaniem geometrii dłoni

Krzysztof Brzegowy
Jarosław Dutka
1 Wstęp.............................................................................................................................................4
2 Systemy oparte o geometrię dłoni.................................................................................................6
3 Proces rozpoznawania....................................................................................................................7
3.1 Sposób pozyskiwania danych.................................................................................................7
3.2 Segmentacja...........................................................................................................................9
3.3 Obróbka zdjęć.......................................................................................................................10
4 Cechy geometrii dłoni...................................................................................................................13
5 Sposób porównywania cech.........................................................................................................14
5.1 Normalizacja.........................................................................................................................14
5.2 Metoda najbliższego sąsiada................................................................................................14
5.3 Metryka................................................................................................................................15
5.4 Wagi......................................................................................................................................15
6 Przeprowadzone badania.............................................................................................................16
6.1 Metoda przeprowadzenia badań..........................................................................................16
6.2 Wyniki testów.......................................................................................................................16
6.2.1 Test 1............................................................................................................................16
6.2.2 Test 2............................................................................................................................16
6.2.3 Test 3............................................................................................................................16
6.3 Interpretacja wyników..........................................................................................................17
6.4 Wnioski.................................................................................................................................17
7 Wstęp do OpenCv + opis wykorzystywanych struktur oraz funkcji..............................................18
7.1 Pliki nagłówkowe..................................................................................................................18
7.2 IplImage – struktura danych obrazowych.............................................................................18
7.2.1 Alokacja obrazu:...........................................................................................................18
7.2.2 Zwalnianie obrazu:........................................................................................................19
7.2.3 Powielanie obrazu:.......................................................................................................19
7.2.4 Odczytywanie obrazu z pliku........................................................................................19
7.2.5 Zapis obrazu do pliku:...................................................................................................19
7.3 CvScalar – struktura przechowująca skalary.........................................................................19
7.4 CvPoint - struktura przechowująca współrzędne punktów..................................................20
7.5 Funkcje rysowania................................................................................................................20
7.5.1 Okrąg............................................................................................................................20
7.5.2 Odcinek.........................................................................................................................20
8 Integracja OpenCV 1.0 ze środowiskiem Visual Studio 2008........................................................21
9 Struktury i funkcje........................................................................................................................23
9.1 Struktury...............................................................................................................................23
9.1.1 Circle.............................................................................................................................23
9.1.2 Finger............................................................................................................................23
9.1.3 Hand.............................................................................................................................24
9.2 Funkcje pomocnicze.............................................................................................................26
9.2.1 get_pixel(IplImage* image, CvPoint p).........................................................................26
9.2.2 set_pixel(IplImage* image, CvPoint p, unsigned char c)...............................................26
9.2.3 calc_distance(CvPoint p1, CvPoint p2)..........................................................................27
10 Proces przetwarzania próbki oraz wyszukiwania cech.............................................................28
10.1 Przygotowanie wektora punktów konturu...........................................................................28
10.2 Znajdowanie końcówek palców oraz zagłębień miedzy palcami..........................................30
10.3 Znajdowanie cech placów.....................................................................................................32
10.4 Znajdowanie punktów podstawy dłoni (lewy oraz prawy dolny punkt dłoni).......................33
10.5 Obliczenie cech dłoni............................................................................................................33
10.6 Obliczanie pole powierzchni palców.....................................................................................35
10.7 Znajdowanie okręgów wpisanych w palce............................................................................38
11 Instrukcja obsługi......................................................................................................................43
12 Literatura..................................................................................................................................46
1 Wstęp

Biometria jest to sposób rozpoznawania i identyfikacji ludzi oparty na cechach fizycznych i


behavioralnych człowieka. Do cech tych zalicza się charakterystyki linii papilarnych, kształtu twarzy
czy dłoni, tęczówki oka, pisma ręcznego, jak również mowy, czy sposób uderzania w klawisze, a
nawet układ żył nadgarstka. Biometryka może być wykorzystana do uniemożliwienia
nieautoryzowanych prób dostępu do bankomatów, komputerów osobistych, sieci komputerowych,
telefonów komórkowych, domowych systemów alarmowych, itd.

Identyfikacja biometryczna jest zdecydowanie wygodniejsza od obecnie stosowanych metod


korzystających z kart dostępu, numerów PIN i haseł z wielu przyczyn.

Najistotniejsze jest to, że obecnie stosowane metody identyfikują numer, kartę czy hasło, a nie
osobę. Uwierzytelnianie w takim przypadku nie jest w żaden sposób związana z osobą - jest związana
wyłącznie z urządzeniem. Prawdziwa, biometryczna identyfikacja zastępuje potrzebę pamiętania
hasła całkowicie. Biometryczne metody identyfikacji mogą być stosowane zarówno oddzielnie jak i
łącznie z dotychczas stosowanymi rozwiązaniami. Zabezpieczeń dla systemów informatycznych i
pomieszczeń wykorzystujących techniki biometryczne umożliwiają całkowitą ochronę przed
dostępem i ingerencją osób niepowołanych.

Informacja przetwarzana i przesyłana w tych systemach jest całkowicie niedostępna dla osób
postronnych. Dzięki zachowywaniu w repozytorium jedynie matematycznego zapisu cech
charakterystycznych, jak również dzięki szyfrowaniu transmisji, dane chronione są zarówno przed
przypadkowym jak i celowym działaniem intruzów. Systemy biometryczne zapewniają wysoki poziom
bezpieczeństwa oraz wygody. Użytkownicy systemu nie muszą nosić ze sobą żadnych specjalnych
kart, pamiętać skomplikowanych haseł itd. Z drugiej strony techniki te nadają się do zabezpieczania
zarówno pilnie strzeżonych zasobów jak i pojedynczych komputerów PC.

Techniki biometryczne są obecnie jednym z najbardziej dynamicznie rozwijających się działów


teleinformatyki. Rosnąca ilość przetwarzanych informacji powoduje, że ważnym problemem staje się
zapewnienie najwyższej jakości kontroli dostępu. Mowa tu zarówno o kontroli dostępu do
pomieszczeń, jak i kontroli logicznej użytkowników korzystających z określonych danych lub
programów. Tradycyjne techniki oparte o karty magnetyczne, procesorowe czy też o system haseł
mają zbyt wiele wad, aby sprostać współczesnym wymaganiom. Przede wszystkim kartę
magnetyczną czy mikroprocesorową można po prostu ukraść (lub zapomnieć), hasło można
podejrzeć lub go po prostu wymyślić (zdecydowana większość użytkowników stosuje trywialne hasła,
jak imię, data urodzin czy numer rejestracyjny samochodu).

Problemy te nie występują w przypadku kontroli biometrycznej. Jest ona bowiem oparta o
specyficzne cechy organizmu (lub jego zachowania), indywidualnych dla każdego człowieka. Warto
podkreślić, że również z punktu widzenia kosztów systemy oparte o karty nie są najbardziej
korzystne: wszak oprócz czytnika trzeba jeszcze zakupić, często niebagatelną, ilość kart. Ponadto, w
przypadku kart magnetycznych konieczna jest ich okresowa wymiana! Dzięki powstaniu rozwiązań w
których weryfikacja jest przeprowadzona przez komputer, czytnik nie musi już być tak wyładowany
elektroniką.

W chwili obecnej wśród najpopularniejszych technik biometrycznych można wymienić:

 systemy oparte o wzór linii papilarnych


 systemy oparte o geometrię dłoni
 systemy oparte o dynamikę mowy
 systemy oparte o wzór tęczówki oka
2 Systemy oparte o geometrię dłoni

Systemy geometrii dłoni są powszechnie stosowane w celu weryfikacji tożsamości. Są to systemy


nie budzące obaw odnośnie zachowania prywatności użytkowników, stosunkowo tanie w budowie i
proste w użyciu, a przy tym zapewniające wysoki poziom bezpieczeństwa. Po pierwsze większość z
nas nie uważa kształtu dłoni za cechę szczególnie osobistą bądź intymną (w przeciwieństwie np. do
odcisków palców, często uznawanych za cechę bardzo osobistą, pomimo faktu, że zostawia się je na
setkach przedmiotów dotykanych każdego dnia). Jesteśmy przyzwyczajeni do wymieniania uścisków
dłoni i dotykania dłonią różnych przedmiotów, stąd bezpośredni kontakt z urządzeniem pomiarowym
zwykle nie budzi naszych obaw (a przykładowo użytkownicy systemów tęczówki często odczuwają
pewną blokadę np. przed spojrzeniem prosto w kamerę). Po drugie, cechy geometrii dłoni nie
zawierają wystarczającej ilości informacji, która pozwalałaby na identyfikację tożsamości. Co więcej,
systemy te wykorzystują proste metody pomiaru i z tego względu są nie tylko tanie w budowie, ale
również proste w użyciu. Czas potrzebny użytkownikowi do nauczenia się, jak korzystać z urządzenia
jest nieporównywalnie krótszy niż w przypadku innych metod biometrycznych. Powyższe cechy
sprawiają, że systemy geometrii dłoni są jednymi z najszerzej akceptowanych ze względów
społecznych, kulturowych, psychologicznych i religijnych metod biometrycznych na świecie.

Warto zauważyć, że mimo iż niemal cała pracująca populacja ma dłonie to system może również
zostać zaadaptowany do obsługi osób niepełnosprawnych (oczywiście tylko z pewnymi typami
niepełnosprawności). W razie konieczności istnieje możliwość integracji z innymi technikami
biometrycznymi, a w szczególności z pomiarami odcisków palców i/lub układu linii papilarnych na
wewnętrznej stronie dłoni (ang. palmprints).

Główną wadą biometrii geometrii dłoni jest fakt, że jakość działania systemu może zostać
poważnie zmniejszona w przypadku poważnych zranień dłoni (opuchnięcia, złamania). Tymczasem
uszkodzenia kończyn górnych są jednymi z najczęściej występujących skutków wypadków przy pracy.
Częściowym rozwiązaniem tego typu problemów może być wprowadzanie do pamięci systemu obu
dłoni każdego użytkownika.

Biometryczne systemy weryfikujące, bazujące na geometrii dłoni wykorzystują jedynie informacje


o fizycznych wymiarach dłoni, takich jak długości i szerokości palców (mierzone w kilku różnych
miejscach) czy pewne geometryczne własności środkowej części dłoni (śródręcza). Należy w tym
miejscu podkreślić, że inne cechy, takie jak odciski palców, wzór linii papilarnych na wewnętrznej
stronie dłoni (ang. palmprints), zdjęcia termiczne dłoni czy układ naczyń krwionośnych dłoni,
jakkolwiek są naturalnie związane z dłonią, uznaje się za oddzielne rodzaje metod biometrycznych
(każda z nich wymaga innego przetwarzania). Większość z powyższych technik może zostać połączona
w jednym urządzeniu z podejściem bazującym na pomiarach geometrii dłoni.

Dane dotyczące kształtu dłoni są obliczane na podstawie zdjęcia dłoni. Po wykonania zdjęcia
dłoni stosuje się specjalne techniki przetwarzania obrazu i wyznacza geometryczne cech dłoni. W celu
porównania dłoni z zapisanym w pamięci systemu wzorcem stosowane są różne techniki klasyfikacji.
Sam proces porównania można rozumieć jako wyznaczanie odległości między wzorcem a aktualnie
weryfikowaną dłonią. Jeśli dłoń należy do tego samego użytkownika, do którego należy wzorzec, to
wyznaczona odległość jest mała (tzn. występuje duże podobieństwo między wzorcem a bieżącym
obrazem dłoni). W przeciwnym wypadku odległość jest duża.

3 Proces rozpoznawania
3.1 Sposób pozyskiwania danych

Pierwszym krokiem w systemie identyfikacji jest pobranie danych cech od użytkownika poprzez
wykonanie zdjęcia dłoni. Zdjęcia można wykonać na kilka różnych sposobów.

Metoda identyfikacji dłoni wymaga m.in. aby:

 kąty między palcami były odpowiedniej miary,


 zdjęcie dłoni zrobione było prostopadle do obiektywu aparatu - jakikolwiek kąt
nachylenia powoduje zmianę powierzchni dłoni, oraz palców (powierzchnie palców oraz
dłoni wykorzystujemy jako jedne z cech)
 odległość dłoni od obiektywu aparatu była za każdym razem taka sama - dłoń położona
bliżej obiektywu jest większa, niż ta sama dłoń położona dalej od obiektywu – różne
długości palców oraz innych elementów wykorzystywanych jako cechy

Najprostszym sposobem do pobrania cech jest budowa specjalistycznego urządzenia,


pokazanego na poniższych rysunkach:
Na rysunku widzimy urządzenie Handkey II oraz ID3D Handkey dostępne komercyjnie na rynku.
W takim urządzeniu pobieranie cech geometrycznych dłoni można połączyć z pobraniem linii
papilarnych na wewnętrznej stronie dłoni, czy odcisków palców. Zdjęcia dostarczone do obróbki nie
stwarzają problemów dzięki kołeczkom między palcami (równy, za każdym razem taki sam rozstaw
palców), a także brak problemów z odizolowaniem dłoni od tła. Wydawało by się, że jest to metoda
idealna, jednakże wymaga budowy specjalistycznego sprzętu.

Bardziej przyjazną metodą, możliwą do realizacji w warunkach domowych jest budowa


urządzenia pokazanego na rysunku poniżej:

Zwykły aparat fotograficzny umieszczamy w stałej odległości od maty na której kładziemy dłoń.
W metodzie tej możliwe są różne kąty pomiędzy palcami (brak kołeczków) co wymaga dodatkowego
modułu dokonującego korekty rozstawień palców, gdyż metoda rozpoznawania dłoni po geometrii
wymaga aby kąty między palcami były odpowiednich stałych wielkości.

Innym sposobem realizacji tego problemu jest budowa urządzenia które składa się z szyby za
którą znajduje się aparat fotograficzny. Do szyby przykładamy dłoń. Dłoń za każdym razem jest w
jednakowej odległości od obiektywu. W takim rozwiązaniu napotykamy problem rozstawu palców
oraz detekcji dłoni od tła, gdyż dłoń może za każdym razem znajdować się na innym tle. Oba
problemy można rozwiązać.

Przykłady zdjęć na szybie:

Najbardziej przyjazną metodą, a zarazem najbardziej estetyczną jest kamera przed którą
obracamy ręką. System wybiera z serii zdjęć zdjęcie najbardziej prostopadłe do obiektywu i
przekazuje do dalszej obróbki. Jednakże w metodzie tej pojawia się poważny problem związany z
odległością ręki od obiektywu. Z lekcji fizyki wiemy, że rzeczy bliższe wydają się większe, a rzeczy
bardziej oddalone mniejsze. Z racji tego, że w naszym systemie do rozpoznawania używamy wiele
cech związanych z szukaniem odległości (palców, długości dłoni, obwodów, itd.) sytuacja taka nie
może mieć miejsca. Potrzebny jest moduł to skalowania obrazów zdjęć.

3.2 Segmentacja

W metodach których dopuszczamy różne rozstawy między palcami – nie używamy kołeczków
między które wstawiamy dłoń musimy dodać moduł dokonujący segmentacji dłoni. Dla każdej dłoni
wymagamy aby linie między wierzchołkami palców a punktami między nimi nie stykały się
wzajemnie. Dłonie mające podobne wady jak na rysunku poniżej należy odrzucić:
3.3 Obróbka zdjęć

Następnym etapem na drodze rozpoznawania jest obróbka otrzymanych zdjęć. Zakładamy, że


dokonaliśmy przeskalowania oraz segmentacji. Wykonujemy między innymi następujące kroki:

 detekcja tła, binaryzacja,

 obrót oraz przesunięcie dłoni,


 korekta śladu do pierścionkach,

 korekcja kątów między palcami,

Kąty między palcami a pivot line:

Kciuk Wskazujący Środkowy Pierścionkowy Mały

150 120 100 80 60

 odcięcie nadgarstka
Na powyższych ilustracjach pokazaliśmy jakie kroki wykonać, aby otrzymać obrazki mogące służyć
jako próbki w naszym programie. Nie jest to jednak zakres naszego projektu. Dokładny opis jak
wykonać powyższe kroki opisują: Ender Konukoğlu, Erdem Yörük, Jerôme Darbon, Bülent Sankur w
pracy pt: “Shape-Based Hand Recognition”

Otrzymujemy próbki jak na rysunku:


Od tego momentu wkraczamy w zakres naszego projektu. Próbki jak na rysunkach powyżej,
zostały wykonane i znormalizowane przez prof. dr Bulent Sankur [3]. Posiadamy po 3 zdjęcia o
rozmiarze 200x200 pikseli, 755 osobników, dla których napiszmy system do identyfikacji
wykorzystujący cechy geometrii dłoni.
4 Cechy geometrii dłoni

Wyszukujemy następujący zestaw cech:

 5x długość palca
 5x pole podstawy palca
 4x największy okrąg wpisany w górnej połowie palców (baz kciuka)
 4x największy okrąg wpisany w dolnej połowie palców (bez kciuka)
 1x największy okrąg wpisany w kciuka
 5x pole powierzchni palców
 5x obwód palców
 4x odległości między początkiem kciuka a środkami podstaw poszczególnych palców (bez
kciuka)
 4x poszczególne boki czworokąta wokół środka dłoni (czerwone linie na rysunku).
5 Sposób porównywania cech

Jako dane wyjściowe programu wyznaczającego cechy dostajemy wektor 39 liczb. Są to wartości
nie znormalizowane i ich zakres zależy od charakteru cechy. W takiej postaci cechy nie nadają się do
porównania gdyż zakres wartości każdej z cech jest różny. Aby temu zaradzić należy poddać wektor
cech normalizacji.

5.1 Normalizacja

Proces normalizacji przeprowadzany jest dla każdej cechy osobno. Pierwszym krokiem jest
znalezienie wartości maksymalnej i minimalnej cech. Następnie wartość każdej cechy jest
przenoszona w zakres 0,0 - 1,0. Przykładowo wartość pewniej cechy w jakiejś próbce wynosi 15.
Wartość minimalna równa się 10 a maksymalna 20. W tym przypadku nowa wartość tej cechy
wyniesie 0,5.

Po procesie normalizacji każda cecha znajduje się w zakresie 0,0 - 1,0 dzięki czemu waga każdej
cechy jest taka sama.

5.2 Metoda najbliższego sąsiada

Jako metodę porównywania próbek wybraliśmy metodę najbliższego sąsiada. Polega ona na
mierzeniu odległości pomiędzy punktami które są reprezentowane poprzez wektor cech w
przestrzeniu n-wymiarowej oraz wybieraniu tych znajdujących sie najbliżej. W naszym przypadku
przestrzeń ta ma 39 wymiarów.

Każdy osobnik wprowadzany do systemu posiada pewną liczbę próbek jego dłoni. Taki zestaw
próbek tworzy tak zwaną klasę. Uwzględniając fakt, że punkty w takiej grupie leżą bardzo blisko
siebie możemy z dużym prawdopodobieństwem wnioskować, że najbliższy punkt aktualnie
badanemu należy do tej samej klasy.

Prosty przykład pokazujący ideę szukania najbliższego sąsiada. W tym przypadku są dwie klasy
punktów 'A' oraz 'B'. Oraz przestrzeń jest dwu-wymiarowa.
5.3 Metryka

Ważnym aspektem jest również sposób mierzenia odległości. Można tu skorzystać z różnych
metryk. My zdecydowaliśmy się na metrykę Euklidesową. Jest ona bardzo popularna i łatwa do
wyobrażenia sobie oraz zapewnie dobrą skuteczność.

Metryka Euklidesowa wyraża się wzorem:


N
d 2 ( x , y )= ∑ ¿¿¿
i=1

5.4 Wagi

Aby poprawić skuteczność metody zostały wprowadzone wagi dla każdej z cech. Wybór
odpowiedniego zestawu wag jest bardzo trudny i musi zostać wykonany automatycznie.

Głównym czynnikiem przyjętym przez nas podczas doboru wag był rozrzut wartości danej
cechy. Im Wartości były bardziej zróżnicowane tym waga była większa. Dobrym wskaźnikiem rozrzutu
jest odchylenie standardowe i właśnie je przyjęliśmy za wagi.

Odchylenie standardowe wyraża się wzorem:


n
1
s= ∑ ¿¿¿
n−1 i=1
6 Przeprowadzone badania
6.1 Metoda przeprowadzenia badań

Do przeprowadzenia badań napisany został specjalny program w języku c#. Wczytuje on


zestaw testowy składający się z kolejnych wektorów cech oraz nazw plików im odpowiadających.

Z uwagi na wybór metody NN program wypisuje n najbliższych sąsiadów próbki w kolejności od


najbliższej względem metryki euklidesowej. Następnie szukamy pierwszy element który jest tej samej
klasy co badana próbka (to znaczy obie próbki są odwzorowaniem dłoni tej samej osoby). Po
odnalezieniu pierwszego elementu tej samej klasy zwiększamy licznik odpowiadający pozycji na
której został dopasowany.

Przykładowy fragment z wyjścia programu:

107: 107 22 202 332 227 199 171 41 185 171


107: 22 22 183 107 332 125 107 197 283 282
108: 108 70 65 243 65 243 44 557 536 362
108: 108 65 65 464 65 371 70 44 243 371
108: 649 509 243 405 620 537 443 141 260 509
109: 109 109 71 71 5 71 74 117 149 149

Pierwsza liczba określa numer aktualnie badanej klasy. Kolejne liczby to klasy najbliższych
sąsiadów. Jak widać w drugiej linijce najbliższy sąsiad dla tej próbki miał klasę numer 22 a poprawna
klasa znalazła się dopiero na czwartej pozycji.

6.2 Wyniki testów

Przeprowadziliśmy testy dla trzech zestawów próbek. Próbki te zostały wybrane przypadkowo.
Na każdego osobnika przypadają po 3 próbki dłoni. Daje to nam rozmiar klasy równy 3. Jest to
wartość bardzo mała. Zwiększenie jest do 5 lub 10 na pewno znacznie poprawiło by wyniki.

6.2.1 Test 1
Liczba próbek: 289
Dopasowanie: 1 - 90,31%, 2 - 93,43%, 3 - 94,46%, 4 - 96,19%, 5 - 96,89%, 6 - 97,23%, 7 - 97,92%, 8 - 98,27%, 9 -
98,62%, 10 - 98,62%,

6.2.2 Test 2
Liczba próbek: 199
Dopasowanie: 1 - 86,93%, 2 - 90,95%, 3 - 92,96%, 4 - 92,96%, 5 - 94,47%, 6 - 94,97%, 7 - 96,48%, 8 - 96,98%, 9 -
96,98%, 10 - 96,98%,

6.2.3 Test 3
Liczba próbek: 2225
Dopasowanie: 1 - 65,66%, 2 - 72,58%, 3 - 77,08%, 4 - 79,60%, 5 - 81,84%, 6 - 83,42%, 7 - 84,54%, 8 - 85,35%, 9 -
86,25%, 10 - 86,97%,
6.3 Interpretacja wyników

Pewne wątpliwości może budzić forma wyników. Otóż zapis 3 - 77,08% oznacza, że z
prawdopodobieństwem równym 77% wśród trzech najbliższych sąsiadów znajdzie się próbka tej
samej klasy co badana. W przypadku systemów wspomagających wybór decyzji w przypadku dostępu
warunkowego ta informacja jest dość istotna.

6.4 Wnioski

W teście numer 3 wyniki nie są zbyt dobre. Spowodowane to jest bardzo małą liczbą próbek
dłoni przypadającą na jedną osobę. W przypadku zwiększenia tej liczby wyniki powinny ulec znacznej
poprawie. W teście 1 gdzie sumaryczna liczba próbek była o rząd mniejsza wyniki prezentują się
znacznie lepiej.
7 Wstęp do OpenCv + opis wykorzystywanych struktur oraz funkcji

W pisaniu kodu wyszukiwania cech na obrazkach użyliśmy biblioteki OpenCv.

OpenCV jest biblioteką funkcji wykorzystywanych podczas obróbki obrazu, opartą o otwarty kod i
zapoczątkowaną przez Intela. Biblioteka ta jest wieloplatformowa, można z niej korzystać w Mac OS
X, Windows jak i Linux. Autorzy jej skupiają się na przetwarzaniu obrazu w czasie rzeczywistym.

Podstawowe struktury:

7.1 Pliki nagłówkowe

#include <cv.h>
#include <cvaux.h>
#include <highgui.h>
#include <cxcore.h> // niekoniecznie - zawarty w cv.h

7.2 IplImage – struktura danych obrazowych

IplImage
|-- int nChannels; // Number of color channels (1,2,3,4)
|-- int depth; // Pixel depth in bits:
| // IPL_DEPTH_8U, IPL_DEPTH_8S,
| // IPL_DEPTH_16U,IPL_DEPTH_16S,
| // IPL_DEPTH_32S,IPL_DEPTH_32F,
| // IPL_DEPTH_64F
|-- int width; // image width in pixels
|-- int height; // image height in pixels
|-- char* imageData; // pointer to aligned image data
| // Note that color images are stored in BGR order
|-- int dataOrder; // 0 - interleaved color channels,
| // 1 - separate color channels
| // cvCreateImage can only create interleaved
// images
|-- int origin; // 0 - top-left origin,
| // 1 - bottom-left origin (Windows bitmaps style)
|-- int widthStep; // size of aligned image row in bytes
|-- int imageSize; // image data size in bytes = height*widthStep
|-- struct _IplROI *roi;// image ROI. when not NULL specifies image
| // region to be processed.
|-- char *imageDataOrigin; // pointer to the unaligned origin of image
// data
| // (needed for correct image deallocation)
|
|-- int align; // Alignment of image rows: 4 or 8 byte alignment
| // OpenCV ignores this and uses widthStep instead
|-- char colorModel[4]; // Color model - ignored by OpenCV

7.2.1 Alokacja obrazu:

IplImage* cvCreateImage(CvSize size, int depth, int channels);


rozmiar:
cvSize(width,height);
głębokość: głębia kolorów w bitach:
IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16U, IPL_DEPTH_16S, IPL_DEPTH_32S,
IPL_DEPTH_32F, IPL_DEPTH_64F
kanały: Liczba kanałów na piksel. Mogą być 1, 2, 3 lub 4. Kanały są z przeplotem.

// Alokacja 1-kanałowego obrazu z danymi bajtowymi


IplImage* img1=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);

// Alokacja 3-kanałowego obrazu z danymi float


IplImage* img2=cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3);

7.2.2 Zwalnianie obrazu:

IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
cvReleaseImage(&img);

7.2.3 Powielanie obrazu:

IplImage* img1=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
IplImage* img2;
img2=cvCloneImage(img1);

7.2.4 Odczytywanie obrazu z pliku.

Obsługiwane formaty: BMP, DIB, JPEG, JPG, JPE, PNG, PBM, PGM, PPM, SR, RAS, TIFF, TIF ):

IplImage* img=0;
img=cvLoadImage(fileName);
if(!img) printf("Nie można załadować pliku: %s\n",fileName);

Ładowany obraz domyślnie jest traktowany jako 3-kanałowy obraz kolorowy. Może to być
zmienione przez zastosowanie:

img=cvLoadImage(fileName,flag);

flag:
>0 the loaded image is forced to be a 3-channel color image
=0 the loaded image is forced to be a 1 channel grayscale image
<0 the loaded image is loaded as is (with number of channels in the file).

7.2.5 Zapis obrazu do pliku:

if(!cvSaveImage(outFileName,img)) printf("Nie mozna zapisac: %s\


n",outFileName);

Format pliku wyjściowego jest określony przez rozszerzenie nazwy pliku.


7.3 CvScalar – struktura przechowująca skalary

CvScalar s = cvScalar(double val0, double val1=0, double val2=0, double


val3=0);

7.4 CvPoint - struktura przechowująca współrzędne punktów

CvPoint p = cvPoint(int x, int y);

CvPoint2D32f p = cvPoint2D32f(float x, float y);


CvPoint3D32f p = cvPoint3D32f(float x, float y, float z);

p.x=5.0;
p.y=5.0;

7.5 Funkcje rysowania


7.5.1 Okrąg

// narysuj okrąg o środku w (100,100) i promieniu 20. Użyj zielonej linii o


grubości 1
cvCircle(img, cvPoint(100,100), 20, cvScalar(0,255,0), 1);

7.5.2 Odcinek

// narysuj odcinek o grubości 1 pomiędzy (100,100) and (200,200)


cvLine(img, cvPoint(100,100), cvPoint(200,200), cvScalar(0,255,0), 1);
8 Integracja OpenCV 1.0 ze środowiskiem Visual Studio 2008

W projekcie została użyta biblioteka OpenCV 1.0. Po zainstalowaniu jej na PC, przed kompilacją
programu należy skonfigurować środowisko Visual Studio według następujących zasad:

 Uruchamiamy Visual Studio C++. W pasku meny wybieramy Tools->Options


 Z listy wybieramy Projects->VC++ Directories.
 Zaznaczamy Library files z listy "Show Directories for" .
 Klikamy w ikonę Insert New, i podajemy lokalizację do folderu w którym zainstalowaliśmy
OpenCV.
 Domyślna lokalizacja "C:/Program Files/OpenCV".
 Dodajemy następujące pliki:
 "C:\Program Files\OpenCV\lib"

Wybieramy z listy Include files i dodajemy następujące katalogi:

 "C:\Program Files\OpenCV\cv\include"
 "C:\Program Files\OpenCV\cxcore\include"
 "C:\Program Files\OpenCV\otherlibs\highgui"
 "C:\Program Files\OpenCV\cvaux\include"
 "C:\Program Files\OpenCV\otherlibs\_graphics\include"
Wybieramy z listy Sources files i dodajemy następujące katalogi:

 "C:\Program Files\OpenCV\cv\src"
 "C:\Program Files\OpenCV\cxcore\src"
 "C:\Program Files\OpenCV\cvaux\src"
 "C:\Program Files\OpenCV\otherlibs\highgui"
 "C:\Program Files\OpenCV\otherlibs\_graphics\src"
Klikamy OK.

9 Struktury i funkcje
9.1 Struktury
9.1.1 Circle

Struktura definiująca okrąg jako środek , oraz promień. Trzymamy w niej okręgi wpisanych w palce.

struct circle
{
CvPoint p;
int r;
};

9.1.2 Finger

Struktura definiująca palec. Zawiera następujące pola:


 first - indeks pierwszego punktu opisującego kontur paleca w wektorze zawierającego
współrzędne punktów konturu – zielone okręgi
 last – indeks ostatniego punktu opisującego kontur paleca w wektorze zawierającym
współrzędne punktów konturu – niebieskie okręgi
 area - pole powierzchni palca
 length – długość palca
 base_lenght – długość podstawy palca
 circuit – obwód palca
 circle circles[2] – struktury przechowujące okręgi wpisane w palec
 first_point – współrzędne pierwszego punktu palca – zielone okręgi
 last_point – współrzędne ostatniego punktu palca – niebieskie okręgi
 finger_end_point – współrzędne środka paznokcia – pomarańczowe okręgi
 base_center_point – współrzędne środka podstawy palca – brązowe okręgi

struct finger
{
int first;
int last;

int area;
int length;
int base_length;
int circuit;
circle circles[2];

CvPoint first_point;
CvPoint last_point;
CvPoint finger_end_point;
CvPoint base_center_point;
};

finger fingers[5];

9.1.3 Hand

Struktura definiująca dłoń. Zawiera następujące pola:


 pole powierzchni dłoni,
 długość dłoni,

 pozycje lewego dolnego punktu dłoni – żółty okrąg


 pozycje prawego dolnego punktu dłoni – zielony okrąg
 długość podstawy dłoni – czerwona linia
 odległość między lewym dolnym punktem dłoni, a lewym punktem palca
wskazującego – zielona linia
 odległość między prawym dolnym punktem dłoni, a prawym punktem palca
małego – różowa linia
 4x odległość między kciukiem, a środkami podstaw poszczególnych palców –
pomarańczowe linie

struct hand
{
int total_area;
int length;
int base_center_point_to_thumb_length[4];
int index_first_point_to_pinkle_last_point_length;
int hand_right_end_point_to_left_end_point_length;
int hand_left_end_point_to_index_first_point_length;
int pinkle_last_point_to_hand_right_end_point_length;

CvPoint left_end_point;
CvPoint right_end_point;
};

hand hand_features;
9.2 Funkcje pomocnicze
9.2.1 get_pixel(IplImage* image, CvPoint p)

Funkcja pobierająca kolor piksela o współrzędnych podanych w strukturze CvPoint, z obrazka


image podanego w parametrach funkcji.

unsigned char get_pixel(IplImage* image, CvPoint p)


{
return image->imageData[(p.x+p.y*image->widthStep)*image->nChannels];
}

9.2.2 set_pixel(IplImage* image, CvPoint p, unsigned char c)

Funkcja ustawiająca kolor piksela o współrzędnych podanych w strukturze CvPoint, na


obrazku image na wartość zawartą w zmiennej c.

void set_pixel(IplImage* image, CvPoint p, unsigned char c)


{
image->imageData[(p.x+p.y*image->widthStep)*image->nChannels] = c;
}
9.2.3 calc_distance(CvPoint p1, CvPoint p2)

Funkcja obliczająca odległość między dwoma punktami:

float calc_distance(CvPoint p1, CvPoint p2)


{
int x_diff = p1.x - p2.x;
int y_diff = p1.y - p2.y;
return (float)sqrt((double)(x_diff*x_diff + y_diff*y_diff));
}
10 Proces przetwarzania próbki oraz wyszukiwania cech
10.1 Przygotowanie wektora punktów konturu

Dane wejściowe – znormalizowany obrazek dłoni:

Dane wyjściowe - wektor w którym znajdują się punkty definiujące kontur dłoni, począwszy od
piksela leżącego o jedną pozycję piksela wyżej niż lewy dolny róg dłoni, zgodnie z ruchem wskazówek
zegara. Ostatni punkt w wektorze to lewy dolny róg dłoni.

std::vector<CvPoint> contour_points;

Tworzenie konturu dłoni podzielone jest na 2 etapy. Pierwszy to usunięcie z wnętrza dłoni
wszystkich białych pikseli tak by na obrazku został tylko kontur o grubości jednego piksela co jest
równoznaczne z tym, że każdy piksel ma tylko dwóch sąsiadów. Pierwszy etap realizuje następująca
funkcja:

void create_contour(IplImage* image1, IplImage* image2)


{
int i, j;
uchar *data =(uchar *)(image1->imageData);
uchar *out_data =(uchar *)(image2->imageData);
for(i=1;i<image1->height-1;i++)
for(j=1;j<image1->width-1;j++)
{
if(data[i*image1->widthStep+j] == 255)
{
bool b1 = data[(i+1)*image1->widthStep+j] == 255;//dół
bool b2 = data[(i-1)*image1->widthStep+j] == 255;//góra
bool b3 = data[i*image1->widthStep+j+1] == 255;//prawo
bool b4 = data[i*image1->widthStep+j-1] == 255;//lewo

if(b1 && b2 && b3 && b4)


out_data[i*image1->widthStep+j] = 0;
else
out_data[i*image1->widthStep+j] = 255;
}
else
out_data[i*image1->widthStep+j] = 0;
}

data =(uchar *)(image2->imageData);


out_data =(uchar *)(image1->imageData);
for(i=1;i<image1->height-1;i++)
for(j=1;j<image1->width-1;j++)
{
if(data[i*image1->widthStep+j] == 255)
{
bool b1 = data[(i+1)*image1->widthStep+j] == 255;//dół
bool b2 = data[(i-1)*image1->widthStep+j] == 255;//góra
bool b3 = data[i*image1->widthStep+j+1] == 255;//prawo
bool b4 = data[i*image1->widthStep+j-1] == 255;//lewo

if((!b1 && b2 && !b3 && b4)||(!b1 && b2 && b3 && !b4)||
(b1 && !b2 && !b3 && b4)||(b1 && !b2 && b3 && !b4))
out_data[i*image1->widthStep+j] = 0;
else
out_data[i*image1->widthStep+j] = 255;
}
else
out_data[i*image1->widthStep+j] = 0;
}
}

Kolejnym etapem jest przekształcenie obrazka na ciąg punktów.

void get_points_from_contour(IplImage* image1)


{
CvPoint new_point;
CvPoint refference_point = cvPoint(image1->width/2, image1->height);
CvPoint start_point = find_start_point(image1);
CvPoint current_point = start_point;
CvPoint last_point = start_point;
last_point.x++;

bool done = false;


while(!done)
{
new_point = find_next_point(image1, current_point, last_point);
last_point = current_point;
current_point = new_point;

float distance = calc_distance(refference_point, current_point);

contour_points.push_back(current_point);
radial_distance_values.push_back(distance);

if(current_point.x == start_point.x && current_point.y ==


start_point.y)
done = true;
}
}
Podczas szukania punktów liczone są jednocześnie odległości tychże punktów od punktu
referencyjnego .

10.2 Znajdowanie końcówek palców oraz zagłębień miedzy palcami

Na powyższym rysunku widać wykres odległości punktów konturu od punktu referencyjnego.


Maksima na tym wykresie odpowiadają końcówkom palców natomiast minima zagłębieniom między
palcami.

Jak widać na wykresie maksima oraz minima mogą być są płaskie. Każde ekstremum
traktowane jest jako pewien przedział gdyż nie można jednoznacznie powiedzieć który piksel jest
wartością maksymalną. Znajdowaniem tych przedziałów zajmuje sie funkcja find_extremums.

void find_extremums()
{
group min_group;
min_group.first = 0;
min_group.last = 0;
group max_group;
max_group.first = 0;
max_group.last = 0;

CvPoint last_min_point = cvPoint(0,0);


CvPoint last_max_point = cvPoint(0,0);
//przedział w jakim badamy zmienność
//punk jest traktowany jako należący do ekstremum
//jeżeli dwa sąsiednie punkty to jest
//(x-step) i (x+step) są mniejsze dla maksimum
//i większe dla minimum
int step = 10;
//group_size oznacza maksymalny rozmiar grupy
int group_size = 20;
for(int i=step; i<radial_distance_values.size()-step; i++)
{
//sprawdzamy czy punkt należy do maksimum
if(radial_distance_values[i]>radial_distance_values[i-step]
&& radial_distance_values[i]>radial_distance_values[i+step])
{
//sprawdzamy czy to jest już nowa grupa
//czy kontynuacja poprzedniej
if(i-max_group.last < group_size)
max_group.last = i;
else
{
if(max_group.first != 0)
max_points_groups.push_back(max_group);
max_group.first = max_group.last = i;
}
}
//sprawdzamy czy punkt należy do minimum
else
if(radial_distance_values[i]<radial_distance_values[i-step]
&& radial_distance_values[i]<radial_distance_values[i+step])
{
//sprawdzamy czy to jest już nowa grupa
//czy kontynuacja poprzedniej
if(i-min_group.last < group_size)
min_group.last = i;
else
{
if(min_group.first != 0)
min_points_groups.push_back(min_group);
min_group.first = min_group.last = i;
}
}
}
max_points_groups.push_back(max_group);
min_points_groups.push_back(min_group);

//szukanie bocznych ekstremów


//wybieramy punkt referencyjny w środku dłoni
CvPoint refference_point = cvPoint(
image1->width/2,
image1->height/1.90f);
group g;

//szukamy najbliżeszego punktu po lewej stronie


//to jest na konturze pomiędzy kciukiem
//a palcem wskazującym
int left_finger_index = find_nearest_point_index(
min_points_groups[0].last,
max_points_groups[1].first,
refference_point);
//oraz z prawej strony za małym placem
int right_finger_index = find_nearest_point_index(
max_points_groups[4].last,
min_points_groups[4].first,
refference_point);

g.first = g.last = left_finger_index;


min_points_groups.insert(++(min_points_groups.begin()), g);

g.first = g.last = right_finger_index;


min_points_groups.insert(--(min_points_groups.end()), g);

min_group.first = 0;
min_group.last = 1;
min_points_groups.insert(min_points_groups.begin(), min_group);
}

10.3 Znajdowanie cech placów

Po znalezieniu kluczowych punktów dłonie policzenie większości cech jest stosunkowo proste.
Sprowadza się to do paru prostych operacji matematycznych jak liczenie odległości między punktami.
Funkcja licząca cechy palców nazywa się calc_fingers_feature i wygląda następująco.

void calc_fingers_feature()
{
for(int i=0; i<1; i++)
{
CvPoint p1 = contour_points[min_points_groups[i].last];
CvPoint p2 = contour_points[min_points_groups[i+1].first];
CvPoint p3 = cvPoint(
(p2.x - p1.x)/2 + p1.x,
(p2.y - p1.y)/2 + p1.y);
CvPoint p4 = find_farrest_point(max_points_groups[i], p3);

fingers[i].first = min_points_groups[i].last;
fingers[i].last = min_points_groups[i+1].first;

fingers[i].base_length = calc_distance(p1, p2);


fingers[i].length = calc_distance(p3, p4);
fingers[i].circuit = fingers[i].last - fingers[i].first;

fingers[i].first_point = p1;
fingers[i].last_point = p2;
fingers[i].base_center_point = p3;
fingers[i].finger_end_point = p4;
}

for(int i=2; i<min_points_groups.size()-2; i++)


{
CvPoint p1 = contour_points[min_points_groups[i].last];
CvPoint p2 = contour_points[min_points_groups[i+1].first];
CvPoint p3 = cvPoint(
(p2.x - p1.x)/2 + p1.x,
(p2.y - p1.y)/2 + p1.y);
CvPoint p4 = find_farrest_point(max_points_groups[i-1], p3);
fingers[i-1].first = min_points_groups[i].last;
fingers[i-1].last = min_points_groups[i+1].first;

fingers[i-1].base_length = calc_distance(p1, p2);


fingers[i-1].length = calc_distance(p3, p4);
fingers[i-1].circuit = fingers[i-1].last - fingers[i-1].first;

fingers[i-1].first_point = p1;
fingers[i-1].last_point = p2;
fingers[i-1].base_center_point = p3;
fingers[i-1].finger_end_point = p4;
}
}

Na szczególną uwagę może jedynie zasługiwać funkcja find_farrest_point która to zwraca


najbliższy punkt, znajdujący się w podanej grupie, podanemu w drugim parametrze. Jako ten punkt
wybierany jest środek podstawy palca.

10.4 Znajdowanie punktów podstawy dłoni (lewy oraz prawy dolny punkt
dłoni).

Dane wejściowe - wektor zawierający punkty konturu:

std::vector<CvPoint> contour_points;

Cel: uzupełnienie pól prawego oraz lewego dolnego punktu w strukturze hand.

Lewy dolny punkt dłoni to ostatni punkt w konturze. Prawy dolny punkt obliczamy korzystając z
wiadomości, że jest to punkt o takiej samej współrzędnej OY jak lewy, natomiast współrzędna OX jest
największa z możliwych.

hand_features.right_end_point = hand_features.left_end_point =
contour_points[contour_points.size()-1];

for(int i = contour_points.size()-10; i >= 0; i--)


{
if( contour_points[i].y == hand_features.left_end_point.y )
hand_features.right_end_point = contour_points[i];
}

10.5 Obliczenie cech dłoni

Całkowite pole powierzchni dłoni obliczamy jako liczba białych pikseli na znormalizowanym obrazku:
Całkowitą długość dłoni obliczamy jako odległość między współrzędną OY lewego dolnego
punktu dłoni, a najmniejszą współrzędną OY z pośród środków paznokci palców.

Pozostałe cechy dłoni (struktura hand) wyliczamy wykorzystując wcześniej omówioną funkcję
calc_distance.

void find_hand_features()
{
int i,j;

//the total area of the hands


hand_features.total_area = 0;

for(i=0;i<image_orginal->height;i++)
for(j=0;j<image_orginal->width;j++)
{
if ( get_pixel(image_orginal, cvPoint(i, j)) == 255 )
hand_features.total_area++;
}

//the lenght of the hand


int max = 255;
for(i=0; i< 5; i++)
if (max > fingers[i].finger_end_point.y ) max =
fingers[i].finger_end_point.y;

hand_features.length = hand_features.left_end_point.y - max;

//fingers base center point to thumb last point length:


CvPoint Thumb = fingers[0].last_point;

for(i=1; i<5; i++)


hand_features.base_center_point_to_thumb_length[i-1] =
(int)calc_distance(Thumb,fingers[i].base_center_point);

//index first point to pinkle last point length


hand_features.index_first_point_to_pinkle_last_point_length =
calc_distance(fingers[1].first_point,fingers[4].last_point);

//hand_right_end_point_to_left_end_point_length
hand_features.hand_right_end_point_to_left_end_point_length =
calc_distance(hand_features.right_end_point,hand_features.left_end_point);
//hand_left_end_point_to_index_first_point_length;
hand_features.hand_left_end_point_to_index_first_point_length =
calc_distance(hand_features.left_end_point,fingers[1].first_point);

//pinkle_last_point_to_hand_right_end_point_length;
hand_features.pinkle_last_point_to_hand_right_end_point_length =
calc_distance(fingers[4].last_point,hand_features.right_end_point);

10.6 Obliczanie pole powierzchni palców.

Do obliczenia pola powierzchni palców używamy algorytmów wypełniania obszarów


ograniczonych. Jako dane wejściowe przyjmujemy czarny obraz na którym narysowany jest biały
kontur dłoni, jak na rysunku poniżej:

Pierwszym krokiem jest odseparowanie palców od całej dłoni, poprzez narysowanie linii
łączących punkty między palcami. Po realizacji tego kroku otrzymujemy:

Następnie dla każdego palca z osobna zapuszczanym algorytm wypełnianie konturowe (ang
boundary-fill) - wszystkie piksele zawarte wewnątrz konturu (zamkniętej linii zbudowanej z pikseli o
tym samym kolorze – linie obwodów palców ) zostają pokolorowane na ten sam kolor wypełnienia.

Podczas malowania ograniczonego obszaru, zliczamy kolorowane piksele. Pola powierzchni


poszczególnych palców to suma pokolorowanych pikseli odpowiednich pól.
Piksel, od którego rozpoczynamy wypełnianie obszaru nazywamy ziarnem wypełnienia (ang.
fill seed). Musi on znajdować się wewnątrz wypełnianego obszaru. Dla naszego algorytmu jest to
punkt znajdujący się w połowie linii łączącej środek paznokcia z środkiem podstawy palca.

Pozostałe piksele znajdujemy wykorzystując fakt, że: do każdego piksela w obszarze można
dotrzeć poruszając się po pikselach tego obszaru tylko w 4 kierunkach - góra, dół, prawo, lewo

Omawiany algorytmy należy do grupy algorytmów wypełniania przez sianie (ang. seed fill
algorithm) . Wewnątrz wypełnianego obszaru umieszczamy ziarno wypełnienia, a następnie
próbujemy je propagować (siać) w czterech kierunkach. Jeśli ziarno trafi na podatny grunt, to
wypełnia go kolorem i próbuje dalej się propagować w czterech kierunkach. W ten sposób cały
obszar zostanie zamalowany określonym kolorem. Ziarno sieje się tylko na pikselu o kolorze różnym
od koloru konturu.

Podczas siania liczymy sumę zasianych ziaren dla poszczególnych pól (palców). Wielkość ta to
pole powierzchni poszczególnych palców.

Realizacja powyższego algorytmu w języku C++:

Kontur – kolor 255.


Obszar zasiania – kolor 155.

void clac_fingers_area()
{
IplImage *img; // obraz z konturem dłoni
img = cvCloneImage(image1);

//odseparowanie palców od całej dłoni


//narysowanie lini łączącej punkty pomiędzy palcami
for(int i=0; i<5; i++)
cvLine(img, fingers[i].first_point,
fingers[i].last_point, cvScalar(255));

// dla każdego palca liczymy pole powierzchni - siejemy


int i;
for(i=0; i<5; i++)
{
// tworzymy stos na którym będą się znajdować współrzędne
// kolejnych ponktów oczekujących na zasianie
std::stack<CvPoint> stack;

// wyznaczamy fill seed –


// punkt wewnącz obszaru od którego rozpoczniemy sianie
CvPoint fill_seed = cvPoint(
(fingers[i].base_center_point.x
+ fingers[i].finger_end_point.x)/2.0,
(fingers[i].base_center_point.y
+ fingers[i].finger_end_point.y)/2.0 );

// dodajemy na stos punkt od którego zaczynamy sianie –


// fill seed
stack.push(fill_seed);

// count – liczba zasianych ziaren – pole powierzchni palca


int count = 0;
unsigned char color; // zmienna pomocnicza

// dopóki jakiś obszar pola palców niezasiany (niepokolorowany)


// wykonaj następujące kroki:
while(!stack.empty())
{
CvPoint p;
// pobierz informacje o pierwszym elemencie na stosie
CvPoint top = stack.top();

// jeżeli pobrany piksel niepokolorowany – niezasiany –


// zasiej (pokoloruj)oraz
// zwiększ liczbe zasianych elementów
if ( get_pixel(img,top) != 155 )
{
count++;
set_pixel(img,top,155);
}

// zdejmij zasiany element ze stosu


stack.pop();

// sprawdz sąsiadów badanego piksela


// jeżeli kolor piksela rózny od koloru konturu, oraz
// piksel nie został pomalowany
// dodaj go na stos – oczekuje na zasiane

// góra
p.x = top.x + 1;
p.y = top.y;
color = get_pixel(img,p);
if ( color != 255 && color != 155 ) stack.push(p);

// dół
p.x = top.x - 1;
p.y = top.y;
color = get_pixel(img,p);
if ( color != 255 && color != 155 ) stack.push(p);

// prawo
p.x = top.x ;
p.y = top.y + 1;
color = get_pixel(img,p);
if ( color != 255 && color != 155 ) stack.push(p);

// lewo
p.x = top.x;
p.y = top.y - 1;
color = get_pixel(img,p);
if ( color != 255 && color != 155 ) stack.push(p);

}
// pole powierzchni palca = liczba zasianych ziaren w danym
// obsarze – liczba pokolorowanych pikseli
fingers[i].area = count;

// zwolnij pamięć obrazka


cvReleaseImage(&img );
}

10.7 Znajdowanie okręgów wpisanych w palce.

Problem znajdowania okręgów wpisanych w palce, można uogólnić jako problem wpisania okręgu w
dowolny obszar zamknięty, jak np.; kwadrat, trapez, pięciokąt lub inny obszar ograniczony.

Do obliczenia największego okręgu wpisanego w obszar zamknięty posłużyliśmy się algorytmem


Euclidean distance transform. Algorytm został dokładnie opisany i zoptymalizowany przez Pedro F.
Felzenszwalb oraz Daniel P. Huttenlocher [7].

Zasada działania algorytmu:

Jako dane wejściowe podajemy tablice zero-jedynkową (obrazek binarny):


 wartość zero – obszar ograniczony w którym chcemy wpisać okrąg
 wartość jeden - pozostałe piksele

Jako dane wyjściowe otrzymujemy tablice której wartości gdzie występowało zero zostały zastąpione
wartościami kwadratu największej odległości promienia okręgu o środku w danym punkcie
wpisanego w obszar ograniczony.

Wejście: 1 1 1 1 1 1 1 1 1
1 1 1 0 0 0 1 1 1
1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 1 1
1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1
1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1
1 1 1 0 0 0 1 1 1 1 1 1 2 4 2 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 4 9 4 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 2 4 2 1 1 1
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
Wyjście: 1 1 1 1 1 1 1 1 1

1 1 1 1 1 1 1 1 1

Następnie wyszukujemy wartość największą występującą w otrzymanej tablicy. W naszym przypadku


jest to wartość 9. Według algorytmu środek największego wpisanego okręgu w dany obszar
ograniczony znajduje się w punkcie w którym występuje wartość największa – tj. w punkcie gdzie
wpisana została liczba 9. Długość promienia to pierwiastek z podanej liczby – tj. sgrt(9) = 3.

Algorytm realizujący powyższe zadanie (dokładne opis zasady działania znajduje się w pracy Pedro F.
Felzenszwalb oraz Daniel P. Huttenlocher dołączonej jako załącznik do projektu):

*_bimg – wskaźnik do tablicy z danymi wejściowi


*_d – wskaźnik do tablicy z danymi wyjściowymi

void dt(unsigned *_d, unsigned char *_bimg,int _h,int _w)


{
unsigned *dd;
unsigned *f;
int *v;
int *z;
int i;
int j;
int k;
int n;

dd = new unsigned[_h*_w];
n=_h>_w?_h:_w;
v = new int[n];
z = new int[n+1];
f = new unsigned[_h];

for(i=0;i<_h;i++)
{
k=-1;
for(j=0;j<_w;j++)if(_bimg[i*_w+j])
{
int s;
s=k<0?0:(v[k]+j>>1)+1;
v[++k]=j;
z[k]=s;
}

if(k<0)
for(j=0;j<_w;j++)dd[j*_h+i]=UINT_MAX;
else
{
int zk;
z[k+1]=_w;
j=k=0;
do
{
int d1;
int d2;
d1=j-v[k];
d2=d1*d1;
d1=d1<<1|1;
zk=z[++k];
for(;;)
{
dd[j*_h+i]=(unsigned)d2;
if(++j>=zk)break;
d2+=d1;
d1+=2;
}
}
while(zk<_w);
}
}

for(j=0;j<_w;j++)
{
int v2;
int q2;
k=-1;
for(i=q2=0;i<_h;i++)
{
unsigned d;
d=dd[j*_h+i];
if(d<UINT_MAX)
{
int s;
if(k<0)s=0;
else for(;;)
{
s=q2-v2+d-f[k];
if(s>0)
{
s=s/(i-v[k]<<1)+1;
if(s>z[k])break;
}
else s=0;
if(--k<0)break;
v2=v[k]*v[k];
}
if(s<_h){
v[++k]=i;
f[k]=d;
z[k]=s;
v2=q2;
}
}
q2+=i<<1|1;
}

if(k<0)
{
memcpy(_d,dd,_w*_h*sizeof(*_d));
break;
}
else
{
int zk;
z[k+1]=_h;
i=k=0;
do
{
int d2;
int d1;
d1=i-v[k];
d2=d1*d1+f[k];
d1=d1<<1|1;
zk=z[++k];
for(;;)
{
_d[i*_w+j]=(unsigned)d2;
if(++i>=zk)break;
d2+=d1;
d1+=2;
}
}
while(zk<_h);
}
}

delete f;
delete z;
delete v;
delete dd;
}

Wykorzystując powyższy algorytm napiszemy funkcję przyjmująca jako dane wejściowe obrazek
binarny typu:

0 0 0 0 0 0 0 0 0
0 0 0 1 1 1 0 0 0
0 0 1 0 0 0 1 0 0
0 1 0 0 0 0 0 1 0
0 1 0 0 0 0 0 1 0
0 1 0 0 0 0 0 1 0
0 0 1 0 0 0 1 0 0
0 0 0 1 1 1 0 0 0
0 0 0 0 0 0 0 0 0

Obszar w którym należy wpisać obrazek został organiczny jedynkami.


Funkcja zwraca największy okrąg który można wpisać w dany obszar jako strukturę circle,
wykorzystując podany wcześniej algorytm:

circle give_circle(IplImage *img)


{
int m,n;
int w = img->width;
int h = img->height;

// tworzymy tablicę wyjściową – rozmaiar równy rozmiaowi obrazka


unsigned char *b = new unsigned char[h*w];

//wypełniamy tablicę wyjściową zerami


for(m=0;m<h;m++)
for(n=0;n<w;n++)
b[m*w+n] = 0;

// przemosimy kontur w który należy wpisać okrąg z obrazka


// do stworzonej tablicy
for(m=0;m<h;m++)
{
for(n=0;n<w;n++)
{
CvPoint p = cvPoint(n,m);
b[m*w+n] = get_pixel(img,p);
}
}

// obszar poza konturem wypełniay wartościami “jeden”


for(m=0;m<h;m++)
for(n=0;n<w;n++)
{
if (b[m*w+n] == 1 ) break;
b[m*w+n] = 1;
}

for(m=0;m<h;m++)
for(n=w-1;n>0;n--)
{
if (b[m*w+n] == 1 ) break;
b[m*w+n] = 1;
}

// po przeprowadzeniu powyzszych operacji z binarnego obrazka


// wejściowego otrzymaliśmy tablicę opisaną jako wejście do
// algorytmu dt - Euclidean distance transform

int *d = new int[h*w];


dt((unsigned *)d,b,h,w); //wywołujwmy wsześniej opisany algorytm

// współrzędne punkt o maksymalnej wartości w tablicy wyjściowej


// algorytmu dt to współrzędne środka okręgu, pierwiatek z tej
// wartości - promień
int max = 0;
CvPoint pMax;

for(m=0;m<h;m++)
for(n=0;n<w;n++)
{
if (max < d[m*w+n])
{
max = d[m*w+n];
pMax = cvPoint(n,m);
}
}

circle abc;
abc.p = pMax;
abc.r = (int)sqrt((float)max);

delete b;
delete d;

return abc;
}

Po wykorzystaniu powyższych funkcji znajdowanie okręgu wpisanego w palec polega na wywołaniu


funkcji give_circle podając jako dane wejściowe binarny obrazek z konturem palca.

11 Instrukcja obsługi

W folderze zawierającym program aby wszystko działało jak należy powinny znajdować się
następujące pliki:

 cv100.dll,
 cxcore100.dll,
 highgui100.dll,
 libguide40.dll,
 handrec.exe,
 run.bat,
 save_to_file.bat,
 HandRecManager.exe,
 ZedGraph.dll

oraz katalog images_in w którym znajdują się zdjęcia rozpoznawanych dłoni.

Program Handrec.exe był tematem naszego projektu. Nie jest bezpośrednio używany lecz korzysta z
niego HandRecManager.exe oraz skrypt save_to_file.bat. Przyjmuje on jako parametr wejściowy
nazwę bitmapy z dłonią, zwraca wektor cech danej dłoni. Program ten został napisany w C++ i
szczegółowo jest omówiony we wcześniejszej części dokumentacji. Korzysta on z OpenCv 1.0 dlatego
w danym folderze muszą się znajdować pliki: cv100.dll, cxcore100.dll, highgui100.dll, libguide40.dll.

Do tworzenia bazy danych z wartościami cech dłoni służy skrypt save_to_file.bat. Stworzona baza
cech dłoni jest używana przez HandRecManager.exe. Program pobiera bitmapy z folderu images_in,
wylicza cechy dłoni korzystając z programu handrec.exe, oraz tworzy i umieszcza je w pliku out.txt.

Stworzenie bazy cech dłoni polega więc na skopiowaniu danych bitmapek z dłońmi do katalogu
images_in oraz uruchomieniu skryptu save_to_file.bat.

Program HandRecManager.exe jest programem dodatkowym stworzonym do sprawdzenia


poprawności stworzonego systemu rozpoznawana dłoni. Do rysowaniu wykresów używa on biblioteki
ZedGraph, wobec czego konieczne jest dołączenie ZedGraph.dll do projektu.
Wczytywanie danych możemy wykonać na dwa sposoby. Poprzez wczytanie plików bmp ze zdjęciami
danych dłoni klikając w przycisk Read samales, lub poprzez wczytanie bazy cech dłoni stworzonych
wcześniej przy pomocy skryptu save_to_file.bat, klikając Read database oraz podając ścieżkę do
stworzonego pliku out.txt.

Po kliknięciu przycisku Test przeprowadzane są testy skuteczności rozpoznawania dłoni przez nas
system. Dokładny opis interpretacji wyników znajduje się w rozdziale 6.1 Metoda przeprowadzenia
badań.

Po kliknięciu przycisku Wagi wyliczane są wagi dla każdej cechy dłoni według zasady opisanej w
paragrafie 5.4 Wagi.

Program posiada możliwość narysowania ilości wystąpień danej wartości pojedynczej cechy –
rozrzut wartości danej cechy. Na podstawie narysowanego histogramu liczone jest odchylenie
standardowe danej cechy oraz ustalana waga. Wartości cechy są znormalizowane do przedziału od 0
do 100. Aby narysować histogram danej cechy wybieramy numer cechy, a następnie klikamy w
przycisk Histogram.

Program pozwala na narysowanie odległości danej próbki podanej w ścieżce według zasady opisanej
w paragrafie 5.2 Metoda najbliższego sąsiada oraz 5.3 Metryka do wszystkich innych znajdujących się
w bazie.
Dla przykładu wybieramy próbkę Hand_2_2.bmp. Odległość danej próbki do samej siebie wynosi zero
– co przedstawia brak słupka na wykresie. Im bardziej podobna próbka, tym wysokość słupka jest
mniejsza. Wynika z tego że najbardziej podobną próbką jest Hand_1_2.bmp, co jest zgodne z
rzeczywistością. Zauważamy także że próbki tego samego osobnika mają podobne wysokości słupków
na wykresie. Oznacza to że dłonie są podobne.

Aby sprawdzić powyżej opisane zależności klikamy w przycisk Check sample, a następnie podajemy
ścieżkę do sprawdzanej próbki dłoni.
12 Literatura
1. Erdem Yoruk , Helin Dutagaci, Bulent Sankur, „Hand biometrics”
2. Erdem Yoruk , Helin Dutagaci, Bulent Sankur, „Comparative analysis of global hand
apperance-based person recognition”
3. Próbki:
http://www.busim.ee.boun.edu.tr/%7Esankur/SankurFolder/normalized_binary_han
ds.zip
4. Yaroslav Bulatov, Sachin Jambawalikar, Piyush Kumar, Saurabh Sethia, „Hand
recognition Rusing geometric classifiers”
5. Xiaoqian Jiang, Wanhog Xu, „Contactless hand recognition”
6. Xiaoqian Jiang, Wanhong Xu, Latanya Sweeney, Yiheng Li, Ralph Gross, Daniel Yurovsky „New
directions In contact free hand recognition”
7. Pedro F. Felzenszwalb, Daniel P. Huttenlocher „Distance Transforms of Sampled Functions”

You might also like