天天看點

OpenCV實戰5: LBP級聯分類器實作人臉檢測

OpenCV中的HAAR與LBP資料

   HAAR特征資料   參看 haarcascade_frontalface_alt.xml 各标簽

    LBP特征資料     參看 lbpcascade_frontalface.xml 各标簽

HAAR與LBP的差別:

    HAAR特征是浮點數計算

    LBP特征是整數計算

    LBP訓練需要的樣本數量要比HAAR大

    同樣的樣本空間, HAAR訓練出來的資料檢測結果要比LBP準确

    擴大LBP的樣本資料,訓練結果可以跟HAAR一樣

    LBP的速度一般可以比HAAR快幾倍

LBP特征的背景介紹

      LBP指局部二值模式,英文全稱:Local Binary Pattern,是一種用來描述圖像局部特征的算子,LBP特征具有灰階不變性和旋轉不變性等顯著優點。它是由T. Ojala, M.Pietikäinen, 和 D. Harwood在1994年提出,由于LBP特征計算簡單、效果較好,是以LBP特征在計算機視覺的許多領域都得到了廣泛的應用,LBP特征比較出名的應用是用在人臉識别和目标檢測中,LBP提取局部特征作為判别依據,LBP方法顯著的優點是對光照不敏感,但是依然沒有解決姿态和表情的問題。不過相比于特征臉方法,LBP的識别率已經有了很大的提升。在計算機視覺開源庫OpenCV中有使用LBP特征進行人臉識别的接口,也有用LBP特征訓練目标檢測分類器的方法,Opencv實作了LBP特征的計算,但沒有提供一個單獨的計算LBP特征的接口。

LBP特征的原理

1、原始LBP特征描述及計算方法

       原始的LBP算子定義在像素3*3的鄰域内,以鄰域中心像素為門檻值,相鄰的8個像素的灰階值與鄰域中心的像素值進行比較,若周圍像素大于中心像素值,則該像素點的位置被标記為1,否則為0。這樣,3*3鄰域内的8個點經過比較可産生8位二進制數,将這8位二進制數依次排列形成一個二進制數字,這個二進制數字就是中心像素的LBP值(通常轉換為十進制數即LBP碼,共256種)。中心像素的LBP值反映了該像素周圍區域的紋理資訊。 

計算LBP特征的圖像必須是灰階圖,如果是彩色圖,需要先轉換成灰階圖。 

上述過程用圖像表示為: 

OpenCV實戰5: LBP級聯分類器實作人臉檢測

用比較正式的公式來定義的話:

OpenCV實戰5: LBP級聯分類器實作人臉檢測

其中

OpenCV實戰5: LBP級聯分類器實作人臉檢測

代表3x3鄰域的中心元素,它的像素值為ic,ip代表鄰域内其他像素的值。s(x)是符号函數,定義如下:

OpenCV實戰5: LBP級聯分類器實作人臉檢測

原始LBP特征計算代碼(Opencv下):

//原始LBP特征計算
template <typename _tp>
void getOriginLBPFeature(InputArray _src,OutputArray _dst)
{
    Mat src = _src.getMat();
    _dst.create(src.rows-2,src.cols-2,CV_8UC1);
    Mat dst = _dst.getMat();
    dst.setTo(0);
    for(int i=1;i<src.rows-1;i++)
    {
        for(int j=1;j<src.cols-1;j++)
        {
            _tp center = src.at<_tp>(i,j);
            unsigned char lbpCode = 0;
            lbpCode |= (src.at<_tp>(i-1,j-1) > center) << 7;
            lbpCode |= (src.at<_tp>(i-1,j  ) > center) << 6;
            lbpCode |= (src.at<_tp>(i-1,j+1) > center) << 5;
            lbpCode |= (src.at<_tp>(i  ,j+1) > center) << 4;
            lbpCode |= (src.at<_tp>(i+1,j+1) > center) << 3;
            lbpCode |= (src.at<_tp>(i+1,j  ) > center) << 2;
            lbpCode |= (src.at<_tp>(i+1,j-1) > center) << 1;
            lbpCode |= (src.at<_tp>(i  ,j-1) > center) << 0;
            dst.at<uchar>(i-1,j-1) = lbpCode;
        }
    }
}
           

        LBP算子利用了周圍點與該點的關系對該點進行量化。量化後可以更有效地消除光照對圖像的影響。隻要光照的變化不足以改變兩個點像素值之間的大小關系,那麼LBP算子的值不會發生變化,是以一定程度上,基于LBP的識别算法解決了光照變化的問題,但是當圖像光照變化不均勻時,各像素間的大小關系被破壞,對應的LBP模式也就發生了變化。 

