从直观来说,一张图像就是一个矩形,这个矩形中每个像素点的积分值,就是以图像左上角像素点为左上角顶点,以该像素点为右下角顶点的矩形中包含的所有元素之和。如下图所示,点(x,y)处的积分值为矩形区域中所有像素之和(包括该点)。
实际计算积分图的时候,为了提高计算效率,通常不会对每一个像素点都重新计算矩形区域包含的所有元素值之和,而是利用相邻点的积分值实现快速计算,如下图所示,点(x,y)的积分值可以使用点(x-1,y)与点(x,y-1)的积分值之和,然后减去重叠区域,也就是减去点(x-1,y-1)的积分值,最后再加上点(x,y)的像素值得到点(x,y)的积分值。
上述原理用公式表示为:
I(x,y) = I(x-1,y) + I(x,y-1) - I(x-1,y-1) + pixel(x,y)
此外还需要考虑边界问题,也就是第一行和第一列的计算。
对于第一行:
I(0,0) = pixel(0,0),x=0,y=0
I(x,0) = I(x-1,0) + pixel(x,0),x>0,y=0
对于第一列:
I(0,y) = I(0,y-1) + pixel(0,y),x=0,y>0
根据上述原理,基于OpenCV与C++的实现代码如下:
voidIntegral(Matsrc, Mat&integal_out) { Mattmp(src.size(), CV_64FC1, 0.0); tmp.ptr<double>(0)[0] = (double)src.ptr<uchar>(0)[0]; for(inti=1; i<src.cols; i++) //第一行 { tmp.ptr<double>(0)[i] =tmp.ptr<double>(0)[i-1] +src.ptr<uchar>(0)[i]; } for(inti=1; i<src.rows; i++) //第一列 { tmp.ptr<double>(i)[0] =tmp.ptr<double>(i-1)[0] +src.ptr<uchar>(i)[0]; } for(inti=1; i<src.rows; i++) //第i行 { for(intj=1; j<src.cols; j++) //第j列 { tmp.ptr<double>(i)[j] =tmp.ptr<double>(i)[j-1] +tmp.ptr<double>(i-1)[j] -tmp.ptr<double>(i-1)[j-1] +src.ptr<uchar>(i)[j]; } } tmp.copyTo(integal_out); }
参考OpenCV的官方文档:
C++: voidintegral(InputArrayimage, OutputArraysum, intsdepth=-1Parameters: image–SourceimageasWXH , 8-bitorfloating-point (32for64f). sum–Integralimageas (W+1) X (H+1) , 32-bitintegerorfloating-point (32for64f). sdepth–Desireddepthoftheintegralandthetiltedintegralimages, CV_32S, CV_32F, orCV_64F.
发现为了能容纳最后一列和最后一行的情况,最终的积分图就应该是 (W + 1) X (H + 1)大小,如下图所示,假设输入图像大小为2x2,则积分图大小为3x3:
OpenCV官方提供两种积分图函数integral(),可以直接调用:
//API一voidintegral( InputArraysrc, //输入图像OutputArraysum, //和表intsdepth=-1,); //和表深度//API二voidintegral( InputArraysrc, //输入图像OutputArraysum, //和表OutputArraysqsum, //平方和表intsdepth=-1, //和表深度,一般为CV_32Sintsqdepth=-1 ); //平方和表深度,一般为CV_32F
OpenCV实现过程:
usingnamespacestd; usingnamespacecv; intmain() { Matsrc, sum, sqrsum; src=imread("1.png", 0); integral(src, sum, sqrsum, CV_32S, CV_32F); normalize(sum, sum, 0, 255, NORM_MINMAX, CV_8UC1, Mat()); normalize(sqrsum, sqrsum, 0, 255, NORM_MINMAX, CV_8UC1, Mat()); imshow("原图", src); imshow("和表积分图", sum); imshow("平方和表积分图", sqrsum); waitKey(0); return0; }
我们还可以对兴趣区域(ROI)内的像素求和或者平方和:
usingnamespacestd; usingnamespacecv; intget_block_sum(Mat&sum, intx1, inty1, intx2, inty2); intmain() { Matsrc, sum, sqrsum; src=imread("1.png", 0); //获取ROI,便于观察(使用ImageWatch插件)intLR=4; intLC=4; intwidth=6; intheight=6; Matroi=src(Rect(LR, LC, width, height)).clone(); //计算积分图integral(src, sum, sqrsum, CV_32S, CV_32F); //利用积分图计算ROI内的像素值总和intvalue=get_block_sum(sum, LR, LC, LR+height, LC+width); cout<<value<<endl; imshow("原图", src); waitKey(0); return0; } intget_block_sum(Mat&sum, intx1, inty1, intx2, inty2) { intBottomRight=sum.at<int>(x2, y2); intTopLeft=sum.at<int>(x1, y1); intTopRight=sum.at<int>(x1, x2); intBottomLeft=sum.at<int>(x2, y1); intsum_value= (BottomRight+TopLeft-TopRight-BottomLeft); returnsum_value; }