Sunday, June 27, 2010

[AVR] Line Follower - An application of PID controller

3pi Line Follower
Line Follower是許多基本款的robot都會有的功能之一, 當然3pi也不例外Line Follower, 指的是robot可以延著我們所設定規劃好的路線去行進, 如左圖, 3pi將會在白底背景下, 延著黑線去行進。 它靠著line sensors去讀取黑線與白底間的比例, 來判斷左轉角度、右轉角度或是直接前進, 當然,很多的方法可以用來判斷左右轉的角度, 本篇文章的重點主要要比較「PID control-based Algorithm」以及Heuristic-based Algorithm兩種方法在line follower實驗下的差別




Heuristic-based Algorithm
  • 直接看Line Follower在程式核心部份所使用的heuristic演算法

// This is the "main loop" - it will run forever.
 while(1)
 {
  // Get the position of the line.  Note that we *must* provide
  // the "sensors" argument to read_line() here, even though we
  // are not interested in the individual sensor readings.
  unsigned int position = read_line(sensors,IR_EMITTERS_ON);

  if(position < 1000)
  {
   // We are far to the right of the line: turn left.

   // Set the right motor to 100 and the left motor to zero,
   // to do a sharp turn to the left.  Note that the maximum
   // value of either motor speed is 255, so we are driving
   // it at just about 40% of the max.
   set_motors(0,100);

   // Just for fun, indicate the direction we are turning on
   // the LEDs.
   left_led(1);
   right_led(0);
  }
  else if(position < 3000)
  {
   // We are somewhat close to being centered on the line:
   // drive straight.
   set_motors(100,100);
   left_led(1);
   right_led(1);
  }
  else
  {
   // We are far to the left of the line: turn right.
   set_motors(100,0);
   left_led(0);
   right_led(1);
  }
 }


  • 我們發現, heuristic演算法靠著sensor所回傳的黑白比例, 來做為左右轉或直走的依據; 並且, 轉彎時始終保持單一速度


PID control-based Algorithm
PID分別為proportional–integral–derivative的縮寫, 它被廣泛應用在溫度、流量或是電機傳動等等的控制上, 其理論基礎這篇文章沒辦法細說, 但概念上我們可以透過下面的說明來瞭解 首先Line Follower這個例子來講, 底下我們分別說明proportionalintegralderivative:
  • Proportional:比例, 經由sensor所傳回量化後的值, 我們可以算出3pi位於白底或黑線上的比例為何, 這就是P
  • Integral: 積分, I值紀錄了3pi的活動軌跡, 也就是在一段運行時間中, 其P」值的加總
  • Derivative: 微分, 「D」值紀錄了前後P」值的
當我們分別得到了PI、D, 且分別加上一些weighting (magic number)[註一], 最後所得到的值, 代表的是3pi的左右兩個motor在setting值上的, 而物理意義代表的是3pi轉彎的角度大小


[註一]: 目前weighting值是turing得到的(所以稱為magic number), 但是, 有許多的研究針對如何turing」提出很多新的方法, 底下截圖來自wiki [1]上針對不同方法的優缺點所做的整理, 有興趣的話倒是可以試試這些方法



  • PID Algorithm的Source Code
// This is the "main loop" - it will run forever.
 while(1)
 {
  // Get the position of the line.  Note that we *must* provide
  // the "sensors" argument to read_line() here, even though we
  // are not interested in the individual sensor readings.
  unsigned int position = read_line(sensors,IR_EMITTERS_ON);

  // The "proportional" term should be 0 when we are on the line.
  int proportional = ((int)position) - 2000;

  // Compute the derivative (change) and integral (sum) of the
  // position.
  int derivative = proportional - last_proportional;
  integral += proportional;

  // Remember the last position.
  last_proportional = proportional;

  // Compute the difference between the two motor power settings,
  // m1 - m2.  If this is a positive number the robot will turn
  // to the right.  If it is a negative number, the robot will
  // turn to the left, and the magnitude of the number determines
  // the sharpness of the turn.
  int power_difference = proportional/20 + integral/10000 + derivative*3/2;

  // Compute the actual motor settings.  We never set either motor
  // to a negative value.
  const int max = 60;
  if(power_difference > max)
   power_difference = max;
  if(power_difference < -max)
   power_difference = -max;

  if(power_difference < 0)
   set_motors(max+power_difference, max);
  else
   set_motors(max, max-power_difference);
 }
  • PID short summary


    PID control uses continuous functions to compute the motor speeds, so that the jerkiness of the previous example can be replaced by a smooth response. [2]
    • 上面這句話是節錄自3pi的使用文件中, 這句話完全說明了整個PID control的精神, 以及使用後所帶來的好處
    • 如果翻成中文, 再加上自己的一些解釋之後, 可以這麼說的: 在Heuristic Algorithm下運作的3pi流暢度低於PID control Algorithm, 因為PID control Algorithm使用了連續函式, 物理意義上可以說它考慮了前後軌跡, 來做為下一個行進路線的參考, 相較於Heuristic Algorithm忽略前面一個軌跡, 3pi在使用PID control Algorithm的行進當然會比較順!