二、LBP特征的改進版本

在原始的LBP特征提出以後,研究人員對LBP特征進行了很多的改進,是以産生了許多LBP的改進版本。

2.1 圓形LBP特征(Circular LBP or Extended LBP)

       由于原始LBP特征使用的是固定鄰域内的灰階值,是以當圖像的尺度發生變化時,LBP特征的編碼将會發生錯誤,LBP特征将不能正确的反映像素點周圍的紋理資訊,是以研究人員對其進行了改進。基本的 LBP 算子的最大缺陷在于它隻覆寫了一個固定半徑範圍内的小區域,隻局限在3*3的鄰域内,對于較大圖像大尺度的結構不能很好的提取需要的紋理特征,是以研究者們對LBP算子進行了擴充。

       新的LBP算子LBP(P,R) 可以計算不同半徑鄰域大小和不同像素點數的特征值,其中P表示周圍像素點個數,R表示鄰域半徑,同時把原來的方形鄰域擴充到了圓形,下圖給出了四種擴充後的LBP例子,其中,R可以是小數,對于沒有落到整數位置的點,根據軌道内離其最近的兩個整數位置像素灰階值,利用雙線性內插補點的方法可以計算它的灰階值:

OpenCV實戰5: LBP級聯分類器實作人臉檢測
OpenCV實戰5: LBP級聯分類器實作人臉檢測

這種LBP特征叫做Extended LBP,也叫Circular LBP。使用可變半徑的圓對近鄰像素進行編碼,可以得到如下的近鄰:

OpenCV實戰5: LBP級聯分類器實作人臉檢測

通過LBP特征的定義可以看出,LBP特征對光照變化是魯棒的,其效果如下圖所示: 

OpenCV實戰5: LBP級聯分類器實作人臉檢測
//圓形LBP特征計算,效率優化版本,聲明時預設neighbors=8
template <typename _tp>
void getCircularLBPFeatureOptimization(InputArray _src,OutputArray _dst,int radius,int neighbors)
{
    Mat src = _src.getMat();
    //LBP特征圖像的行數和列數的計算要準确
    _dst.create(src.rows-2*radius,src.cols-2*radius,CV_8UC1);
    Mat dst = _dst.getMat();
    dst.setTo(0);
    for(int k=0;k<neighbors;k++)
    {
        //計算采樣點對于中心點坐标的偏移量rx,ry
        float rx = static_cast<float>(radius * cos(2.0 * CV_PI * k / neighbors));
        float ry = -static_cast<float>(radius * sin(2.0 * CV_PI * k / neighbors));
        //為雙線性插值做準備
        //對采樣點偏移量分别進行上下取整
        int x1 = static_cast<int>(floor(rx));
        int x2 = static_cast<int>(ceil(rx));
        int y1 = static_cast<int>(floor(ry));
        int y2 = static_cast<int>(ceil(ry));
        //将坐标偏移量映射到0-1之間
        float tx = rx - x1;
        float ty = ry - y1;
        //根據0-1之間的x,y的權重計算公式計算權重,權重與坐标具體位置無關,與坐标間的內插補點有關
        float w1 = (1-tx) * (1-ty);
        float w2 =    tx  * (1-ty);
        float w3 = (1-tx) *    ty;
        float w4 =    tx  *    ty;
        //循環處理每個像素
        for(int i=radius;i<src.rows-radius;i++)
        {
            for(int j=radius;j<src.cols-radius;j++)
            {
                //獲得中心像素點的灰階值
                _tp center = src.at<_tp>(i,j);
                //根據雙線性插值公式計算第k個采樣點的灰階值
                float neighbor = src.at<_tp>(i+x1,j+y1) * w1 + src.at<_tp>(i+x1,j+y2) *w2 \
                    + src.at<_tp>(i+x2,j+y1) * w3 +src.at<_tp>(i+x2,j+y2) *w4;
                //LBP特征圖像的每個鄰居的LBP值累加,累加通過與操作完成,對應的LBP值通過移位取得
                dst.at<uchar>(i-radius,j-radius) |= (neighbor>center) <<(neighbors-k-1);
            }
        }
    }
}
           

