【DIP】基于積分圖的濾波算法加速

數(shù)字圖像處理中經(jīng)常會用到均值濾波芜茵,快速的均值方式通常會使用行求和,得到中間圖像然后進行列求和倡蝙。最后除以模版的面積九串。

基于積分圖的均值濾波可以在O(1)時間內(nèi)求出任意半徑的均值濾波結(jié)果。前提是求出積分圖寺鸥。
先貼結(jié)果猪钮,如果下圖積分圖,中間圖片是模版胆建,則原圖模版內(nèi)的像素和為
I_4 + I_1 - I_2 - I_3
I1, I2, I3, I4為中間模板的四個頂點烤低,對應(yīng)的是積分圖中的像素值,中心點到矩形模版四條邊的距離為r, 則均值濾波后中間像素值為:
\frac{I_4 + I_1 - I_2 - I_3}{(2*r+1)^2}

plot by ItchyHacker.jpeg

下面看看公式是如何得出的:
I1 的像素值是原圖A矩形內(nèi)像素值和笆载,I2 的像素值是原圖A + C矩形內(nèi)像素值和, I3 的像素值是原圖A+B矩形內(nèi)像素值和, I4 的像素值是原圖A+B+C+D矩形內(nèi)像素值和,
I_4 + I_1 - I_2 - I_3 = A+B+C+D + A - A - C - A - B = D
因此只要有積分圖之后扑馁,任意半徑的的均值濾波結(jié)果只需要取四個數(shù)加減然后除以均值濾波模版的面積便可。
OpenCV內(nèi)置積分算法凉驻,下面貼一下我自己實現(xiàn)的積分算法和內(nèi)置積分算法對比腻要,還有基于積分圖的均值濾波和兩次求和遍歷取平均的結(jié)果。程序邊緣區(qū)域沒有處理涝登,只是簡單的測試程序雄家。

