[Object Tracking] MeanShift
Opencv均值漂移pyrMeanShiftFiltering彩色图像分割流程剖析
MeanShift - cv.MeanShift
Mean Shift均值漂移算法是无参密度估计理论的一种,无参密度估计不需要事先知道对象的任何先验知识,完全依靠训练数据进行估计,并且可以用于任意形状的密度估计,在某一连续点处的密度函数值可由该点邻域中的若干样本点估计得出。
Mean shift将特征空间视为先验概率密度函数,那么输入就被视为是一组满足某种概率分布的样本点,这样一来,特征空间中数据最密集的地方,对应于概率密度最大的地方,且概率密度的质心就可以被视为是概率密度函数的局部最优值,也就是要求的聚类中心。对于每一个样本点,计算以它为中心的某个范围内所有样本点的均值,作为新的中心(这就是shift既中心的移动),移动直至收敛。这样每一轮迭代,中心都会向数据更密集的地方移动,直到最后稳定收敛到样本的“质心”。
【以下这个描述容易理解】
可以直观理解为:在样本空间中,任选一个点,然后以这个点为圆心,划定一个圆形的区域。在此区域内的所有点以圆心为起点,产生N个向量,然后把这些向量都相加,再以向量的终点为圆心,划定同样半径的圆形区域,执行同样操作,如此迭代,直到收敛。
Finds an object on a back projection image.
C++: int meanShift(InputArray probImage, Rect& window, TermCriteria criteria)
Python: cv2.meanShift(probImage, window, criteria) → retval, window
C: int cvMeanShift(const CvArr* prob_image, CvRect window, CvTermCriteria criteria, CvConnectedComp* comp)
Python: cv.MeanShift(prob_image, window, criteria) → comp
Parameters:
probImage – Back projection of the object histogram. See calcBackProject() for details.
window – Initial search window.
criteria – Stop criteria for the iterative search algorithm.
Returns:
Number of iterations CAMSHIFT took to converge.
以下程序实现了在一个视频中跟踪移动目标,大致步骤如下:
-
- 1. 在视频播放过程中,通过鼠标框选需要跟踪的目标target
-
- 2. 计算目标图像target的HSV中H、S分量的直方图targetHist
-
- 3. 用targetHist反向投影计算原图像中的目标的概率分布
-
- 4. 用meanShift通过迭代获取目标的新的位置window
-
- 5. 以新的位置window执行步骤2
targetHist反向投影 - 解释
-
假设你已经通过下图得到一个肤色直方图(Hue-Saturation), 旁边的直方图就是 模型直方图 ( 代表手掌的皮肤色调).你可以通过掩码操作来抓取手掌所在区域的直方图:
-
下图是另一张手掌图(测试图像) 以及对应的整张图像的直方图:
-
我们要做的就是使用 模型直方图 (代表手掌的皮肤色调) 来检测测试图像中的皮肤区域。以下是检测的步骤
-
对测试图像中的每个像素 (
),获取色调数据并找到该色调(
)在直方图中的bin的位置。
-
查询 模型直方图 中对应的bin -
- 并读取该bin的数值。
-
将此数值储存在新的图像中(BackProjection)。 你也可以先归一化 模型直方图 ,这样测试图像的输出就可以在屏幕显示了。
-
通过对测试图像中的每个像素采用以上步骤, 我们得到了下面的 BackProjection 结果图:
-
使用统计学的语言, BackProjection 中储存的数值代表了测试图像中该像素属于皮肤区域的 概率 。比如以上图为例, 亮起的区域是皮肤区域的概率更大(事实确实如此),而更暗的区域则表示更低的概率(注意手掌内部和边缘的阴影影响了检测的精度)。
-
肤色直方图(Hue-Saturation)的一个简单的例子:
meanShfit均值漂移算法 - pyrMeanShiftFiltering
【注意,这里本质是一种 fitering】
meanShfit均值漂移算法是一种通用的聚类算法,它的基本原理是:对于给定的一定数量样本,任选其中一个样本,以该样本为中心点划定一个圆形区域,求取该圆形区域内样本的质心,即密度最大处的点,再以该点为中心继续执行上述迭代过程,直至最终收敛。
可以利用均值偏移算法的这个特性,实现彩色图像分割,Opencv中对应的函数是pyrMeanShiftFiltering。
这个函数严格来说并不是图像的分割,而是图像在色彩层面的平滑滤波,它可以中和色彩分布相近的颜色,平滑色彩细节,侵蚀掉面积较小的颜色区域,所以在Opencv中它的后缀是滤波“Filter”,而不是分割“segment”。先列一下这个函数,再说一下它“分割”彩色图像的实现过程。
-
Performs initial step of meanshift segmentation of an image. C++: void pyrMeanShiftFiltering(InputArray src, OutputArray dst, double sp, double sr, int maxLevel=1, TermCriteria termcrit=TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS,5,1) ) Python: cv2.pyrMeanShiftFiltering(src, sp, sr[, dst[, maxLevel[, termcrit]]]) → dst C: void cvPyrMeanShiftFiltering(const CvArr* src, CvArr* dst, double sp, double sr, int max_level=1, CvTermCriteria termcrit=cvTermCriteria(CV_TERMCRIT_ITER+CV_TERMCRIT_EPS,5,1))
第一个参数src,输入图像,8位,三通道的彩色图像,并不要求必须是RGB格式,HSV、YUV等Opencv中的彩色图像格式均可;
第二个参数dst,输出图像,跟输入src有同样的大小和数据格式;
第三个参数sp,定义的漂移物理空间半径大小;
第四个参数sr,定义的漂移色彩空间半径大小;
第五个参数maxLevel,定义金字塔的最大层数;
第六个参数termcrit,定义的漂移迭代终止条件,可以设置为迭代次数满足终止,迭代目标与中心点偏差满足终止,或者两者的结合;
pyrMeanShiftFiltering函数的执行过程是这样的:
1. 迭代空间构建:
以输入图像上src上任一点P0为圆心,建立物理空间上半径为sp,色彩空间上半径为sr的球形空间,物理空间上坐标2个—x、y,色彩空间上坐标3个—R、G、B(或HSV),构成一个5维的空间球体。【其实就是升维的思想】
其中物理空间的范围x和y是图像的长和宽,色彩空间的范围R、G、B分别是0~255。
2. 求取迭代空间的向量并移动迭代空间球体后重新计算向量,直至收敛:
在1中构建的球形空间中,求得所有点相对于中心点的色彩向量之和后,移动迭代空间的中心点到该向量的终点,并再次计算该球形空间中所有点的向量之和,如此迭代,直到在最后一个空间球体中所求得的向量和的终点就是该空间球体的中心点Pn,迭代结束。
3. 更新输出图像dst上对应的初始原点P0的色彩值为本轮迭代的终点Pn的色彩值,如此完成一个点的色彩均值漂移。
4. 对输入图像src上其他点,依次执行步骤1,、2、3,遍历完所有点位后,整个均值偏移色彩滤波完成,这里忽略对金字塔的讨论。
【貌似更高级一点,但是三通道也意味着更大的计算量,其实黑白照片能足够辨别的话,还是采用一通道的好】
到这里,meanShift均值偏移算法对彩色图像的平滑操作就完成了,为了达到分割的目的,需要借助另外一个漫水填充函数的进一步处理来实现,那就是floodFill。
但感觉这个方案不是很好。略。
CamShift - cv.CamShift
CamShift算法全称是“Continuously Adaptive Mean-Shift”(连续的自适应MeanShift算法),是对MeanShift算法的改进算法,
可以在跟踪的过程中随着目标大小的变化实时调整搜索窗口大小,
对于视频序列中的每一帧还是采用MeanShift来寻找最优迭代结果,至于如何实现自动调整窗口大小的,可以查到的论述较少,我的理解是通过对MeanShift算法中零阶矩的判断实现的。
反向投影,CamShift和MeanShift的运算都是在反向投影图像上进行的,反向投影的实现过程如下:
计算并生成目标区域的H分量的直方图,反向投影其实就是把目标图像上每一个像素点的像素值替换为当前像素值所在bin对应的直方图bin的数值。
Finds an object center, size, and orientation.
C++: RotatedRect CamShift(InputArray probImage, Rect& window, TermCriteria criteria)
Python: cv2.CamShift(probImage, window, criteria) → retval, window
C: int cvCamShift(const CvArr* prob_image, CvRect window, CvTermCriteria criteria, CvConnectedComp* comp, CvBox2D* box=NULL )