2.2 旋轉不變LBP特征

   從上面可以看出,上面的LBP特征具有灰階不變性,但還不具備旋轉不變性,是以研究人員又在上面的基礎上進行了擴充,提出了具有旋轉不變性的LBP特征。首先不斷的旋轉圓形鄰域内的LBP特征,根據選擇得到一系列的LBP特征值,從這些LBP特征值選擇LBP特征值最小的作為中心像素點的LBP特征。具體做法如下圖所示:

OpenCV實戰5: LBP級聯分類器實作人臉檢測

  假設一開始得到的LBP特征為10010000,那麼将這個二進制特征,按照順時針方向旋轉,可以轉化為00001001的形式,這樣得到的LBP值是最小的。無論圖像怎麼旋轉,對點提取的二進制特征的最小值是不變的,用最小值作為提取的LBP特征,這樣LBP就是旋轉不變的了。當P=8時,能産生的不同的二進制特征數量是2^8個,經過上述表示,就變為36個。(我以為應當是2^8/8=32個)

//旋轉不變圓形LBP特征計算,聲明時預設neighbors=8
template <typename _tp>
void getRotationInvariantLBPFeature(InputArray _src,OutputArray _dst,int radius,int neighbors)
{
    Mat src = _src.getMat();
    //LBP特征圖像的行數和列數的計算要準确
    _dst.create(src.rows-2*radius,src.cols-2*radius,CV_8UC1);
    Mat dst = _dst.getMat();
    dst.setTo(0);
    for(int k=0;k<neighbors;k++)
    {
        //計算采樣點對于中心點坐标的偏移量rx,ry
        float rx = static_cast<float>(radius * cos(2.0 * CV_PI * k / neighbors));
        float ry = -static_cast<float>(radius * sin(2.0 * CV_PI * k / neighbors));
        //為雙線性插值做準備
        //對采樣點偏移量分别進行上下取整
        int x1 = static_cast<int>(floor(rx));
        int x2 = static_cast<int>(ceil(rx));
        int y1 = static_cast<int>(floor(ry));
        int y2 = static_cast<int>(ceil(ry));
        //将坐标偏移量映射到0-1之間
        float tx = rx - x1;
        float ty = ry - y1;
        //根據0-1之間的x,y的權重計算公式計算權重,權重與坐标具體位置無關,與坐标間的內插補點有關
        float w1 = (1-tx) * (1-ty);
        float w2 =    tx  * (1-ty);
        float w3 = (1-tx) *    ty;
        float w4 =    tx  *    ty;
        //循環處理每個像素
        for(int i=radius;i<src.rows-radius;i++)
        {
            for(int j=radius;j<src.cols-radius;j++)
            {
                //獲得中心像素點的灰階值
                _tp center = src.at<_tp>(i,j);
                //根據雙線性插值公式計算第k個采樣點的灰階值
                float neighbor = src.at<_tp>(i+x1,j+y1) * w1 + src.at<_tp>(i+x1,j+y2) *w2 \
                    + src.at<_tp>(i+x2,j+y1) * w3 +src.at<_tp>(i+x2,j+y2) *w4;
                //LBP特征圖像的每個鄰居的LBP值累加,累加通過與操作完成,對應的LBP值通過移位取得
                dst.at<uchar>(i-radius,j-radius) |= (neighbor>center) <<(neighbors-k-1);
            }
        }
    }
    //進行旋轉不變處理
    for(int i=0;i<dst.rows;i++)
    {
        for(int j=0;j<dst.cols;j++)
        {
            unsigned char currentValue = dst.at<uchar>(i,j);
            unsigned char minValue = currentValue;
            for(int k=1;k<neighbors;k++)
            {
    //循環左移
                unsigned char temp = (currentValue>>(neighbors-k)) | (currentValue<<k);
                if(temp < minValue)
                {
                    minValue = temp;
                }
            }
            dst.at<uchar>(i,j) = minValue;
        }
    }
}
           

