機器學習 之 LBP特徵
綜述::
LBP特徵:Local Binary Pattern,區域性二值模式特徵,是一種用來描述影象區域性紋理特徵的運算元。LBP特徵運算元計算簡單、效果較好,資料量小,因此LBP特徵在計算機視覺的許多領域都得到了廣泛的應用,LBP特徵比較多用於目標檢測中。LBP計算出的特徵具有灰度不變性和旋轉不變性等顯著優點,例如對光照不敏感。
LBP的基本運算元
原始的LBP運算元定義為在3*3的視窗內,以視窗中心畫素為閾值,將相鄰的8個畫素的灰度值與其進行比較,若周圍畫素值大於中心畫素值,則該畫素 點的位置被標記為1,否則為0。這樣,3*3鄰域內的8個點經過比較可產生8個0或1,將這8個0或1作為二進位制數按照一定的次序排列形成一個二進位制數字,這個二進位制數字就是中心畫素的LBP值。LBP值共有
LBP特徵的改進
圓形LBP運算元:
所謂圓形LBP運算元:如果要計算某個畫素的點LBP特徵,以這個畫素點為中心,以一個任意大小半徑R畫一個圓,將落在圓內的畫素與中心點畫素比較得到LBP運算元。如下圖:
這樣做的好處是:它能夠適應一定程式上紋理的尺度變化,並達到灰度和旋 轉不變性的要求。
LBP旋轉不變模式
首先講下為什麼LBP特徵會有旋轉可變這個情況,就是以上面3x3區域的畫素值為例:
逆時針旋轉一格後得到的LBP值與原始不同。
那麼旋轉不變模式是什麼呢,就是旋轉圓形鄰域一週得到一系列初始定義的 LBP值,取其最小值作為該鄰域的 LBP 值,這樣無論怎樣旋轉,該點LBP值始終不變。如下圖所示:
LBP等價模式
具體可見上面三個部落格裡的介紹,總的來說:
原來是LBP的每個位都作為該特徵的決定項,例如01100100與01101100被認為是兩個不同的特徵值,採用LBP的等價模式後,只認為0到1的跳變與1到0的跳變為一個特徵決定項,那麼這樣說的話,01100100中0~1有2個,1~0有2個,而01101100中0~1有2個,1~0也是有2個,我們認為它是同一個特徵,這樣LBP特徵的種類就大減少了。
opencv中LBP特徵實現
opencv中LBP特徵的實現原理:將一個影象視窗畫分成9個格子,統計每個格子的畫素和,然後把周圍8個畫素和與中間那個格子的畫素和比較,大於的取1,小於的取0,按順序組成一個8位的LBP特徵值。
opencv中主要是Lbpfeatures.cpp和Lbpfeatures.h兩個檔案來實現計算LBP特徵的原始碼。
Lbpfeatures.h原始碼註釋:
#ifndef _OPENCV_LBPFEATURES_H_
#define _OPENCV_LBPFEATURES_H_
#include "traincascade_features.h"
#define LBPF_NAME "lbpFeatureParams"
struct CvLBPFeatureParams : CvFeatureParams
{
CvLBPFeatureParams();
};
/*LBP特徵類定義*/
class CvLBPEvaluator : public CvFeatureEvaluator
{
public:
virtual ~CvLBPEvaluator() {}
virtual void init(const CvFeatureParams *_featureParams,
int _maxSampleCount, Size _winSize );
virtual void setImage(const Mat& img, uchar clsLabel, int idx);
virtual float operator()(int featureIdx, int sampleIdx) const
{ return (float)features[featureIdx].calc( sum, sampleIdx); }
virtual void writeFeatures( FileStorage &fs, const Mat& featureMap ) const;
protected:
virtual void generateFeatures();
class Feature
{
public:
Feature();
Feature( int offset, int x, int y, int _block_w, int _block_h );
uchar calc( const Mat& _sum, size_t y ) const;
void write( FileStorage &fs ) const;
Rect rect;
int p[16];
};
vector<Feature> features;
Mat sum;
};
/*將9個視窗中周圍8個視窗的畫素和與中間視窗的畫素和比較得到LBP特徵值*/
inline uchar CvLBPEvaluator::Feature::calc(const Mat &_sum, size_t y) const
{
const int* sum = _sum.ptr<int>((int)y);
int cval = sum[p[5]] - sum[p[6]] - sum[p[9]] + sum[p[10]];
return (uchar)((sum[p[0]] - sum[p[1]] - sum[p[4]] + sum[p[5]] >= cval ? 128 : 0) | // 0
(sum[p[1]] - sum[p[2]] - sum[p[5]] + sum[p[6]] >= cval ? 64 : 0) | // 1
(sum[p[2]] - sum[p[3]] - sum[p[6]] + sum[p[7]] >= cval ? 32 : 0) | // 2
(sum[p[6]] - sum[p[7]] - sum[p[10]] + sum[p[11]] >= cval ? 16 : 0) | // 5
(sum[p[10]] - sum[p[11]] - sum[p[14]] + sum[p[15]] >= cval ? 8 : 0) | // 8
(sum[p[9]] - sum[p[10]] - sum[p[13]] + sum[p[14]] >= cval ? 4 : 0) | // 7
(sum[p[8]] - sum[p[9]] - sum[p[12]] + sum[p[13]] >= cval ? 2 : 0) | // 6
(sum[p[4]] - sum[p[5]] - sum[p[8]] + sum[p[9]] >= cval ? 1 : 0)); // 3
}
#endif
Lbpfeatures.cpp原始碼註釋:
#include "lbpfeatures.h"
#include "cascadeclassifier.h"
CvLBPFeatureParams::CvLBPFeatureParams()
{
maxCatCount = 256;
name = LBPF_NAME;
}
/*Lbp特徵的物件初始化*/
void CvLBPEvaluator::init(const CvFeatureParams *_featureParams, int _maxSampleCount, Size _winSize)
{
CV_Assert( _maxSampleCount > 0);
/*分配積分圖記憶體*/
sum.create((int)_maxSampleCount, (_winSize.width + 1) * (_winSize.height + 1), CV_32SC1);
/*呼叫父類初始化函式*/
CvFeatureEvaluator::init( _featureParams, _maxSampleCount, _winSize );
}
/*設定影象寬高,並計算積分影象*/
void CvLBPEvaluator::setImage(const Mat &img, uchar clsLabel, int idx)
{
CV_DbgAssert( !sum.empty() );
CvFeatureEvaluator::setImage( img, clsLabel, idx );
Mat innSum(winSize.height + 1, winSize.width + 1, sum.type(), sum.ptr<int>((int)idx));
integral( img, innSum );
}
/*將特徵值寫到檔案中*/
void CvLBPEvaluator::writeFeatures( FileStorage &fs, const Mat& featureMap ) const
{
_writeFeatures( features, fs, featureMap );
}
/*計算視窗內LBP特徵:將視窗內影象區域分成9個cell,
計算中間那個cell與其他8個cell的畫素值和的大小關係,以實現LBP特徵計算*/
void CvLBPEvaluator::generateFeatures()
{
int offset = winSize.width + 1;
for( int x = 0; x < winSize.width; x++ )
for( int y = 0; y < winSize.height; y++ )
for( int w = 1; w <= winSize.width / 3; w++ )
for( int h = 1; h <= winSize.height / 3; h++ )
if ( (x+3*w <= winSize.width) && (y+3*h <= winSize.height) )
features.push_back( Feature(offset, x, y, w, h ) );
numFeatures = (int)features.size();
}
/*初始化特徵的視窗座標:Feature類建構函式*/
CvLBPEvaluator::Feature::Feature()
{
rect = cvRect(0, 0, 0, 0);
}
/*給特徵視窗點賦座標值:Feature類建構函式*/
CvLBPEvaluator::Feature::Feature( int offset, int x, int y, int _blockWidth, int _blockHeight )
{
Rect tr = rect = cvRect(x, y, _blockWidth, _blockHeight);
CV_SUM_OFFSETS( p[0], p[1], p[4], p[5], tr, offset )
tr.x += 2*rect.width;
CV_SUM_OFFSETS( p[2], p[3], p[6], p[7], tr, offset )
tr.y +=2*rect.height;
CV_SUM_OFFSETS( p[10], p[11], p[14], p[15], tr, offset )
tr.x -= 2*rect.width;
CV_SUM_OFFSETS( p[8], p[9], p[12], p[13], tr, offset )
}
/*將特徵座標寫到檔案*/
void CvLBPEvaluator::Feature::write(FileStorage &fs) const
{
fs << CC_RECT << "[:" << rect.x << rect.y << rect.width << rect.height << "]";
}