//
//  integralCompare.cpp
//  FaceBeauty
//
//  Created by yuhua.cheng on 2018/11/6.
//  Copyright ? 2018 yuhua.cheng. All rights reserved.
//
#include <iostream>
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
void myIntegral(const cv::Mat& img, cv::Mat& integral){
    // 我自己實現(xiàn)的積分圖算法
    int height = img.rows;
    int width = img.cols;
    cv::Mat tempImg;
    img.convertTo(tempImg, CV_32FC3, 1/255.0);
    integral = tempImg.clone();
    // 第一行積分圖值計算
    float* imgPtr = tempImg.ptr<float>(0);
    float* interPrevPtr = integral.ptr<float>(0);
    float* interPtr = integral.ptr<float>(0);
    interPtr += 3;
    for(int j = 1; j < width; j++){
        interPtr[0] = interPrevPtr[0] + imgPtr[0];
        interPtr[1] = interPrevPtr[1] + imgPtr[1];
        interPtr[2] = interPrevPtr[2] + imgPtr[2];
        imgPtr += 3;
        interPrevPtr += 3;
        interPtr += 3;
    }
    // 第一列積分圖值計算
    for(int i = 1; i < height; i++){
        interPrevPtr = integral.ptr<float>(i-1);
        interPtr = integral.ptr<float>(i);
        imgPtr = tempImg.ptr<float>(i);
        interPtr[0] = interPrevPtr[0] + imgPtr[0];
        interPtr[1] = interPrevPtr[1] + imgPtr[1];
        interPtr[2] = interPrevPtr[2] + imgPtr[2];
    }
    // 利用第一行和第一列信息計算積分圖
    for(int i = 1; i < height; i++){
        interPrevPtr = integral.ptr<float>(i-1)+3;
        interPtr = integral.ptr<float>(i)+3;
        imgPtr = tempImg.ptr<float>(i)+3; // 這里可以改進
        float rowSum[3] = {0};
        for(int j = 1; j < width; j++){
            rowSum[0] += (imgPtr-3)[0];
            rowSum[1] += (imgPtr-3)[1];
            rowSum[2] += (imgPtr-3)[2];
            interPtr[0] = interPrevPtr[0] + imgPtr[0] + rowSum[0];
            interPtr[1] = interPrevPtr[1] + imgPtr[1] + rowSum[1];
            interPtr[2] = interPrevPtr[2] + imgPtr[2] + rowSum[2];
            
            interPrevPtr += 3;
            interPtr += 3;
            imgPtr += 3;
        }
    }
}
void integralBoxBlur(const cv::Mat& img, cv::Mat& dst, int radius){
    // 使用積分圖實現(xiàn)的boxBlur算法
    int r = radius;
    cv::Mat tempImg;
    img.convertTo(tempImg, CV_32FC3, 1/255.0);
    cv::Mat integral;
    myIntegral(img, integral);
    int weight = (2*r+1)*(2*r+1);
    for(int row = r; row < img.rows - r; row++){
        float* intePtr1 = integral.ptr<float>(row-r);
        float* intePtr2 = integral.ptr<float>(row+r);
        float* imgPtr = tempImg.ptr<float>(row);
        intePtr1 += 3*r;
        intePtr2 += 3*r;
        imgPtr += 3*r;
        for(int col = r; col < img.cols - r; col++){
            imgPtr[0] = ((intePtr2+3*r)[0] + (intePtr1-3*r)[0] - (intePtr1+3*r)[0] - (intePtr2-3*r)[0])/weight;
            imgPtr[1] = ((intePtr2+3*r)[1] + (intePtr1-3*r)[1] - (intePtr1+3*r)[1] - (intePtr2-3*r)[1])/weight;
            imgPtr[2] = ((intePtr2+3*r)[2] + (intePtr1-3*r)[2] - (intePtr1+3*r)[2] - (intePtr2-3*r)[2])/weight;
            intePtr1 += 3;
            intePtr2 += 3;
            imgPtr += 3;
        }
    }
    tempImg.convertTo(dst, CV_8UC3, 255);
}
void sumBoxBlur(const cv::Mat& img, cv::Mat& dst, int radius){
    // 普通求和平均的計算方式
    int r = radius;
    int weight = (2*r+1)*(2*r+1);
    int height = img.rows;
    int width = img.cols;
    cv::Mat tempImg;
    img.convertTo(tempImg, CV_32FC3, 1/255.0);
    cv::Mat rowSum;
    rowSum = tempImg.clone();
    cv::Mat colSum;
    colSum = tempImg.clone();
    dst = tempImg.clone();
    // 行求和
    for(int i = 0; i < height; i++){
        float *imgPtr = tempImg.ptr<float>(i);
        float *colPtr = colSum.ptr<float>(i);
        for(int j = 0; j < width; j++){
            if(j < r){
                imgPtr += 3;
                colPtr += 3;
            }else{
                for(int bias = -r; bias <= r; bias++){
                    colPtr[0] += (imgPtr+3*bias)[0];
                    colPtr[1] += (imgPtr+3*bias)[1];
                    colPtr[2] += (imgPtr+3*bias)[2];
                }
                imgPtr +=3;
                colPtr += 3;
            }   
        }
    }
    cv::imshow("colSum",colSum);
    // 列求和
    for(int j = 0; j < width; j++){
        for(int i = 0; i < height; i++){
            if(i < r){
                continue;
            }else{
                float *rowPtr = rowSum.ptr<float>(i)+3*j;
                for(int bias = -r; bias <= r; bias++){
                    rowPtr[0] += (colSum.ptr<float>(i+bias)+3*j)[0];
                    rowPtr[1] += (colSum.ptr<float>(i+bias)+3*j)[1];
                    rowPtr[2] += (colSum.ptr<float>(i+bias)+3*j)[2];
                }
            }
        }
    }
    dst = rowSum / weight;
    dst.convertTo(dst, CV_8UC3, 255);
}
int main(){
    cv::Mat img = cv::imread("/Users/yuhua.cheng/Documents/dataset/Face/10.jpg");
    int height = img.rows;
    int width = img.cols;
    cv::Mat result1, result2, result3;
    cv::Mat tempImg;
    img.convertTo(tempImg, CV_32FC3, 1/255.0);
    cv::Mat integral;
    double t0, t1;
    t0 =  cv::getTickCount();
    cv::integral(tempImg, integral, -1);
    t1 = cv::getTickCount();
    std::cout << "Opencv integral time: " << (t1 - t0) / cv::getTickFrequency() << std::endl;
    t0 =  cv::getTickCount();
    myIntegral(img, integral);
    t1 = cv::getTickCount();
    std::cout << "My integral time: " << (t1 - t0) / cv::getTickFrequency() << std::endl;
    t0 =  cv::getTickCount();
    integralBoxBlur(img, result1, 11);
    t1 = cv::getTickCount();
    std::cout << "Integral box Blur Time: " << (t1 - t0) / cv::getTickFrequency() << std::endl;
    cv::imshow("my BoxBlur", result1);
    t0 =  cv::getTickCount();
    cv::boxFilter(img, result2, -1, cv::Size(23,23));
    t1 = cv::getTickCount();
    std::cout << "Opencv box Filter Time: " << (t1 - t0) / cv::getTickFrequency() << std::endl;
    cv::imshow("opencv BoxFilter", result2);
    t0 =  cv::getTickCount();
    sumBoxBlur(img, result3, 11);
    t1 = cv::getTickCount();
    std::cout << "sumBoxFilter Time: " << (t1 - t0) / cv::getTickFrequency() << std::endl;
    cv::imshow("sum BoxFilter", result3);
    cv::waitKey(0);
}
結(jié)果: 
Opencv integral time: 0.0155424
My integral time: 0.0254387
Integral box Blur Time: 0.0337875
Opencv box Filter Time: 0.00485557
sumBoxFilter Time: 0.319712

