当逐像素地对像素进行邻域操作时(比如邻域求和,求邻域图像块的直方图等),运算量就会很大。这个时候可以想到用积分图像,可以这样看,积分图像是占用了前期时间,减少了后期大量重复运算。
下面介绍一种快速计算图像任意子区域直方图的方法,方法实现的过程描述如下:
假设最终得到的直方图箱子数(通俗点就是直方图的横坐标的单位数)为nplanes,nplanes是2的幂;输入的灰度图大小为rows*cols。
首先创建一个rows*cols大小、nplanes个通道的图像planes,可以将每个通道理解为二维的箱子(而通常直方图的箱子都是一维的);
然后将planes的每个通道按一定规则填充二值图像,在当前通道的某位置,若源图像该位置的灰度值属于该箱子则值为1,否则为0;
//将灰度图转化为二值图的多通道图像
//nplanes为通道数,可以是256,128,...必须是2的幂,其实就是减色
void convertToBinaryPlane(const cv::Mat& input,cv::Mat& output,int nplanes)
{
//屏蔽位数
int n = 8 - static_cast<int>(log(static_cast<float>(nplanes)) / log(2.0));
uchar mask = 0xff << n;
//减色后的输入图像
cv::Mat reduced;
reduced=input & mask;
//计算每个二值平面
std::vector<cv::Mat> planes;
for (int i = 0; i < nplanes; i++)
{
planes.push_back(reduced==(i<<n)&0x1);
}
//创建多通道图像
cv::merge(planes,output);//planes和output仅仅是形式的区别,一个vector,一个是mat。所以这里output即是叙述中的planes,
}
再计算planes的积分图像。
最后任意划出一块区域,就可以利用积分图像分别计算每个箱子的值,每个箱子只需要计算4次(2次加法2次减法),计算复杂度为nplanes*4。
//后两步包括计算积分图像和计算任意区域直方图,通过一个很巧妙的模板类来实现
template<typename T,int N>
class IntegralImage
{
private:
cv::Mat integralImage;
public:
IntegralImage(cv::Mat image)
{
//计算积分图像,很耗时,待计算的图像大小为cols*rows*N
cv::integral(image, integralImage, cv::DataType<T>::type);
}
//共N层,每层返回一个数,总的就是一个vector,或者说N行1列的Mat。
//这样就通过()操作符,实现了就算任意子区域直方图计算
cv::Vec<T, N> operator()(int x0,int y0,int width,int height)
{
return (integralImage.at<cv::Vec<T, N>>(y0 + height, x0 + width) -
integralImage.at<cv::Vec<T, N>>(y0, x0 + width) -
integralImage.at<cv::Vec<T, N>>(y0 + height, x0) +
integralImage.at<cv::Vec<T, N>>(y0, x0));
}
};
怎么使用上面的方法还有类呢?
例,输入图像为image,求取子区域Rect(x0,y0,width,height)的直方图,直方图的箱子数设为N,代码如下:
//首先创建N个平面的二值图像
cv::Mat planes;
convertToBinaryPlane(image,planes,N);
//然后计算积分图像
IntegralImage<float, N> intHistogram(planes);
//用积分图像计算N个箱子的直方图
cv::Vec<float,N> ROIHist = intHistogram(x0, y0, width, height);
最后总结一句:这个模板类设计得实在是太棒了。目前我还尚未驾驭模板类哪~