Experiments
  • 實驗目標及方向很明確, 我們想要證明: 3pi在使用PID control-based Algorithm時會比使Heuristic Algorithm走得順, 沒錯, 就是要證明一個字 順!
  • 底下我們直接展示兩種不同演算法在3pi上的成果, 相信大家都看得出來哪一個比較smooth
  • Heuristic Algorithm
  •  
     
  • PID control-based Algorithm
     




Conclusion
Line Follower, 常被拿來當成機器人競賽的項目, 而賽車比賽中的勝負常常在轉彎的時候來決定, 從本篇文章實驗的部份就可發現不同的方法影響了轉彎時不同的流暢度, 它靠的是PID control-based AlgorithmHeuristic-based Algorithm多考慮了歷史的軌跡。然而, PID control-based Algorithm畢竟採用的是magic number, 如何讓這些參數變得更有意義將會是未來研究上的議題


「Reference
  1. http://en.wikipedia.org/wiki/PID_controller
  2. http://www.pololu.com/docs/0J21/7

Monday, June 21, 2010

How to Install OpenCV on Linux (Ubuntu)

前言
對許多人來講, 在Windows下安裝OpenCV會比在Linux下安裝相對來得輕鬆Windows下, 沒錯, 您幾乎只需要重複「下一步」的動作就可以安裝大部分的程式, 但其實在Linux下亦不難, 或許只是您不熟悉如何去下指令? 沒關係, 這篇文章簡單整理了在Ubuntu底下安裝OpenCV的兩種方法其一, 是當您想更改OpenCV的source code並自己重新編譯的方法;  另外, 則是一般的apt-get安裝方式。底下我將一步步來介紹整個流程

Before the installation」
  • 為了可以重新編譯OpenCV, 系統中必須要有C++ compiler, 如果沒有, 我們可以透過以下指令來安裝

    $ sudo apt-get install g++
    
  • 為了圖片的播放, 我們需要GTK+, 它是一個跨平台的圖形介面函式庫, GNOME即是利用GTK+來開發的。首先, 我們可以藉由以下命令來查看系統安裝了哪個版本的GTK+

    $ dpkg -l | grep libgtk
    or, 可以直接安裝

    $ sudo apt-get install libgtk2.0-dev


「Install OpenCV on Ubuntu (method 1)」
  1. http://sourceforge.net/projects/opencvlibrary/files/下載原始碼(Linux版
  2. 解壓該原始碼檔案, 且切換至該目錄假設下載的是opencv-1.0.0.tar.gz, 則我們可以使用下列命令

    tar zxvf opencv-1.0.0.tar.gz
    cd opencv-1.0.0
  3. 檢查系統所需環境是否已經安裝完畢
  4. ./configure
    當出現「Now run make...」則表示可以開始編譯了, 否則, 必須安裝所需安裝的套件
  5. 編譯OpenCV
  6. make
  7. 使用root權限來安裝OpenCV
  8. sudo make install
  9. 增加路徑/usr/local/lib」到/etc/ld.so.conf」中, 這一步主要是在設定動態函式庫的路徑,  其中/etc/ld.so.conf就是目前系統中主要用來放置函式庫的目錄
  10. 設定完路徑後, 必須重建動態函式庫目錄
  11. ldconfig



「Install OpenCV on Ubuntu (method 2)
Ubuntu底下的Synaptic Package Manager方便了我們對於各種軟體的安裝, 並且隨時可以線上更新最新版本。對於利用Synaptic來安裝OpenCV, 我們只需收尋OpenCV且套用所收尋到的套件, 就可以直接安裝了, 安裝畫面如下


或者, 您也可以打開terminal輸入想安裝的套件, 如下面指令
$ sudo apt-get install libcv4 libcvaux4 libhighgui4
$ sudo apt-get install libcv-dev libcvaux-dev libhighgui-dev
當然, 指令會因函式庫的版本名稱不同而有所不一樣最後, 設定環境變數
export LD_LIBRARY_PATH=/home/opencv/lib
export PKG_CONFIG_PATH=/home/opencv/lib/pkgconfig

