用均值平移算法查找目标 - sumpig/OpenCV GitHub Wiki
均值平移算法广泛应用于视觉追踪。
假设我们已经识别出一个感兴趣的物体(例如狒狒的脸),如下图所示:

我们希望在第二幅图片中,能追踪到感兴趣物体新的位置。

因为狒狒脸部有非常独特的粉红色,使用像素的色调很容易标识狒狒脸部,因此第一步就是把图像转换成 HSV 色彩空间,计算出 色调直方图。
在使用颜色的色调分量时,要把它的饱和度考虑在内(饱和度是向量的第二个入口)。如果颜色的饱和度很低,它的色调信息就会变得不稳定且不可靠。这是因为低饱和度颜色的 B、G 和R 分量几乎是相等的,这导致很难确定它所表示的准确颜色。因此,我们决定忽略低饱和度颜色的色彩分量,也就是不把它们统计进直方图中(在 getHueHistogram 方法中使用 minSat 参数可屏蔽掉饱和度低于此阈值的像素)。
cv::Mat image = cv::imread("baboon01.jpg");
# 狒狒脸部的 ROI
cv::Rect rect(110, 45, 35, 45);
cv::Mat imageROI = image(rect);
# 得到狒狒脸部的直方图
int minSat = 65;
ColorHistogram hc;
cv::Mat colorhist = hc.getHueHistogram(imageROI, minSat);cv::Mat getHueHistogram(const cv::Mat& image, int minSaturation = 0) {
cv::Mat hist;
// 转换成HSV 色彩空间
cv::Mat hsv;
cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV);
// 掩码
cv::Mat mask;
if (minSaturation > 0) {
// 将3 个通道分割进3 幅图像
std::vector<cv::Mat> v;
cv::split(hsv, v);
// 屏蔽低饱和度的像素
cv::threshold(v[1], mask, minSaturation, 255,
cv::THRESH_BINARY);
}
// 准备一维色调直方图的参数
hranges[0] = 0.0; // 范围为0~180
hranges[1] = 180.0;
channels[0] = 0; // 色调通道
// 计算直方图
cv::calcHist(&hsv,
1, // 只有一幅图像的直方图
channels, // 用到的通道
mask, // 二值掩码
hist, // 生成的直方图
1, // 这是一维直方图
histSize, // 箱子数量
ranges // 像素值范围
);
return hist;
}现在打开第二幅图像,然后对第一幅图像的直方图做反向投影。
# 色调直方图作归一化处理
ContentFinder finder;
finder.setHistogram(colorhist);
image= cv::imread("baboon2.jpg");
// 转换成HSV 色彩空间
cv::cvtColor(image, hsv, CV_BGR2HSV);
// 得到色调直方图的反向投影
int ch[1]={0};
finder.setThreshold(-1.0f); // 不做阈值化
cv::Mat result= finder.find(hsv, 0.0f, 180.0f, ch);void setHistogram(const cv::Mat& h) {
cv::normalize(h, histogram, 1.0);
}
cv::Mat find(const cv::Mat& image, float minValue, float maxValue, int* channels) {
cv::Mat result;
hranges[0] = minValue;
hranges[1] = maxValue;
for (int i = 0; i < histogram.dims; i++)
this->channels[i] = channels[i];
cv::calcBackProject(&image,
1, // 只使用一幅图像
channels, // 通道
histogram, // 直方图
result, // 反向投影的图像
ranges, // 每个维度的值范围
255.0 // 把概率值从1 映射到255
);
// 对反向投影结果做阈值化,得到二值图像
if (threshold > 0.0)
cv::threshold(result, result, 255.0 * threshold, 255.0, cv::THRESH_BINARY);
return result;
}最后,用 OpenCV 的 meanShift 算法追踪狒狒脸部的新位置。
cv::TermCriteria criteria(cv::TermCriteria::MAX_ITER | cv::TermCriteria::EPS,
10, // 最多迭代10 次
1); // 或者重心移动距离小于1 个像素
cv::meanShift(result, rect, criteria);
# 显示狒狒脸部区域
cv::rectangle(image, rect, cv::Scalar(0, 255, 0));
cv::namedWindow("Image 2 result");
cv::imshow("Image 2 result", image);
cv::waitKey(0);
均值偏移算法是一个迭代过程,用于定位概率函数的局部最大值,方法是寻找预定义窗口内部数据点的重心或加权平均值。然后,把窗口移动到重心的位置,并重复该过程,直到窗口中心收敛到一个稳定的点。OpenCV 实现该算法时定义了两个停止条件:迭代次数达到最大值(MAX_ITER);窗口中心的偏移值小于某个限值(EPS),可认为该位置收敛到一个稳定点。这两个条件存储在一个 cv::TermCriteria 实例中。cv::meanShift 函数返回已经执行的迭代次数。显然,结果的好坏取决于指定初始位置提供的概率分布图的质量。注意,这里用颜色直方图表示图像的外观。也可以用其他特征的直方图(例如边界方向直方图)来表示物体。