用积分图像统计像素 - sumpig/OpenCV GitHub Wiki
累计图像某个子区域内的像素总数是很多计算机视觉算法中的常见过程。
现在假设需要对图像中的多个感兴趣区域计算几个此类直方图,这些计算过程马上都会变得非常耗时。这种情况下,积分图像 可以极大地提高统计图像子区域像素的效率。
常规获得感兴趣区域全部像素的累加和的方法:
// 打开图像
cv::Mat image= cv::imread("bike55.bmp",0);
// 定义图像的ROI
int xo = 97, yo = 112, width = 25, height = 30;
cv::Mat roi(image,cv::Rect(xo, yo, width, height));
// 计算累加值,返回一个多通道图像下的Scalar 数值
cv::Scalar sum= cv::sum(roi);
使用积分图像来计算累加和:
// 计算积分图像
cv::Mat integralImage;
cv::integral(image, integralImage, CV_32S);
// 用三个加/减运算得到一个区域的累加值
int sumInt = integralImage.at<int>(yo+height, xo+width) –
integralImage.at<int>(yo+height, xo) –
integralImage.at<int>(yo, xo+width) +
integralImage.at<int>(yo, xo);
两种做法得到的结果是一样的。但计算积分图像需要遍历全部像素,因此速度比较慢。关键在于,一旦这个初始计算完成,你只需要添加四个像素就能得到感兴趣区域的累加和,与区域大小无关。 因此,如果需要在多个尺寸不同的区域上计算像素累加和,最好采用积分图像。
对光影不均匀的图像应用阈值来创建二值图像。
采用局部阈值,即根据每个像素的邻域计算阈值。这种策略称为自适应阈值化。
OpenCV 中 adaptiveThreshold 函数实现了这种方法:
cv::Mat image = cv::imread("book.jpg", 0);
cv::Mat binaryFixed;
int blockSize = 21;
int threshold = 10;
cv::adaptiveThreshold(image, // 输入图像
binaryAdaptive, // 输出二值图像
255, // 输出的最大值
cv::ADAPTIVE_THRESH_MEAN_C, // 方法
cv::THRESH_BINARY, // 阈值类型
blockSize, // 块的大小
threshold); // 阈值
上面的代码实现了以下功能:
cv::Mat image = cv::imread("book.jpg", 0);
cv::Mat binary = image.clone();
int nl = binary.rows;
int nc = binary.cols;
int blockSize = 21; // 邻域的尺寸
int threshold = 10; // 像素将与(mean-threshold)进行比较
cv::Mat iimage;
cv::integral(image, iimage, CV_32S);
// 逐行
int halfSize = blockSize / 2;
for (int j=halfSize; j<nl-halfSize-1; j++) {
// 得到第 j 行的地址
uchar* data = binary.ptr<uchar>(j); // 第 10 行
int* idata1 = iimage.ptr<int>(j-halfSize); // 第 0 行
int* idata2 = iimage.ptr<int>(j+halfSize+1);// 第 21 行
// 一个线条的每个像素
for (int i=halfSize; i<nc-halfSize-1; i++) {
// 计算累加平均值
int sum = (idata2[i+halfSize+1] - idata2[i-halfSize] -
idata1[i+halfSize+1] + idata1[i-halfSize]) / (blockSize*blockSize);
// 应用自适应阈值
if (data[i] < (sum-threshold))
data[i] = 0;
else
data[i] = 255;
}
}
还可以使用图像运算符来编写自适应阈值化过程:
cv::Mat filtered;
cv::Mat binaryFiltered;
// boxFilter 计算矩形区域内像素的平均值
cv::boxFilter(image, filtered, CV_8U, cv::Size(blockSize,blockSize));
// 检查像素是否大于(mean + threshold)
binaryFiltered = image >= (filtered-threshold);