Opencv笔记(13)积分图

积分图时一种允许子区域快速求和的数据结构,这种求和在很多方面都很有用,值得一提的是haar小波的计算,它用于人脸识别和类似的算法。Opencv支持积分图的三种变体,分别是总和、平方求和以及倾斜求和。每种情况的结果图像在图像的每个方向上都加1之后,与原始图像的大小相同。

通过积分图,你可以对图像的任意直立或“倾斜”的矩形区域求和、平均以及标准差。即使目标区域大小不确定,也可以高效进行快速模糊、近似梯度、求均值以及标准差,即使对可变大小的窗口,也可以执行快速块相关。

 如果要计算中间区域的像素和,我们可以这样计算398-9-10+1=380。使用四个量就可以对任意矩形区域计算并且将计算复杂度控制为O(1)。

cv::integral()标准求和积分

void integral( InputArray src, OutputArray sum, int sdepth = -1 );

 第一个参数和第二个参数是输入图像和输出图像,如果输入图像的大小时W×H,输出图像的大小就是(W+1)×(H+1)。第三个参数sdepth指明求和图像(结果图像)需要的深度。sdepth可以是CV:S32, CV::F32或CV::F64.

cv::integral()平方求和积分

void integral( InputArray src, OutputArray sum, OutputArray sqsum, int sdepth = -1, int sqdepth = -1 );

 sqsum参数告诉函数除了标准求和还要计算平方和。与之前一样,sdepth指明目标图像的深度。sdepth可以是CV:S32, CV::F32或CV::F64.

cv::integral()倾斜求和积分

void integral( InputArray src, OutputArray sum, OutputArray sqsum, OutputArray tilted, int sdepth = -1, int sqdepth = -1 );

 附加参数titled作为一个附加的项由函数计算而来,其他参数全部相同。

积分图应用(1)——均值滤波

通过利用积分图实现任意窗口大小的均值滤波,代码实现如下:

#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int getBlockSum(Mat& sum, int row1,int col1,int row2, int col2) {
    int br = sum.at<int>(col2 + 1, row2 + 1);
    int tl = sum.at<int>(col1, row1);
    int tr = sum.at<int>(col2 + 1, row1);
    int bl = sum.at<int>(col1, row2 + 1);
    return br + tl - tr - bl;
}
void blur_demo(Mat &src, Mat& dst, int ksize) {
    int h = src.rows;
    int w = src.cols;
    int radius = ksize / 2;
    Mat sum;
    integral(src, sum, CV_32S);
    int row1, col1, row2, col2;
    for (int i = 0; i < w; i++) {
        for (int j = 0; j < h; j++) {
            row1 = i - radius < 0 ? 0 : i - radius;
            col1 = j - radius < 0 ? 0 : j - radius;
            row2 = i + radius < w ? i + radius : w - 1;
            col2 = j + radius < h ? j + radius : h - 1;
            dst.at<uchar>(j, i) = (uchar)(getBlockSum(sum, row1, col1, row2, col2)/ ksize/ ksize);
        }
    }
}

int main() {
    Mat src = imread("D:/Backup/桌面/1.bmp", 0);
    clock_t start, end;
    start = clock();
    int ksize = 5;
    cout << "ksize = " << ksize << endl;
    // 均值滤波
    Mat dst1;
    blur(src, dst1,Size(ksize, ksize),Point(-1,-1), BORDER_CONSTANT);
    end = clock();
    cout << "blur花费"<< end - start << endl;
    // 积分图
    start = clock();
    Mat dst2 = Mat::zeros(src.size(), CV_8UC1);
    blur_demo(src, dst2, ksize);    
    end = clock();
    cout << "积分图花费" << end - start << endl;
    return 0;
}

 

积分图应用(2)——动态阈值分割dyn_threshold

原理: 动态阈值分割是指在图像分割的过程中,不用人为的去设置阈值,而是根据图像中存在的特征,进行分割。一般是将原图像与处理后的图像作差,然后去计算差值图像中的亮色区域或者暗色区域。其本质相当于对图像灰度直方图的平滑,进而求取图像中的波谷或者波峰。

pixel = (pixel > (mean - c)) ? object : background

其中默认情况下参数C取值为0。object表示前景像素,background表示背景像素。

特点: 动态阈值分割具有抗干扰性强,稳定性强的特点,对光照变化不敏感。

  • 使用均值滤波+二值化实现动态阈值