「執行OpenCV範例程式
底下, 我測試了OpenCV給的範例程式camshiftdemo.c,切換到範例所在目錄且利用以下命令編譯及執行
$ g++ -I/usr/include/opencv -lcxcore -lhighgui -lm camshiftdemo.c
$ ./a.out
執行畫面如下

Monday, April 12, 2010

[OpenCV] Face Detection (人臉偵測)


何謂OpenCV?
Open代表Open Source,CV為Computer Vision,Computer Vision被廣泛應用在計算機科學及醫學等領域,OpenCV是由Intel公司參與及開發出來的,感謝Intel的Open Source :)。OpenCV支援Windows跟Linux兩種平台,並具有強大的圖形跟矩陣的運算。然而,近幾年OpenCV為人垢病的是,它並不再更新,因此無法提供一些新的演算法。But,這個問題似乎得到了回應,最近OpenCV已經開始在改版了,加入了一些新的函數,像是近年來在電腦視覺領域上,極常被使用及提及的Scale-invariant feature transform(SIFT)

下載及安裝OpenCV
最新版的OpenCV可至以下網址下載:
http://sourceforge.net/projects/opencvlibrary/

Face Detection
There are numerous researches making efforts toward how to correctly detect faces in digital images. 而OpenCV所使用的方法是由Viola & Jones」所發表的AdaBoost Learning with Haar-Like Features來實現人臉偵測。下面就簡單介紹一下這個方法:
  1. 首先定義一些Haar-Like Features
  2. 再來,我們必須給定一些sample例如: 假如我們要偵測的是人臉,那麼我們就要輸入一些人臉的sample有了這些sample我們將會利用AdaBoost learning algorithm來挑出某幾個代表性的Haar-Like Features,以這個例子來講,我們可以想成這些被挑選出來的feature就是代表人臉的feature
  3. 每個被挑選出來的feature都代表一種classifier,許多種被挑選出來的feature因此構成了一連串的classifier,我們稱為strong classifier。而每個classifier皆用來判斷所輸入的圖片是否為人臉,並且回傳,最後,通過所有classifier的圖片將被判定是一張人臉
以上就是Viola與Jones所提出的Object Detection方法,除了人臉當然它還可以拿來用在其它的物件偵測,只要我們改變一開始輸入訓練用的sample型態即可。

「Source Code
OpenCV 有提供Face Detection的source code,不過它註解很少,並且有點難懂,所以我試著修改它,並加上一些註解

/*  
 * Face Detection in Digital Photographs  
 *  
 * We leverage the AdaBoost learning-based face detection method with Haar-like features to detect faces in photos.  
 * However, OpenCV supplies Haar Cascade classifier for object (face) detection.
 * 
 * (C) Matt Swanson & TrekLee 
 * http://seeyababy.blogspot.com/  
 *  
*/ 

// Path setting for OpenCV Library
#pragma comment(lib,"../OpenCV/lib/cv.lib")
#pragma comment(lib,"../OpenCV/lib/cxcore.lib")
#pragma comment(lib,"../OpenCV/lib/highgui.lib")

// Path setting for OpenCV Header files
#include "../OpenCV/include/cv.h"
#include "../OpenCV/include/cxcore.h"
#include "../OpenCV/include/highgui.h"
  
#include <stdio.h>
#include <iostream>

using namespace std;

// the minimum object size
int min_face_height    = 20;
int min_face_width    = 10;

//Face Detection
int runFaceDetection(IplImage* image_detect);

void main()
{    
    IplImage* pImg = cvLoadImage( "image.jpg", 1);    
    runFaceDetection(pImg);
    cvReleaseImage( &pImg ); 
}