2.3 Uniform Pattern LBP特征

      Uniform Pattern,也被稱為等價模式或均勻模式,由于一個LBP特征有多種不同的二進制形式,對于半徑為R的圓形區域内含有P個采樣點的LBP算子将會産生2^P種模式。很顯然,随着鄰域集内采樣點數的增加,二進制模式的種類是以指數形式增加的。例如:5×5鄰域内20個采樣點,有2^20=1,048,576種二進制模式。這麼多的二進制模式不利于紋理的提取、分類、識别及存取。例如,将LBP算子用于紋理分類或人臉識别時,常采用LBP模式的統計直方圖來表達圖像的資訊,而較多的模式種類将使得資料量過大,且直方圖過于稀疏。是以,需要對原始的LBP模式進行降維,使得資料量減少的情況下能最好的表示圖像的資訊。

        為了解決二進制模式過多的問題,提高統計性,Ojala提出了采用一種“等價模式”(Uniform Pattern)來對LBP算子的模式種類進行降維。Ojala等認為,在實際圖像中,絕大多數LBP模式最多隻包含兩次從1到0或從0到1的跳變。是以,Ojala将“等價模式”定義為:當某個LBP所對應的循環二進制數從0到1或從1到0最多有兩次跳變時,該LBP所對應的二進制就稱為一個等價模式類。如00000000(0次跳變),00000111(隻含一次從0到1的跳變),10001111(先由1跳到0,再由0跳到1,共兩次跳變)都是等價模式類。除等價模式類以外的模式都歸為另一類,稱為混合模式類,例如10010111(共四次跳變)。

OpenCV實戰5: LBP級聯分類器實作人臉檢測

      通過這樣的改進,二進制模式的種類大大減少,而不會丢失任何資訊。模式數量由原來的2^P種減少為 P ( P-1)+2種,其中P表示鄰域集内的采樣點數。對于3×3鄰域内8個采樣點來說,二進制模式由原始的256種減少為58種,即:它把值分為59類,58個uniform pattern為一類,其它的所有值為第59類。這樣直方圖從原來的256維變成59維。這使得特征向量的維數更少,并且可以減少高頻噪聲帶來的影響。

       具體實作:采樣點數目為8個,即LBP特征值有2^8種,共256個值,正好對應灰階圖像的0-255,是以原始的LBP特征圖像是一幅正常的灰階圖像,而等價模式LBP特征,根據0-1跳變次數,将這256個LBP特征值分為了59類,從跳變次數上劃分:跳變0次—2個,跳變1次—0個,跳變2次—56個,跳變3次—0個,跳變4次—140個,跳變5次—0個,跳變6次—56個,跳變7次—0個,跳變8次—2個。共9種跳變情況,将這256個值進行配置設定,跳變小于2次的為等價模式類,共58個,他們對應的值按照從小到大分别編碼為1—58,即它們在LBP特征圖像中的灰階值為1—58,而除了等價模式類之外的混合模式類被編碼為0,即它們在LBP特征中的灰階值為0,是以等價模式LBP特征圖像整體偏暗。

//等價模式LBP特征計算
template <typename _tp>
void getUniformPatternLBPFeature(InputArray _src,OutputArray _dst,int radius,int neighbors)
{
    Mat src = _src.getMat();
    //LBP特征圖像的行數和列數的計算要準确
    _dst.create(src.rows-2*radius,src.cols-2*radius,CV_8UC1);
    Mat dst = _dst.getMat();
    dst.setTo(0);
    //LBP特征值對應圖像灰階編碼表,直接預設采樣點為8位
    uchar temp = 1;
    uchar table[256] = {0};
    for(int i=0;i<256;i++)
    {
        if(getHopTimes(i)<3)
        {
            table[i] = temp;
            temp++;
        }
    }
    //是否進行UniformPattern編碼的标志
    bool flag = false;
    //計算LBP特征圖
    for(int k=0;k<neighbors;k++)
    {
        if(k==neighbors-1)
        {
            flag = true;
        }
        //計算采樣點對于中心點坐标的偏移量rx,ry
        float rx = static_cast<float>(radius * cos(2.0 * CV_PI * k / neighbors));
        float ry = -static_cast<float>(radius * sin(2.0 * CV_PI * k / neighbors));
        //為雙線性插值做準備
        //對采樣點偏移量分别進行上下取整
        int x1 = static_cast<int>(floor(rx));
        int x2 = static_cast<int>(ceil(rx));
        int y1 = static_cast<int>(floor(ry));
        int y2 = static_cast<int>(ceil(ry));
        //将坐标偏移量映射到0-1之間
        float tx = rx - x1;
        float ty = ry - y1;
        //根據0-1之間的x,y的權重計算公式計算權重,權重與坐标具體位置無關,與坐标間的內插補點有關
        float w1 = (1-tx) * (1-ty);
        float w2 =    tx  * (1-ty);
        float w3 = (1-tx) *    ty;
        float w4 =    tx  *    ty;
        //循環處理每個像素
        for(int i=radius;i<src.rows-radius;i++)
        {
            for(int j=radius;j<src.cols-radius;j++)
            {
                //獲得中心像素點的灰階值
                _tp center = src.at<_tp>(i,j);
                //根據雙線性插值公式計算第k個采樣點的灰階值
                float neighbor = src.at<_tp>(i+x1,j+y1) * w1 + src.at<_tp>(i+x1,j+y2) *w2 \
                    + src.at<_tp>(i+x2,j+y1) * w3 +src.at<_tp>(i+x2,j+y2) *w4;
                //LBP特征圖像的每個鄰居的LBP值累加,累加通過與操作完成,對應的LBP值通過移位取得
                dst.at<uchar>(i-radius,j-radius) |= (neighbor>center) <<(neighbors-k-1);
                //進行LBP特征的UniformPattern編碼
                if(flag)
                {
                    dst.at<uchar>(i-radius,j-radius) = table[dst.at<uchar>(i-radius,j-radius)];
                }
            }
        }
    }
}
//計算跳變次數
int getHopTimes(int n)
{
    int count = 0;
    bitset<8> binaryCode = n;
    for(int i=0;i<8;i++)
    {
        if(binaryCode[i] != binaryCode[(i+1)%8])
        {
            count++;
        }
    }
    return count;
}
           