int ksize = 7;
Mat mean, sub, dst1;
blur(src, mean, Size(ksize, ksize));
subtract(mean, src, sub);
threshold(sub, dst1, 10, 255, THRESH_BINARY_INV);
  • 使用积分图实现动态阈值
/*实现步骤
1. 彩色图像转灰度图像
2. 获取灰度图像的像素数据,预计算积分图
3. 根据输入的参数窗口半径大小从积分图中获取像素总和,求得平均值
4. 循环每个像素,根据局部均值实现中心像素的二值化赋值
5. 输出二值图像*/
void
dyn_threshold(Mat &src, Mat& dst, int ksize, int c) { int h = src.rows; int w = src.cols; int radius = ksize / 2; Mat sum; integral(src, sum, CV_32S); int row1, col1, row2, col2; for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { row1 = i - radius < 0 ? 0 : i - radius; col1 = j - radius < 0 ? 0 : j - radius; row2 = i + radius < w ? i + radius : w - 1; col2 = j + radius < h ? j + radius : h - 1; dst.at<uchar>(j, i) = src.at<uchar>(j, i) - (uchar)(getBlockSum(sum, row1, col1, row2, col2) / ksize / ksize) + c>0 ? 255 : 0; } } }

结果:运行时间差不多

积分图应用(3)——var_threshold

var_threshold的分割,非常适用于具有较为复杂背景的标识内容进行分割,它能够对获取指定蒙板大小且具有相同灰度的目标进行分割。

算子的函数签名如下:

var_threshold(Image : Region : MaskWidth, MaskHeight, StdDevScale, AbsThreshold, LightDark : )

其中,MaskWidth、 MaskHeight 定义的过滤器蒙板大小决定了要分割的对象的最大尺寸,但是,如果选择的蒙版太大,则可能会合并非常接近的对象。

StdDevScale 局部标准差用作图像中噪声的度量,它可以通过StdDevScale进行缩放,以反映所需的灵敏度,值越高,表示仅选择与其周围环境截然不同的像素。

AbsThreshold则为目标区域的绝对灰度值,在图像的均匀区域中,标准偏差较低;因此,单个灰度值的影响很大,为了降低在均匀区域中的灵敏度,可以调整AbsThreshold。因此,可以忽略同质环境中的微小灰度值变化。

请注意,对于 StdDevScale 的负值,AbsThreshold 也应选择负值。

lightDark: dark 暗 light 光亮 equal 等于 not_equal 不等于

令g(x,y)为点(x,y)处的灰度值,m(x,y) 为均值,d(x,y) 为方差(实际是标准差),则点(x,y)处的阈值v(x,y)定义为:

当LightDark = ‘light’:时,满足下述条件的点被选中到输出区域

当LightDark = ‘dark’:时,满足下述条件的点被选中到输出区域

当LightDark = ‘equal’:时,满足下述条件的点被选中到输出区域

 

当LightDark = ‘not_equal’:时,满足下述条件的点被选中到输出区域

代码实现:

void var_threshold(Mat& src, Mat& dst, int StdDevScale, int AbsThreshold, int ksize) {
    int h = src.rows;
    int w = src.cols;
    int radius = ksize / 2;
    Mat mean, sub, sum, qsum;
    blur(src, mean, Size(ksize, ksize));
    subtract(mean, src, sub);
    integral(sub, sum, qsum, CV_32S, CV_32S);
    int row1, col1, row2, col2;
    for (int i = 0; i < w; i++) {
        for (int j = 0; j < h; j++) {
            row1 = i - radius < 0 ? 0 : i - radius;
            col1 = j - radius < 0 ? 0 : j - radius;
            row2 = i + radius < w ? i + radius : w - 1;
            col2 = j + radius < h ? j + radius : h - 1;
            int varthreshold = max(StdDevScale * (int)sqrt(getBlockSum(qsum, row1, col1, row2, col2)) / ksize, AbsThreshold);
            dst.at<uchar>(j, i) = src.at<uchar>(j, i) - mean.at<uchar>(j, i) - varthreshold>0?255:0;
        }
    }
}

 

 

 

 

参考文献:

1. 图像处理之积分图应用四(基于局部均值的图像二值化算法)

2. OpenCV中积分图介绍与应用

posted @ 2022-07-04 20:09  湾仔码农  阅读(637)  评论(0编辑  收藏  举报