int runFaceDetection(IplImage* image_detect)
{ 
    // Load the pre-trained Haar classifier data.
    CvHaarClassifierCascade* classifier = (CvHaarClassifierCascade*)cvLoad("haarcascade_frontalface_alt.xml", 0, 0, 0);
 
    // Quit the application if the input source or the classifier data failed to load properly.
    if( !classifier)
    {
        cerr << "ERROR: Could not load classifier cascade." << endl;
        return -1;
    }

    // Create a CvMemStorage object for use by the face detection function.
    CvMemStorage* facesMemStorage = cvCreateMemStorage(0);

    IplImage* tempFrame = cvCreateImage(cvSize(image_detect->width, 
        image_detect->height), IPL_DEPTH_8U, image_detect->nChannels);

    // Copy the current frame into the temporary image.  Also, make 
    // sure the images have the same orientation.
    if(image_detect->origin == IPL_ORIGIN_TL)
    {
        cvCopy(image_detect, tempFrame, 0);
    }
    else
    {
        cvFlip(image_detect, tempFrame, 0);
    }

    /* Perform face detection on the temporary image, adding a rectangle around the detected face. */

    // "Resets" the memory but does not deallocate it.
    cvClearMemStorage(facesMemStorage);
 
    // Run the main object recognition function.  The arguments are: 
    // 1. the image to use
    // 2. the pre-trained Haar classifier cascade data
    // 3. memory storage for rectangles around recognized objects
    // 4. a scale factor "by which the search window is scaled between the 
    //    subsequent scans, for example, 1.1 means increasing window by 10%"
    // 5. the "minimum number (minus 1) of neighbor rectangles that makes up 
    //    an object. All the groups of a smaller number of rectangles than 
    //    min_neighbors-1 are rejected. If min_neighbors is 0, the function 
    //    does not any grouping at all and returns all the detected candidate 
    //    rectangles, which may be useful if the user wants to apply a 
    //    customized grouping procedure."
    // 6. flags which determine the mode of operation
    // 7. the minimum object size (if possible, increasing this will 
    //    really speed up the process)
    CvSeq* faces = cvHaarDetectObjects(tempFrame, classifier, facesMemStorage, 1.1, 
        2, CV_HAAR_DO_CANNY_PRUNING, cvSize(min_face_width, min_face_height));

    // If any faces were detected, draw rectangles around them.
    if (faces)
    {    
        for(int i = 0; i < faces->total; ++i)
        {
            // Setup two points that define the extremes of the rectangle, 
            // then draw it to the image..
            CvPoint point1, point2;
            CvRect* rectangle = (CvRect*)cvGetSeqElem(faces, i);
            
            point1.x = rectangle->x;
            point2.x = rectangle->x + rectangle->width;
            point1.y = rectangle->y;
            point2.y = rectangle->y + rectangle->height;

            cvRectangle(tempFrame, point1, point2, CV_RGB(255,0,0), 3, 8, 0);
        }
    }
    
    // Show the result in the window.
    cvNamedWindow("Face Detection Result", 1);
    cvShowImage("Face Detection Result", tempFrame);
    cvWaitKey(0);
    cvDestroyWindow("Face Detection Result");

    // Clean up allocated OpenCV objects.
    cvReleaseMemStorage(&facesMemStorage);
    cvReleaseImage(&tempFrame);
    cvReleaseHaarClassifierCascade( &classifier );

    return 0;
}


「Experiment
接下來我挑張照片來做個簡單的實驗,下圖是做完人臉偵測,並框出系統判斷為人臉的結果



















我們可以發現接近右下角的地方有個誤判的情況發生,但不得不說的是,仔細看它真的長的很像一張邪惡的人臉...囧|||...,回歸正題,我們該如何解決這個誤判? 除了加入膚色判斷,或是讓face detector更準確之外,其實有個立即就可以針對這個問題來解決的方法,當我們觀察以下這個函式

 CvSeq* faces = cvHaarDetectObjects(tempFrame, classifier, facesMemStorage, 1.1, 
        2, CV_HAAR_DO_CANNY_PRUNING, cvSize(min_face_width, min_face_height));

其中cvSize(min_face_width, min_face_height)設定被偵測人臉的最小size,上面被誤判為人臉的圖片明顯小於其它正常的人臉,所以我們可以考慮將size放大,看看是否可以解決這個問題,因此我將min_face_heightmin_face_width由原本的size各加上10個pixel,重新偵測所得到的結果如下


















我們發現誤判果然被解決了



「Conclusion
本篇文章首先介紹了Viola與Jones所提出的利用AdaBoost Learning with Haar-Like Features來做人臉偵測的方法,並且提供了source code,這份程式改進了OpenCV所提供的source code其難讀(for me)、註解少的缺失,最後,做了一個簡單的實驗及討論


Coming soon...
接下來,我希望有機會及時間可以介紹人臉辨識(Face Recognition)的部份,也就是判斷某張人臉是哪個人,如此,加上Face Detection,便可以讓我們很方便、很快的從數以千或萬計的數位照片當中挑出某個人出現過的照片了