4、LBP特征用于檢測的原理

        顯而易見的是,上述提取的LBP算子在每個像素點都可以得到一個LBP“編碼”,那麼,對一幅圖像(每個像素點的灰階值)提取其原始的LBP算子之後,得到的原始LBP特征依然是“一幅圖檔”(每個像素點的LBP值)。

OpenCV實戰5: LBP級聯分類器實作人臉檢測

        LBP的應用中,如紋理分類、人臉分析等,一般都不将LBP圖譜作為特征向量用于分類識别,而是采用LBP特征譜的統計直方圖作為特征向量用于分類識别。

       因為,從上面的分析我們可以看出,這個“特征”跟位置資訊是緊密相關的。直接對兩幅圖檔提取這種“特征”,并進行判别分析的話,會因為“位置沒有對準”而産生很大的誤差。後來,研究人員發現,可以将一幅圖檔劃分為若幹的子區域,對每個子區域内的每個像素點都提取LBP特征,然後,在每個子區域内建立LBP特征的統計直方圖。如此一來,每個子區域,就可以用一個統計直方圖來進行描述;整個圖檔就由若幹個統計直方圖組成;

        例如:一幅100*100像素大小的圖檔,劃分為10*10=100個子區域(可以通過多種方式來劃分區域),每個子區域的大小為10*10像素;在每個子區域内的每個像素點,提取其LBP特征,然後,建立統計直方圖;這樣,這幅圖檔就有10*10個子區域,也就有了10*10個統計直方圖,利用這10*10個統計直方圖,就可以描述這幅圖檔了。之後,我們利用各種相似性度量函數,就可以判斷兩幅圖像之間的相似性了,LBP即可以用于人臉檢測,也可用于人臉識别。

對LBP特征向量進行提取的步驟:

(1)首先将檢測視窗劃分為16×16的小區域(cell);

(2)對于每個cell中的一個像素,将相鄰的8個像素的灰階值與其進行比較,若周圍像素值大于中心像素值,則該像素點的位置被标記為1,否則為0。這樣,3*3鄰域内的8個點經比較可産生8位二進制數,即得到該視窗中心像素點的LBP值;

(3)然後計算每個cell的直方圖,即每個數字(假定是十進制數LBP值)出現的頻率;然後對該直方圖進行歸一化處理。

(4)最後将得到的每個cell的統計直方圖進行連接配接成為一個特征向量,也就是整幅圖的LBP紋理特征向量;

然後便可利用SVM或者其他機器學習算法進行分類了。

from:LBP原理加源碼解析

from:Haar ,LBP 級聯分類器

from:目标檢測的圖像特征提取之(二)LBP特征

from:人臉識别經典算法二:LBP方法

   OpenCV實作了LBP+Adaboost+級聯器檢測方法

    LBP+Adaboost方法用在目标檢測中的效果比Haar特征、HOG特征都要好(HOG特征用的不多,主要是Haar和LBP),而且LBP特征的訓練速度比Haar和HOG都要快很多。在LBP+Adaboost中,LBP特征主要是用作輸入的訓練資料(特征),使用的LBP特征應該是DLBP(維基百科上說的,沒太看明白Cascade中LBP特征的計算方式),具體用法需要看源碼。Opencv的TrainCascade中使用的LBP特征是MB-LBP。 

     OpenCV附帶了一個名為traincascade的工具,用于訓練LBP,Haar和HOG。特别是對于人臉檢測,他們甚至以traincascade所需的格式處理了24x24像素大小的3000幅圖像。

    檢測器以24x24的滑動視窗來尋找面部。從級聯分類器的第1階段到第20級步進,如果它可以顯示目前24x24視窗可能不是面部,則它拒絕它并在視窗上移動一到兩個像素到下一個位置;否則它将進入下一階段。

    在每個階段,檢查3-10個左右的LBP特征。每個LBP特征在視窗内都有一個偏移量和一個大小,它覆寫的區域完全包含在目前視窗中。計算每個給定位置LBP特征,可能導緻通過或失敗。根據LBP功能是成功還是失敗,将特定于該功能的正或負權重添加到累加器。Evaluating an LBP feature at a given position can result in either a pass or fail. 

