極限優化:Haar特徵的另一種的快速計算方法—boxfilter

這種以Boxfilter替代integral image 的方法很難使用到haar、LBP等特徵檢測中,因爲像下面說的,它不支持多尺度,也就是說所提取的特徵必須是同一個大小,最起碼同一個寬高比的,這一點對寬高不定的haar特徵、LBP特徵都有很大的限制,但對於HOG特徵因爲尺度不像另外兩個那樣靈活,還是有跡可循的。採長補短


申明:以下非筆者原創,原文轉載自:http://www.cnblogs.com/easymind223/archive/2012/11/13/2768680.html


這個項目大概是在2年前了,因爲要用嵌入式編程,所以無法用opencv的庫函數,一切算法純靠手寫(是不是很坑爹?),其中一部分程序需要計算Haar特徵,於是就有了下面的故事:

  在模式識別領域,Haar特徵是大家非常熟悉的一種圖像特徵了,它可以應用於許多目標檢測的算法中。與Haar相似,圖像的局部矩形內像素的和、平方和、均值、方差等特徵也可以用類似Haar特徵的計算方法來計算。這些特徵有時會頻繁的在某些算法中使用,因此對它的優化勢在必行。Boxfilter就是這樣一種優化方法,它可以使複雜度爲O(MN)的求和,求方差等運算降低到O(1)或近似於O(1)的複雜度,它的缺點是不支持多尺度

  第一個提出Haar特徵快速計算方法的是CVPR2001上的那篇經典論文Rapid Object Detection using a Boosted Cascade of Simple Features ,它提出了integral image的概念,這個方法使得圖像的局部矩形求和運算的複雜度從O(MN)下降到了O(4)。它的原理很簡單:首先建立一個數組A,寬高與原圖像相等,然後對這個數組賦值,每個元素的值A[i]賦爲該點與圖像原點所構成的矩形中所有像素的和。初始化之後,想要計算某個矩形像素和的時候可以採用如下方法:如圖D矩形的像素和就等於A[4] – A[2] – A[3] + A[1],共4次運算,即O(4)。Integral Image極大的提高了Haar特徵的計算速度,它的優點在於能夠快速計算任意大小的矩形求和運算

  Boxfilter的原理有點類似Integral Image,而且比它還要快,但是實現步驟比較複雜。在計算矩形特徵之前,Boxfilter與Integral Image都需要對圖像進行初始化(即對數組A賦值),不同於Integral Image, Boxfilter的數組A中的每個元素的值是該像素鄰域內的像素和(或像素平方和),在需要求某個矩形內像素和的時候,直接訪問數組中對應的位置就可以了。因此可以看出它的複雜度是O(1)。

 

Boxfilter的初始化過程如下:(此處較繁瑣,如睡意來襲可以略過)

1、給定一張圖像,寬高爲(M,N),確定待求矩形模板的寬高(m,n),如圖紫色矩形。圖中每個黑色方塊代表一個像素,紅色方塊是假想像素。

2、開闢一段大小爲M的數組,記爲buff, 用來存儲計算過程的中間變量,用紅色方塊表示

3、將矩形模板(紫色)從左上角(0,0)開始,逐像素向右滑動,到達行末時,矩形移動到下一行的開頭(0,1),如此反覆,每移動到一個新位置時,計算矩形內的像素和,保存在數組A中。以(0,0)位置爲例進行說明:首先將綠色矩形內的每一列像素求和,結果放在buff內(紅色方塊),再對藍色矩形內的像素求和,結果即爲紫色特徵矩形內的像素和,把它存放到數組A中,如此便完成了第一次求和運算。

4、每次紫色矩形向右移動時,實際上就是求對應的藍色矩形的像素和,此時只要把上一次的求和結果減去藍色矩形內的第一個紅色塊,再加上它右面的一個紅色塊,就是當前位置的和了,用公式表示 sum[i] = sum[i-1] - buff[x-1] + buff[x+m-1]

5、當紫色矩形移動到行末時,需要對buff進行更新。因爲整個綠色矩形下移了一個像素,所以對於每個buff[i], 需要加上一個新進來的像素,再減去一個出去的像素,然後便開始新的一行的計算了。

 

  Boxfilter的初始化過程非常快速,每個矩形的計算基本上只需要一加一減兩次運算。從初始化的計算速度上來說,Boxfilter比Integral Image要快一些,大約25%。在具體求某個矩形特徵時,Boxfilter比Integral Image快4倍,所謂的4倍其實就是從4次加減運算降低到1次,雖然這個優化非常渺小,但是把它放到幾層大循環裏面,還是能節省一些時間的。對於那些實時跟蹤檢測算法,一幀的處理時間要嚴格在40ms以下,正是這些細小的優化決定了程序的效率,積少成多,聚沙成塔。

 

下面的程序是Boxfilter的示例代碼,謹供參考

.h

複製代碼
#pragma once

typedef unsigned char uchar;

class Boxfilter
{
public:
    Boxfilter(void);
    ~Boxfilter(void);

