OpenCV  
开源计算机视觉库
正在加载...
正在搜索...
没有匹配项
均值漂移和CamShift

上一教程: 如何使用背景减除方法
下一教程: 光流

目标

在本章中,

  • 我们将学习均值漂移和CamShift算法,用于在视频中跟踪物体。

均值漂移

均值漂移背后的直觉很简单。假设你有一组点。(它可以是像直方图反投影这样的像素分布)。给你一个小的窗口(可能是一个圆形),你需要将这个窗口移动到像素密度最大(或点数最多)的区域。下图简单地展示了这个过程。

图像

初始窗口显示为蓝色圆圈,名为“C1”。其原始中心用蓝色矩形标记,名为“C1_o”。但是,如果你找到该窗口内点的质心,你将得到点“C1_r”(用小蓝色圆圈标记),它就是窗口的实际质心。显然它们并不匹配。因此,将你的窗口移动,使得新窗口的圆圈与之前的质心匹配。再次找到新的质心。很可能,它不会匹配。所以再次移动它,并继续迭代,直到窗口的中心及其质心落在同一个位置(或在小的期望误差范围内)。因此,你最终得到的是一个具有最大像素分布的窗口。它用绿色圆圈标记,名为“C2”。如你在图中看到的,它包含最多的点。下面的静态图像演示了整个过程

图像

因此,我们通常传递直方图反投影图像和初始目标位置。当物体移动时,显然移动会反映在直方图反投影图像中。因此,均值漂移算法将我们的窗口移动到具有最大密度的新的位置。

OpenCV 中的均值漂移

要在 OpenCV 中使用均值漂移,首先我们需要设置目标,找到它的直方图,以便我们可以将目标反投影到每一帧上,用于计算均值漂移。我们还需要提供窗口的初始位置。对于直方图,这里只考虑色调。此外,为了避免由于弱光造成的错误值,使用 cv.inRange() 函数来丢弃弱光值。

  • 可下载代码: 点击 这里
  • 代码概览
    #include <iostream>
    using namespace cv;
    using namespace std;
    int main(int argc, char **argv)
    {
    const string about =
    "此示例演示了均值漂移算法。\n"
    "示例文件可以从以下地址下载:\n"
    " https://www.bogotobogo.com/python/OpenCV_Python/images/mean_shift_tracking/slow_traffic_small.mp4";
    const string keys =
    "{ h help | | 打印此帮助消息 }"
    "{ @image |<none>| 图像文件路径 }";
    CommandLineParser parser(argc, argv, keys);
    parser.about(about);
    if (parser.has("help"))
    {
    parser.printMessage();
    return 0; 0;
    }
    string filename = parser.get<string>("@image");
    if (!parser.check())
    {
    parser.printErrors();
    return 0; 0;
    }
    VideoCapture capture(filename);
    if (!capture.isOpened()){
    // 打开视频输入时出错
    cerr << "无法打开文件!" << endl;
    return 0; 0;
    }
    Mat frame, roi, hsv_roi, mask;
    // 获取视频的第一帧
    capture >> frame;
    // 设置窗口的初始位置
    Rect track_window(300, 200, 100, 50); // 简单地对值进行硬编码
    // 设置用于跟踪的 ROI
    roi = frame(track_window);
    cvtColor(roi, hsv_roi, COLOR_BGR2HSV);
    inRange(hsv_roi, Scalar(0, 60, 32), Scalar(180, 255, 255), mask);
    float range_[] = {0, 180};
    const float* range[] = {range_};
    Mat roi_hist;
    int histSize[] = {180};
    int channels[] = {0};
    calcHist(&hsv_roi, 1, channels, mask, roi_hist, 1, histSize, range);
    normalize(roi_hist, roi_hist, 0, 255, NORM_MINMAX);
    // 设置终止准则,要么迭代 10 次,要么至少移动 1 个像素
    TermCriteria term_crit(TermCriteria::EPS | TermCriteria::COUNT, 10, 1);
    while(true){
    Mat hsv, dst;
    capture >> frame;
    if (frame.empty())
    break;;
    cvtColor(frame, hsv, COLOR_BGR2HSV);
    calcBackProject(&hsv, 1, channels, roi_hist, dst, range);
    // 应用均值漂移以获取新位置
    meanShift(dst, track_window, term_crit);
    // 在图像上绘制它
    rectangle(frame, track_window, 255, 2);
    imshow("img2", frame);
    int keyboard = waitKey(30);
    if (keyboard == 'q' || keyboard == 27)
    break;;
    }
    }
    capture >> frame;
    }
    cv::CommandLineParser
    用于命令行解析的实用程序类。
    cv::Mat
    n 维密集数组类
    二维矩形的模板类。
    定义 types.hpp:444
    cv::TermCriteria
    定义迭代算法终止准则的类。
    cv::VideoCapture
    用于从视频文件、图像序列或相机捕获视频的类。
    cv::normalize
    void normalize(InputArray src, InputOutputArray dst, double alpha=1, double beta=0, int norm_type=NORM_L2, int dtype=-1, InputArray mask=noArray())
    cv::imshow
    void imshow(const String &winname, InputArray mat)
    cv::waitKey
    int waitKey(int delay=0)
    cv::cvtColor
    void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0)
    cv::rectangle
    void rectangle(InputOutputArray img, Point pt1, Point pt2, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)
    cv::calcBackProject
    void calcBackProject(const Mat *images, int nimages, const int *channels, InputArray hist, OutputArray backProject, const float **ranges, double scale=1, bool uniform=true)
    cv::calcHist
    void calcHist(const Mat *images, int nimages, const int *channels, InputArray mask, OutputArray hist, int dims, const int *histSize, const float **ranges, bool uniform=true, bool accumulate=false)
    int meanShift(InputArray probImage, Rect &window, TermCriteria criteria)
    在反投影图像上查找物体。
    定义 highgui_qt.cpp:3
    imgcodecs.hpp

