【DIP】基于积分图的滤波算法加速

数字图像处理中经常会用到均值滤波,快速的均值方式通常会使用行求和,得到中间图像然后进行列求和。最后除以模版的面积。

基于积分图的均值滤波可以在O(1)时间内求出任意半径的均值滤波结果。前提是求出积分图。
先贴结果,如果下图积分图,中间图片是模版,则原图模版内的像素和为
I_4 + I_1 - I_2 - I_3
I1, I2, I3, I4为中间模板的四个顶点,对应的是积分图中的像素值,中心点到矩形模版四条边的距离为r, 则均值滤波后中间像素值为:
\frac{I_4 + I_1 - I_2 - I_3}{(2*r+1)^2}

plot by ItchyHacker.jpeg

下面看看公式是如何得出的:
I1 的像素值是原图A矩形内像素值和,I2 的像素值是原图A + C矩形内像素值和, I3 的像素值是原图A+B矩形内像素值和, I4 的像素值是原图A+B+C+D矩形内像素值和,
I_4 + I_1 - I_2 - I_3 = A+B+C+D + A - A - C - A - B = D
因此只要有积分图之后,任意半径的的均值滤波结果只需要取四个数加减然后除以均值滤波模版的面积便可。
OpenCV内置积分算法,下面贴一下我自己实现的积分算法和内置积分算法对比,还有基于积分图的均值滤波和两次求和遍历取平均的结果。程序边缘区域没有处理,只是简单的测试程序。

//
//  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){
    // 我自己实现的积分图算法
    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){
    // 使用积分图实现的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);
}
结果: 
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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容