前言:
有时候只需要计算图像中某个特定区域的直方图。实际上累计图像的某个子区域内的像素总和,是很多计算机视觉算法中常见的过程。现在假设需要对图像中的多个兴趣区域计算几个此类直方图。这些计算过程都马上会变得非常耗时。这种情况下,有一个工具可以极大地提高统计图像子区域像素的效率:积分图像。
一、积分图像的原理
为了理解积分图像的实现原理,我们先对它下一个定义。取图像左上侧的全部像素计算累加和,并用这个累加和替换图像中的每一个像素,用这种方式得到的图像称为积分图像。计算积分图像时只需对图像扫描一次。这是因为当前像素的积分值等于上一像素的积分值加上当前行的累计值。因此积分图像就是一个包含像素累加和的新图像。为了防止溢出,积分图像的值通常采用int类型或float类型。例如下图中,积分图像的像素L1包含左上角区域。
计算完积分图像后,只需要访问四个像素就可以得到任何矩形区域的像素累加和。这里解释一下原因。再来看看前面的图片,计算由L1、L2、L3、L4四个像素表示区域的像素累计和,先读取L4的积分值,然后减去L2的像素值和L3左手边区域的像素值。但是这样就把L1左上角的像素累加和减去两次,因此需要重新加上L1的积分值。所以计算L1、L2、L3、L4区域内的像素累加的正式公式为:L4-L2-L3+L1。
二、代码实现
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
int main( )
{
Mat src_img = imread("test3.png");
if (src_img.empty())
{
printf("could not load the image...\n");
return -1;
}
namedWindow("原图", CV_WINDOW_AUTOSIZE);
imshow("原图", src_img);
Mat gray_img;
cvtColor(src_img, gray_img,COLOR_BGR2GRAY);
namedWindow("灰度图", CV_WINDOW_AUTOSIZE);
imshow("灰度图", gray_img);
Mat sum_img = Mat::zeros(gray_img.rows + 1, gray_img.cols + 1, CV_32FC1);
Mat sqsum_img = Mat::zeros(gray_img.rows + 1, gray_img.cols + 1, CV_64FC1);
integral(gray_img, sum_img, sqsum_img);
Mat result_img;
normalize(sum_img, result_img, 0, 255, NORM_MINMAX, CV_8UC1, Mat());
imshow("Integral-Image", result_img); // 显然积分图像是右下角亮,左上角暗
waitKey(0);
return 0;
}
运行程序,如下所示:
三、对比实验
假设我们有一个兴趣区域 ,如下所示:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
int main( )
{
Mat src_img = imread("test3.png");
if (src_img.empty())
{
printf("could not load the image...\n");
return -1;
}
Mat gray_img;
cvtColor(src_img, gray_img,COLOR_BGR2GRAY);
rectangle(src_img,Rect(240,240,100,100), Scalar(0, 0, 255), 2, 8, 0);
namedWindow("原图", CV_WINDOW_AUTOSIZE);
imshow("原图", src_img);
waitKey();
return 0;
}
如果我们要获取上面红色框出的兴趣区域全部像素的累加和,通常有两种方法,如下所示:
(一) 采用常规方法:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main( )
{
Mat src_img = imread("test3.png");
if (src_img.empty())
{
printf("could not load the image...\n");
return -1;
}
namedWindow("原图", CV_WINDOW_AUTOSIZE);
imshow("原图", src_img);
Mat gray_img;
cvtColor(src_img, gray_img,COLOR_BGR2GRAY);
int xo = 240, yo = 240;
int width = 100, height = 100;
Mat roi_img = gray_img(Rect(xo, yo, width, height)).clone(); //定义图像的ROI(兴趣区域)
namedWindow("兴趣区域", CV_WINDOW_AUTOSIZE);
imshow("兴趣区域", roi_img);
//计算兴趣区域灰度值的累加值
int integral_value = sum(roi_img)[0];
cout << integral_value << endl;
waitKey();
return 0;
}
运行程序,如下:
(二) 采用积分图法
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main( )
{
Mat src_img = imread("test3.png");
if (src_img.empty())
{
printf("could not load the image...\n");
return -1;
}
namedWindow("原图", CV_WINDOW_AUTOSIZE);
imshow("原图", src_img);
Mat gray_img;
cvtColor(src_img, gray_img,COLOR_BGR2GRAY);
int xo = 240, yo = 240;
int width = 100, height = 100;
Mat roi_img = gray_img(Rect(xo, yo, width, height)).clone(); //定义图像的ROI(兴趣区域)
namedWindow("兴趣区域", CV_WINDOW_AUTOSIZE);
imshow("兴趣区域", roi_img);
//计算兴趣区域灰度值的累加值
Mat sum_img = Mat::zeros(gray_img.rows + 1, gray_img.cols + 1, CV_32SC1);
Mat sqsum_img = Mat::zeros(gray_img.rows + 1, gray_img.cols + 1, CV_64FC1);
integral(gray_img, sum_img, sqsum_img); // 计算积分图
// 用三个加/减运算得到兴趣区域的累加值
int integral_value = sum_img.at<int>(yo + height, xo + width) - sum_img.at<int>(yo + height, xo)
- sum_img.at<int>(yo, xo + width)
+ sum_img.at<int>(yo, xo);
cout << integral_value << endl;
waitKey();
return 0;
}
运行程序,如下:
经过对比,其实两种做法得到的结果是一样的。但计算积分图像需要遍历全部像素,因此速度比较慢。关键是一旦这个初始计算完成,只需要通过四个像素就能得到兴趣区域的累加和,与区域的尺寸无关。因此,如果需要在多个兴趣区域上计算像素的累加和,就最好采用积分图像。