计算机视觉
图像处理

OpenCV—使用积分图像统计像素

文章目录

本文为对《Opencv计算机视觉编程攻略(第二版)》4.8节积分图像部分的学习笔记。

1.积分图像的基本概念

(1)为什么要用积分图像?

直方图的计算方法为遍历图像的全部像素并累计每个强度值在图像中出现的次数。有时仅需要计算图像中某个特定区域的直方图,而如果需要计算图像中多个区域的直方图,这些计算过程将变得非常耗时。在这种情况下使用积分图像将极大地提高统计图像子区域像素的效率。积分图像在程序中应用非常广泛。

(2)什么是积分图像?

积分图像的定义:取图像左上侧的全部像素计算累加和,并用这个累加和替换图像中的每一个像素,使用这种方式得到的图像称为积分图像。

(3)如何计算积分图像?

计算积分图像的函数:C++接口

  • void integral(InputArray image,OutputArray sum, int sdepth=-1 )
  • void integral(InputArray image,OutputArray sum, OutputArray sqsum, int sdepth=-1 )
  • void integral(InputArray image,OutputArray sum, OutputArray sqsum, OutputArray tilted,int sdepth=-1 )

函数参数:

  • image   输入W×H源图像,8bit字符型,或32bit、64bit浮点型矩阵
  • sum     输出(W+1)×(H +1)积分图像,32bit整型或32bit、64bit浮点型矩阵
  • sqsum   输出(W+1)×(H +1)平方积分图像,双精度浮点型矩阵。
  • tilted     输出旋转45°的(W+1)×(H +1)积分图像,数据类型同sum
  • sdepth  积分图像sum或titled的位深度:CV_32S、CV_32F或CV_64F

根据定义,计算源图像的积分图像的函数执行如下操作:

按照上面的公式,P0点的积分值为蓝色方框中像素的强度累加和,P3点的积分像素为黄色方框内像素点的强度累加和。从上图可知,想要从原始图像获得积分图像,只需要对整幅图像扫描一次。这是因为对同一行(列)的相邻两像素,当前像素的积分值等于上一像素的积分值加上当前像素所在列(行)的左上侧累计值。可见,积分图像是一个包含像素累加和的新图像。为了防止累加和过大溢出,积分图像的值通常都使用int类型变量(CV_32S)或float类型变量(CV_32F)。

对于多通道图像,每个通道都将独立累积计算积分图像。

(4)如何使用积分图像?

假设一幅图像中4个点ABCD,其积分图像中A(x1,y1)点的值为其左上侧所有像素的值的累加和,也就是蓝色区域中所有像素点的值累加和。同理积分图像中的B(x2,y1)、C(x1,y2)、D(x2,y2)点值分别是绿色、紫色和黄色区域像素值的累加和。ABCD四点的位置关系在右下角图所示。

那么如果需要计算由ABCD组成的ROI的累加值就只需要使用如下公式:

即:D-C-B+A。显然,计算量不受区域尺寸的影响。所以,如果需要在多个尺寸的区域上计算像素累加和,最好采用积分图像。

2.利用积分图像进行自适应阈值分割

局部阈值,即根据每个像素的邻域计算阈值。这种策略叫做自适应阈值化,包括将每个像素的值与邻域的平均值进行比较。如果某像素的值与它的局部平均值差别很大,就会被当做异常值在阈值化过程中剔除。因此,自适应阈值化需要计算每个像素周围的局部平均值。这需要多次计算图像窗口的累计值,可以通过积分图像来提高计算效率。

(1)自适应阈值分割函数AdaptiveThreshold()

首先,读取一幅图片

  1. cv::Mat image= cv::imread(“f:\\images\\book.jpg”,0);
  2.     if (!image.data)
  3.         return 0;
  4.     // rotate the image for easier display
  5.     cv::transpose(image, image);
  6.     cv::flip(image, image, 0);
  7.     // display original image
  8.     cv::namedWindow(“Original Image”);
  9.     cv::imshow(“Original Image”,image);

原始图像

观察一下使用固定阈值分割得到的结果

  1. // using a fixed threshold
  2.     cv::Mat binaryFixed;
  3.     cv::Mat binaryAdaptive;
  4.     cv::threshold(image,binaryFixed,180,255,cv::THRESH_BINARY);//固定阈值分割
  5.     cv::namedWindow(“Fixed Threshold”);
  6.     cv::imshow(“Fixed Threshold”,binaryFixed);

使用固定阈值分割的结果

显然,固定阈值难以完成这个任务,难以给出不同光照区域的最佳分割。

对于这类问题,可以使用自适应阈值分割。实现自适应阈值分割有很多方法,最简单的就是直接使用opencv中的自适应阈值分割函数。

  1. // using as adaptive threshold
  2.     int blockSize= 21; // size of the neighborhood
  3.     int threshold=10;  // pixel will be compared to (mean-threshold)
  4.     cv::adaptiveThreshold(image,             // input image
  5.                       binaryAdaptive,    // output binary image
  6.                   255,               // max value for output
  7.                       cv::ADAPTIVE_THRESH_MEAN_C, // adaptive method方法
  8.                   cv::THRESH_BINARY, // threshold type
  9.                   blockSize,         // size of the block
  10.                   threshold);        // threshold used
  11.     cv::namedWindow(“Adaptive Threshold”);
  12.     cv::imshow(“Adaptive Threshold”,binaryAdaptive);

自适应阈值函数cv::AdaptiveThreshold的分割结果

(2)使用积分图像integral()实现自适应阈值分割

