Summed Area Table是一种数据结构和算法,用于快速有效地生成网格矩形子集中的值总和。在图像处理领域,它也被称为积分图像。
记源图像为 I m × n I_{m \times n} Im×n,积分图为SAT(Summed Area Table)则
S A T ( x , y ) = Σ i ≤ x , j ≤ y I ( i , j ) SAT(x,y) = \underset{i\leq x,j\leq y}{\Sigma }I(i,j) SAT(x,y)=i≤x,j≤yΣI(i,j)
S Q A T ( x , y ) = Σ i ≤ x , j ≤ y I ( i , j ) 2 SQAT(x,y) = \underset{i\leq x,j\leq y}{\Sigma }I(i,j)^2 SQAT(x,y)=i≤x,j≤yΣI(i,j)2
计算积分图的递推式
S A T ( x , y ) = S A T ( x − 1 , y ) + S A T ( x , y − 1 ) − S A T ( x − 1 , y − 1 ) + I ( x , y ) SAT(x,y) = SAT(x-1,y) + SAT(x,y-1) - SAT(x-1,y-1) + I(x,y) SAT(x,y)=SAT(x−1,y)+SAT(x,y−1)−SAT(x−1,y−1)+I(x,y)
上面的公式中起始项必须从第二行第二列开始,因为递推式中包含-1项,也就是需要单独把第一行和第一列先单独计算出来,后面的所有点都可以用递推式写出来。
一点漂亮的改进
让积分图比原图多一行一列:上面一行,左边一列,且行列初始值为0!这样的话就可以不需要考虑-1项完全采用递推式得到积分图!opencv就是这么干的
图片摘自Wikipedia。1为源图,2为积分图每一个像素点的值都是它在源图上对应位置的上面、左边所有像素值的和。
当要计算源图中的某个矩形区域的像素和时可以用查表的方式快速计算,构建好积分图后求区域和的复杂度将是 O ( 1 ) O(1) O(1)。
设在源图中的矩形 R R R左上角坐标为 ( t p x , t p y ) (tpx,tpy) (tpx,tpy),左下角坐标为 ( b t x , b t y ) (btx,bty) (btx,bty),则计算该矩形区域内所有像素值和可以写为下式。
以【NCC】之一:从Pearson相关系数到模板匹配的NCC方法中的 S x , y S_{x,y} Sx,y的方差计算为例
计算区域方差
σ ( S x , y ) = v a r ( S x , y ) = Σ i = 1 m Σ j = 1 n ( S x , y ( i , j ) − S x , y ˉ ) 2 m n \sigma(S_{x,y})=\sqrt{var(S_{x,y})}=\sqrt{\frac{\Sigma_{i=1}^{m}\Sigma_{j=1}^{n}{(S_{x,y}(i,j)-\bar{S_{x,y}}})^2}{mn}} σ(Sx,y)=var(Sx,y)=mnΣi=1mΣj=1n(Sx,y(i,j)−Sx,yˉ)2
let A = Σ i = 1 m Σ j = 1 n ( S x , y ( i , j ) − S x , y ˉ ) 2 A = \Sigma_{i=1}^{m}\Sigma_{j=1}^{n}{(S_{x,y}(i,j)-\bar{S_{x,y}}})^2 A=Σi=1mΣj=1n(Sx,y(i,j)−Sx,yˉ)2 则有
A = Σ i = 1 m Σ j = 1 n ( S x , y ( i , j ) 2 − 2 S x , y ˉ S x , y ( i , j ) + S x , y ˉ 2 ) = S Q A T ( m , n ) S x , y − 2 S x , y ˉ × S A T ( m , n ) S x , y + m n × S x , y ˉ 2 = S Q A T ( m , n ) S x , y − 2 S x , y ˉ × S A T ( m , n ) S x , y + S x , y ˉ × S A T ( m , n ) = S Q A T ( m , n ) S x , y − S x , y ˉ × S A T ( m , n ) S x , y \begin{aligned} \begin{aligned} A &=\Sigma_{i=1}^{m}\Sigma_{j=1}^{n}( S_{x,y}(i,j)^2 -2\bar{S_{x,y}}S_{x,y}(i,j) + \bar{S_{x,y}}^2) \\ &= \underset{S_{x,y}}{SQAT(m,n)} -\underset{S_{x,y}}{2\bar{S_{x,y}}\times SAT(m,n)} + mn\times \bar{S_{x,y}}^2 \\ &=\underset{S_{x,y}}{SQAT(m,n)}-\underset{S_{x,y}}{2\bar{S_{x,y}}\times SAT(m,n)} + \bar{S_{x,y}}\times SAT(m,n) \\ &=\underset{S_{x,y}}{SQAT(m,n)}-\underset{S_{x,y}}{\bar{S_{x,y}}\times SAT(m,n)} \end{aligned} \end{aligned} A=Σi=1mΣj=1n(Sx,y(i,j)2−2Sx,yˉSx,y(i,j)+Sx,yˉ2)=Sx,ySQAT(m,n)−Sx,y2Sx,yˉ×SAT(m,n)+mn×Sx,yˉ2=Sx,ySQAT(m,n)−Sx,y2Sx,yˉ×SAT(m,n)+Sx,yˉ×SAT(m,n)=Sx,ySQAT(m,n)−Sx,ySx,yˉ×SAT(m,n)
/**
* @brief 计算输入图的积分图,为了提高计算效率,可以让积分图比输入图多一行一列,
* 具体的就是在原图左边插入一列0,上面插入一行0,设原图为I,积分图为SAT(sum area of table)
* 则:SAT(i,j) = SAT(i,j-1) + SAT(i-1,j) - SAT(i-1,j-1) + I(i,j)
* 这样就不用考虑下边界的情况,省掉很多判断条件
*
* @param image : 输入图CV_8UC1,MxN
* @param integral_image : 积分图CV_32FC1,(M+1)x(N+1)
* @return int
*/
int integral(const cv::Mat &image,cv::Mat &integral_image)
{
if(image.empty())
{
MYCV_ERROR(kImageEmpty,"image empty");
return kImageEmpty;
}
int h = image.rows;
int w = image.cols;
integral_image = cv::Mat::zeros(cv::Size(w+1,h+1),CV_64FC1);
//SAT(i,j) = SAT(i,j-1) + SAT(i-1,j) - SAT(i-1,j-1) + I(i,j)
for(int i = 0; i < h ; i++)
{
const uchar *ps = image.ptr<uchar>(i);
double *pd_m1 = integral_image.ptr<double>(i);//integral 的"上一行"
double *pd = integral_image.ptr<double>(i+1); //integral 的"当前行"
for(int j = 0; j < w; j++)
{
pd[j+1] = pd[j] + pd_m1[j+1] - pd_m1[j] + ps[j];
}
}
return kSuccess;
}
/**
* @brief 计算输入图的积分图,为了提高计算效率,可以让积分图比输入图多一行一列,
* 具体的就是在原图左边插入一列0,上面插入一行0,设原图为I,积分图为SAT(summed area table)
* 则:SAT(i,j) = SAT(i,j-1) + SAT(i-1,j) - SAT(i-1,j-1) + I(i,j)
* SQAT(i,j) = SQAT(i,j-1) + SQAT(i-1,j) - SQAT(i-1,j-1) + I(i,j) * I(i,j)
* 这样就不用考虑下边界的情况,省掉很多判断条件
*
* @param image : 输入图CV_8UC1,MxN
* @param integral_image : 积分图CV_32FC1,(M+1)x(N+1)
* @param integral_sq : 平方的积分图CV_32FC1,(M+1)x(N+1)
* @return int
*/
int integral(const cv::Mat &image,cv::Mat &integral_image,cv::Mat &integral_sq)
{
if(image.empty())
{
MYCV_ERROR(kImageEmpty,"image empty");
return kImageEmpty;
}
int h = image.rows;
int w = image.cols;
integral_image = cv::Mat::zeros(cv::Size(w+1,h+1),CV_64FC1);
integral_sq = cv::Mat::zeros(cv::Size(w+1,h+1),CV_64FC1);
//SAT(i,j) = SAT(i,j-1) + SAT(i-1,j) - SAT(i-1,j-1) + I(i,j)
//SQAT(i,j) = SQAT(i,j-1) + SQAT(i-1,j) - SQAT(i-1,j-1) + I(i,j) * I(i,j)
for(int i = 0; i < h ; i++)
{
const uchar *ps = image.ptr<uchar>(i);
double *pd_m1 = integral_image.ptr<double>(i);//integral 的"上一行"
double *pd = integral_image.ptr<double>(i+1); //integral 的"当前行"
double *pqd_m1 = integral_sq.ptr<double>(i);
double *pqd = integral_sq.ptr<double>(i+1);
for(int j = 0; j < w; j++)
{
pd[j+1] = pd[j] + pd_m1[j+1] - pd_m1[j] + (double)ps[j];
pqd[j+1] = pqd[j] + pqd_m1[j+1] - pqd_m1[j] + (double)ps[j] * (double)ps[j];
}
}
return kSuccess;
}
采用积分图计算均值和方差
source image size w,h = (1095,680)
target image size w,h = (89,91)
my NCC run 10 times,average use 4741.000000 ms
opencv NCC run 10 times,average use 15.000000 ms
opencv的速度是这一版本的316.06倍,相比上一版882.78倍还是有点进步。
[1] https://en.wikipedia.org/wiki/Summed-area_table
[2] 积分图(一) - 原理及应用