Celem projektu było utworzenie adaptacji gry Air %Hockey napisanej w języku C++
w środowisku 2d, której główną cechą jest sterowanie przy użyciu kamerki internetowej. Miały być zrealizowane trzy tryby gry:
* tryb jednoosobowy z przeciwnikiem sterowanym przez komputer
* tryb dla dwóch graczy przy jednym komputerze
* tryb gry w sieci lokalnej
Sterowanie miało się odbywać przez detekcję jednokolorowych okrągłych wskaźników przy częstotliwości rejestracji klatek pozwalającej na komfortową obsługę. Warstwa graficzna oraz komunikacja sieciowa miała być zrealizowana przez bibliotekę SFML, która jest prosta ale spełniająca nasze wymagania. Komunikacja z kamerką internetową oraz narzędzia do przetwarzania obrazu w czasie rzeczywistym miała być zrealizowana dzięki potężnej bibliotece OpenCV. Pobocznym celem było napisanie prostego interfejsu graficznego dostosowanego do naszych potrzeb z uwagi na nie stosowanie dodatkowych bibliotek rozszerzających SFML np. SFGUI. Dodatkowym celem było napisanie aplikacji działającej na platformie Windows oraz Linux co ułatwiły wybrane przez nas biblioteki.
Do prawidłowego działania aplikacji potrzebne są jednokolorowe wskaźniki. Kształt zarejestrowanego wskaźnika powinien być okrągły.
Proponowane przez nas wskaźniki to piłeczki pingpongowe z wyciętą dziurą do zakładania na palec (wskazujący oraz kciuk).
Dla lepszej wygody i dopasowania można wyścielić dziurę tkaniną, przymocować ją np klejem na gorąco.
Należy pamiętać aby kolor wskaźników był unikatowy w obrębie sceny, co pomoże uniknąć niemożliwych do odfiltrowania szumów tła. Wskaźniki białe również są możliwe do zastosowania lecz wymagają większych starań odnośnie tła (odbicia światła słonecznego i sztuczne źródła światła mogą być źródłem niepożądanych zakłóceń).
Alternatywnie jako wskaźnik można użyć swojego smartphone'a. Należy zaopatrzyć się w odpowiedni obrazek z dwoma jednokolorowymi kołami np. taki:
Wykonywalny plik aplikacji to airhockey.exe (Windows) lub airhockey (Linux). Po jego otworzeniu ukaże się napis:
Jeżeli mamy podłączoną kamerkę to napis powinien zniknąć w ciągu kilku sekund. Jeżeli mimo podłączenia nadal występuje należy dowiedzieć się pod jakim identyfikatorem występuje i spróbować ustawić go w pliku program.conf.
Główne menu pozwala przejść do:
* Podmenu gry Air %Hockey
* Paint testu
* Ustawień
W menu gry Air %Hockey mamy do wyboru:
* Grę jednoosobową z przeciwnikiem sterowanym przez komputer.
* Grę wieloosobową
* Powrót do menu głównego
Na załączonym obrazku przedstawiony jest widok gry w trybie jednoosobowym.
Gra polega na odbijaniu krążka celem zdobycia bramki przeciwnika. Krążek porusza się w środowisku niewielkiego tarcia. Paletka gracza może poruszać się tylko w obszarze własnej połowy. Każda runda rozpoczyna się od umiejscowienia krążka w centrum areny.
Gra kończy się po zdobyciu 10 punktów jednak z wymaganą przewagą 2 punktów.
Po zakończonej grze możemy powrócić do menu lub zagrać ponownie.
Paint test służy do testowania działania wskaźników. Zawiera przycisk czyszczenia płótna. Aby przejść do menu głównego należy wcisnąć przycisk Escape.
Z menu gry wieloosobowej możemy przejść do:
* Gry "side by side" - czyli granie przy jednym komputerze dla 2 osób
* Utworzenie serwera LAN
* Połączenie z serwerem LAN
W menu tworzenia serwera możemy ustawić nazwę serwera. Następnie możemy przejść do lobby lub powrócić do wcześniejszego menu.
Obrazek przedstawia widok lobby po stronie serwera. Przycisk play pojawia się wtedy gdy klient wybierze nasz serwer. Po kliknięciu play rozpoczyna się rozgrywka wieloosobowa.
Widok listy serwerów po stronie klienta.
Na przedstawionym obrazku widać pierwszą stronę ustawień. Kamerka pracowała w słabym oświetleniu. Źródłem światła była jedynie lampka nocna lecz dzięki odpowiedniej konfiguracji detekcja punktów wskaźnika działa prawidłowo.
Parametry do konfiguracji to:
* Ekspozycja - im wyższa tym dłużej naświetlana jest klatka. Powoduje zwiększenie jasności lecz przy tanich kamerkach także spadek FPS poniżej akceptowalnych wartości.
* Gamma - im wyższa tym ciemniejsze obszary są rozjaśniane, zbyt duża wartość powoduje utratę ogólnego kontrastu.
* Balans bieli - pozwala skompensować nadmierne przesunięcie barw w stronę czerwonego lub niebieskiego
* Przesunięcie barwy - pozwala dokonać dużego przesunięcia barw w przestrzeni HSV. Przydatne gdy wykrywany kolor jest w okolicach początku (czerwony) lub końca (fioletowy). Pozwoli to na lepsze dobranie składowej H niskiej oraz wysokiej
* Składowa detektora H niska - oznacza dolną granicę wykrywanej barwy w przestrzeni HSV
* Składowa detektora H wysoka - oznacza górną granicę wykrywanej barwy w przestrzeni HSV
* Składowa detektora S niska - oznacza dolną granicę wykrywanego nasycenia w przestrzeni HSV
* Składowa detektora S wysoka - oznacza górną granicę wykrywanego nasycenia w przestrzeni HSV
* Składowa detektora V niska - oznacza dolną granicę wykrywanej jasności w przestrzeni HSV
* Składowa detektora V wysoka - oznacza górną granicę wykrywanej jasności w przestrzeni HSV
* Próg ruchu - służy do konfiguracji maski ruchu. Im wyższa wartość tym różnica klatek aktualnej i poprzedniej musi być większa aby maska zadziałała w tych punktach.
* Próg maski okręgu 1 oraz 2 - służy do konfiguracji maski detektora okręgów. Niska wartość powoduje wykrycie większej ilości okręgów. Może powodować spowolnienie procesu przetwarzania.
* Minimalny rozmiar - oznacza minimalny rozmiar wykrywanego punktu. Przydatne do odfiltrowania punktowych szumów mniejszych od właściwego znacznika a które nie mogą zostać wyeliminowane w inny sposób.
* Limit punktów - ogranicza nadmierną ilość wykrytych punktów. Do prawidłowego działania wystarczą 4.
Druga strona ustawień zawiera następujące opcje:
* Przycisk przełączający wyświetlanie obrazu z kamery w tle
* Przycisk przełączający kolisty obszar kliknięcia wskaźnika
* Przycisk przełączający widok wskaźników w grze
* Przycisk do testowania algorytmu paletki
Przykładowa zawartość pliku konfiguracyjnego
deviceId 0 #Id urządzenia przechwytującego, ręczna konfiguracja może być przydatna gdy jest podłączonych kilka urządzeń przechwytujących obraz.
maxId 5 #Maksymalny id dla pętli auto-wykrywania. Zmiana może być przydatna przy nietypowo wysokim identyfikatorze urządzenia przechwytującego
exposure 299
gamma 180
whiteBalance 5001
hueShift 90
hueLow 80
hueHigh 110
saturationLow 55
saturationHigh 255
valueLow 65
valueHigh 255
moveThreshold 15
circleMaskThreshold1 177
circleMaskThreshold2 88
pointsLimit 4
minimumSize 18.548212
serverName Uw
==
#Nazwa serwera zakodowana w base64winSizeX 1039 #Szerokość okna
winSizeY 644 #Wysokość okna
winPosX 0 #Pozycja X okna
winPosY 0 #Pozycja Y okna
leftNick QQ
==
#Lewy nick zakodowany w base64rightNick Qg
==
#Prawy nick zakodowany w base64pointerClickCircle 1 #Wyświetlanie promienia kliknięcia
pointerInGame 1 #Wyświetlanie wskaźników w grze
videoBackground 1 #Wyświetlanie obrazu z kamerki w grze
OpenCV
Programowanie
git
Tworzenie dokumentacji
Libre Office Writer
Tworzenie prezentacji
Działanie na platformie Windows oraz Linux dzięki SFML i OpenCV
Implementacja algorytmu wykrywania wskaźników z wykorzystaniem biblioteki OpenCV
Paint test
Gra Air %Hockey
Automatycznie skalowana arena do rozmiaru okna
Interfejs graficzny
Suwak do nastawy wartości liczbowych, z możliwością ustawienia wskaźnika na docelową zmienną
Ustawienia
Zaawansowane lobby z funkcją czatu i ustawień rozgrywki
Interfejs graficzny
Aby skompilować projekt należy posiadać następujące biblioteki:
* SFML >= 2.3.3
* OpenCV >= 3.2
* Video4Linux (tylko dla dystrybucji Linux)
Opcjonalnie można zainstalować doxygen do generacji dokumentacji html.
Zalecana jest kompilacja z użyciem narzędzia make, które należy zainstalować jeżeli nie jest dostępne.
Dodatkowo wymagany jest kompilator C++
obsługujący standard C++
11. Zaleca się użycie g++
6.
Instalacja pakietów dla dystrybucji wywodzących się z Debiana:
sudo apt-get install build-essential libsfml-dev libv4l-dev libopencv-dev
Jeżeli wersja biblioteki OpenCV w repozytorium jest nieodpowiednia należy przeprowadzić kompilacje biblioteki ze źródeł OpenCV GIT.
Po udanej kompilacji należy pamiętać o uruchomieniu komendy ldconfig jako root.
Następnie należy przejść do katalogu z kodem źródłowym:
cd NAZWA_KATALOGU_ŹRÓDŁOWEGO
make PH=off ARG=-O3
Użycie PH=off powoduje że nie zostaną utworzone prekompilowane nagłówki natomiast ARG=-O3 przekazuje dodatkową flagę optymalizacji 3 stopnia do kompilatora.
Po udanej kompilacji powinien zostać wygenerowany plik binarny airhockey
Można go uruchomić z terminala, znajdując się w tym samym katalogu:
./airhockey
Dodatkowe możliwości pliku makefile:
Usuwanie plików *.o
make clean
Usuwanie plików .o oraz prekompilowane nagłówki .gch i pomocnicze .md5
make cleanall
Wybieramy:
File > Import... > C/C++ > Existing code as Makefile Project
Wybieramy lokacje kodu źródłowego oraz zaznaczamy język C++
oraz Toolchain for Indexer Settings na Linux GCC
Po imporcie ustawiamy plik wykonywalny
Run > Run Configurations... > C/C++ Application > (PPM) New
Następnie wpisujemy nazwę pliku:
airhockey
Możemy także usprawnić naszą kompilacje przez użycie wielu rdzeni:
Projekt > Properties > C/C++ Build
Odznaczamy Use default build command i dla 4 rdzeni wpisujemy w build commmand:
make -j4
Kompilacja dla systemów Windows może być przeprowadzona w Visual Studio.
Na początku należy utworzyć nowy pusty projekt C++
a następnie przejść do ustawień:
Project > NAZWA_PROJEKTU properties
C/C++ > General
W Additional Include Directories dodajemy katalog include z SFML.
Następnie przechodzimy do ustawień:
Linker > Input
W Additional Dependencies należy ustawić następujące pliki:
sfml-graphics-d.lib
sfml-main-d.lib
sfml-system-d.lib
sfml-window-d.lib
sfml-network-d.lib
Dla kompilacji release należy wybrać analogiczne pliki bez końcówki -d
Dalej przechodzimy do ustawień:
Linker > General
W Additional Library Directories dodajemy katalog lib z SFML.
Bibliotekę OpenCV można zainstalować przez NuGet:
Tools > NuGet Package Manager > Package Manager Console
Wpisujemy komendę:
Install-Package OpenCV -Version 2.4.11
Ostatnią czynnością jest dodanie wszystkich plików źródłowych .hpp i .cpp.
Aplikacja działa w oparciu o pętlę programu wykonywaną w wątku głównym a także pętlę wykonywaną w wątku pomocniczym.
Decyzja o zastosowaniu dodatkowego wątku została podjęta z uwagi na fakt, że odczyt ramki z kamerki internetowej odbywa się w stosunkowo długim czasie co mogłoby spowodować utratę płynności działania aplikacji.
Główny wątek odpowiada za odczytywanie zdarzeń z okna i reakcją na nie. Dokonywane jest nawiązanie połączenia z kamerką internetową i nastawa jej parametrów. Dodatkowo w nim są aktualizowane wszystkie obiekty potrzebne w aktualnym widoku. Jest tam między innymi obsługa elementów interfejsu graficznego jak i obsługa komunikacji sieciowej. Ostatnią czynnością jest wyrysowanie wszystkich obiektów na scenie i wyświetlenie wygenerowanej klatki.
Pomocniczy wątek odpowiada za odczytanie klatki z urządzenia przechwytującego a następnie przeprowadzenie detekcji punktów wskaźnika lub dwóch wskaźników w zależności od stanu aplikacji.
Na powyższych rysunkach przedstawiono w uproszczony sposób algorytmu detekcji wskaźników.
Metody OpenCV warte uwagi:
* cv::absdiff - do wyznaczania absolutnej różnicy pomiędzy dwoma ramkami
* cv::cvtColor - do konwersji przestrzeni barw
* cv::threshold - do progowania ramki
* cv::erode - do operacji erozji
* cv::dilate - do operacji rozszerzania
* cv::bitwise_or - do bitowej sumy dwóch ramek
* cv::bitwise_and - do bitowego iloczynu dwóch ramek
* cv::inRange - do selekcji kolorów
* cv::HoughCircles - do wykrywania okręgów
* cv::SimpleBlobDetector - do do wykrywania jednobarwnych plam
Detekcja kolizji krążka z obróconą paletką może być nieco problematyczna. Istnieje natomiast prosty sposób jak to osiągnąć. Zamiast obracać prostokąt (paletkę) należy obrócić krążek pod tym samym kątem pod którym obrócilibyśmy paletkę. Możemy to osiągnąć za pomocą następujących wzorów:
x’ = cos(theta) * (cx – originX) – sin(theta) * (cy – originY) + originX
y’ = sin(theta) * (cx – originX) + cos(theta) * (cy – originY) + originY
Na poniższym rysunku został przedstawiony ten proces. Niebieskie elementy to te które gracz widzi na ekranie, natomiast obliczenia są przeprowadzane dla elementów o kolorze czarnym.
Następnie za pomocą prostych instrukcji warunkowych należy znaleźć punkt na paletce, który jest najbliżej środka krążka. Ostatnim krokiem jest sprawdzenie odległości między tym punktem, a środkiem krążka za pomocą twierdzenia Pitagorasa. Jeżeli odległość ta jest mniejsza od promienia krążka, to oznacza, że nastąpiła kolizja.
Do obliczenia nowego kąta piłki po odbiciu zastosowaliśmy obliczenia wektorowe. Kąt pod którym leci piłka rozbijamy na postać wektorową, na składową x oraz y.
x = cos(angle);
y = cos(angle);
Następnie tworzymy 2 nowe wektory. Wektor u, który jest prostopadły do obiektu, z którym nastąpiła kolizja oraz wektor w równoległy do tej ściany.
Te wektory uzyskujemy za pomocą wzorów:
u = (v · n / n · n) * n
w = v − u
Gdzie n oznacza wektor siły prostopadłej do ściany.
Posiadając już te 2 wektory możemy uzyskać wektor piłki po odbiciu (v' = w - u) oraz przekształcić go do postaci kątowej.