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跟踪,可参考点击打开链接

meanshift算法学习(二):opencv中的meanshift相关推荐

  1. 【转】PCA算法学习_1(OpenCV中PCA实现人脸降维)

    前言: PCA是大家经常用来减少数据集的维数,同时保留数据集中对方差贡献最大的特征来达到简化数据集的目的.本文通过使用PCA来提取人脸中的特征脸这个例子,来熟悉下在oepncv中怎样使用PCA这个类. ...

  2. OpenCV学习(22) opencv中使用kmeans算法

    kmeans算法的原理参考:http://www.cnblogs.com/mikewolf2002/p/3368118.html 下面学习一下opencv中kmeans函数的使用. 首先我们通过Ope ...

  3. opencv中的meanshift图像分割

    Meanshift(均值漂移)是一种在一组数据的密度分布中寻找局部极值的稳定的方法.Meanshift不仅可以用于图像滤波,视频跟踪,还可以用于图像分割. 通过给出一组多维数据点,其维数是(x,y,r ...

  4. pythongui显示图片_opencv2.4.13+python2.7学习笔记--opencv中的Gui特性--图片:读图像,显示图像,保存图像...

    #-*- coding: utf-8 -*- """Created on Tue Mar 14 19:39:11 2017 @author: Thinkpad" ...

  5. pca算法 c语言,Opencv中的pca算法

    对于PCA,一直都是有个概念,没有实际使用过,今天终于实际使用了一把,发现PCA还是挺神奇的. 在OPENCV中使用PCA非常简单,只要几条语句就可以了. 1.初始化数据 //每一行表示一个样本 Cv ...

  6. 数据结构与算法 学习笔记(中)

    油管上的CS61B的视频 学习代码 随看随记 Dijkstra's algorithm再理解 Asymptotics 本意是渐近的意思:这里代指当参数为无穷大时,所需要进行运算的次数,和我们常说的复杂 ...

  7. 算法学习二,红黑树查找算法

    二叉查找树,对于大多数情况下的查找和插入在效率上来说是没有问题的,但是他在最差的情况下效率比较低. 红黑树保证在最坏的情况下插入和查找效率都能保证在对数(Log(n))的时间复杂度内完成. 1.红黑树 ...

  8. 动态规划算法学习二:最长公共子序列

    文章目录 前言 一.问题描述 二.DP实现 1.最优子结构性质***** 2.状态表示***** 3.状态递归方程***** 4.计算最优值***** 5.代码实现:输出最长公共子序列 6.代码实现: ...

  9. Java算法学习二:二分法

    文章目录 一.前置条件一条有顺序的数组 二.查找代码 数组声明方式 1.int [] aa; 2.int aa [];不推荐 数组实例化方式(必须要声明数组大小) int [] aa=new int[ ...

  10. OpenCV均值移位(Meanshift)和Camshift算法

    OpenCV Meanshift和Camshift算法 Meanshift和Camshift算法 目标 均值漂移Meanshift OpenCV中的Meanshift Camshift OpenCV中 ...

最新文章

  1. oracle实例没有连到监听上6,oracle LISTENER未监听到oracle实例问题解决
  2. MultiBaC包消除不同组学数据之间的批次效应
  3. 使用Java代码在应用层获取Android系统属性
  4. ES Next Arrow function Promise Iterator Generator yield Async Await
  5. (C++)函数参数传递中的一级指针和二级指针
  6. js数字最多保留两位小数_8085微处理器中最多两个8位数字
  7. bind merge r 和join_R语言并行读取csv:地表最快csv合并方法
  8. mysql 从库可以写入吗_mysql主从库配置读写分离以及备份
  9. javascript-分支与循环
  10. spring cloud微服务分布式云架构-commonservice-config配置服务搭建
  11. 苹果发明超薄触摸显示技术:iPhone 12系列有望首发搭载
  12. 有关微信小程序用户登录界面跳转问题
  13. 快速跳转到行首/行尾 快捷键
  14. 克服J2SE 1.3-1.4 的不兼容性
  15. oracle 企业管理器网页打不开 解决https://localhost:1158/em问题
  16. 五大内存分区,堆与栈的区别
  17. 对HackTheBox里面的Netmon进行攻破
  18. select查询基础的总结
  19. 电动车铅酸电池的正确充电方法(过度充电,会导致板栅金属层变薄,容易断栅格;过度放电会导致极板硫化,活性海绵组织失效,缩短极板寿命)
  20. 常见的web服务器软件

热门文章

  1. win7打开计算机死机,win7系统进入桌面总是死机或者卡死怎么办
  2. 微信公众号H5音频视频自动播放(安卓,苹果)
  3. NoSQL:文档数据库
  4. 微信公众号图片转pdf
  5. 一文带你了解云原生安全 | 云安全解决方案(CASB、CSPM、CWPP、CNAPP)
  6. 从玄学走向科学:在字节跳动广告投放这么干
  7. PWM驱动sg90电机
  8. 计算机win764位相机驱动,Win7万能驱动64位
  9. android自定义鼠标指针,修改Android系统的鼠标光标
  10. Android软件安全开发实践