Gra sterowana kamerą - pomaluj płot
19.05.2013 - Filip Mróz
![]() ![]() Szkielet gryW tej części napiszemy działający szkielet gry, który potem uzupełnimy o część analizującą obraz z kamery.
Gotowy kod z tej części można znaleźć w paczce ze źródłami w katalogu: Wersja_1_Szkielet
Zacznijmy od opracowania schematu gry. Z uwagi na przyjęte założenia dotyczące gry, nie będzie on zbyt skomplikowany. ![]()
Sednem gry będzie wywoływanie wzdłuż ścieżki StanGry Implementacja szkieletuMamy już ogólny zamysł, pora go więc przenieść na odpowiednie klasy C++. Będziemy to robić od góry, to znaczy zaczniemy od main.cpp, w którym zainicjujemy nową grę i dodamy przetwarzającą pętlę. Najpierw dodajmy odpowiednie deklaracje: #include "Stany.h" VideoCapture kamera; StanGry stanGry; Mat aktualnyObraz; Mat wyswietlanyObraz;
Po udanym połączeniu z kamerą, ustawimy nazwę gracza i za pomocą metody printf("Kamera podlaczona!\n"); stanGry.gracz.nazwa = "Filip"; stanGry.rozpocznijGre(20); Teraz zastąpimy wyświetlanie testowego obrazu główną pętlą programu: while(waitKey(10)!=' ') { kamera >> aktualnyObraz; stanGry.uaktualnijStanGry(aktualnyObraz); aktualnyObraz.copyTo(wyswietlanyObraz); stanGry.wyswietlStanGry(wyswietlanyObraz); imshow("Maluj plot!",wyswietlanyObraz); if(stanGry.graTrwa() == false) break; } Pętlę będzie można przerwać naciśnięciem spacji. Na podstawie pobranego obrazu uaktualnimy stan gry, który następnie wyświetlimy na kopii pobranego obrazu. Całość pokażemy w oknie o nazwie "Maluj plot!". Na koniec sprawdzimy, czy gra się już nie skończyła. StanGry
Zejdźmy poziom niżej do klasy #pragma once #include <string> #include <cstdlib> #include <ctime> using namespace std; #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" using namespace cv; #include "Uzytki.h"
Plik Uzytki.h będzie zbiorem różnych prostych metod wykorzystywanych w różnych stanach. Dodajmy teraz klasę class StanGry { protected: bool _graTrwa; double _czasRozgrywki; double _pozostalyCzasGry; clock_t _rozpoczecieGry; public: void rozpocznijGre(double czasRozgrywki = 20); void zakonczGre(); bool graTrwa(); }; Informacje o rozpoczęciu gry i pozostałym czasie pozwolą nam dość dokładnie wyznaczać czas trwania jednej "klatki" gry.
Oczywiście stan gry musi zawierać jeszcze
Przejdźmy do implementacji tych funkcji w pliku StanGry.cpp. Część dotycząca sterowania rozgrywką jest trywialna: #include "Stany.h" void StanGry::rozpocznijGre(double czasRozgrywki) { _rozpoczecieGry = clock(); _czasRozgrywki = czasRozgrywki; _pozostalyCzasGry = czasRozgrywki; _graTrwa = true; } void StanGry::zakonczGre() { _graTrwa = false; } bool StanGry::graTrwa() { return _graTrwa; } Po prostu ustawiamy nazwę gracza i przepisujemy resztę pól, zapisując przy okazji moment rozpoczęcia rozgrywki. W przypadku uaktualnienia stanu gry musimy wyliczyć czas, jaki upłynął od ostatniego wywołania i przesłać tę informację do stanu gracza. Na szczęście mamy wszystkie potrzebne do tego elementy: void StanGry::uaktualnijStanGry(Mat obraz) { if(_graTrwa) { double nowyPozostalyCzasGry = _czasRozgrywki - (double)(clock() - _rozpoczecieGry)/CLOCKS_PER_SEC; gracz.uaktualnijStanGracza(_pozostalyCzasGry - nowyPozostalyCzasGry,obraz); _pozostalyCzasGry = nowyPozostalyCzasGry; if(_pozostalyCzasGry < 0) zakonczGre(); } }
Obliczamy pozostały czas rozgrywki i odejmujemy go od czasu zapisanego w ostatniej klatce. Z tą informacją udajemy się do Pozostało wyświetlanie stanu gry, gdzie poza przesłaniem wywołania dalej (tzn. do stanu gracza), chcemy także wyświetlić pozostałą ilość czasu. void StanGry::wyswietlStanGry(Mat gdzie) { if(_graTrwa) { gracz.wyswietlStanGracza(gdzie); char pozostalyCzas[20]; sprintf(pozostalyCzas,"%.2lf s.", _pozostalyCzasGry); Uzytki::wypiszTekst(gdzie,pozostalyCzas,Point(0,gdzie.rows-20)); } }
Mamy już implementację StanGracza
Dodajmy deklarację klasy class StanGracza { public: string nazwa; StanMalowania stanMalowania; void uaktualnijStanGracza(double czas, Mat obraz); void wyswietlStanGracza(Mat gdzie); };
Pora na implementację, która jest krótka, ponieważ spychamy całą pracę na #include "Stany.h" void StanGracza::uaktualnijStanGracza(double czas, Mat obraz) { stanMalowania.uaktualnijStan(czas,obraz); } void StanGracza::wyswietlStanGracza(Mat gdzie) { stanMalowania.wyswietlStan(gdzie); Uzytki::wypiszTekst(gdzie,nazwa,Point(0,25)); Uzytki::wypiszTekst(gdzie,"Pomalowano: " + Uzytki::ftos(stanMalowania.wyznaczPomalowanaCzesc()*100,0) + "%",Point(0,50)); }
Widać, że w StanMalowaniaW tej klasie zawrzemy całą informację o malowaniu oraz metody nim sterujące. Zgodnie ze zwyczajem, najpierw dodajmy deklaracje: #include "PrzetwarzanieObrazow.h" class StanMalowania { protected: static const int ROZMIAR_PEDZLA = 20; Mat _aktualnyStan; public: Vec3b kolor; PrzetwarzanieObrazow przetwarzanieObrazow; StanMalowania(); double wyznaczPomalowanaCzesc(); void uaktualnijStan(double czas, Mat obraz); void wyswietlStan(Mat gdzie); };
Zgodnie z ogólnym projektem, w Implementację umieścimy w pliku StanMalowania.cpp. W konstruktorze stanu malowania ustawimy kolor farby: StanMalowania::StanMalowania() { kolor = Vec3b(0,0,255); } Wbrew pozorom nie będzie to kolor niebieski, a czerwony, ponieważ używana jest reprezentacja BGR (blue, green, red), czyli odwrotna do powszechnego RGB.
W wyznaczeniu pomalowanej części płotu wykorzystamy funkcję double StanMalowania::wyznaczPomalowanaCzesc() { return (double)cv::countNonZero(_aktualnyStan)/_aktualnyStan.total(); } Zajmijmy się teraz kluczowymi dla stanu malowania funkcjami, czyli uaktualnieniem stanu malowania i jego wyświetleniem. void StanMalowania::uaktualnijStan(double czas, Mat obraz) { if(_aktualnyStan.data==NULL) { _aktualnyStan = Mat(obraz.size(),CV_8U,Scalar(0)); } Point polozenie = przetwarzanieObrazow.polozeniePedzla(obraz); if(polozenie.x>0) circle(_aktualnyStan,polozenie,ROZMIAR_PEDZLA,255,-1); }
Przy pierwszym uruchomieniu zainicjalizujemy
Wyświetlenie stanu to po prostu zamalowanie kolorem miejsc gdzie void StanMalowania::wyswietlStan(Mat gdzie) { if(_aktualnyStan.data!=NULL) { gdzie.setTo(kolor,_aktualnyStan); } }
Do implementacji pozostały UżytkiDodajmy deklaracje w pliku Uzytki.h: #pragma once #include <string> #include <cstdlib> using namespace std; #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" using namespace cv; namespace Uzytki { void wypiszTekst(Mat gdzie, String tekst, Point polozenie); string ftos(double liczba, int dokladnosc); }
Do implementacji wypisywania tekstu wykorzystamy funkcję OpenCV #include "Uzytki.h" void Uzytki::wypiszTekst(Mat gdzie, String tekst, Point polozenie) { putText(gdzie, tekst, polozenie, CV_FONT_HERSHEY_SIMPLEX, 1, cvScalar(0, 255, 0, 0),3); }
Do zamiany liczby zmiennoprzecinkowej na tekst z określoną dokładnością użyjemy operacji string Uzytki::ftos(double liczba, int dokladnosc) { char znacznik[10]; sprintf(znacznik,"%%.%dlf",dokladnosc); char wynik[20]; sprintf(wynik,znacznik,liczba); return wynik; }
By doprowadzić ten szkielet do kompilacji, musimy jeszcze napisać Szkielet przetwarzania obrazówW kolejnej części zajmiemy się prawdziwą implementacją tej klasy. Teraz chcemy tylko doprowadzić do kompilacji i uruchomienia programu. Zacznijmy od deklaracji w PrzetwarzanieObrazow.h #pragma once #include <string> #include <cstdlib> using namespace std; #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" using namespace cv; class PrzetwarzanieObrazow { public: Vec3b kolor; Point polozeniePedzla(Mat obraz); }; Implementacja w pliku PrzetwarzanieObrazow.cpp będzie udawana, czyli jej zadaniem będzie tylko umożliwienie poprawnej kompilacji. #include "PrzetwarzanieObrazow.h" Point PrzetwarzanieObrazow::polozeniePedzla(Mat obraz) { return Point(100,100); } UruchomienieNależy pamiętać o dodaniu opencv_imgproc230.lib do linkera, inaczej w przyszłości pojawią się błędy podczas linkowania. Projekt powinien się już kompilować, w razie problemów można zerknąć do kodu źródłowego. Po uruchomieniu powinniśmy zobaczyć podobny widok: ![]() (2 ocen) |
Copyright © 2008-2010 Wrocławski Portal Informatyczny
design: rafalpolito.com