当然,也可以自己写程序实现自适应阈值分割

  1. // compute integral image
  2.     IntegralImage<int,1> integral(image);//IntegralImage为计算积分图像的方法封装的模板类,实例化一个变量
  3.     cv::Mat binary= image.clone();//深度复制图像
  4.     // 使用指针遍历图像来计算积分图像
  5.     int nl= binary.rows; // number of lines
  6.     int nc= binary.cols; // total number of elements per line
  7.     // compute integral image
  8.     cv::Mat iimage;
  9.     cv::integral(image,iimage,CV_32S);//计算积分图像
  10.     // for each row
  11.     int halfSize= blockSize/2;
  12.     <span style=”white-space:pre”>    </span>for (int j=halfSize; j<nl-halfSize-1; j++) {
  13.           // get the address of row j
  14.           uchar* data= binary.ptr<uchar>(j);
  15.           int* idata1= iimage.ptr<int>(j-halfSize);
  16.           int* idata2= iimage.ptr<int>(j+halfSize+1);
  17.           // for pixel of a line
  18.           for (int i=halfSize; i<nc-halfSize-1; i++) {
  19.               // compute sum
  20.               int sum= (idata2[i+halfSize+1]-idata2[i-halfSize]-
  21.                         idata1[i+halfSize+1]+idata1[i-halfSize])/(blockSize*blockSize);
  22.               // apply adaptive threshold
  23.               if (data[i]<(sum-threshold))
  24.                   data[i]= 0;
  25.               else
  26.                   data[i]=255;
  27.           }
  28.     }
  29.     cv::namedWindow(“Adaptive Threshold (integral)”);
  30.     cv::imshow(“Adaptive Threshold (integral)”,binary);

在积分图像上实现自适应阈值分割

用积分图像实现自适应阈值分割的结果与使用自适应阈值分割函数AdaptiveThreshold()完全一样。上段代码中的IntegralImage类,在integral.h头文件中定义,该步骤将积分计算的过程封装进了模板类中。

  1. #if !defined IINTEGRAL
  2. #define IINTEGRAL
  3. #include <opencv2/core/core.hpp>
  4. #include <opencv2/highgui/highgui.hpp>
  5. #include <opencv2/imgproc/imgproc.hpp>
  6. #include <vector>
  7. template <typename T, int N> //将积分计算过程封装进模板类<T>为向量数据类型,<N>为向量数量
  8. class IntegralImage {
  9.       cv::Mat integralImage;
  10.   public:
  11.       IntegralImage(cv::Mat image) {
  12.         // (costly) computation of the integral image
  13.         cv::integral(image,integralImage,cv::DataType<T>::type);
  14.       }
  15.       // compute sum over sub-regions of any size from 4 pixel access
  16.       cv::Vec<T,N> operator()(int xo, int yo, int width, int height) {
  17.           // window at (xo,yo) of size width by height
  18.           return (integralImage.at<cv::Vec<T,N> >(yo+height,xo+width)//Vec<T,N> ,T为类型,N为向量元素数量
  19.                   -integralImage.at<cv::Vec<T,N> >(yo+height,xo)
  20.                   -integralImage.at<cv::Vec<T,N> >(yo,xo+width)
  21.                   +integralImage.at<cv::Vec<T,N> >(yo,xo));
  22.       }
  23.       // compute sum over sub-regions of any size from 4 pixel access
  24.       cv::Vec<T,N> operator()(int x, int y, int radius) {
  25.           // square window centered at (x,y) of size 2*radius+1
  26.           return (integralImage.at<cv::Vec<T,N> >(y+radius+1,x+radius+1)
  27.                   -integralImage.at<cv::Vec<T,N> >(y+radius+1,x-radius)
  28.                   -integralImage.at<cv::Vec<T,N> >(y-radius,x+radius+1)
  29.                   +integralImage.at<cv::Vec<T,N> >(y-radius,x-radius));
  30.       }
  31. };
  32. // convert to a multi-channel image made of binary planes
  33. // nPlanes must be a power of 2
  34. void convertToBinaryPlanes(const cv::Mat& input, cv::Mat& output, int nPlanes) {
  35.         // number of bits to mask out
  36.         int n= 8-static_cast<int>(log(static_cast<double>(nPlanes))/log(2.0));
  37.         // mask used to eliminate least significant bits
  38.         uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
  39.         // create a vector of 16 binary images
  40.         std::vector<cv::Mat> planes;
  41.         // reduce to nBins bins by eliminating least significant bits
  42.         cv::Mat reduced= input&mask;
  43.         // compute each binary image plane
  44.         for (int i=0; i<nPlanes; i++) {
  45.             // 1 for each pixel equals to i<<shift
  46.             planes.push_back((reduced==(i<<n))&0x1);
  47.         }
  48.         // create multi-channel image
  49.         cv::merge(planes,output);
  50. }
  51. #endif

(3)使用图像运算符来实现自适应阈值分割

还可以使用OpenCV的图像运算符来实现自适应阈值分割的过程。

  1. // adaptive threshold using image operators
  2.     cv::Mat filtered;
  3.     cv::Mat binaryFiltered;
  4.     cv::boxFilter(image,filtered,CV_8U,cv::Size(blockSize,blockSize));
  5.     filtered= filtered-threshold;
  6.     binaryFiltered= image>= filtered;

通过实验,上述3中方法中,使用第一种自适应阈值化函数花费的时间较少。

转载注明来源:CV视觉网 » OpenCV—使用积分图像统计像素

分享到:更多 ()
扫描二维码,给作者 打赏
pay_weixinpay_weixin

请选择你看完该文章的感受:

0不错 0超赞 0无聊 0扯淡 0不解 0路过

评论 抢沙发

评论前必须登录!