积分图是存储原图左上角矩形区域像素累加和的预处理表,使任意矩形区域求和从O(n)降至O(1);其核心公式为sum = iir2+1 - iir1 - iir2+1 + iir1,均值即该和除以区域面积。

什么是积分图,为什么它能加速区域均值计算
积分图(Integral Image)本质是一张预处理的累加表:ii[i][j] 存储原图左上角矩形区域 (0,0) 到 (i,j) 的所有像素和。一旦构建完成,任意矩形区域 (r1,c1) 到 (r2,c2) 的像素和只需 4 次查表 + 3 次加减:sum = ii[r2][c2] - ii[r1-1][c2] - ii[r2][c1-1] + ii[r1-1][c1-1]
均值就是这个和除以区域面积。它把每次区域求和从 O(n) 降到 O(1),代价是 O(H×W) 预处理时间和等大的额外内存。
用 OpenCV 构建积分图最稳妥的方式
OpenCV 的 cv::integral() 是首选,它自动处理边界、数据类型转换和内存对齐:
- 输入必须是单通道(
CV_32F或CV_64F最安全;CV_8U会隐式转为CV_32S,但大图易溢出) - 输出图像类型默认为
CV_32S(有符号 32 位整数),若原图像素值总和可能超 2³¹−1,务必显式指定CV_64F - 不要自己手写循环实现——边界判断、缓存友好性、SIMD 优化都已被 OpenCV 封装好
cv::Mat img = cv::imread("input.png", cv::IMREAD_GRAYSCALE);
cv::Mat ii;
cv::integral(img, ii, CV_64F); // 关键:用 CV_64F 避免溢出
用积分图算任意矩形区域均值的正确步骤
核心是严格按坐标规则索引 ii,并注意 OpenCV 坐标系(行优先,0-based)和积分图定义(含边界外推):
- 积分图
ii的尺寸是(H+1) × (W+1),第 0 行/列全为 0,所以合法索引范围是[0, H]和[0, W] - 给定 ROI 左上
(r1, c1)、右下(r2, c2)(闭区间,且0 ≤ r1 ≤ r2 , <code>0 ≤ c1 ≤ c2 ),对应积分图查询点为:<ul> <li><code>ii.at<double>(r2+1, c2+1)</double> ii.at<double>(r1, c2+1)</double>ii.at<double>(r2+1, c1)</double>ii.at<double>(r1, c1)</double>
(r2 - r1 + 1) * (c2 - c1 + 1),不能从积分图尺寸推导
double getRegionMean(const cv::Mat& ii, int r1, int c1, int r2, int c2) {
int h = r2 - r1 + 1;
int w = c2 - c1 + 1;
double sum = ii.at<double>(r2+1, c2+1)
- ii.at<double>(r1, c2+1)
- ii.at<double>(r2+1, c1)
+ ii.at<double>(r1, c1);
return sum / (h * w);
}
容易被忽略的三个坑
-
cv::integral() 对 CV_8U 输入默认输出 CV_32S,一张 4000×3000 的灰度图,最大可能和接近 4000×3000×255 ≈ 3.06e9,已超过 INT_MAX (2.14e9),直接溢出变负数——必须强制用 CV_64F
- ROI 坐标传错导致越界访问:比如
r2+1 超过 ii.rows-1,at() 不做检查,会读到随机内存,结果不可预测
- 积分图本身不压缩也不归一化,如果后续要做浮点运算(如归一化模板匹配),建议从一开始就用
CV_32F 或 CV_64F 输入,避免反复类型转换
cv::integral() 对 CV_8U 输入默认输出 CV_32S,一张 4000×3000 的灰度图,最大可能和接近 4000×3000×255 ≈ 3.06e9,已超过 INT_MAX (2.14e9),直接溢出变负数——必须强制用 CV_64F
r2+1 超过 ii.rows-1,at() 不做检查,会读到随机内存,结果不可预测CV_32F 或 CV_64F 输入,避免反复类型转换实际项目里,积分图的价值不在单次计算,而在高频小区域查询(比如滑动窗口、Haar-like 特征)。一旦预处理完,后续成千上万次区域和计算就真的只是四次内存读取加三次加减。


















