第九章
对输入轴帧实时处理,这可以为动态的机器或者设备提供动力,让设备监视周围的环境,寻找感兴趣的对象,运动,图案,颜色等等。我们只需要确保正确的将每一帧都读取到cv::Mat类实例中,然后作为一个又一个图像传递到这些函数中。本章学习MeanShift算法和CamShift算法来实现实时对象跟踪,并且继续对视频进行运动分析。背景/前景检测是也是本章学习的重点。
9.1 理解直方图
在灰度图像中,直方图图形就是表示在灰度图像中包含每个可能强度(0-255之间的值)的像素数量。在RGB彩色图像中,直方图可以是三个图,每个图表示包含所有可能的红色、绿色或者蓝色强度的像素数量,注意:像素值不一定表示颜色或强度值。例如在HSV(色调,饱和度,值)颜色空间的颜色图像中,直方图将包含色调、饱和度以及数值数据。
OpenCV中的直方图使用calcHist函数进行计算,并储存在Mat类中,使用以下参数:
images\input images:用来计算的图像
nimagess:第一个参数中的图像数量
channels:数组,包含用于计算直方图通道的索引号
mask:用来掩膜图像,以便只输入图像的一部分计算直方图,不需要则传入空Mat类
hist:是输出直方图,是Mat类,用计算的直方图填满
dims:直方图的维度 1-32之间取值
histSize:是包含的直方图每一维大小的数组,或者称为组的大小
ranges:数组的数组,包含每个通道值的范围
uniform:布尔标志,标记直方图是否均匀
accumulate:布尔标志,用于标记计算之前是否应该清除直方图
例子:
int bins = 256;
int channels[] = {0};
int histSize[] = { bins };
float rangeGray[] = {0,255};
const float* ranges[] = { rangeGray };
Mat histogram;
calcHist(&grayImg,
1, //number of images
channels,
Mat(), //no masks, an empty Mat
histogram,
1, //dimensionality
histSize,
ranges,
true, //uniform
false); //not accumulate
使用下列代码绘制储存在这个Mat类中的每个值,输出将是柱状图中的直方图可视化:
将直方图中的每个值绘制为一个矩形,对每个矩形而言,左上角的点用图像的value变量和宽除以组(bin)数(即histSize)来计算。
double maxVal = 0;
minMaxLoc(histogram,
Q_NULLPTR, //don't need min
&maxVal,
Q_NULLPTR, //don't need index min
Q_NULLPTR); //don't need index max
outputImage.creat(640, //any image width
360, //any image height
CV_8UC(3));
outputImage = Scalar::all(128); //empty grayish image
Point p1(0,0),p2(0,outputImage.rows-1);
for(int i = 0; i < bins; i++){
float value = histogram.at<float>(1,0);
value = manVal - value; //invert
value = value / maxVal * outputImage.rows; //scale
p1.y = value;
p2.x = float( i + 1) * float(outputImage.cols) //float( bins );
rectangle(outputImage,
p1,
p2,
Scalar::all(0),
CV_FILLED);
p1.x = p2.x;
}
如何分割图像得到三个Mat类,每个类代表一个单独的通道:
vector<Mat> planes;
split(inputImage, planes);
9.2 理解反投影图像
可以使用直方图的反投影对图像进行修改。
//计算反投影
calcBackProject(&image,
1,
channels,
histogram,
backprojection,
ranges);
//对直方图结果标准化,否则反投影数值会溢出
normalize(histogram,
histogram,
0,
255,
NORM_MINMAX); // 将值控制在0-255
转换为HSV颜色空间
Mat hsvImg;
cvtColor(inputImage, hsvImg, CV_BGR2HSV);
9.2.1 直方图比较
使用calcHist函数计算出的两个直方图,可以通过使用compareHist方法进行相互比较,以找出二者之间的距离或者差异(散度),compareHist接受的参数:
HISTCMP_CORREL、HISTCMP_CHISQR、HISTCMP_INTERSECT、
HISTCMP_BHATTACHARYYA、HISTCMP_HELLINGER、HISTCMP_CHISQR_ALT、
HISTCMP_KLDIV
下面这个例子是使用现存方法来计算两张图像之间的差异:
Mat img1 = imread();
Mat img2 = imread();
float range[] = { 0, 255 };
const float* ranges[] = { range };
int bins[] = { 100 };
Mat hist1,hist2;
calcHist(&img1, 1, 0, Mat(), hist1, 1, bins, ranges );
calcHist(&img2, 1, 0, Mat(), hist2, 1, bins, ranges );
qDebug()<<compareHist(hist1, hist2, HISTCMP_CORREL);
qDebug()<<compareHist(hist1, hist2, HISTCMP_CHISQR);
qDebug()<<compareHist(hist1, hist2, HISTCMP_INTERSECT);
qDebug()<<compareHist(hist1, hist2, HISTCMP_BHATTACHARYYA);
qDebug()<<compareHist(hist1, hist2, HISTCMP_HELLINGER);
qDebug()<<compareHist(hist1, hist2, HISTCMP_KLDIV);
9.2.2 直方图均衡化
图像的直方图可以用来调整图像的亮度和对比度。OpenCV提供了称为equalizeHist的函数,他可以在内部计算一个给定图像的直方图,然后正则化此直方图,并计算直方图的积分(所有bin之和),最后使用更新的直方图作为一个查找表来更新输入图像的像素,从而得到输入图像中的标准化亮度和对比度。
equalizeHist(image, equalizedImg);
9.3 MeanShift算法和CamShift
MeanShift有一个非常使用的定义:查找反投影图像上的对象
原理就是将一个反投影图像中的白色像素看做二维平面上的散点,MeanShift就是一个迭代方法,用来寻找平面上点分布最密集的地方,用一个小方框不断迭代寻找质量中心,直到需要的移动小于给定的阈值或者达到最大迭代次数。
9.4 背景/前景检测
也叫做背景差分,该方法可以用在图像中区域分运动或变化区域,而不是那些或多或少的恒定或静态区域。
OpenCV中默认安装了BackgroundSubtractorKNN和BackgroundSubtractorMOG2两个方法,这些类也来自cv::Algorithm类。
Ptr<BackgroundSubtractorMOG2> subtractor = createBackgroundSubtractorMOG2();
注意,背景差分只需要构造BackgroundSubtractorMOG2类和调用apply函数,在使用时再没有其他操作。可以使用默认参数,也可以自行修改:
history:默认为500,是影响背景差分算法的最后帧数
varThreshold:默认为16,是算法的方差阈值
detectShadows:默认情况为ture,可用于忽略或者计算检测到的阴影变化
切换类很简单,就像下面这样:
Ptr<BackgroundSubtractorKNN> subtractor = createBackgroundSubtractorKNN();
大部分参数与上文函数相同:
history:与之前相同 (增加history值,有助于检测更小的运动)
detectShadows:也与之前相同
dist2Threshold:默认值为400.0,是像素和样本之间距离平方的阈值