Depending on whether an LBP feature succeeds or fails, a positive or negative weight particular to that feature is added to an accumulator.

     将每個階段的輸出的值與每個階段的門檻值進行比較。如果輸出低于門檻值則該階段失敗,如果輸出高于門檻值則通過。同樣如果一個階段失敗,則退出級聯并且視窗移動到下一個位置。

與haar基本類似,不在贅述!

執行個體:

//cascades+LBP檢測器的運用,用于人臉識别和人眼識别

#include<opencv2/opencv.hpp>
#include <iostream>   
using namespace std;
using namespace cv;

CascadeClassifier face_cascade;
CascadeClassifier eyes_cascade;
void detectAndDisplay(Mat frame);

int main(int argc, char** argv)
{
	Mat srcImage;
	srcImage = imread("1.jpg", 1);    
	imshow("原圖", srcImage);
	//============加載分類器=========
  
	if (!face_cascade.load("D:\\Program Files\\OpenCV\\opencv\\sources\\data\\lbpcascades\\lbpcascade_frontalface.xml"))//也可用Haar分類器
	{
		printf("人臉檢測器加載失敗\n");
		return -1;
	}
	if (!eyes_cascade.load("D:\\Program Files\\OpenCV\\opencv\\sources\\data\\haarcascades_cuda\\haarcascade_eye.xml"))
	{
		printf("人眼檢測器加載失敗\n");
		return -1;
	};
	//============調用人臉檢測函數  =========
	detectAndDisplay(srcImage);
	waitKey(0); 
}


void detectAndDisplay(Mat dispFace)
{
	//定義變量
	std::vector<Rect> faces;
	std::vector<Rect>eyes;
	Mat srcFace, grayFace, eqlHistFace;
	int eye_number = 0;

	cvtColor(dispFace, grayFace, CV_BGR2GRAY);   
	equalizeHist(grayFace, eqlHistFace);   //直方圖均衡化  
    //人臉檢測******************
	face_cascade.detectMultiScale(eqlHistFace, faces, 1.1, 3, 0 | CV_HAAR_SCALE_IMAGE, Size(10, 10));
	//增大第四個參數可以提高檢測精度,但也可能會造成遺漏
	//人臉尺寸minSize和maxSize,關鍵參數,自行設定,随圖檔尺寸有很大關系,

	for (unsigned int i = 0; i < faces.size(); i++)
	{
		//用藍色橢圓标記檢測到的人臉
		Point center(faces[i].x + faces[i].width / 2, faces[i].y + faces[i].height / 2);
		ellipse(dispFace, center, Size(faces[i].width / 2, faces[i].height * 65 / 100), 0, 0, 360, Scalar(255, 0, 0), 2, 8, 0);
		//人眼檢測*****************
		Mat faceROI = eqlHistFace(faces[i]);
		eyes_cascade.detectMultiScale(faceROI, eyes, 1.2, 3, 0 | CV_HAAR_SCALE_IMAGE, Size(15, 15), Size(80, 80));
		eye_number += eyes.size();//人眼計數

		 //用綠色圓标記檢測到的人眼*****************
		for (unsigned int j = 0; j <eyes.size(); j++)
		{
			Point center(faces[i].x + eyes[j].x + eyes[j].width / 2, faces[i].y + eyes[j].y + eyes[j].height / 2);
			int radius = cvRound((eyes[j].width + eyes[i].height)*0.25);
			circle(dispFace, center, radius, Scalar(0, 255, 0), 2, 8, 0);
		}
	}
	//*****************3.0檢測結果輸出*****************
	cout << "檢測結果\n人臉: " << faces.size() << " 張" << endl;
	cout << "人眼: " << eye_number << " 隻" << endl;
	imshow("人臉識别結果", dispFace);
}