MeanShift算法(均值漂移)
假设有一堆数据,不均匀的在平面空间分布,首先通过均值计算,评估中心点在哪里,然后中心向密度的地方平移最后找到密度最高的地方。
MeanShift算法使用的直方图比较与模板匹配类似,这两个算法只能匹配Windows固定的roi区域。
如果视频中选择的roi区域的移动忽远忽近(甚至变形),这两种算法就不能再准确的跟踪到roi区域这时候用CAMShift
:窗口尺寸自动变化
:适合变形目标检测
步骤:
是第一帧的话则:
不是第一帧的话则:
相关API:
mixChannels( // 将输入数组的指定通道复制到输出数组的指定通道
const Mat* src, //输入数组或向量矩阵,所有矩阵的大小和深度必须相同。
size_t nsrcs, //矩阵的数量
Mat* dst, //输出数组或矩阵向量,大小和深度必须与src相同
size_t ndsts,//矩阵的数量
const int* fromTo,//指定被复制通道与要复制到的位置组成的索引对
size_t npairs //fromTo中索引对的数目
);
calcHist(// 计算直方图
const Mat* images,//输入图像指针
int images,// 图像数目
const int* channels,// 要计算的通道数的下标,可以传一个数组 {0, 1} 表示计算第0通道与第1通道的直方图,此数组长度要与histsize ranges 数组长度一致
InputArray mask,// 输入mask,可选,如有,则表示只计算mask元素值为255的位置的直方图
OutputArray hist,//输出的直方图数据
int dims,// 维数
const int* histsize,// 直方图级数, 对应 bins
const float** ranges,// 值域范围
bool uniform,// true by default 是否归一化到 0-1 之间
bool accumulate// false by defaut 多通道时true
)
calcBackProject ( // 反向投影
const Mat * images, // 输入图像,图像深度必须位CV_8U,CV_16U或CV_32F中的一种,尺寸相同,每一幅图像都可以有任意的通道数
int nimages, // 输入图像的数量
const int * channels, // 用于计算反向投影的通道列表,通道数必须与直方图维度相匹配,第一个数组的通道是从0到image[0].channels()-1,
第二个数组通道从图像image[0].channels()到image[0].channels()+image[1].channels()-1计数
InputArray hist, // 输入的直方图,直方图的bin可以是密集(dense)或稀疏(sparse)
OutputArray backProject, // 目标反向投影输出图像,是一个单通道图像,与原图像有相同的尺寸和深度
const float ** ranges, // 直方图中每个维度bin的取值范围
double scale = 1, // 可选输出反向投影的比例因子
bool uniform = true // 直方图是否均匀分布(uniform)的标识符,默认值true
)
RotatedRect CamShift( // CAMShift跟踪算法
InputArray probImage, // 反向投影图像
CV_IN_OUT Rect& window, // 输入和输出的搜索窗口/目标窗口,window的尺寸在跟踪过程中会随着跟踪目标的大小变化自动调整
TermCriteria criteria // 迭代收敛终止条件
);
返回值为跟踪到的目标所在的旋转了的矩形框
CAMShift跟踪算法对于跟踪的目标,即使发生远近变换,形变,都能准确跟踪到,甚至跟踪目标离开了屏幕再回来也能继续跟踪到,
但是离开的期间也会产生 selection,所以这个时候的selection是不对的,当跟踪目标回到屏幕后也不是立即就察觉到,需要一定时间
so CAMShift无法判断目标是否离开屏幕,这段期间产生的误差如何解决?
同时CAMShift跟踪算法适合比较简单的图像,如果图像颜色数据很复杂,有很多大量与跟踪目标颜色重复的像素的话(也就是干扰很强),CAMShift效果就不太好了
代码:
#include
#include
#include
#include
#include
#include
#include
using namespace cv::face;
using namespace cv;
using namespace std;
using namespace cv::xfeatures2d;
int smin = 15; // 二值化时,s 最小值的阈值
int vmin = 40; // 二值化时,v 最小值的阈值
int vmax = 256; // 二值化时,s v 最大值的阈值
int bins = 16; // 直方图bins
int smin = 15; // 二值化时,s 最小值的阈值
int vmin = 40; // 二值化时,v 最小值的阈值
int vmax = 256; // 二值化时,s v 最大值的阈值
int bins = 16; // 直方图bins
int main()
{
VideoCapture capture;
capture.open("C:/Users/Administrator/Desktop/pic/3.avi");
bool firstRead = true;
float hrange[] = { 0, 180 };
const float* hranges = hrange;
Rect selection; // roi区域
Mat frame, hsv, hue, mask, hist, backprojection;
Mat drawImg = Mat::zeros(300, 300, CV_8UC3); // 直方图
char roiName[] = "CAMShift Tracking";
while (capture.read(frame))
{
if (firstRead)
{
cout << "frame.size=" << frame.size() << endl;
// 从frame中选择roi区域,程序会阻断,等待选择。返回值为选择的roi区域,参数windowName要与后面的imshow的name一致
Rect2d first = selectROI(roiName, frame); // roi可以选择整个黄色小球区域,也可以只选择其内部一部分区域
selection.x = first.x;
selection.y = first.y;
selection.width = first.width;
selection.height = first.height;
printf("ROI.x= %d, ROI.y= %d, width = %d, height= %d\n", selection.x, selection.y, selection.width, selection.height);
}
// convert to HSV
cvtColor(frame, hsv, COLOR_BGR2HSV);
inRange(hsv, Scalar(0, smin, vmin), Scalar(180, vmax, vmax), mask); // 二值化,为CAMShift跟踪算法去掉一定的干扰
hue = Mat(hsv.size(), hsv.depth());
int channels[] = { 0, 0 }; // hsv 的第0通道,输出到hue的第0通道
mixChannels(&hsv, 1, &hue, 1, channels, 1); // 将输入数组的指定通道复制到输出数组的指定通道
if (firstRead)
{
// ROI 直方图计算
Mat roi(hue, selection); // 抠出hue中roi区域
Mat maskroi(mask, selection); // 抠出mask中roi区域
calcHist(&roi, 1, 0, maskroi, hist, 1, &bins, &hranges); // 这里直方图只计算绘制一次第一帧上选择的roi区域
// hist size=[1 x 16], depth=5, type=5 CV_32F
cout << "hist size=" << hist.size() << ", depth=" << hist.depth() << ", type=" << hist.type() << endl;
normalize(hist, hist, 0, 255, NORM_MINMAX); // 归一化,排除光强干扰进行比较
// show histogram
int binw = drawImg.cols / bins;
Mat colorIndex = Mat(1, bins, CV_8UC3);
for (int i = 0; i < bins; i++)
{
colorIndex.at(0, i) = Vec3b(saturate_cast(i * 180 / bins), 255, 255); // 按照HSV赋值,下面再转换为BGR颜色空间
}
cvtColor(colorIndex, colorIndex, COLOR_HSV2BGR); // 不转换为BGR也是可以的,只是直方图颜色不一样而已
for (int i = 0; i < bins; i++)
{
// 如果roi选择的是黄色小球区域或其内部一部分区域,hist的值基本属于两值分化的状态,0 或 255
int val = saturate_cast<int>(hist.at<float>(i)*drawImg.rows / 255); // val 等于 0-drawImg.rows 之间
rectangle(drawImg, Point(i*binw, drawImg.rows), Point((i + 1)*binw, drawImg.rows - val), Scalar(colorIndex.at(0, i)), -1, 8, 0); // 绘制直方图
}
}
calcBackProject(&hue, 1, 0, hist, backprojection, &hranges); // 反响映射
imshow("backprojection", backprojection);
imshow("mask", mask);
// CAMShift tracking
backprojection &= mask; // &=运算符重载,backprojection与mask必须同size,返回结果为它们相同位置的像素值都为255的像素保留,否则为0
imshow("back&mask", backprojection);
// 参数 TermCriteria((TermCriteria::COUNT | TermCriteria::EPS), 10, 1) 表示MeanShift寻找中心点的算法时,最多迭代10次,当中心点的距离插值小于1,立即结束
RotatedRect trackBox = CamShift(backprojection, selection, TermCriteria((TermCriteria::COUNT | TermCriteria::EPS), 10, 1)); // 算法速度很快,满足实时性
cout << "selection.size=" << selection.size() << ", ";
// draw location on frame;
ellipse(frame, trackBox, Scalar(0, 0, 255), 3, 8); // 在跟踪的roi区域绘制椭圆
if (firstRead) firstRead = false;
imshow(roiName, frame);
imshow("ROI Histogram", drawImg);
}
capture.release();
waitKey(0);
}