    void init(int width, int height, int mwidth=5, int mheight=5);
    void boxfilter(unsigned char* img);
public:
    float    getMean(int x, int y);    //以x,y爲中心點,mwidth,mheight爲直徑的局部區域,下同
    float    getVar(int x, int y);
    int        getSum(int x, int y);
    int        getSquareSum(int x, int y);
    int        getLocalSize();

private:
    int mwidth ;
    int mheight ;
    unsigned char* img;
    int width;
    int height;
    int* f_sum;
    int* f_sum2;

};
複製代碼

.cpp

複製代碼
#include "Boxfilter.h"
#include <assert.h>
#include <string>

int* buff = 0;
int* buff2 = 0;
int boxwidth;
int boxheight;

Boxfilter::Boxfilter(void)    
{    
    f_sum = 0;
    f_sum2 = 0;
}

Boxfilter::~Boxfilter(void)
{
    if(f_sum)    delete[] f_sum;
    if(f_sum2)    delete[] f_sum2;
    if(buff)    delete[] buff;
    if(buff2)    delete[] buff2;
}

void Boxfilter::init(int width, int height, int mwidth, int mheight)
{
    this->mwidth = mwidth;
    this->mheight = mheight;
    this->width = width;
    this->height = height;

    boxwidth = width - mwidth;
    boxheight = height - mheight;
    f_sum = new int[boxwidth *boxheight];
    f_sum2 = new int[boxwidth *boxheight];

    buff = new int[width];
    buff2= new int[width];
}

void Boxfilter::boxfilter (unsigned char* img)
{
    int j,x,y;

    memset(buff, 0, width *sizeof(int));
    memset(buff2, 0, width *sizeof(int));
    memset(f_sum, 0, boxwidth *boxheight);
    memset(f_sum2, 0, boxwidth *boxheight);

    for(y=0; y<mheight; y++)
    {
        for(x=0; x<width; x++)
        {
            uchar pixel = img[y *width + x];
            buff[x] += pixel;
            buff2[x] += pixel*pixel;
        }
    }

    for(y=0; y<height - mheight;y++)
    {
        int Xsum=0;
        int Xsum2=0;

        for(j=0; j<mwidth; j++)
        {
            Xsum += buff[j];
            Xsum2 += buff2[j];
        }

        for(x=0; x<width - mwidth; x++)
        {
            if(x!=0)
            {
                Xsum = Xsum-buff[x-1]+buff[mwidth-1+x];
                Xsum2 = Xsum2-buff2[x-1]+buff2[mwidth-1+x];
            }
            f_sum[y*(width - mwidth)+x] = (float) Xsum ;
            f_sum2[y*(width - mwidth)+x] = Xsum2;            
        }

        for(x=0; x<width; x++)
        {
            uchar pixel = img[y *width + x];    
            uchar pixel2 = img[(y+mheight) *width + x];    
            buff[x] = buff[x] - pixel + pixel2;    
            buff2[x] = buff2[x] - pixel*pixel +    pixel2*pixel2;    
        }
    }
}

float Boxfilter::getMean(int x, int y)
{
    return getSum(x,y) / (float)(mwidth*mheight);
}

float Boxfilter::getVar(int x, int y)
{
    float mean = getMean(x, y);
    return (float)getSquareSum(x, y)/(mwidth *mheight) - mean*mean;
}

int Boxfilter::getSquareSum(int x, int y)
{
    if(y>mheight/2 && y<height - mheight/2 && x>mwidth/2 && x<width - mwidth/2)
        return f_sum2[(y - mheight/2) *boxwidth + (x - mwidth/2)];
    else
        return -1;
}

int    Boxfilter::getSum(int x, int y)
{
    if(y>mheight/2 && y<height - mheight/2 && x>mwidth/2 && x<width - mwidth/2)
        return f_sum[(y - mheight/2) *boxwidth + (x - mwidth/2)];
    else
        return -1;
}

int    Boxfilter::getLocalSize()
{
    return mwidth > mheight ? mwidth : mheight;
}
複製代碼

測試用例

複製代碼
// cv2.4 test.cpp : 定義控制檯應用程序的入口點。
//

#include "stdafx.h"
#include "opencv2/opencv.hpp"
#include "Boxfilter.h"
#include <iostream>
using namespace cv;
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    Mat src = imread("C:\\Documents and Settings\\Administrator\\桌面\\img1.png",0);

    Boxfilter box;
    box.init(src.cols, src.rows, 5, 5);
    box.boxfilter((uchar*)src.data);

    int x = 50, y = 50;
    float a = box.getMean(x, y);    //求出以(x,y)爲中心的矩形的均值
    float b = box.getVar(x, y);
    int c = box.getSum(x, y);
    int d = box.getSquareSum(x, y);
    int e = box.getLocalSize();

    cout<<"mean: "        <<a<<endl;
    cout<<"var:    "        <<b<<endl;
    cout<<"sum:    "        <<c<<endl;
    cout<<"squaresum: "    <<d<<endl;
    cout<<"size:    "    <<e<<endl;

    getchar();
    return 0;
}

發佈了20 篇原創文章 · 獲贊 11 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章