我在视频中使用的三帧如下图所示

图像

连续自适应均值漂移

你仔细观察最后一个结果了吗?存在一个问题。无论汽车距离摄像头很远还是很近,我们的窗口始终保持相同的大小。这不好。我们需要根据目标的大小和旋转来调整窗口的大小。再次,解决方案来自“OpenCV Labs”,它被称为 CAMshift(连续自适应均值漂移),由 Gary Bradsky 在 1998 年的论文“用于感知用户界面的计算机视觉人脸跟踪”中发表 [39]

它首先应用均值漂移算法。均值漂移算法收敛后,它会更新窗口的大小,s=2×M00256。它还会计算最适合它的椭圆的方向。它再次使用新的缩放搜索窗口和之前的窗口位置应用均值漂移算法。该过程会持续进行,直到满足所需的精度为止。

图像

OpenCV 中的连续自适应均值漂移

它类似于均值漂移算法,但会返回一个旋转的矩形(即我们的结果)和框参数(用作下一迭代中的搜索窗口)。请查看以下代码

  • 可下载代码: 点击 这里
  • 代码概览
    #include <iostream>
    using namespace cv;
    using namespace std;
    int main(int argc, char **argv)
    {
    const string about =
    "此示例演示了连续自适应均值漂移算法。\n"
    "示例文件可以从以下地址下载:\n"
    " https://www.bogotobogo.com/python/OpenCV_Python/images/mean_shift_tracking/slow_traffic_small.mp4";
    const string keys =
    "{ h help | | 打印此帮助消息 }"
    "{ @image |<none>| 图像文件路径 }";
    CommandLineParser parser(argc, argv, keys);
    parser.about(about);
    if (parser.has("help"))
    {
    parser.printMessage();
    return 0; 0;
    }
    string filename = parser.get<string>("@image");
    if (!parser.check())
    {
    parser.printErrors();
    return 0; 0;
    }
    VideoCapture capture(filename);
    if (!capture.isOpened()){
    // 打开视频输入时出错
    cerr << "无法打开文件!" << endl;
    return 0; 0;
    }
    Mat frame, roi, hsv_roi, mask;
    // 获取视频的第一帧
    capture >> frame;
    // 设置窗口的初始位置
    Rect track_window(300, 200, 100, 50); // 简单地对值进行硬编码
    // 设置用于跟踪的 ROI
    roi = frame(track_window);
    cvtColor(roi, hsv_roi, COLOR_BGR2HSV);
    inRange(hsv_roi, Scalar(0, 60, 32), Scalar(180, 255, 255), mask);
    float range_[] = {0, 180};
    const float* range[] = {range_};
    Mat roi_hist;
    int histSize[] = {180};
    int channels[] = {0};
    calcHist(&hsv_roi, 1, channels, mask, roi_hist, 1, histSize, range);
    normalize(roi_hist, roi_hist, 0, 255, NORM_MINMAX);
    // 设置终止准则,要么迭代 10 次,要么至少移动 1 个像素
    TermCriteria term_crit(TermCriteria::EPS | TermCriteria::COUNT, 10, 1);
    while(true){
    Mat hsv, dst;
    capture >> frame;
    if (frame.empty())
    break;;
    cvtColor(frame, hsv, COLOR_BGR2HSV);
    calcBackProject(&hsv, 1, channels, roi_hist, dst, range);
    // 应用连续自适应均值漂移算法获取新的位置
    RotatedRect rot_rect = CamShift(dst, track_window, term_crit);
    // 在图像上绘制它
    Point2f points[4];
    rot_rect.points(points);
    for (int i = 0; i < 4; i++)
    line(frame, points[i], points[(i+1)%4], 255, 2);
    imshow("img2", frame);
    int keyboard = waitKey(30);
    if (keyboard == 'q' || keyboard == 27)
    break;;
    }
    }
    该类表示平面上的旋转(即非直立)矩形。
    定义 types.hpp:531
    void points(Point2f pts[]) const
    void line(InputOutputArray img, Point pt1, Point pt2, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)
    绘制连接两个点的线段。
    RotatedRect CamShift(InputArray probImage, Rect &window, TermCriteria criteria)
    查找目标的中心、大小和方向。

结果的三帧如下图所示

图像

其他资源

  1. 维基百科法语页面介绍了 连续自适应均值漂移。 (两个动画来自该页面)
  2. Bradski, G.R., "Real time face and object tracking as a component of a perceptual user interface," Applications of Computer Vision, 1998. WACV '98. Proceedings., Fourth IEEE Workshop on , vol., no., pp.214,219, 19-21 Oct 1998

练习

  1. OpenCV 附带一个 Python 示例,用于演示连续自适应均值漂移算法的交互式演示。使用它,修改它,理解它。