Reference

  1. https://blog.csdn.net/lijianlarry/article/details/78678297
  2. https://blog.csdn.net/u010839382/article/details/48241929
  3. Non-local Means Algorithm Introduction and Implementation: http://www.ipol.im/pub/art/2011/bcm_nlm/
  4. 積分圖的并行算法:https://blog.csdn.net/10km/article/details/51610735
  5. 快速均值濾波算法 : https://www.opengl.org/discussion_boards/showthread.php/167435-fastest-wide-width-box-filter
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市胀滚,隨后出現(xiàn)的幾起案子趟济,更是在濱河造成了極大的恐慌,老刑警劉巖咽笼,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件顷编,死亡現(xiàn)場離奇詭異,居然都是意外死亡褐荷,警方通過查閱死者的電腦和手機勾效,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叛甫,“玉大人层宫,你說我怎么就攤上這事∑浼啵” “怎么了萌腿?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長抖苦。 經(jīng)常有香客問我毁菱,道長米死,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任贮庞,我火速辦了婚禮峦筒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窗慎。我一直安慰自己物喷,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布遮斥。 她就那樣靜靜地躺著峦失,像睡著了一般。 火紅的嫁衣襯著肌膚如雪术吗。 梳的紋絲不亂的頭發(fā)上尉辑,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天,我揣著相機與錄音较屿,去河邊找鬼隧魄。 笑死,一個胖子當(dāng)著我的面吹牛吝镣,可吹牛的內(nèi)容都是我干的堤器。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼末贾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了整吆?” 一聲冷哼從身側(cè)響起拱撵,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎表蝙,沒想到半個月后拴测,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡府蛇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年集索,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片汇跨。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡务荆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出穷遂,到底是詐尸還是另有隱情函匕,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布蚪黑,位于F島的核電站盅惜,受9級特大地震影響中剩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抒寂,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一结啼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧屈芜,春花似錦妆棒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至毅糟,卻和暖如春红选,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背姆另。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工喇肋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人迹辐。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓蝶防,卻偏偏與公主長得像,于是被迫代替她去往敵國和親明吩。 傳聞我的和親對象是個殘疾皇子间学,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容