积分图像绘制直方图
- 在例4.2中,我们可以利用
histogram.h
头文件中的Histogram1D
类生成一维直方图,可以利用colorhistogram.h
头文件中的ColorHistogram
类生成三维直方图、色调直方图、ab色度直方图等等。 - 本例将在灰度图像中定位物体,需要在一幅图像中计算大量感兴趣区域的直方图,如果使用
Histogram1D
类生成直方图效率较低,故使用积分图像绘制直方图。 - 积分图像绘制直方图的优点是:在类对象构造的时候耗费时间较长,但构造完只需简单的加减计算即可得出其内部区域的直方图。本例封装
IntegralImage
模板类,代码如下。
//------------------【程序说明】----------------------
//本头文件为计算积分图像等功能封装IntegralImage模板类
//创建时间:2020/12/22 作者:Eric 南京航空航天大学
//----------------------------------------------------
//-----------------【具体功能】-----------------------
//1.通过访问四个像素,计算任何尺寸子区域的累计值
//2.通过访问四个像素,计算任何方形子区域的累计值
//3.转换成二值图层组成的多通道图像
//----------------------------------------------------
//利用积分图像生成直方图具体步骤:
//【1】先用convertToBinaryPlanes函数创建多通道二值图
//【2】再通过利用二值图有参构造创建IntegralImage类对象
//【3】重载()即可返回一个cv::Vec<T, N>类向量即为直方图
//----------------------------------------------------
#pragma once
#include <opencv2/opencv.hpp>
#include <vector>
template <typename T, int N>
class IntegralImage
{
private:
cv::Mat integralImage;
public:
IntegralImage(cv::Mat image)
{
//计算积分图像(耗时)
cv::integral(image, integralImage, cv::DataType<T>::type);
}
//通过访问四个像素,计算任何尺寸子区域的累计值
cv::Vec<T, N> operator()(int xo, int yo, int width, int height)
{
//(xo,yo)处的窗口,尺寸为width x height
return (integralImage.at<cv::Vec<T, N> >(yo + height, xo + width)
- integralImage.at<cv::Vec<T, N> >(yo + height, xo)
- integralImage.at<cv::Vec<T, N> >(yo, xo + width)
+ integralImage.at<cv::Vec<T, N> >(yo, xo));
}
//通过访问四个像素,计算任何方形子区域的累计值
cv::Vec<T, N> operator()(int x, int y, int radius)
{
//(xo,yo)处的方形窗口,尺寸为radius+1 x radius+1
return (integralImage.at<cv::Vec<T, N> >(y + radius + 1, x + radius + 1)
- integralImage.at<cv::Vec<T, N> >(y + radius + 1, x - radius)
- integralImage.at<cv::Vec<T, N> >(y - radius, x + radius + 1)
+ integralImage.at<cv::Vec<T, N> >(y - radius, x - radius));
}
};
//转换成二值图层组成的多通道图像
//nPlanes必须是2的幂
void convertToBinaryPlanes(const cv::Mat& input, cv::Mat& output, int nPlanes)
{
//需要屏蔽的位数
int n = 8 - static_cast<int>(log(static_cast<double>(nPlanes)) / log(2.0));
//用来消除最低有效位的掩码
uchar mask = 0xFF << n; //如输入nPlanes=8,mask=0xE0
//创建二值图像的向量
std::vector<cv::Mat> planes;
//消除最低有效位
cv::Mat reduced = input & mask;
//计算每个二值图像平面
for (int i = 0; i < nPlanes; i++)
{
//将每个i<<shift的像素设为1
planes.push_back((reduced == (i << n)) & 0x1);
}
//创建多通道图像
cv::merge(planes, output);
}
- 利用积分图像生成直方图具体步骤:
- 先用
convertToBinaryPlanes
函数创建多通道二值图。 - 再通过利用二值图有参构造创建
IntegralImage
类对象。 - 重载()即可返回一个
cv::Vec<T, N>
类向量即为直方图。
视觉追踪实例
//灰度形式读取第一幅图像
cv::Mat image = cv::imread("bike55.bmp", 0);
//定义感兴趣区域
int xo = 97, yo = 112;
int width = 25, height = 30;
cv::Mat roi(image, cv::Rect(xo, yo, width, height));
//通过第一种方式:Histogram1D类创建含有16个箱子的直方图
Histogram1D h;
h.setNBins(16);
//计算感兴趣区域的直方图
cv::Mat refHistogram = h.getHistogram(roi);
//灰度形式读取第二幅图像
cv::Mat secondImage = cv::imread("bike65.bmp", 0);
//通过第二种方式:积分图像创建含有16个箱子的直方图
//创建16通道的二值图像
cv::Mat planes;
convertToBinaryPlanes(secondImage, planes, 16);
//计算积分图像
IntegralImage<float, 16> intHistogram(planes);
//准备生成直方图
cv::Vec<float, 16> histogram;
//准备最大相似值和最优位置坐标
double maxSimilarity = 0.0;
int xbest, ybest;
//遍历原始图像中女孩位置周围的水平长条
for (int y = 110; y < 120; y++)
{
for (int x = 0; x < secondImage.cols - width; x++)
{
//用积分图像计算直方图
histogram = intHistogram(x, y, width, height);
//计算与基准直方图的差距
double distance = cv::compareHist(refHistogram, histogram, cv::HISTCMP_INTERSECT);
//找到最像素直方图的位置
if (distance > maxSimilarity)
{
xbest = x;
ybest = y;
maxSimilarity = distance;
}
std::cout << "Distance(" << x << "," << y << ")=" << distance << std::endl;
}
}
std::cout << "Best solution= (" << xbest << "," << ybest << ")=" << maxSimilarity << std::endl;
//在第一幅图中标出感兴趣区域
cv::rectangle(image, cv::Rect(xo, yo, width, height), 0);
cv::imshow("Initial Image", image);
//在第二幅图中在搜索区域画矩形
cv::rectangle(secondImage, cv::Rect(0, 110, secondImage.cols, height + 10), 255);
//在第二幅图中在最优位置画矩形
cv::rectangle(secondImage, cv::Rect(xbest, ybest, width, height), 0);
cv::imshow("Object location", secondImage);
cv::waitKey();
效果
总结
可以增大箱子个数提升查找准确度,也可以修改搜索窗口大小,同时计算量也同步上升。