meanshift算法学习(二):opencv中的meanshift

article/2025/11/6 14:32:54

0.前言

      接着上一篇文章点击打开链接说,opencv中提供的meanshift可以用来实现跟踪,其基本原理是迭代求解概率分布的“局部极值”。这一篇内容,我只讲opencv中的meanshift的用法和源代码分析。因为:(1)具体的数学原理推导,涉及到一些其他方面的知识譬如无参数估计、核函数等方面内容较多(2)opencv中的meanshift严格意义上来说是最简化版本的算法,结合着数学原理讲反而不好讲。。所以后面会再写一篇文章,专门讲meanShift的数学原理推导,和C++的具体实现

1.meanShift原理的直观表现

如下图所示,一堆分布不均匀的桌球,我们想不通过遍历的方式找到最密集的区域。一种做法是,框选一个区域(途中蓝色圆圈),计算该区域的“重心”,将圆环圆心移动到重心处再次计算“重心”,重复上述步骤,直到满足一定条件(迭代次数或者变化范围),最终我们可以到达一个局部密度最集中的区域。这个操作看起来很合乎常识,不过在数学上它是可以被证明的,而且证明过程比想象中复杂一丢丢(所以放在下一篇文章中讲。。)。


2.opencv中使用meanshift

再把上一篇文章中的猴子脸部“概率密度”分布图拿过来,分布图中每一点的灰度值代表其分布概率的高低,我们就可以理解为高概率的地方就是桌球分布更加密集的地方。那后面就好办了,框选一个区域,计算该区域的重心,将区域中心移动至重心处;重复上述过程即可迭代求解“概率密度”分布图的局部极值了呗。事实上opencv的meanShift就是提供了这样的接口和操作。直接把meanshift的调用和执行结果放出来。代码如下:

#include <iostream>
#include <vector>
#include <core/core.hpp>
#include <imgproc/imgproc.hpp>
#include <highgui/highgui.hpp>
#include <opencv2/nonfree/features2d.hpp>
#include <features2d/features2d.hpp>
#include <legacy/legacy.hpp>
#include <opencv2/video/tracking.hpp>using namespace std;
using namespace cv;int meanShiftTest(Mat probImage, Rect& window, int maxCount, double epsilon);int main()
{/* 第一部分:将输入图像转为灰度图后计算反投影 */Mat image = imread("baboon1.jpg", 0);Mat image_2 = imread("baboon3.jpg", 0);Mat imageROI = image(Rect(110, 260, 35, 40));Mat image_show;image.copyTo(image_show);rectangle(image_show, Rect(110, 260, 35, 40), Scalar(0));imshow("image_show", image_show);int histSize[1];float hranges[2];const float* ranges[1];int channels[1];histSize[0] = 256;hranges[0] = 0.f;hranges[1] = 255.f;ranges[0] = hranges;channels[0] = 0;MatND hist;calcHist(&imageROI, 1, channels, Mat(), hist, 1, histSize, ranges);normalize(hist, hist, 1.0);Mat result;	calcBackProject(&image_2, 1, channels, hist, result, ranges, 255.0);imshow("result", result);/* 第二部分:转换为HSV后利用颜色信息计算反投影 */image = imread("baboon1.jpg");image_2 = imread("baboon3.jpg");imageROI = image(Rect(110, 260, 35, 40));image.copyTo(image_show);rectangle(image_show, Rect(110, 260, 35, 40), Scalar(0, 0, 0));imshow("image_show_hsv", image_show);Mat image_hsv;cvtColor(imageROI, image_hsv, CV_BGR2HSV);Mat mask;int minSaturation = 65;vector<Mat> v;split(image_hsv, v);threshold(v[1], mask, minSaturation, 255, THRESH_BINARY);histSize[0] = 256;hranges[0] = 0.f;hranges[1] = 180.f;ranges[0] = hranges;channels[0] = 0;MatND hist_hsv;calcHist(&image_hsv, 1, channels, mask, hist_hsv, 1, histSize, ranges);normalize(hist_hsv, hist_hsv, 1.0);Mat image_2_hsv;cvtColor(image_2, image_2_hsv, CV_BGR2HSV);vector<Mat> v_2;split(image_2_hsv, v_2);threshold(v_2[1], v_2[1], minSaturation, 255, THRESH_BINARY);Mat result_hsv;calcBackProject(&image_2_hsv, 1, channels, hist_hsv, result_hsv, ranges, 255.0);bitwise_and(result_hsv, v_2[1], result_hsv);imshow("result_hsv", result_hsv);/* 第三部分:调用opencv中的 meanShift计算位置 */Rect rect(110, 260, 35, 40);TermCriteria criteria(TermCriteria::MAX_ITER, 10, 0.01);long long t = getTickCount();meanShift(result_hsv, rect, criteria);cout<<"time: "<<(getTickCount() - t)/getTickFrequency()<<endl;rectangle(image_2, rect, Scalar(255, 255, 255));/* 测试自己写的meanshift */Rect rect2(110, 260, 35, 40);t = getTickCount();meanShiftTest(result_hsv, rect2, 30, 0.01);cout<<"time: "<<(getTickCount() - t)/getTickFrequency()<<endl;//rectangle(image_2, rect2, Scalar(255, 255, 255));rectangle(image_2, Rect(110, 260, 35, 40), Scalar(0, 0, 0));imshow("image_show_hsv_result", image_2);cv::waitKey();return 0;}int meanShiftTest(Mat probImage, Rect& window, int maxCount, double epsilon)
{if(probImage.type() != CV_8UC1){return -1;}/* 应该要做window是否合适的判断,譬如参数是否合适,是否位于probImage内部等* 这里偷懒不写了*/Mat imageROI = probImage(window);int x_o = imageROI.cols / 2;int y_o = imageROI.rows / 2;for(int i = 0; i < maxCount; i++)// 迭代次数{float x_dst, y_dst;x_dst = y_dst = 0.f;float weight_sum = 0;int sum = 0;/* 计算meanshift向量(重心相对于中心的偏移) */for(int m = 0; m < imageROI.rows; m++){for(int n = 0; n< imageROI.cols; n++){if(imageROI.at<unsigned char>(m,n)){sum++;int weight = imageROI.at<unsigned char>(m,n);weight_sum += weight;x_dst += (n - x_o)*weight;y_dst += (m - y_o)*weight;}}}if(sum > 0){x_dst /= (sum*weight_sum);y_dst /= (sum*weight_sum);window.x += x_dst;window.y += y_dst;if(fabs(x_dst) + fabs(y_dst) < epsilon)// 位移变化阈值break;}imageROI = probImage(window);}return 0;
}
       计算结果如下图所示,最右边图像中的黑色方框是初始位置,白色方框是最终计算的位置;中间的概率分布图就是meanShift的输入。结果看来很好的找到了新图像中猴子的脸部。


3.meanShift函数源码分析

把opencv310版本的meanShift()抠出来加上注释如下(函数位于opencv\sources\modules\video\src\camshift.cpp中)

int cv::meanShift( InputArray _probImage, Rect& window, TermCriteria criteria )
{CV_INSTRUMENT_REGION()Size size;int cn;Mat mat;UMat umat;bool isUMat = _probImage.isUMat();if (isUMat)umat = _probImage.getUMat(), cn = umat.channels(), size = umat.size();elsemat = _probImage.getMat(), cn = mat.channels(), size = mat.size();Rect cur_rect = window;CV_Assert( cn == 1 );// 单通道图像if( window.height <= 0 || window.width <= 0 )CV_Error( Error::StsBadArg, "Input window has non-positive sizes" );window = window & Rect(0, 0, size.width, size.height);// 选择区域位于图像内部double eps = (criteria.type & TermCriteria::EPS) ? std::max(criteria.epsilon, 0.) : 1.;eps = cvRound(eps*eps);int i, niters = (criteria.type & TermCriteria::MAX_ITER) ? std::max(criteria.maxCount, 1) : 100;for( i = 0; i < niters; i++ ){cur_rect = cur_rect & Rect(0, 0, size.width, size.height);if( cur_rect == Rect() ){cur_rect.x = size.width/2;cur_rect.y = size.height/2;}cur_rect.width = std::max(cur_rect.width, 1);cur_rect.height = std::max(cur_rect.height, 1);// 选择区域位于图像内部Moments m = isUMat ? moments(umat(cur_rect)) : moments(mat(cur_rect));// 计算图像的矩// Calculating center of massif( fabs(m.m00) < DBL_EPSILON )break;int dx = cvRound( m.m10/m.m00 - window.width*0.5 );// m.m10/m.m00就是图像重心的x坐标int dy = cvRound( m.m01/m.m00 - window.height*0.5 );// m.m01/m.m00就是图像重心的y坐标int nx = std::min(std::max(cur_rect.x + dx, 0), size.width - cur_rect.width);int ny = std::min(std::max(cur_rect.y + dy, 0), size.height - cur_rect.height);dx = nx - cur_rect.x;dy = ny - cur_rect.y;cur_rect.x = nx;cur_rect.y = ny;// 更新区域中心// Check for coverage centers mass & windowif( dx*dx + dy*dy < eps )break;}window = cur_rect;return i;
}

4.其他

(1)代码中的meanShiftTest()是自己写的用于验证meanShift原理的测试程序,通过测试验证对原理的理解没有问题

(2)meanShift得到的是“局部极值”,不能保证一定收敛到全局极值。可以尝试把猴子脸部区域的初始位置修改一下就会发现此时不一定能找到新图像中猴子脸部的正确位置了。

(3)既然meanshift配合calcBackProject可以实现新图像中某区域的定位,那么稍加修改就能实现简单的视频中物体跟踪。唯一区别就在于逐帧读取图像后调用meanshift不断更新区域的位置了。可以参考http://blog.csdn.net/dcrmg/article/details/52694557

(4)使用C++自己实现meanShift跟踪,可参考点击打开链接


http://chatgpt.dhexx.cn/article/oZrIMgIS.shtml

相关文章

聚类算法之Mean Shift

Mean Shift聚类算法 1. 基本原理 对于Mean Shift算法&#xff0c;是一个迭代得步骤&#xff0c;即每次迭代的时候&#xff0c;都是找到圆里面点的平均位置作为新的圆心位置。说的简单一点&#xff0c;使得圆心一直往数据密集度最大的方向移动。 2. 基本的Mean Shift向量形式…

meanshift跟踪算法

以下是用meanshift实现目标跟踪的实验报告&#xff08;包含源码&#xff09;&#xff0c;实验中详细介绍了meanshift跟踪算法的原理&#xff0c;结合OTB100跟踪数据集对meanshift跟踪效果进行了分析。 目 录 一.实验名称 二.实验目的 三.实验原理 3.1 前言 3.2 meanshift …

传统目标跟踪——MeanShift算法

目录 一、均值漂移&#xff08;MeanShift&#xff09; 二、流程 三、代码 3.1 meanshift&#xff0b;固定框的代码 3.2 优化&#xff1a;meanshift鼠标选择 3.3 meanshift自己实现函数 四、补充知识 4.1 直方图 4.2 归一化 4.3 直方图反投影 一、均值漂移&#xff08;…

Mean Shift 聚类算法

原文&#xff1a;https://blog.csdn.net/hjimce/article/details/45718593 一、mean shift 算法理论 Mean shift 算法是基于核密度估计的爬山算法&#xff0c;可用于聚类、图像分割、跟踪等&#xff0c;因为最近搞一个项目&#xff0c;涉及到这个算法的图像聚类实现&#xff…

mean shift聚类matlab,机器学习:Mean Shift聚类算法

今天的文章介绍如何利用Mean Shift算法的基本形式对数据进行聚类操作。而有关Mean Shift算法加入核函数计算漂移向量部分的内容将不在本文讲述范围内。实际上除了聚类&#xff0c;Mean Shift算法还能用于计算机视觉等场合&#xff0c;有关该算法的理论知识请参考这篇文章。 Mea…

Python 实现MeanShift算法

原理 大家自行百度吧&#xff0c;我懒得码字了 推荐一下原理原理https://blog.csdn.net/jinshengtao/article/details/30258833 代码 直接上代码了&#xff0c;看不懂&#xff0c;就参照一下原理 # author: wdq # contact: 1920132572qq.com # datetime:2022/3/15 17:40 # …

Sklearn聚类算法之meanshift

以二维来说明可能更容易理解&#xff0c;下图中的很多的红点就是我们的样本特征点&#xff0c;meanshift就是在这些点中的任意一个点为圆心&#xff0c;然后以半径R画一个圆&#xff08;在opencv中是一个矩形&#xff09;&#xff0c;然后落在这个圆中的所有点和圆心都会对应的…

Python实现Mean Shift聚类算法

Mean Shift算法&#xff0c;又称均值聚类算法&#xff0c;聚类中心是通过在给定区域中的样本均值确定的&#xff0c;通过不断更新聚类中心&#xff0c;直到聚类中心不再改变为止&#xff0c;在聚类、图像平滑、分割和视频跟踪等方面有广泛的运用。 Mean Shift向量 对于给定的…

mean shift 跟踪算法

说明一&#xff1a; Mean Shift算法,一般是指一个迭代的步骤,即先算出当前点的偏移均值,移动该点到其偏移均值,然后以此为新的起始点,继续移动,直到满足一定的条件结束. 1. Meanshift推导 给定d维空间Rd的n个样本点 ,i1,…,n,在空间中任选一点x&#xff0c;那么Mean Shift向量…

Python实现Mean Shift算法

声明&#xff1a;代码的运行环境为Python3。Python3与Python2在一些细节上会有所不同&#xff0c;希望广大读者注意。本博客以代码为主&#xff0c;代码中会有详细的注释。相关文章将会发布在我的个人博客专栏《Python从入门到深度学习》&#xff0c;欢迎大家关注~ 在K-Means算…

meanshift算法 java_Meanshift,聚类算法

记得刚读研究生的时候&#xff0c;学习的第一个算法就是meanshift算法&#xff0c;所以一直记忆犹新&#xff0c;今天和大家分享一下Meanshift算法&#xff0c;如有错误&#xff0c;请在线交流。 Mean Shift算法,一般是指一个迭代的步骤,即先算出当前点的偏移均值,移动该点到其…

保边滤波之Mean shift filter

Mean shift filter 目录 Mean shift filter 一、算法原理 二、练手实现的算法代码如下&#xff1a; 三、实现结果 一、算法原理 在OpenCV中&#xff0c;meanshift filter函数为 pyrMeanShiftFiltering&#xff0c; 它的函数调用格式如下&#xff1a; C: void pyrMeanShif…

mean shift

参考&#xff1a; http://blog.csdn.net/google19890102/article/details/51030884 http://www.cvvision.cn/5778.html https://wenku.baidu.com/view/5862334827d3240c8447ef40.html http://blog.csdn.net/qq_23968185/article/details/51804574 https://www.cnblogs.com…

机器学习算法原理与实践(二)、meanshift算法图解以及在图像聚类、目标跟踪中的应用

【原创】Liu_LongPo 转载请注明出处 【CSDN】http://blog.csdn.net/llp1992 最近在关注跟踪这一块的算法&#xff0c;对于meanshift的了解也是来自论文和博客&#xff0c;本博客将对meanshift算法进行总结&#xff0c;包括meanshift算法原理以及公式推导&#xff0c;图解&…

基于MeanShift的目标跟踪算法及实现

这次将介绍基于MeanShift的目标跟踪算法&#xff0c;首先谈谈简介&#xff0c;然后给出算法实现流程&#xff0c;最后实现了一个单目标跟踪的MeanShift算法【matlab/c两个版本】 csdn贴公式比较烦&#xff0c;原谅我直接截图了… 一、简介 首先扯扯无参密度估计理论&#xff0c…

聚类算法:Mean Shift

目录 简介 mean shift 算法理论 Mean Shift算法原理 算法步骤 算法实现 其他 聚类算法之Mean Shift Mean Shift算法理论 Mean Shift向量 核函数 引入核函数的Mean Shift向量 聚类动画演示 Mean Shift的代码实现 算法的Python实现 scikit-learn MeanShift演示 s…

meanshift算法通俗讲解

这几天学习《学习OpenCV》中的第十章运动跟踪&#xff0c;里面讲到了meanshift算法&#xff0c;根据书上所讲实在难以理解&#xff0c;meanshift在运动跟踪这个过程中到底起到什么作用&#xff0c;于是经过几天不断地看相关资料和别人的博客文章&#xff0c;慢慢思路清晰了&…

机器学习实验 - MeanShift聚类

目录 一、报告摘要1.1 实验要求1.2 实验思路1.3 实验结论 二、实验内容2.1 方法介绍2.2 实验细节2.2.1 实验环境2.2.2 实验过程2.2.3 实验与理论内容的不同点 2.3 实验数据介绍2.4 评价指标介绍2.5 实验结果分析 三、总结及问题说明四、参考文献附录&#xff1a;实验代码 报告内…

聚类 之 MeanShift

文章目录 Meanshift 聚类基本原理Meanshift 聚类流程简述实例演示MeanShift聚类简易应用示例总结拓展阅读 上篇博客介绍了基于距离的K-Means聚类&#xff0c;这次给大家推荐一个基于密度的聚类算法&#xff1a;Meanshift&#xff08;均值漂移&#xff09;。 Meanshift 聚类基本…

Muduo源码剖析

1、总体流程 1. acceptor 进行listen阶段后&#xff0c; 往channel中注册可读事件。 2. acceptor可读处理中生成TcpConnection指针&#xff0c;通过EventloopThreadPool 轮询出其中一个线程的eventloop, 并将此TcpConnection的可读、可写等事件注册到自己Channel&#xff08;ev…