poniedziałek, 8 sierpnia 2011

Zbiór przykładowych aplikacji Qt Quick

0 komentarze
Jak widać, patrząc na moje ostatnie wpisy na blogu - a raczej ich brak :-), zrobiłem sobie dłuższą przerwę od MeeGo i Qt. Nie wynika to bynajmniej z utraty zainteresowania tymi dwoma technologiami, ale raczej z chronicznego braku czasu. Po rozpoczętej na początku tego roku serii wpisów dotyczących systemów mobilnych, nadszedł czas na zapoznanie się z mocno ostatnio promowanym Qt Quick. Nie będę jednak zbyt wiele pisał o tej technologii, bo przyznam szczerze, że nie wiele się na niej znam. Po przerobieniu jednego prostego kursu, nie będę udawał, że jestem ekspertem w tej dziedzinie :-).

Pierwsze przymiarki do Qt Quick i QMLa rozpocząłem od przeszukania Internetu za gotowymi przykładami. Najwięcej jest ich oczywiście na stronie Nokii i tam polecam zajrzeć. Niestety jest mały bałagan w ich lokalizacji i mimo prób ich gromadzenia na Wiki Qt lub też w Portalu dla Developerów Noki ciągle znajdują się one w kilku miejscach. Udało mi się nad tym zapanować tworząc odpowiednie kategorie w moich Ulubionych. Oczywiście udostępniam je również Wam.

Przykłady dołączone do oficjalnej dokumentacji:
Przykłady opisane na Wiki Qt (link):
Przykłady z portalu dla developerów Noki:

Oficjalnie wspierane (link):
Rozwijane przez społeczność (link) - wybrałem te, które wizualnie zrobiły na mnie największe wrażenie:
Inne przykłady znalezione na stronie Qt:
Kursy i poradniki pokazujące jak coś zrobić:
Repozytoria:
Przykłady i projekty społeczności:
Patrząc na tą listę nie wiem czy udało mi się znaleźć dużo, czy mało przykładów aplikacji zaimplementowanych z użyciem Qt Quick. Dla osób raczkujących w tej technologi, tak jak ja, jest ich jednak w zupełności wystarczająco na start.

    poniedziałek, 25 lipca 2011

    "Generative art" teraz w moim plecaku

    2 komentarze
    Nareszcie przyszła druga zamówione przez mnie książka w Amazon. Czekałem na nią długo - ok. 2 miesiące. Na szczęście w tym czasie spokojnie mogłem przejrzeć "Form+Code".

    Pierwsze moje wrażenie po otwarciu "Generative art" było takie: "to nie tak miało być! spodziewałem się czegoś innego!". Moje lekkie rozczarowanie wynikało po prostu stąd, że po obejrzeniu przykładowego rozdziału, który jest dostępny na stronie autora książki Matta Pearsona (podaje link, aby było szybciej: Rozdział 1) spodziewałem się w pełni ilustrowanej książki, coś w rodzaju katalogu projektów i opisu dobrych praktyk.

    Na szczęście, to było tylko pierwsze i jak się okazało powierzchowne wrażenie. No bo jak budować opinie na temat książki przeglądając jedynie parę stron. Wystarczy poświecić jej trochę więcej czasu, aby dostrzec, że jest ona pełna opisów dobrych praktyk, zawiera naprawdę sporo wartościowych przykładów i pokazuje krok po kroku coraz bardziej zaawansowane techniki generowania  ilustracji komputerowych.

    Więcej nie zdradzam. Zachęcam do przejrzenia :-)

    I jeszcze jedna dobra wiadomość: po zakupie książki ebooka dostajemy gratis :-D.





    wtorek, 12 lipca 2011

    Google Matrix

    0 komentarze
    Wszystko wskazuje na to, że Google wie o mnie wszystko. Nie tylko to z kim koresponduję, to o czym wtedy piszę, to czego szukam w Internecie i komu robię fotki wakacjach.

    Wie również z jakiego systemu korzystam i (o zgrozo) z jakiej przeglądarki nie korzystam - a wg Google powinienem ;-)

    Najlepszym przykładem tego o czym właśnie piszę, może być reklama jaką dzisiaj zaserwował mi Google w swoim czytniku RSSów:



    Czyżby nadchodził Google Matrix?

    poniedziałek, 13 czerwca 2011

    "Form+Code" w mojej biblioteczce

    0 komentarze
    Po kilku tygodniach czekania w końcu doszła do mnie nowa książka. "Form+Code" zamówiłem jakiś czas temu przez Amazon razem z innym tytułem, o którym napiszę jaki mi go prześlą (ma być pod koniec czerwca, bo na razie nie mają go na stanie). Książka, która do mnie dotarła kilka dni temu, tak jak przewidywałem, nie jest za bardzo techniczno - informatyczna. Zawiera ona za to wiele opisów wdrożeń i instalacji światowej czołówki artystów zajmujących się sztuką generowaną (Generative Art) i kreatywnym kodowaniem (Creative Coding). Na szczęści książka zawiera również algorytmiczny opis kilkunastu ciekawych efektów, dzięki czemu można trochę podejrzeć jakich metod i narzędzi używają najlepsi w branży :-)


    Bardzo cennym uzupełnieniem książki jest również obszerna strona internetowa jej poświęcona. Można na niej zobaczyć niektóre strony z książki - jest ona głównie ilustrowana więc jest co oglądać, przejrzeć spis treści, a co najważniejsze pobrać przykładowe programy dołączone do książki.


    Polecam!

    poniedziałek, 16 maja 2011

    OpenCV - obsługa klawiatury i myszki

    0 komentarze
    Pełny kod źródłowy
    Kontynuując rozpoczęty ponad miesiąc temu cykl wpisów dotyczących pisania aplikacji interaktywnych nadszedł czas na wprowadzenie kolejnego elementu do wcześniej zaproponowanego szablonu. Dzisiaj zostanie omówiona zasadnicza kwestia pozwalająca na wprowadzenie przynajmniej podstawowej interakcji do programu: obsługa klawiatury i myszy. Oczywiście dla każdego programisty naturalnym jest to, że program z definicji coś takiego posiada, tzn. że możemy klikać na przyciski, przesuwać suwaki, wybierać zakładki i wpisywać tekst do formularzy. Jednak w przypadku OpenCV, należy pamiętać, że jest to biblioteka głównie do przetwarzania obrazu i chociaż obsługa klawiatury i myszy jest dostępna, to nie jest ona widoczna na pierwszy rzut oka.

    W tym wpisie poza omówieniem mechanizmów API do obsługi klawiatury i myszy, zostanie dodatkowo rozszerzony omawiany we wcześniejszych wpisach przykład z kulkami. Zachęcam również do samodzielnych testów i modyfikacji udostępnionego kodu.

    Obsługa zdarzeń myszy

    Myszka w OpenCV jest w pełni obsługiwana za pomocą modułu highgui, który jest również odpowiedzialny za wyświetlanie obrazków. Obsługę taką możemy włączyć za pomocą funkcji cvSetMouseCallback, w której przywiązujemy do danego okienka funkcję obsługującą zdarzenia myszy. Dodatkowo, ponieważ jest to funkcja globalna, a nie lokalna naszej klasy MainApp, należy do niej przekazać wskaźnik do aktualnego obiektu. Dzięki temu będziemy mieć dostęp do jego funkcji składowych. Podsumowując: należy dodać do funkcji MainApp::run() następującą linijkę:

    cvSetMouseCallback(win_canvas, mouse_callback, this);

    Drugi parametr tej funkcji jest wskaźnikiem do właściwej funkcji obsługującej myszkę. Funkcja ta odbiera nie tylko zdarzenie jakie wystąpiło (naciśnięcie i zwolnienie klawisza, podwójny klik, przesunięcie myszki), ale również bieżące współrzędne wskaźnika myszki.

    void mouse_callback(int event, int x, int y, int flags, void* param)
    {
      MainApp *app = ((MainApp *)param);
      switch (event) {
        case  CV_EVENT_LBUTTONDBLCLK:
          app->mouseDoubleClick(x,y,APP_MOUSE_LEFT);   break;
        case  CV_EVENT_RBUTTONDBLCLK:
          app->mouseDoubleClick(x,y,APP_MOUSE_RIGHT);  break;
        case  CV_EVENT_MBUTTONDBLCLK:
          app->mouseDoubleClick(x,y,APP_MOUSE_MIDDLE); break;
        case  CV_EVENT_LBUTTONDOWN:
          app->mousePressed(x,y,APP_MOUSE_LEFT);       break;
        case  CV_EVENT_RBUTTONDOWN:
          app->mousePressed(x,y,APP_MOUSE_RIGHT);      break;
        case  CV_EVENT_MBUTTONDOWN:
          app->mousePressed(x,y,APP_MOUSE_MIDDLE);     break;
        case  CV_EVENT_LBUTTONUP:
          app->mouseReleased(x,y,APP_MOUSE_LEFT);      break;
        case  CV_EVENT_RBUTTONUP:
          app->mouseReleased(x,y,APP_MOUSE_RIGHT);     break;
        case  CV_EVENT_MBUTTONUP:
          app->mouseReleased(x,y,APP_MOUSE_MIDDLE);    break;
        case  CV_EVENT_MOUSEMOVE:
          app->mouseMoved(x,y);                        break;
      }
    }

    Oczywiście w tym miejscu można już zaprogramować co ma się wydarzyć po wystąpieniu konkretnego zdarzenia, ale proponuję przekazać podejmowanie tej decyzji do wnętrza klasy, do odpowiednich funkcji. Oto one:

    void MainApp::mouseMoved(int mouseX, int mouseY ){
      if (m_mousePressed) mouseDragged(mouseX, mouseY);
    }
    
    void MainApp::mouseDragged(int mouseX, int mouseY){
    }
    
    void MainApp::mousePressed(int mouseX, int mouseY, int button){
      m_mousePressed = true;
    }
    
    void MainApp::mouseReleased(int mouseX, int mouseY, int button){
      m_mousePressed = false;
    }
    
    void MainApp::mouseDoubleClick(int mouseX, int mouseY, int button){
    }

    Jak widać nie ma tutaj nic szczególnego - kilka pustych funkcji. Czekają one po prostu na oprogramowanie przez użytkownika, a ich treść zależy od tego co w danym momencie chcemy osiągnąć. Na końcu wpisu zostanie podany przykład jak za pomocą kliknięcia myszką w element usunąć go z ekranu, więc polecam doczytać do końca :-).

    Obsługa zdarzeń klawiatury

    Z klawiaturą mamy już dużo łatwiej, aniżeli z myszką. Można zupełnie obejść się bez dodatkowych funkcji i wszystko zaprogramować w ciele funkcji, która zawiera pętlę główną programu. Wystarczy w tym celu skorzystać z funkcji cvWaitKey(int), aby sprawdzić czy użytkownik wcisnął jakiś klawisz.

    int key;
    while(1) {
      key = cvWaitKey(DELAY);  // wait for keyboard input
      if (key == 'q') break;   // 'q' pressed, quit the program
      if (key != -1 ) keyPressed(key);
    
      update();
      draw();
      
      imshow(win_canvas, canvas);
    }

    Podejście to pomimo tego, że poprawne, może niepotrzebnie wprowadzić nam zamieszanie nadmiarem kodu w ważnym miejscu programu. Polecam zatem wprowadzić dodatkową funkcję, która będzie zajmowała się wyłącznie reagowaniem na zdarzenia klawiatury. Jej deklaracja jest następująca:


    void MainApp::keyPressed(int key){
    
      switch (key){
        case ' ':
          pauseBallsMove = !pauseBallsMove;
          break;
      }
    }

    Patrząc na tą funkcję widać, że została zdefiniowana akcja, która wydarzy się po wciśnięciu przez użytkownika spacji. Nie dzieje się tutaj nic więcej, aniżeli prosta negacja zmiennej logicznej pauseBallsMove. Zmienna ta może się przydać np. do zatrzymania ruchu elementów w naszym przykładowym programie:

    MainApp::update()
    if (!pauseBallsMove) {
      for (int i = 0; i < myBall.size(); i++) {
        myBall[i].update();
      }
    }

    Przykładowa aplikacja

    A teraz obiecany przykład :-) Nie zawiera on szczególnie wiele kodu, ale efekt jaki pozwala osiągnąć wydaje się całkiem interesujący. Na początek polecam dodać dodatkowe polecenia obsługujące zdarzenie naciśnięcia klawisza 'a' i 'd'. Naciśniecie pierwszej literki będzie powodować dodanie kolejnych kulek na ekranie, natomiast naciśnięcie drugiej zmniejszenie ich liczny.

    case 'a':
      myBall.push_back( Ball(&canvas) );
      break;
    case 'd':
      if (myBall.size()>=1) myBall.pop_back();
      break;

    Kolejną zmianą jaką można wprowadzić jest dodanie kilku linii kodu, reagujących na klikniecie myszką. Uzupełniamy ciało funkcji mousePressed następującym kodem:


    for(int i=0; i < myBall.size(); i++) {
      if(myBall[i].checkMouseOver(mouseX, mouseY)) {
        myBall[i].decrementLives();
    
        if(myBall[i].isEndLive()) myBall.erase(myBall.begin()+i);
      }
    }

    Teraz jeszcze wystarczy dodać kilka nowych funkcji do klasy Ball:


    bool Ball::checkMouseOver(int _mouseX, int _mouseY) {
      bool mouseIsOver = false;
    
      if ( dist(_mouseX,_mouseY,x,y) < dim ) mouseIsOver = true;
    
      return mouseIsOver;
    }
    
    void Ball::decrementLives() {
      lives--;
    }
    
    bool Ball::isEndLive() {
      return (lives <= 0);
    }
    

    i chyba nie trudno się domyślić jaki w ten sposób efekt możemy uzyskać :-)

    Dla osób zniecierpliwionych lub takich, które nie mają czasu na samodzielną kompilację przykładu, przygotowałem krótki filmik pokazujący efekty wprowadzenia obsługi klawiatury i myszki do omawianej aplikacji:


    wtorek, 19 kwietnia 2011

    Repozytorium dla przykładów OpenCV na GitHub

    0 komentarze
    W ostatnim czasie na blogu pojawiło się kilka wpisów o OpenCV. Wiele z nich zawierało przykładowy kod źródłowy, który wklejałem we fragmentach, aby nie wydłużać samych postów. Były to głównie kluczowe fragmenty opisywanych programów i nie zawierały one najczęściej początku i końca pliku. Wiem, że nie jest to żaden kłopot dla osób, które miały do czynienia z OpenCV, ale dla początkującego użytkownika może to być mały problem. Łatwo przecież zrobić drobny błąd, literówkę lub coś przeoczyć, a trudniej to później znaleźć. W związku z tym utworzyłem publiczne repozytorium kodu źródłowego GitHub, do którego wypchnąłem (push - w terminologii Git) opracowane do tej pory przykłady. Do repozytorium można przejść klikając w poniższy obrazek.

    poniedziałek, 11 kwietnia 2011

    Poprawiony szkielet aplikacji interaktywnej dla OpenCV

    1 komentarze
    Pełny kod źródłowy
    Kilka dni temu zaproponowałem na tym blogu obiektowy szkielet aplikacji interaktywnej dla OpenCV. W dzisiejszym wpisie chciałbym pokazać jak można zrobić to jeszcze lepiej, aby kod był nie tylko bardziej obiektowy, ale również miał lepiej zorganizowaną strukturę. Pomimo tego, że przedstawiony tam schemat aplikacji całkiem nieźle się sprawdza, a kod jest w miarę czytelny, ma on kilka wad i usterek, które zapewne niejeden zaawansowany programista C++ szybko mógłby wytknąć. Oto dwie z nich:
    • Cały kod programu został umieszczony w jednym pliku. Dla małych projektów jest to w miarę dobre rozwiązanie, jednak przy większych aplikacjach może być uciążliwe. Umieszczanie całego kodu w jednym pliku łamie również powszechnie stosowaną zasadę umieszczania każdej klasy w osobnym pliku. Inną powszechnie znaną praktyką, która nie została tam zastosowana, jest rozdział deklaracji klasy od jej ciała i umieszczenie ich osobno w pliku nagłówkowym (*.h) i w pliku z implementacją funkcji (*.cpp).
    • Trudno nazwać przedstawiony w poprzednim wpisie szkielet za w pełni obiektowy, jeśli znajduje się tam jedna klasa, która zawiera całą funkcjonalność aplikacji. Szczególnie problem jest widoczny w braku osobnej reprezentacji obiektowej dla znajdujących się tam kulek. Problem ten nie jest szczególnie zauważalny w przypadku istnienia jednej kulki, ale jest już odczuwalny w przypadku ich większej ilości. Wystarczy spojrzeć na funkcję MainApp::update() i spróbować sobie wyobrazić, jak mogłaby ona wyglądać, jeśli trzeba by było zarządzać ruchem i rysowaniem np. 10 kulek. Widać wyraźnie, że coś jest tutaj nie tak i wręcz konieczne wydaje się rozszerzenie modelu obiektowego własnie o te obiekty, które są rysowane na ekranie, a które mają w miarę niezależny schemat poruszania się. 
    Patrząc na te problemy postanowiłem szybko wprowadzić niezbędne poprawki, aby wyeliminować opisane niedogodności. Z jednego pliku powstało ich sześć. Dodatkowo utworzyłem klasę Ball reprezentująca ruch i wygląd kulek. Pozwoliło mi to na wprowadzenie łatwej personalizacji dla każdej kulki z osobna. Struktura obydwu klas (MainApp i Ball) została rozdzielona na pliki nagłówkowe i pliki z implementacją ich funkcji składowych.

    Krótka charakterystyka szkieletu aplikacji interaktywnej OpenCV

    interactive_app_tutorial-main.cpp - plik ten zawiera jedynie funkcję główną programu main(), której jedynym zadaniem jest uruchomienie i wyświetlenie głównego okna aplikacji reprezentowanego przez klasę MainApp. Dodatkowo na początku tego pliku inicjalizowany jest generator liczb losowych OpenCV wartością równą liczbie milisekund, która upłynęła od ustalonej daty w przeszłości (w systemie Linux jest to 1 styczeń 1970 roku).

    #include <opencv2/highgui/highgui.hpp>
    #include <opencv2/imgproc/imgproc.hpp>
    
    #include "interactive_app_tutorial-MainApp.h"
    
    using namespace cv;
    
    RNG rng(cvGetTickCount());
    
    int main(int, char**)
    {
      MainApp::getInstance().run();
      
      return 0;
    }

    interactive_app_tutorial-util.h - tutaj najlepiej umieszczać funkcje ogólnego przeznaczenia oraz funcke narzędziowe, które mogą się przydać w różnych miejscach programu. Na razie znajduje się tu jedynie funkcja zwracająca składowe koloru, dla przekazanej jako parametr liczby.

    #ifndef UTIL_H
    #define UTIL_H
    
    static Scalar randomColor(RNG& rng)
    {
      int icolor = (unsigned)rng;
      return Scalar(icolor&255, (icolor>>8)&255, (icolor>>16)&255);
    }
    
    #endif

    interactive_app_tutorial-MainApp.h - jest to plik nagłówkowy zawierający deklarację klasy MainApp i jej składowych. Na pierwszy rzut nie widać, aby coś się zmieniło względem szkieletu aplikacji opisanego w poprzednim wpisie. Jednak jak będzie to widać dalej, implementacja poszczególnych funkcji jest już zupełnie inna. Patrząc na zmienne prywatne jakie tutaj zostały zadeklarowane, można zauważyć, że klasa będzie zarządzać trzema kulkami. Jak sobie poradzić z tym ograniczeniem zostanie pokazane na końcu tego wpisu.

    #ifndef MAINAPP_H
    #define MAINAPP_H
    
    #include <opencv2/highgui/highgui.hpp>
    #include <opencv2/imgproc/imgproc.hpp>
    
    #include "interactive_app_tutorial-Ball.h"
    
    using namespace cv;
    
    //MainApp singelton class
    class MainApp
    {
    private:
      Mat canvas;               // Image for drawing
      Scalar bgr_color;         // Background color
      Ball *myBall_1, *myBall_2, *myBall_3;
      
    public: // Some global params:
      static int DELAY;
      static int CANVAS_WIDTH;  
      static int CANVAS_HEIGHT;  
      
    private:
      MainApp() {}
      MainApp(const MainApp &);
      MainApp& operator=(const MainApp&);
      
      void setup();   // Initial commands for setup processing
      void update();  // Commands to modify the parameters
      void draw();    // Drawing functions:
      
    public:
      static MainApp& getInstance()
      {
        static MainApp instance;
        return instance;
      }
      
      // Main loop function with displaying image support
      // and handle mouse and keyboard events
      void run();
    };
    
    #endif

    interactive_app_tutorial-MainApp.cpp - plik ten zawiera właściwą implementację funkcji zawartych w klasie MainApp. Nie są one jakość szczególnie interesujące ponieważ wywołują jedynie funkcje składowe klasy Ball o identycznych nazwach.

    #include <opencv2/highgui/highgui.hpp>
    #include <opencv2/imgproc/imgproc.hpp>
    
    #include "interactive_app_tutorial-MainApp.h"
    
    using namespace cv;
    
    // Best place to initialize global MainApp params:
    int MainApp::DELAY        = 5;
    int MainApp::CANVAS_WIDTH = 500;
    int MainApp::CANVAS_HEIGHT= 350;
    
    void MainApp::run() {
      setup();
      
      const char *win_canvas = "Canvas";
      namedWindow(win_canvas, CV_WINDOW_AUTOSIZE);
      
      while (cvWaitKey(4) == -1) {
        update();
        draw();
        
        imshow(win_canvas, canvas);
        waitKey(DELAY);
      }
    }
    
    void MainApp::setup() 
    {
      bgr_color = Scalar(200,200,200);
      canvas = Mat(CANVAS_HEIGHT, CANVAS_WIDTH, CV_8UC3, bgr_color);
      
      myBall_1 = new Ball(&canvas);
      myBall_2 = new Ball(&canvas);
      myBall_3 = new Ball(&canvas);
    }
    
    void MainApp::draw() {
      canvas = bgr_color;
      
      myBall_1->draw();    
      myBall_2->draw();    
      myBall_3->draw();    
    }
    
    void MainApp::update() 
    {
      myBall_1->update();
      myBall_2->update();
      myBall_3->update();
    }

    interactive_app_tutorial-Ball.h - tak naprawdę to tutaj zaczynają się większe zmiany względem poprzedniego szkieletu aplikacji. Plik ten zawiera deklarację nowej klasy Ball, która już sama będzie dbać o swój wygląd, wyrysowanie siebie na ekranie oraz wyliczenie właściwej lokalizacji dla kolejnej iteracji. Sprawą, na którą należy tutaj zwrócić szczególną uwagą jest postać konstruktorów. Konstruktor domyślny Ball() został specjalnie zablokowany, aby zachęcić (zmusić ;-)) użytkownika do korzystania z dodatkowego konstruktora, który jako parametr przyjmuje wskaźnik do struktury cv::Mat, po której będzie można rysować. Generalnie powinien być tutaj przekazywany wskaźnik do głównego obrazka z klasy MainApp, na którym odbywa się rysowanie.

    #ifndef BALL_H
    #define BALL_H
    
    #include <opencv2/highgui/highgui.hpp>
    #include <opencv2/imgproc/imgproc.hpp>
    
    using namespace cv;
    
    //Ball class
    class Ball 
    {
    private:
      Mat *canvas;
      float x, y, dim;
      float speedX, speedY;
      Scalar color;
      
      Ball();
      void setup();
      
    public:
      Ball(Mat *can)
      {
        canvas = can;
        setup();
      }
      
      void update();
      void draw();
    };
    
    #endif

    interactive_app_tutorial-Ball.cpp - osoby, które już wcześniej analizowały poprzedni szkielet aplikacji zapewne zauważyły, że istotna część treści funkcji MainApp::update() i MainApp::draw() zostały przeniesione do funkcji o identycznych nazwach w klasie Ball. Jest to najważniejsza zmiana w nowym szablonie. Teraz to każda kulka samodzielnie dba o siebie i kontroluje swoje zachowanie.

    #include <opencv2/highgui/highgui.hpp>
    #include <opencv2/imgproc/imgproc.hpp>
    
    #include "interactive_app_tutorial-Ball.h"
    #include "interactive_app_tutorial-MainApp.h"
    #include "interactive_app_tutorial-util.h"
    
    using namespace cv;
    
    extern RNG rng;
    
    void Ball::setup()
    {
      x = rng.uniform(0, MainApp::CANVAS_WIDTH); // give some random positioning
      y = rng.uniform(0, MainApp::CANVAS_HEIGHT);
      speedX = rng.uniform((float)-2, (float)2); // and random speed and direction
      speedY = rng.uniform((float)-2, (float)2);
      dim    = rng.uniform(20,60);
      
      color = randomColor(rng);
    }
    
    void Ball::update() 
    {
      if(x < 0 ){
        x = 0;
        speedX *= -1;
      } else if(x > MainApp::CANVAS_WIDTH){
        x = MainApp::CANVAS_WIDTH;
        speedX *= -1;
      }
      
      if(y < 0 ){
        y = 0;
        speedY *= -1;
      } else if(y > MainApp::CANVAS_HEIGHT){
        y = MainApp::CANVAS_HEIGHT;
        speedY *= -1;
      } 
      
      x+=speedX;
      y+=speedY;  
    }
    
    void Ball::draw()
    {
      circle(*canvas, Point(x,y), dim, color,-1,CV_AA); 
    }

    CMakeLists.txt - plik ten zawiera konfigurację procesu kompilacji CMake dla omówionego przykładu. Wcześniej był tylko jeden plik, teraz jest ich sześć. Aby się w tym nie pogubić warto skorzystać właśnie z CMake.

    PROJECT(OpenCvInteractiveAppsAndExamples-InteractiveAppTutorial2)
    
    cmake_minimum_required(VERSION 2.8)
    
    FIND_PACKAGE( OpenCV REQUIRED )
    
    ADD_EXECUTABLE(interactive_app_tutorial_2 interactive_app_tutorial-main.cpp interactive_app_tutorial-MainApp.cpp interactive_app_tutorial-Ball.cpp)
    TARGET_LINK_LIBRARIES(interactive_app_tutorial_2 ${OpenCV_LIBS})

    Drobne poprawki na koniec

    Wydawać by się mogło, ze w opisanym powyżej szkielecie aplikacji OpenCv wszystko jest OK, ale jeden szczegół na dłuższą metę może okazać się bardzo uciążliwy. W tym momencie w klasie MainApp zosłao z góry określone ile kulek będzie rysowane na ekranie. Trzy kulki to trochę mało. Co by było gdyby trzeba ich było narysować np. trzysta? Najlepiej w takiej sytuacji skorzystać z dynamicznie rozszerzanych kontenerów dostępnych w C++. Klasa vector będzie w zupełności wystarczająca. Na początek warto zmienić deklarację klasy MainApp usuwając z niej zmienne lokalne *myBall_1, *myBall_2, *myBall_3, a wprowadzając na to miejsce deklarację wektora vector <Ball> myBall;. Ja dodatkowo wprowadziłem jeszcze jedną zmienną statyczną, która będzie regulować liczbę kulek wyświetlanych na ekranie. Po tych zmianach kod kod może wyglądać jak poniżej:

    interactive_app_tutorial-MainApp.h

    private:
      vector <Ball> myBall;
      //...
      
    public:
      static int INITIAL_BALLS_NUMBER;
      //...

    Kolejna zmiana, która należy wprowadzić to aktualizacja funkcji setup(), update() i draw() klasy MainApp. Teraz one powinny wyglądać następująco:

    interactive_app_tutorial-MainApp.cpp

    void MainApp::setup() 
    {
      bgr_color = Scalar(200,200,200);
      canvas = Mat(CANVAS_HEIGHT, CANVAS_WIDTH, CV_8UC3, bgr_color);
      
      for (int i = 0; i < INITIAL_BALLS_NUMBER; i++){
        myBall.push_back( Ball(&canvas) );
      }
    
    }
    
    void MainApp::draw() {
      canvas = bgr_color;
      
      for (int i = 0; i < myBall.size(); i++){
        myBall[i].draw();
      }  
    }
    
    void MainApp::update() 
    {
      for (int i = 0; i < myBall.size(); i++){
        myBall[i].update();
      } 
    }

    Ostatecznie aplikacja po powyższych zmianach i ustawieniu liczby kulek na 30 może dać rezultat podobny jak ten na obrazku poniżej:

    W miarę wolnego czasu w kolejnych wpisach będę chciał pokazać jak wprowadzić do powyższego szablonu możliwości interakcji za pomocą klawiatury i myszki oraz za pomocą ruchu zarejestrowanego kamerą.

    czwartek, 7 kwietnia 2011

    Obiektowy szkielet aplikacji interaktywnej dla OpenCV

    2 komentarze
    Pełny kod źródłowy
    Biblioteka OpenCV pomimo tego, że zawiera sporo konstrukcji obiektowych i pozwala pisać programy w C++, sama w sobie nie precyzuje i nie podpowiada jaką strukturę powinien mieć dobrze napisany kod programu. Wpływa to oczywiście na morale wielu programistów, którzy widząc, że można napisać kod liniowo w całości w funkcji main, idą na skróty i własnie tak robią. Nie chciałbym tutaj nikogo szczególnie piętnować ;), ale większość przykładowych programów, które ostatnio analizowałem tak własnie jest napisana. Dotyczy to również standardowych przykładów dołączonych do biblioteki OpenCV. Oczywiście tak nie musi być. Programy napisane w OpenCV przy zastosowaniu podstawowych konstrukcji obiektowych mogą być nie tylko czytelniejsze dla osoby, która je pierwszy raz przegląda, ale również znacznie łatwiejsze w pielęgnacji i dalszym rozwoju dla osoby lub zespołu, który taką aplikację napisał.

    W tym wpisie chciałem się podzielić szablonem aplikacji OpenCV, którego ostatnio używam. Jest to szablon, który może być szczególnie użyteczny dla aplikacji interaktywnych, w których użytkownik ma wpływ na zachowanie wyświetlanych elementów. Przygotowując ten szablon starałem się zachować podejście znane z programowania aplikacji okienkowych, gdzie pracujemy raczej na komponentach odnoszących się do interfejsu użytkownika, a nie na wielu obiektach z mniej lub bardziej złożoną strukturą powiązań. Jak widać z analizy poniższego kodu funkcja main została zredukowana do minimum, a cała logika aplikacji została przeniesiona do klasy MainApp. Klasa ta nie powinna być wielokrotnie tworzona, dlatego została ona skonstruowana w oparciu o wzorzec Singelton, który tego pilnuje.

    Polecam teraz dokładnie przeanalizować podany szablon, a dalsze wyjaśnienia będzie można przeczytać poniżej.

    #include <opencv2/highgui/highgui.hpp>
    #include <opencv2/imgproc/imgproc.hpp>
    
    using namespace cv;
    
    //MainApp singelton class
    class MainApp
    {
    private:
      Mat canvas;               // Image for drawing
      Scalar bgr_color;         // Background color
      int x, y, speedX, speedY; // Circle local params
      
    public: // Some global params:
      static int DELAY;
      static int CANVAS_WIDTH;  
      static int CANVAS_HEIGHT;  
      
    private:
      MainApp() {}
      MainApp(const MainApp &);
      MainApp& operator=(const MainApp&);
      
      // Initial commands for setup processing
      void setup() 
      {
        x = 0;
        y = 0;
        speedX = 1;
        speedY = 1;
        
        bgr_color = Scalar(120,235,139);
        canvas = Mat(CANVAS_HEIGHT, CANVAS_WIDTH, CV_8UC3, bgr_color);
      }
      
      // Commands to modify the parameters
      void update() 
      {
        if(x < 0 ){
          x = 0;
          speedX *= -1;
        } else if(x > CANVAS_WIDTH){
          x = CANVAS_WIDTH;
          speedX *= -1;
        }
        
        if(y < 0 ){
          y = 0;
          speedY *= -1;
        } else if(y > CANVAS_HEIGHT){
          y = CANVAS_HEIGHT;
          speedY *= -1;
        } 
        
        x+=speedX;
        y+=speedY;    
      }
      
      // Drawing functions:
      void draw() {
        canvas = bgr_color;
        circle(canvas, Point(x,y), 30, Scalar(0,255,255),-1,CV_AA);
      }
      
    public:
      static MainApp& getInstance()
      {
        static MainApp instance;
        return instance;
      }
      
      // Main loop function with displaying image support
      // and handle mouse and keyboard events
      void run() {
        setup();
    
        const char *win_canvas = "Canvas";
        namedWindow(win_canvas, CV_WINDOW_AUTOSIZE);
    
        while (cvWaitKey(4) == -1) {
          update();
          draw();
          
          imshow(win_canvas, canvas);
          waitKey(DELAY);
        }
      }
    };
    
    // Best place to initialize global MainApp params:
    int MainApp::DELAY        = 5;
    int MainApp::CANVAS_WIDTH = 500;
    int MainApp::CANVAS_HEIGHT= 350;
    
    int main(int, char**)
    {
      MainApp::getInstance().run();
      
      return 0;
    }

    Patrząc na powyższy kod nie trudno zauważyć, że został on podzielony na kilka części. Dotyczy to szczególnie klasy MainApp. Została ona podzielona na funkcje, które mają ściśle określone role:
    • run() - publicznie dostępna funkcja, która obsługuje główną pętlę programu. Program będzie się w niej wykonywał do momentu, aż użytkownik wciśnie klawisz Escape. Funkcja ta nie tylko wywołuje kolejne, niżej opisane funkcje, ale również odpowiada za wyświetlanie głównego okienka programu i obsługę zdarzeń nadchodzących z klawiatury i myszki.
    • setup() - funkcja wywoływana tylko raz, na początku działania programu, której zadaniem jest inicjalizacja zmiennych lokalnych i utworzenie obrazka, na którym będą rysowane pozostałe elementy.
    • update() - funkcja modyfikująca wybrane parametry działania programu. Tak naprawdę to tutaj powinna być zapisana główna logika działania programu. 
    • draw() - zawiera wszystkie operacje rysowania na głównej scenie programu.
    Aby jeszcze dokładniej prześledzić organizację kodu w podanym szablonie przeanalizujmy przykład, który jest do niego dołączony. Mi wygodnie będzie to zrobić za pomocą następującego dialogu:

    A. Tych funkcji jest kilka. Kodu samego nie ma wiele. Czy robi on coś konkretnego?

    B. Wszystko co możemy zobaczyć na ekranie odbywa się w funkcji draw(). W tym przykładzie widać wyraźnie, że rysuje ona okręgi o promieniu 30 w punkcie o współrzędnych x, y.

    A. Nie wydaje się to nic szczególnie ciekawego.

    B. Warto teraz zajrzeć do funkcji update(). To tutaj znajduje się właściwe centrum sterowania aplikacji. To tutaj iteracyjnie modyfikowane są wybrane parametry elementów, które są wyświetlane na ekranie. W przypadku okręgu, o którym mowa, własnie w tej funkcji jest zaprogramowane, aby poruszał się on w linii prostej i odbijał się od krawędzi.


    Domyślam się, że przedstawiony szablon nie pokazuje nic szczególnie odkrywczego ;-). Wielu zaawansowanych programistów OpenCV, do których ja się raczej nie zaliczam, pewnie stosuje podobne i lepsze rozwiązania od dawna. Mam jedynie nadzieje, że okaże się on przydatny dla osób, które dopiero zaczynają przygodę z OpenCV i chcą napisać swój pierwszy interaktywny program.

    Czekam oczywiście na uwagi i komentarze co można poprawić i co można zrobić lepiej.

    Aktualizacja 2011.04.11: Opisany w tym wpisie szkielet aplikacji OpenCV ma kilka niedoskonałości. Opisałem je we wpisie Poprawiony szkielet aplikacji interaktywnej dla OpenCV. Zaproponowałem tam również jego udoskonaloną wersję. Szablon opisany w tym wpisie może być jednak z powodzeniem stosowany do prostszych aplikacji.

    środa, 6 kwietnia 2011

    Detekcja ruchu w OpenCV - porównanie klatek

    4 komentarze
    Pełny kod źródłowy
    OpenCV zostało stworzone i jest głównie używane tam, gdzie jest potrzebna zaawansowana analiza sekwencji wideo. Jest to narzędzie, które zawiera nie tylko funkcje obsługujące pobieranie obrazu z kamery, ale również funkcje, które potrafią ten obraz przetwarzać i wydobywać z niego ważne dla nas informacje. W przypadku analizy sekwencji wideo jednym z ważniejszych zadań jest detekcja miejsc, gdzie wystąpił ruch. Sama detekcja nie musi być szczególnie problematyczna, jeśli chodzi nam jedynie o proste wskazanie obszarów, na których coś się rusza. Wystarczy jedynie sprawdzić, które piksele na kolejnych klatkach obrazu zmieniają swoją wartość i podjąć decyzję na ile ta zmiana powinna być istotna, aby wykryć rzeczywisty ruch obiektu, a nie np. zaburzenia oświetlenia, bądź też ruch cienia obiektu. Oczywiście istnieje wiele problemów, które wymagają bardziej zaawansowanych algorytmów, aniżeli proste porównywanie obrazów. Możemy do nich mi.in zaliczyć rozpoznawanie i śledzenie wybranych obiektów.

    W dzisiejszym wpisie chciałem pokazać proste rozwiązanie problemu detekcji ruchu z wykorzystaniem biblioteki OpenCV. Skorzystam tutaj z opisanej powyżej koncepcji sprawdzania, które punkty obrazu zmieniają swoją wartość na kolejnych obrazach w sekwencji wideo. Najłatwiej w tym celu skorzystać z operacji odejmowania obrazów, a następnie binaryzacji obrazu wynikowego w celu określenia, jaka zmiana wartości pikseli powinna zostać uznana za ruch obiektu, a jaka za brak ruchu. Najczęściej stosuje się tutaj jedną z dwoch strategii odejmowania obrazów:
    • odejmowanie dwóch sąsiednich obrazów, tzn. odejmowanie od bieżącej klatki, klatki poprzedniej,
    • odejmowanie od bieżącej klatki, pierwszej klatki, która została zarejestrowana i która jest uznawana jako tło (zakładamy oczywiście, że na pierwszej klatce nie ma obiektów, które później będą zmieniać swoje położenie).
    Oba powyższe podejścia pomimo tego, że są bardzo zbliżone do siebie dają całkiem różne rezultaty. Pierwszy sposób wskazuje jedynie, na fragmenty obiektu, które zmieniły swoją lokalizację w sekwencji ruchu, podczas gdy drugi powinien wskazać cały obszar poruszającego się obiektu.

    Implementacja opisanych metod w OpenCV nie nastręcza wiele problemów. Obydwa sposoby mogą być zaimplementowane z pomocą poniższego fragmentu kodu:

    //TESTED WITH OpenCV 2.2
    
    int diffType = atoi( argv[1] );
    int thresval = atoi( argv[2] );
    
    Mat cam_frame, img_gray, img_prev, img_diff, img_bin;
    
    bool first_frame = true;
    
    // LOOP FOR GRABBING IMAGE FROM WEBCAM 
    
    cap >> cam_frame;
    cvtColor(cam_frame, img_gray, CV_BGR2GRAY);
    
    if (first_frame) {
        img_prev=img_gray.clone();
        first_frame = false;
        continue;
    }
    
    absdiff(img_gray, img_prev, img_diff);
    threshold(img_diff, img_bin, thresval, 255, THRESH_BINARY);
    erode(img_bin,  img_bin, Mat(), Point(-1,-1), 3);
    dilate(img_bin, img_bin, Mat(), Point(-1,-1), 1);
    
    imshow(win_cam,  cam_frame);
    imshow(win_gray, img_gray);
    imshow(win_diff, img_bin);
    
    if (diffType == 1) img_prev=img_gray.clone();
    
    // END LOOP

    Analizując powyższy kod łatwo zauważyć, że jego działaniem możemy sterować za pomocą dwóch zmiennych:
    • diffType - określa, którą klatkę należy odejmować od klatki bieżącej, czy poprzednią (wartość = 1), czy też pierwszą (wartość = 2). To sprawdzanie odbywa się w ostatniej linijce powyższego kodu, gdzie jest ustawiana klatka, która będzie odejmowana.
    • thresval - zmienna ta będzie służyć do określania, co uznajemy za ruch obiektu, a co nie. Polecam ustawiać ją w przedziale od 30 do 100 (60 powinno być w miarę bezpieczną, a zarazem skuteczną wartością progu).
    Aby nie wchodzić w szczegółową analizę działania powyższego kodu, opiszę jedynie w skrócie jego najważniejsze fragmenty:
    • W pierwszym przebiegu pętli zapamiętujemy jedynie pierwszą klatkę obrazu. Dzięki temu w drugiej iteracji będziemy mieli co odejmować od klatki bieżącej.
    • absdiff - bezwzględne odjęcie (pomijamy znak) dwóch klatek od siebie.
    • threshold - operacja progowania wykonywana na obrazie różnicowym. Pomijamy punkty, które nieznacznie zmieniły swoją wartość.
    • erode - usunięcie drobnych elementów, oraz wygładzenie krawędzi tych większych. Skutkiem ubocznym tej operacji jest zmniejszenie rozmiaru wszystkich obiektów.
    • dilate - dylatację stosujemy po etapie erozji, aby delikatnie powiększyć obiekty, tak aby miały podobny rozmiar jak to było przed erozją. 

    piątek, 1 kwietnia 2011

    Obsługa parametrów wywołania programu w C++

    6 komentarze
    Pisząc programy konsolowe niejednokrotnie spotykamy się problemem przekazywania parametrów startowych. Najczęstszym rozwiązaniem jest ich wpisywanie zaraz po nazwie programu, dzięki czemu będą one później dostępne w tablicy argv funkcji main. Innym rozwiązaniem, rzadziej stosowanym jest ich zapisywanie do pliku tekstowego, np. XML i dalsze jego parsowanie zaraz po starcie programu. To drugie rozwiązanie zalecane jest raczej w przypadku naprawdę złożonych parametrów wywołania. W większości przypadków w zupełności wystarcza proste przekazywanie parametrów po nazwie programu w trakcie jego wywołania.

    Podejście klasyczne o przykładowym zapisie:
    int zmienna1 = atoi (argv[1]);
    int zmienna2 = atoi (argv[3]);
    może być kłopotliwe w stosowaniu, ponieważ wymaga dokładnego pamiętania kolejności poszczególnych parametrów. Znacznie lepszym rozwiązaniem są parametry nazwane, co do których ten wymóg nie jest już konieczny. Parametry tego typu znane są na pewno każdemu, kto jakiś czas pracował w konsoli tekstowej i potrzebował zmodyfikować domyślne parametry działania programu lub podać własne.

    Obecnie jest kilka mechanizmów obsługi tego typu parametrów w języku C++. Niestety nie są one wbudowane do biblioteki standardowej i wymagają stosowania zewnętrznych bibliotek, które mają nie tylko mocne, ale również słabe strony. Oto niektóre z nich:
    • Boost.Program_options - jeśli ktoś nie miał do tej pory potrzeby korzystania z bibliotek Boost to nie wydaje się dobrym pomysłem włączanie ich do projektu tylko ze względu na obsługę parametrów wejściowych. Bardziej szczegółowy opis biblioteki można znaleźć tutaj.
    • GNU getopt - użycie tego narzędzia w praktyce wymaga od użytkownika napisania kodu zawierającego instrukcję pętli while i przełącznik switch, przez co implementacja programu niepotrzebnie się wydłuża i może tracić czytelność (szczególnie w przypadku krótkich programów). Przykładowy program można zobaczyć w oficjalnej dokumentacji
    • Do innych rozwiązań tego typu można zaliczyć: bibliotekę google-gflags, bibliotekę CLPPargstreamclp.
    Każde z powyższych rozwiązań, nawet jeśli w praktyce działa bardzo dobrze, wymaga korzystania z dodatkowych bibliotek, co do których należy mieć pewność, że zostały poprawnie zainstalowane w systemie. Dodatkowo w przypadku, niedużych programów, nie wydaje się sensowne korzystanie z bibliotek, które swoim rozmiarem znacznie przewyższają kod implementowanego algorytmu. Ja osobiście nie przekonałem się do żadnej z powyższych bibliotek, dlatego postanowiłem sam zaprogramować obsługę parametrów wywołania programu.

    Poniższe rozwiązanie przygotowałem na podstawie przykładu ze strony: http://stackoverflow.com/questions/865668/parse-command-line-arguments/868894#868894. Przykład ten niestety nie działał do końca poprawnie i ostateczne rozwiązanie udało mi sie przygotowac analizując kod biblioteki CImg, którą używam do przetwarzania obrazu, a która to miała zaimplementowany podobny mechanizm. Poniższego kodu nie będe szczegółowo opisywał. Mam nadzieje, że jest on wystarczająco czytelny :-).

    Funkcja parsująca parametry wywołania programu w C++:

    inline const char*  getCmdOption(const char * name, const char * defaut, const int argc, const char *const * argv)
    {
      const char *res = 0;
      
      if (argc > 0) {
        int k = 0;
        while (k < argc && strcmp(argv[k],name)) ++k;
        res = (k++==argc?defaut:(k==argc?argv[--k]:argv[k]));
      } else res = defaut;
      
      return res;
    }

    Przykładowe użycie funkcji getCmdOption:

    Funkcja getCmdOption już okazała mi się pomocna w kilku programach z zakresu przetwarzania obrazu. Poniżej fragment programu do wczytywania danych binarnych, ze szczególnym uwzględnieniem obsługi parametrów wejściowych:

    if (argc == 1) {
      printf("Usage: %s -f filename.raw -dx [value] -dy [value] -dz [value]\n", argv[0]);
      exit(0);
    }
    
    const char * filename  = getCmdOption("-f",    (char*)0, argc, argv );
    const char * _dx       = getCmdOption("-dx",   (char*)0, argc, argv );
    const char * _dy       = getCmdOption("-dy",   (char*)0, argc, argv );
    const char * _dz       = getCmdOption("-dz",   (char*)0, argc, argv );
    
    if ((char*)0 == filename) { 
      printf("Please specify input data file name (-f)\n"); 
      exit(0);
    }
    if ((char*)0 == _dx || (char*)0 == _dx || (char*)0 == _dx) { 
      printf("Please specify input data dimension sizes (-dx, -dy, -dz)\n"); 
      exit(0);
    }
    
    int dx = atoi( _dx );
    int dy = atoi( _dy );
    int dz = atoi( _dz );

    To co było dla mnie najważniejsze i co mam nadzieje udało mi się uzyskać, to duża czytelność kodu. Mam nadzieje, że proponowana funkcja getCmdOption, okaże się również przydatna dla innych.

    czwartek, 24 marca 2011

    Wygodna konfiguracja procesu budowania programów OpenCV w CMake

    0 komentarze
    O sposobach kompilacji programów OpenCV w Linuksie pisałem ostatnio we wpisie OpenCV - instalacja i pierwszy przykład w Ubuntu. Pokazalem tam również przykladowy plik konfiguracyjny dla CMake: CMakeLists.txt. Na pierwszy rzut oka wydaje się on w porządku, jednakże jeśli zacznie nam przybywać nowych programów, wówczas kopiowanie tych dwóch linijek rozpoczynających się od ADD_EXECUTABLETARGET_LINK_LIBRARIES może być irytujące. Można oczywiście sobie z tym poradzić i przekonać CMake d bardziej zautomatyzowanej pracy - przykład poniżej:

    PROJECT(NazwaProjektu)
    
    cmake_minimum_required(VERSION 2.8)
    
    FIND_PACKAGE( OpenCV REQUIRED )
    
    SET(SOURCES
      aplikacja1
      aplikacja2
      aplikacja3
      aplikacja4 
    )
    
    FOREACH(source ${SOURCES})
      ADD_EXECUTABLE(${source} ${source}.cpp)
      TARGET_LINK_LIBRARIES(${source} ${OpenCV_LIBS})
    ENDFOREACH(source)

    Jak łatwo zauważyć, dodawanie nowego programu do kompilacji nie wymaga wiele wysiłku. Wystarczy dodać nazwę pliku z kodem źródłowym w sekcji SOURCES i gotowe.

    Życzę przyjemnej kompilacji programów OpenCV :-)

    wtorek, 22 marca 2011

    Dostęp do składowych piksela w OpenCV 2.2

    2 komentarze
    Pełny kod źródłowy
    Wraz ze zmianą wersji biblioteki OpenCV z 2.1 na 2.2 doszło do wielu zmian nie tylko w zakresie organizacji modułów, ale również w zakresie sposobu programowania. Dokładnie nie śledziłem jakie zaszły zmiany, więc nie będę ich dokładnie opisywał, jednak zauważyłem, że został położony większy nacisk na programowanie obiektowe. Widoczne jest to szczególnie w strukturach przechowujących obraz. Od zawsze do tego celu korzystało się ze struktury IplImage, natomiast teraz zalecane jest używanie klasy cv::Mat, która reprezentuje wielowymiarowe macierze danych. Aby szczegółowo zapoznać się z konstrukcją tej klasy najlepiej zajrzeć do oficjalnej dokumentacji pod hasło Basic Structures → Mat.

    Pierwszym problemem podczas przejścia ze struktury IplImage do używania klasy Mat będzie zapewne to, jak odczytać i zmodyfikować zawartość poszczególnych składowych piksela. Do tej pory było kilka sposobów, żeby sobie z tym poradzić (polecam wpis na oficjalnym Wiki pod hasłem: How to access image pixels). Teraz podobnie, mamy kilka możliwości, aby dostać się do piksela, jednak dokumentacja opisuje to bardzo pobieżnie. W rozdziale Introduction → Fast Element Access zostały pokazane 3 różne sposoby operowania na pikselach obrazu, jednak dla mnie wydają się one kłopotliwe w użyciu. To właśnie było powodem przygotowania niniejszego poradnika, w którym chciałem przedstawić własne i znalezione w Internecie wygodne sposoby dostępu do składowych piksela.

    Obrazy w skali szarości

    Na początek, aby nabrać wprawy najlepiej zrobić proste testy z wykorzystaniem obrazów 1-kanałowych. W tym celu obrazy pozyskane z kamery można przekonwertować na obraz szary i na nim wykonywać dalsze operacje.

    Poniżej został pokazany fragment kodu, który zawiera 3 sposoby dostępu do piksela, począwszy od tego najbardziej tradycyjnego, gdzie operujemy bezpośrednio na tablicy z danymi (metoda 1), poprzez użycie funkcji at(), której przekazujemy współrzędne interesującego nas punktu (metoda 2), a skończywszy na zastosowaniu wskaźnika do każdego analizowanego wiersza obrazu (metoda 3). Z tych 3 metod podejście obiektowe jest reprezentowane przez sposób 2, podczas gdy pozostałe 2 przypadki działają bardziej tradycyjnie.

    Aby skupić się na tym co istotne, poniższy kod zawiera jedynie najważniejszą część programu, z deklaracją używanych struktur reprezentujących obraz, oraz środkiem pętli przetwarzającej obraz z kamery. Aby uruchomić ten program można skorzystać z szablonu, który podałem w poprzednim wpisie OpenCV - instalacja i pierwszy przykład w Ubuntu w sekcji Ten sam przykład po nowemu.

    Mat cam_frame, img_gray_v1, img_gray_v2, img_gray_v3;
    
    // LOOP FOR GRABBING IMAGE FROM WEBCAM 
    
    cap >> cam_frame;
    
    // Method 1: Old style method operating on data array
    cvtColor(cam_frame, img_gray_v1, CV_BGR2GRAY);
    
    for(int i = 0; i < img_gray_v1.rows; i++)
      for(int j = 0; j < img_gray_v1.cols; j++) {
        img_gray_v1.data[img_gray_v1.step*i + j] = 255 - img_gray_v1.data[img_gray_v1.step*i + j];
      }
    
    // Method 2: Assign pixel using .at() function
    cvtColor(cam_frame, img_gray_v2, CV_BGR2GRAY);
    
    for(int i = 0; i < img_gray_v2.rows; i++)
      for(int j = 0; j < img_gray_v2.cols; j++)
        img_gray_v2.at(i,j) = 255 - img_gray_v2.at(i,j);      
    
    // Method2: Use plain C operator []. More efficient than method 2 
    //    if you need to process a whole row of a 2d array
    cvtColor(cam_frame, img_gray_v3, CV_BGR2GRAY);
    
    for(int i = 0; i < img_gray_v3.rows; i++)
    {
      uchar* img_gray_v3_i = img_gray_v3.ptr(i);
      for(int j = 0; j < img_gray_v3.cols; j++)
        img_gray_v3_i[j] = 255 - img_gray_v3_i[j];
    }
    
    // END LOOP

    Obrazy wielokanałowe

    Większy problem aniżeli obrazy 1-kanałowe sprawiają obrazy kolorowe, domyślnie uzyskiwane z tradycyjnych kamer cyfrowych. Należy tutaj pamiętać nie tylko o współrzędnych punktu, który jest analizowany, ale również o jego składowej koloru. na szczęście jest na to kilka sposobów, które można prześledzić poniżej.

    Podejście tradycyjne

    Podobnie jak w przypadku obrazów szarych, dla obrazów wielokanałowych można również skorzystać z podejścia znanego z obróbki struktur IplImage i przeliczać wskaźnik do elementu, który nas interesuje. Przykład poniżej:

    Mat frame, result;
    
    // LOOP FOR GRABBING IMAGE FROM WEBCAM 
    
    cap >> frame;
    
    result = frame.clone();
    
    // invert the image
    for(i=0; i < frame.rows; i++) 
      for(j=0; j < frame.cols; j++) 
        for(k=0; k < channels; k++) {
    
          uchar* temp_ptr = &((uchar*)(result.data + result.step*i))[j*3];
          temp_ptr[0] = 255 - temp_ptr[0];
          temp_ptr[1] = 255 - temp_ptr[1];
          temp_ptr[2] = 255 - temp_ptr[2];    
        }
    
    // END LOOP

    Sposoby opisane w oficjalnej dokumentacji

    Tak jak wspomniałem na początku tego wpisu, w oficjalnej dokumentacji we wpisie Introduction → Fast Element Access pokazano 3 sposoby na to jak dostać się do składowych piksela. Dla mnie osobiście dziwne wydaje się rozbijanie obrazu wielokanałowego na obrazy zawierające jeden kanał i po wykonaniu obliczeń ponowne ich scalanie. Nie dość, że wydłuża to zapis algorytmu obróbki obrazu, to mam wrażenie, że powoduje dodatkowy narzut obliczeniowy. Kod z drobnymi poprawkami względem wersji oryginalnej znajduje się poniżej:

    Mat img_frame, img_resultA, img_resultB, img_resultC;
    vector planesA,planesB,planesC;
    
    // LOOP FOR GRABBING IMAGE FROM WEBCAM 
    
    cap >> img_frame;
        
    // Method 1. process Y plane using an iterator
    
    split(img_frame, planesA); // split the image into separate color planes
    
    MatIterator_ 
      it1     = planesA[0].begin(),
      it1_end = planesA[0].end(),
      it2     = planesA[1].begin(),
      it3     = planesA[2].begin();
    
    for(; it1 != it1_end; ++it1,++it2,++it3 )
    {
      *it1 = 255 - *it1;
      *it2 = 255 - *it2;
      *it3 = 255 - *it3;
    }
    
    merge(planesA, img_resultA);
    
    // Method 2. process the first chroma plane using pre-stored row pointer.
    
    split(img_frame, planesB);
    
    for( int y = 0; y < img_frame.rows; y++ )
    {
      uchar* Uptr1 = planesB[0].ptr(y);
      uchar* Uptr2 = planesB[1].ptr(y);
      uchar* Uptr3 = planesB[2].ptr(y);
      
      for( int x = 0; x < img_frame.cols; x++ )
      {
        Uptr1[x] = 255 - Uptr1[x];
        Uptr2[x] = 255 - Uptr2[x];
        Uptr3[x] = 255 - Uptr3[x];
      }
    }
    
    merge(planesB, img_resultB);
    
    // Method 3. process the second chroma plane using
    //           individual element access operations
    
    split(img_frame, planesC);
    
    for( int y = 0; y < img_frame.rows; y++ )
    {
      for( int x = 0; x < img_frame.cols; x++ )
      {
        uchar& Vxy1 = planesC[0].at(y, x);
        uchar& Vxy2 = planesC[1].at(y, x);
        uchar& Vxy3 = planesC[2].at(y, x);
        
        Vxy1 = 255 - Vxy1;
        Vxy2 = 255 - Vxy2;
        Vxy3 = 255 - Vxy3;        
      }
    }
    
    merge(planesC, img_resultC); 
    
    // END LOOP

    Ostatnia deska ratunku

    Powyżej opisane sposoby dla obrazów wielokanałowych nie wydają się nazbyt wygodne i raczej zniechęcają, a nie zachęcają do stosowania klasy Mat. Na szczęście światełkiem w tunelu jest klasa pochodna po cv::Mat o niemal identycznej nazwie: cv::Mat_. Klasa ta ma opracowane operatory dostępu do składowych piksela poprzez zastosowanie konstrukcji ( x , y ). Możemy w ten sposób uzyskać wskaźnik do konkretnego punktu obrazu, i dalej stosując zapis tablicowy [] możemy dostać się do konkretnej wartości danego kanału.

    Przykład 1: Operowanie na oryginalnym obrazie:

    Mat frame;
    
    // LOOP FOR GRABBING IMAGE FROM WEBCAM 
    
    cap >> frame;
    
    Mat_& frame_ = (Mat_&)frame;
    
    for(int i = 0; i < frame_.rows; i++)
      for(int j = 0; j < frame_.cols; j++)
        for(int k = 0; k < 3; k++)
          frame_(i,j)[k] = 255 - frame_(i,j)[k];
    
    // END LOOP

    Przykład 2Operowanie na obrazie pomocniczym:

    Mat frame;
    Mat_ rgb;
    
    // LOOP FOR GRABBING IMAGE FROM WEBCAM 
    
    cap >> frame;
    
    cvtColor(frame, rgb, CV_BGR2RGB);
    
    for(int i = 0; i < rgb.rows; i++)
      for(int j = 0; j < rgb.cols; j++)
        for(int k = 0; k < 3; k++)
          rgb(i,j)[k] = 255 - rgb(i,j)[k];
    
    // END LOOP

    Podsumowując zebrane w tym poradniku sposoby dostępu do składowych piksela, najwygodniejsze wydaje się użycie operatora at() dla obrazów jednokanałowych,  natomiast dla obrazów wielokanałowych najprostszy i najbardziej czytelny zapis daje klasa cv::Mat_.

    sobota, 19 marca 2011

    OpenCV - instalacja i pierwszy przykład w Ubuntu

    6 komentarze
    Już od dłuższego czasu przymierzałem się, aby więcej czasu poświęcić systemom interaktywnym, a w szczególności problematyce analizy ruchu. Zagadnieniami wizualizacji i przetwarzania obrazów medycznych oczywiście nie przestaje się dalej zajmować, jednakże opisywanie tej tematyki na blogu nie dość, że było i jest bardzo wymagające czasowo, to niestety interesuje tylko bardzo małą grupę osób. Natomiast analiza ruchu ma wiele różnych zastosowań - nie tylko tych naukowych, ale również rozrywkowych. Podobnie z punktu widzenia nauki przetwarzania informacji obrazowej, dla wielu młodych osób ciekawsze wydaje się analizowanie obrazu z kamerki na żywo, zamiast analizy statycznych obrazów medycznych.

    Tym wpisem rozpoczynam serię poradników dotyczących właśnie analizy ruchu. Głównym narzędziem (biblioteką) jak będzie używana będzie OpenCV, o którym pisałem ponad 2 lata temu na tym blogu. Dzisiaj chciałbym opisać proces instalacji tej biblioteki w Ubuntu i pokazać prosty przykład z użyciem kamerki internetowej.

    Instalacja z repozytorium

    Aby zainstalować OpenCV z repozytorium wpisujemy następujące polecenie w okienku terminala:
    sudo apt-get install libcv-dev libcvaux-dev libhighgui-dev

    Patrząc na tą komendę można zauważyć, że instalowane są tylko pakiety programistyczne. A co z bibliotekami? Zainstalują się one oczywiście same jako zależności wyżej wymienionych pakietów. Takie podejście jest oczywiście bardziej uniwersalne w przypadku instalacji OpenCV, ponieważ biblioteki te mają dodaną końcówkę 2.1 (np. libcv2.1), a tak nie będzie zawsze. Dla przykładu na stronie projektu jest już dostępna wersja 2.2.

    Instalacja ze źródeł

    Tak jak wspomniałem powyżej, wersja, która znajduje się w repozytorium nie jest najświeższa i osobom, którym szczególnie zależy, aby pracować na ostatniej wersji tej biblioteki polecam samodzielną kompilację ze źródeł. Nie jest ona skomplikowana, dlatego również zostanie opisana w tym poradniku.

    Najnowszą stabilną wersję źródeł OpenCV można pobrać z portalu SourceForge, a dokładnie ze strony: http://sourceforge.net/projects/opencvlibrary/files/opencv-unix/. Można pobrać ją również bezpośrednio z repozytorium, aby mieć dostęp do aktualnie wprowadzanych funkcji. W tym celu wystarczy wpisać w konsoli następujące polecenie:
    svn co https://code.ros.org/svn/opencv/trunk

    Ja osobiście polecam wersję stabilną. Obecnie jest to wersja 2.2.

    Teraz kiedy mamy pobrane już źródła biblioteki OpenCV, możemy przystąpić do jej kompilacji. Wykonujemy w tym celu następujące kroki:
    • Utworzenie katalogu build i uruchomienie w nim graficznej nakładki na CMake:
      mkdir build
      cd build
      cmake-gui ../
    • Przy pierwszej kompilacji najlepiej nie ustawiać zbyt wiele opcji, aby mieć pewność że podstawowa wersja się skompiluje. Ja ustawiłem tylko typ kompilacji na „Release
      CMAKE_BUILD_TYPE                 Release
    • Uruchomienie kompilacji. Jeśli mamy wiecej niż jeden rdzeń warto z tego skorzystać stosując przełącznik j:
      make -j2
    • Jeśli wszystko dobrze się skompiluje można wówczas uruchomić kolejne przebiegi kompilacji z włączonymi następującymi opcjami:
      BUILD_EXAMPLES                   ON
      BUILD_NEW_PYTHON_SUPPORT         ON
      OPENCV_BUILD_3RDPARTY_LIBS       ON
      Oczywiście tych opcji jest dużo więcej, dlatego polecam samodzielne testowanie pozostałych, chociaż te o których napisałem powinny w zupełności wystarczyć.
    Jeśli pojawiły się jakieś niespodziewane błędy podczas kompilacji polecam zajrzeć na stronę:
    http://opencv.willowgarage.com/wiki/InstallGuide w celu znalezienia rozwiązania.

    Pierwsze testy

    Na początek warto przetestować przykłady załączone w katalogu samples. Po kompilacji odpowiadające im wersje binarne powinny znaleźć się w katalogu: build/bin/. Ja sprawdziłem wszystkie i od razu mogę podpowiedzieć, że z kamerką działają te z nich:
    • adaptiveskindetector
    • bgfg_codebook
    • bgfg_segm
    • camshiftdemo
    • fback
    • fback_c
    • laplace
    • lkdemo
    • motempl
    • segment_objects
    Pomimo tego, że nie są one szczególnie złożone, na początek warto zacząć od jeszcze prostszych przykładów.

    Wyświetlanie obrazu z kamery

    Jednym z pierwszych przykładów, które warto przeanalizować i uruchomić w OpenCV jest pobieranie obrazu z kamery i wyświetlenie go na ekranie monitora. Nie jest to oczywiście trudne, ale wymaga znajomości kilku elementarnych funkcji takich jak:
    • cvCaptureFromCAM - inicjalizacja kamery,
    • cvQueryFrame - pobranie pojedynczej klatki obrazu,
    • cvShowImage - wyświetlenie obrazka we wskazanym oknie.
    Cały przykład wygląda następująco:
    #include "opencv/highgui.h"
    
    int main() 
    {
      CvCapture *cam = cvCaptureFromCAM(-1);
      
      const char *window = "Example 1";
      cvNamedWindow(window, CV_WINDOW_AUTOSIZE);
      
      while (cvWaitKey(4) == -1) {
        IplImage *frame = cvQueryFrame(cam);
        cvShowImage(window, frame);
      }
      
      cvDestroyAllWindows();
      cvReleaseCapture(&cam);
      
      return 0;
    }

    Aby go skompilować wystarczy uruchomić:
    gcc camera1.cpp -o camera1 -lhighgui

    Ten sam przykład po nowemu

    Przykład przedstawiony powyżej działa zarówno w bieżącej wersji OpenCV jak i w wersjach poprzednich. Jednakże własnie od wersji bieżącej 2.2 biblioteka ta została uporządkowana i podzielona na moduły. Dodatkowo zostały wprowadzone nowe funkcje i został zaproponowany bardziej obiektowy model programowania. Wszystkie te zmiany mają za zadanie ułatwić programowanie, ale nie pozwalają już na kompilację nowych programów tam gdzie jest stara wersja biblioteki. Program po zmianach wg nowych zaleceń wygląda tak:

    #include <opencv2/highgui/highgui.hpp>
    #include <opencv2/imgproc/imgproc.hpp>
    
    using namespace cv;
    
    int main(int, char**)
    {
      VideoCapture cap(0);
      if( !cap.isOpened() ) return -1;
      
      Mat frame;
      
      const char *window = "Example 2";
      namedWindow(window, CV_WINDOW_AUTOSIZE);
      
      while (cvWaitKey(4) == -1) {
        cap >> frame;
        imshow(window, frame);
      }
      
      return 0;
    }

    Kompilacja tak napisanego programu w konsoli nie jest już taka trywialna jak poprzednio. W takiej sytuacji najlepiej będzie skorzystać z narzędzia CMake, które jest podstawą budowania całej biblioteki OpenCV. W katalogu ze źródłami omawianych aplikacji tworzymy nowy plik CMakeLists.txt. Powinien on zawierać taką treść:

    PROJECT(opencv_example)
    
    cmake_minimum_required(VERSION 2.8)
    
    FIND_PACKAGE( OpenCV REQUIRED )
    
    # Declare the target (an executable)
    ADD_EXECUTABLE(camera1 camera1.cpp)
    TARGET_LINK_LIBRARIES(camera1 ${OpenCV_LIBS})
    
    ADD_EXECUTABLE(camera2 camera2.cpp)
    TARGET_LINK_LIBRARIES(camera2 ${OpenCV_LIBS})

    Teraz już kompilacja sprowadza się do wpisania w konsoli:

    mkdir build
    cd build
    cmake-gui ../

    i określenia w parametrach CMake ścieżki do katalogu gdzie mamy zbudowany OpenCV. U mnie wygląda to tak:

    OpenCV_DIR   /home/rafal/installed/opencv/OpenCV-2.2.0/build

    Teraz już wystarczy wpisać make i oba programy zostaną skompilowane.

    W ten sposób udało nam się przygotować dwie aplikacje, które niby nie robią wiele, ale stanowią szkielet większości aplikacji interaktywnych w OpenCV.

    czwartek, 3 marca 2011

    Nie do końca udana wirtualizacja MeeGo w VirtualBoxie

    2 komentarze
    Na początek od razu wyjasnię o co chodzi w tytule, bo wiem, że może on nie zachęcać do czytania dalej. Przez ostatnie kilka dni, mając trochę wolnego czasu postanowiłem przetestować pełną wersję MeeGo Netbook w środowisku wirtualnym z którego zawsze korzystam, czyli w VirtualBoxie. Skłoniło mnie do tego nie tylko to, że wersja dostępna w emulatorze QEMU (jak zainstalować środowisko uruchomieniowe pisałem we wpisie MeeGo - pierwsze starcie) jest mocno okrojona, ale również to, że działa ona dosyć powoli. Miałem nadzieję, że w VirtualBoxie będzie inaczej, bo to w końcu uznana marka wśród narzędzi do wirtualizacji. Sama instalacja pomimo tego, że nie obyła się bez problemów zakończyła się sukcesem. Niestety problem z powolnym działaniem systemu i brakiem jego responsywności również tutaj był mocno widoczny. Próbowałem różnych sposobów i ostatecznie żaden nie pomógł. Nawet deweloperzy MeeGo sami twierdzą, że z VirtualBoxem mogą być problemy i w miarę możliwości zachęcają do instalacji systemu na kompatybilnych urządzeniach.

    Pomimo tego, że MeeGo w VirtualBoxie nie działa zachwycająco, to dla osób, które nie mają odpowiedniego sprzętu do dyspozycji lub nie chcą instalować kolejnego systemu jedynie w celach testowych na maszynie, na której pracują na co dzień, myślę, że zastosowanie VirtualBoxa może być dobrym rozwiązaniem. W tym wpisie nie będę szczegółowo rozpisywał się na jakie problemy napotkałem podczas instalacji MeeGo w VirtualBoxie, a jedynie przedstawię niezbędne kroki, które pozwolą na instalację i uruchomienie systemu.

    Pobranie obrazu instalacyjnego

    Płytę z obrazem instalacyjnym MeeGo Netbook pobieramy ze strony: http://meego.com/downloads/releases/netbook. Ponieważ VirtualBox nie widzi płyt img możemy bez żadnego problemu zmienić rozszerzenie pobranego pliku na iso.

    Utworzenie nowej maszyny wirtualnej

    Dalej należy utworzyć pustą maszynę wirtualną. W kreatorze VirtualBoxa, który do tego służy możemy podać następujące parametry:
    • nazwa: MeeGo Netbook v1.1
    • typ systemu: Linux/Fedora
    • pamięć: 512MB
    • dysk: dynamicznie rozszerzalny, 8GB
    Dodatkowo samodzielnie należy wejść do opcji i ustawić parametry jak poniżej:
    • SystemProcesorWłącz PAE/NX
    • EkranWideoWłącz akcelerację 3D
    • Nośnikikontroler IDEChoose a virtual CD disc file (wskazujemy na wcześniej pobrany plik z obrazem).
    Instalacja MeeGo NetBook

    Po uruchomieniu systemu wirtualnego powinno nam się pojawić okienko jak poniżej:

    Wybieramy na nim opcję "Instalation only". Po chwili czekania powinna się rozpocząć instalacja systemu. Wita nas ona takim obrazkiem:

    Na kolejnych ekranach wystarczy jedynie ustawić język i skonfigurować dysk. W przypadku pustego dysku wirtualnego nie jest to zbytnio skomplikowane. Potwierdzamy jedynie komunikaty, które się pojawią. O zakończeniu instalacji zostaniemy powiadomieni obrazem jak poniżej:
    Kliknięcie na przycisk Zamknij spowoduje restart maszyny wirtualnej. Warto ją w tym momencie w ogóle zatrzymać i odmontować z napędu CD płytę instalacyjną.

    Po ponownym uruchomieniu systemu powinniśmy zobaczyć kreator pierwszego uruchomienia. Pyta nas on o ustawienia klawiatury, strefę czasową i prosi o utworzenie użytkownika.

    Jeśli system teraz wystartuje to mamy szczęście :-) Ja tego szczęścia niestety nie miałem i zobaczyłem czarny ekran. Po ponownym restarcie komputera wyświetlił mi się tylko obraz tła.

    Problem przy pierwszym uruchomieniu

    Przy rozwiązywaniu problemów, które zaczęły się pojawiać bardzo pomocna okazała się ta strona na Wiki MeeGohttp://wiki.meego.com/MeeGo_1.0_Netbook_VirtualBox. Korzystając z porad tam zawartych wykonałem następujące czynności:
    • Resetujemy maszynę wirtualną i zanim zacznie startować system wciskamy Escape, a następnie Tab.
    • Pojawi nam się linijka z tekstem jak na obrazku poniżej, którą możemy wyedytować. Kasujemy tutaj słowo 'quiet' i dodajemy literkę 's'.
    • Klikamy Enter i czekamy. Po chwili powinniśmy zobaczyć wiersz poleceń ze znaczkiem zachęty symbolizującym tryb administracyjny. Skoro już tutaj jesteśmy warto ustawić hasło roota (polecenie 'passwd') i zainstalować 2 przydatne narzędzia (wget i nano), które mogą okazać się niezbędne na kolejnych etapach konfiguracji.
      zypper install wget nano
    • Następnie w konsoli wpisujemy następujące polecenie:
      chmod +s /usr/bin/Xorg
    • i restartujemy system:
      reboot
    Teraz system powinien wystartować, ale wcale nie ma takiej pewności :-) U mnie na jednym komputerze opisane podejście zadziałało, a na innym nie przyniosło żadnego rezultatu.

    Jeśli system ciągle nie startuje warto sprawdzić, czy nie występuje problem jak ten opisany na forum MeeGo w wątku: INIT: Id "x" respawning too fast: disabled. U mnie właśnie tak było i polecam sprawdzić opisane tam rozwiązanie.

    Jeśli będziemy mieć szczęście (a na pewno w końcu się uda ;) ) to system powinien  wystartować i przywitać nas ekranem jak ten poniżej:

    Próba włączenia wsparcia OpenGL

    Niestety, tak jak wspomniałem na początku, MeeGo w podstawowej konfiguracji w Virtualboxie nie zachwyca szybkością - chodzi tak samo ociężale jak wersja dostępna na QEMU. Na Wiki MeeGo jest rozwiązanie tego problemu, ale przyznam szczerze, że u mnie nie działa. Testowałem to na kilka sposobów i albo nie było żadnego efektu, albo system nie mógł wystartować. Opiszę jednak kroki, które wykonałem, bo może u kogoś zadziała lub po prostu ktoś będzie w stanie podpowiedzieć mi co źle robiłem.

    Po kolei co należy zrobić, aby spróbować uruchomić akcelerację OpenGl w MeeGo zainstalowanego w VirtualBox (w skrócie):
    • Uruchamiamy okienko terminala: zakładka Aplikacje → Narzędzia systemowe.
    • Instalujemy następujące pakiety i restartujemy system:
      sudo zypper install gcc make kernel-netbook-devel patch nano wget yum yum-utils 
      sudo reboot
    • Instalujemy dodatki VirtualBoxa. Przed wykonaniem poniższych czynności najpierw należy wybrać w opcjach maszyny: Urządzenia → Zainstaluj dodatki.
      sudo mount /dev/sr0 /mnt 
      cd /mnt 
      sudo ./VBoxLinuxAdditions-x86.run
      sudo reboot
    • Tworzymy katalog tymczasowy i próbujemy pobrać źródła Cluttera z repozytorium:
      cd ~
      mkdir tmp
      cd tmp
      yumdownloader --source clutter
    • Niestety u mnie ta próba kończy się niepowodzeniem. Dostaję komunikat podobny do tego:
      Error: Cannot retrieve repository metadata (repomd.xml) for repository: meego-core. Please verify its path and try again 
    • Nie pozostaje nam zatem nic innego jak samodzielne pobranie odpowiedniego archiwum ze źródłami
      wget http://repo.meego.com/MeeGo/releases/1.1/core/repos/source/clutter-1.2.8-2.131.src.rpm
      rpm2cpio clutter-1.2.6-2.2.src.rpm|cpio -id
      tar jvxf clutter-1.2.6.tar.bz2
    • Następnie pobieramy zależności do zbudowania Cluttera. Można to zrobić instalując pakiet clutter-devel, co pozwoli na automatyczne dociągnięcie wszystkich zależnych pakietów:
      sudo zypper install clutter-devel
    • Ostatecznie uruchamiamy procedure budowania i instalacji Cluttera. Między czasie pobieramy i stosujemy dodatkowa łatkę z forum MeeGo.
      cd clutter-1.2.8
      wget http://wiki.meego.com/images/Clutter-xvisual-patch.txt
      patch -p1 < ./Clutter-xvisual-patch.txt
      ./configure --prefix=/usr
      make
      sudo make install
      reboot
    Po wykonaniu opisanych czynności restartujemy komputer. Należ się również upewnić, że jest włączona akceleracja 3D w ustawieniach VirtualBoxa.

    Prezentacja systemu

    Na koniec wpisu załączam małą galerię slajdów wykonanych nie tylko na kolejnych etapach instalacji i konfiguracji MeeGo w Virtualboxie, ale również slajdy prezentujące wygląd poszczególnych ekranów systemu.

    piątek, 25 lutego 2011

    Pierwsza mobilna aplikacja dla MeeGo

    0 komentarze
    Witam. Podobnie jak w przypadku poprzednich dwóch postów, dzisiaj również ostrzegam :-), że nie będzie nic odkrywczego względem oficjalnego Wiki dla programistów MeeGo. Pomimo tego, że sporo korzystam z materiałów tam zawartych, to moje wpisy nie są ich tłumaczeniami, ale raczej opisem własnych doświadczeń z testowania dostępnych tam poradników.

    Tym razem będzie o tym jak napisać, skompilować i uruchomić swoją pierwszą aplikacje mobilną dla MeeGo. Wpis ten jest mocno inspirowany następującą stroną na Wiki MeeGo:
    http://wiki.meego.com/SDK/Docs/1.1/Creating_Hello_World.

    Zakładam na początek, że mamy już zainstalowane środowisko wdrożeniowe i uruchomieniowe oraz, że została przeprowadzona wymagana konfiguracja Qt Creatora.

    W pierwszym momencie - zapewne jak się nie trudno domyśleć, należy uruchomić Qt Creatora. W następnym kroku przystępujemy do stworzenia nowego projektu wybierając odpowiednio w menu PlikNowy plik lub projektMobilna aplikacja Qt. Potwierdzamy klikając Wybierz… .

    Określamy następnie nazwę projektu i wybieramy jego docelową lokalizację.

    W kolejnym kroku wybieramy jako wersję Qt tą dostępną po instalacji MeeGo SDK.

    Możemy również zmienić domyślne nazwy plików, ale skoro ma to być tylko aplikacja testowa polecam pozostawienie tych zaproponowanych przez kreatora.

    Ostatecznie dostajemy ogólne podsumowanie nowo utworzonego projektu:

    Prawdopodobnie Qt Creator otworzy się w trybie projektowania interfejsu, dlatego przełączamy się do trybu Edycji kodu źródłowego. Posługując się nawigatorem projektu otwieramy plik main.cpp i zamieniamy jego zawartość na następującą:
    #include <qapplication>
    #include <qlabel>
    #include <qsysteminfo>
    using namespace QtMobility;
    
    int main(int argc, char *argv[])
    {
      QApplication app(argc, argv);
      QSystemInfo s;
      QLabel *label = new QLabel(QObject::tr("hello").
        append(s.currentCountryCode()));
      label->show();
      label->resize(100,30);
      return app.exec();
    }

    Teraz należy wyedytować plik z ustawieniami projektu *.pro dodając do niego następujące wpisy:
    CONFIG += mobility
    MOBILITY += systeminfo

    Aby zapisać wszystkie wprowadzone zmiany najlepiej skorzystać z menu PlikZachowaj wszystko.

    Przed uruchomieniem przygotowanej aplikacji należy najpierw przejść do zakładki Projekty i tam na zakładce Produkty docelowe wybrać MeeGo i Uruchom.

    Tutaj należy określić jedynie Device configuration, gdzie powinniśmy wskazać na skonfigurowane wcześniej środowisko uruchomieniowe (jak to zrobić pisałem we wpisie "MeeGo SDK - Konfiguracja Qt Creator", w sekcji "Dostęp do środowiska uruchomieniowego MeeGo"). U mnie to jest akurat: MeeGo NetBook Emulator. Oczywiście należy pamiętać o tym, aby wystartować wybrane środowisko uruchomieniowe MeeGo. Możemy to zrobić wpisując w konsoli polecenie tego typu:
    sudo mad remote -r meego-netbook-ia32-qemu-1.1.2-runtime poweron

    Po tym wszystkim możemy uruchomić projekt klikając na zielony "trójkącik" symbolizujący start aplikacji. Prawdopodobnie w konsoli Qt Creatora zobaczymy komunikat podobny do tego:
    Cleaning up remote leftovers first ...
    Initial cleanup done.
    Files to deploy: /home/rafal/qthello-build-meego/rrpmbuild/qthello-0.0.1-1.i586.rpm.
    Deployment finished.
    Starting remote application.
    access control disabled, clients can connect from any host
    Could not find Hal
    Could not find Hal
    Teoretycznie teraz powinniśmy zobaczyć w testowym środowisku MeeGo wystartowaną aplikację. Ja niestety na pierwszy rzut oka nie dostrzegłem żadnych zmian. Po kilku kliknięciach w interfejs zauważyłem, że aplikacja jest jednak uruchomiona, ale znajduje się "w tle". Aby zobaczyć ją na pełnym ekranie wystarczy kliknąć na pierwszą zakładkę - tą z domkiem, symbolizującą strefę MyZones (widać to na rysunku poniżej).

    Teraz już powinniśmy zobaczyć naszą pierwszą mobilną aplikację dla MeeGo :-D :

    Czy miałem podczas wykonywania tych kroków jakieś błędy? Tak, był jeden, ale był on związany z tym, że próbowałem uruchomić aplikację w Qt Creatorze nie mając wcześniej wystartowanego środowiska testowego. Dostałem wówczas taki komunikat:
    Cleaning up remote leftovers first ...
    Error running initial cleanup: Nie można połączyć się z hostem.
    Jeśli będą pojawiać się inne komunikaty i inne błędy, polecam najpierw upewnić się, że Qt Creator poprawnie łączy się z środowiskiem uruchomieniowym MeeGo. Można to sprawdzić przechodząc odpowiednio do NarzędziaOpcjeProjektyMeeGo Device Configuration i tam wybierając z listy wcześniej przygotowaną konfigurację możemy skorzystać z opcji Test. Jeśli tutaj wszystko pójdzie OK, nie powinno być problemów z wystartowaniem aplikacji.

    Mamy już pierwszą aplikację. Co dalej? Czekam na sugestie czytelników o czym chcieli by poczytać :-).