视频分析与对象跟踪-CamShift 算法

1. 1 算法介绍

CamShift算法的全称是"Continuously Adaptive Mean-SHIFT",即:连续自适应的MeanShift算法。基本思想是对视频序列的所有图像帧都作MeanShift运算,并将上一帧的结果(即搜索窗口的中心位置和窗口大小)作为下一帧MeanShift算法的搜索窗口的初始值,如此迭代下去。简单点说,meanShift是针对单张图片寻找最优迭代结果,而camShift则是针对视频序列来处理,并对该序列中的每一帧图片都调用meanShift来寻找最优迭代结果。正是由于camShift针对一个视频序列进行处理,从而保证其可以不断调整窗口的大小,如此一来,当目标的大小发生变化的时候,该算法就可以自适应地调整目标区域继续跟踪。

opencv自带例子中,camShift算法是通过计算目标在HSV空间下的H分量直方图,利用直方图反向投影得到目标像素的概率分布,然后通过调用OpenCV的CAMSHIFT算法,自动跟踪并调整目标窗口的中心位置与大小。该算法对于简单背景下的单目标跟踪效果较好,但如果被跟踪目标与背景颜色或周围其它目标颜色比较接近,则跟踪效果较差。另外,由于采用颜色特征,所以它对被跟踪目标的形状变化有一定的抵抗能力。

算法过程主要分为三个部分:

1) 计算色彩投影图(反向投影)

(1)RGB颜色空间对光照亮度变化较为敏感,为了减少此变化对跟踪效果的影响,首先将图像从RGB空间转换到HSV空间.(2)然后对其中的H分量(色调)作直方图,在直方图中代表了不同H分量值出现的概率或者像素个数,就是说可以查找出H分量大小为h的概率或者像素个数,即得到了颜色概率查找表。(3)将图像中每个像素的值用其颜色出现的概率对替换,就得到了颜色概率分布图。这个过程就叫反向投影,颜色概率分布图是一个灰度图像。

2)meanshift

meanshift算法是一种密度函数梯度估计的非参数方法,通过迭代寻优找到概率分布的极值来定位目标。

算法的过程为:(1)在颜色概率分布图中选取搜索窗W(2)计算零阶距

视频分析与对象跟踪-CamShift 算法

计算一阶距

视频分析与对象跟踪-CamShift 算法

计算搜索窗的质心

视频分析与对象跟踪-CamShift 算法

(3)调整搜索窗大小

宽度为

视频分析与对象跟踪-CamShift 算法

长度为 1.2s;

(4)移动搜索窗的中心到质心,如果移动距离大于预设的固定阈值,则重复(2)(3)(4),直到搜索窗的中心与质心间的移动距离小于预设的固定阈值,或者循环运算的次数达到某一最大值,停止计算。

3)camshift

将meanshift算法扩展到连续图像序列,就是camshift算法。它将视频的所有帧做meanshift运算,并将上一帧的结果,即搜索窗的大小和中心,作为下一帧meanshift算法搜索窗的初始值。如此迭代下去,就可以实现对目标的跟踪。

算法的过程为:(1)初始化搜索窗(2)计算搜索窗的颜色概率分布(反向投影)(3)运行meanshift算法,获得搜索窗新的大小和位置。(4)在下一帧视频图像中用(3)中的值重新初始化搜索窗的大小和位置,再跳转到(2)继续进行。

总结:camshift能有效解决目标变形和遮挡的问题,对系统资源要求不高,时间复杂度低,在简单背景下能够取得良好的跟踪效果。但当背景较为复杂,或者有许多与目标颜色相似像素干扰的情况下,会导致跟踪失败。因为它单纯的考虑颜色直方图,忽略了目标的空间分布特性,所以这种情况下需加入对跟踪目标的预测算法。

例子代码:

<code>#include<opencv2>
#include<iostream>
using namespace std;
using namespace cv;

int smin = 15;
int vmin = 40;
int vmax = 256;
int bins = 16;

void test(){
    VideoCapture capture("Video_demo_CAMShift.wmv");
    if (!capture.isOpened())
    {
        cout <    }

    namedWindow("CAMShift Tracking", CV_WINDOW_AUTOSIZE);
    namedWindow("ROI Histogram", CV_WINDOW_AUTOSIZE);

    Mat frame,hsv,hue,mask,hist,backprojection;
    bool firstRead = true;
    Rect selection;
    float hrange[] = { 0, 180 };  //H 的范围
    const float *hranges = hrange;

    Mat drawImg = Mat::zeros(300, 300, CV_8UC3); //定义画直方图的图像
    while (capture.read(frame))
    {
        // 如果是第一帧
        if (firstRead)
        {
            //选择 ROI
            Rect2d first = selectROI("CAMShift Tracking", frame);  //中心化选择
            selection.x = first.x;
            selection.y = first.y;
            selection.width = first.width;
            selection.height = first.height;

            cout <                 <                 <                 <
        }

        // 转为 HSV,并提取 H 

        cvtColor(frame, hsv, COLOR_BGR2HSV);
        inRange(hsv, Scalar(0, smin, vmin), Scalar(180, vmax, vmax), mask); //过滤
        hue = Mat(hsv.size(), hsv.depth());
        int channels[] = { 0, 0 };
        mixChannels(&hsv, 1, &hue, 1, channels, 1);

        //计算直方图 (只对选择的ROI计算)
        if (firstRead)
        {
            Mat roi(hue, selection);
            Mat maskRoi(mask, selection);
            calcHist(&roi, 1, 0, maskRoi, hist, 1, &bins, &hranges);
            normalize(hist, hist, 0, 255, NORM_MINMAX);  //归一化直方图

            //显示直方图(这里直方图有 16 个bins)

            int binw = drawImg.cols / bins;  //获取每个 bins 的宽度
            Mat colorIndex = Mat(1, bins, CV_8UC3);
            for (int i = 0; i             {
                colorIndex.at<vec3b>(0, i) = Vec3b(saturate_cast<uchar>(i * 180 / bins), 255, 255);
            }
            cvtColor(colorIndex, colorIndex, COLOR_HSV2BGR);  //转回BGR

            for (int i = 0; i             {
                //255去做归一化 (0 - 300 之间)
                int val = saturate_cast(hist.at<float>(i)*drawImg.rows / 255);
                rectangle(drawImg, Point(i*binw, drawImg.rows),
                    Point((i + 1)*binw, drawImg.rows - val),
                    Scalar(colorIndex.at<vec3b>(0, i)), -1, 8, 0);
            }
        }

        //反向投影
        calcBackProject(&hue, 1, 0, hist, backprojection, &hranges);

        // CAMShift 
        backprojection &= mask;  //把干扰排除,两个都是
        //终止条件
        RotatedRect trackBox = CamShift(backprojection, selection,
            TermCriteria((TermCriteria::COUNT| TermCriteria::EPS), 10, 1));

        //画椭圆

        ellipse(frame, trackBox, Scalar(0, 0, 255), 3, 8);
        if (firstRead)
        {
            firstRead = false;
        }

        imshow("ROI Histogram", drawImg);
        imshow("CAMShift Tracking", frame);

        char c = waitKey(50);
        if (c == 27)
        {
            break;
        }
    }
    capture.release();
}

int main(){
    test();
    waitKey(0);
    return 0;
}/<vec3b>/<float>
/<uchar>/<vec3b>/<iostream>/<opencv2>/<code>

效果:

由于放视频会有一些问题,这里就只贴图片啦!

视频分析与对象跟踪-CamShift 算法

2. 视频中移动对象的统计

统计车流量

基本思路与步骤:

(1)基于背景消去(BSM)模型(2)提取前景ROI区域对象轮廓(3)排除干扰与统计

例子代码:

<code>#include<opencv2>
#include<iostream>
using namespace std;
using namespace cv;

void test(){
    VideoCapture capture("Video.wmv");
    if (!capture.isOpened())
    {
        cout <    }

    namedWindow("input video", CV_WINDOW_AUTOSIZE);
    namedWindow("Result", CV_WINDOW_AUTOSIZE);

    //实例化背景消除模型
    Ptr<backgroundsubtractormog2> pMOG2 = createBackgroundSubtractorMOG2();
    //定义结构元素
    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));

    //定义发现的轮廓
    vector<vector>> contours;
    vector<vec4i> hireachy;  //层次
    int count = 0;
    Mat frame,grayImg,mogMask;
    while (capture.read(frame))
    {

        imshow("input video", frame);

        //应用混合高斯模型去除背景
        pMOG2->apply(frame, mogMask);

        threshold(mogMask, mogMask, 100, 255, THRESH_BINARY);  //二值化
        morphologyEx(mogMask, mogMask, MORPH_OPEN, kernel, Point(-1, -1));  //开操作

        //寻找最外层轮廓
        findContours(mogMask, contours, hireachy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));
        count = 0;
        char numText[8];  //定义字符数组
        for (int i = 0; i         {
            double area = contourArea(contours[i]);
            if (area                 continue;
            Rect selection = boundingRect(contours[i]);

            //去掉明显不符合被检测物体形状的轮廓
            if (selection.width                 continue;
            count++;
            rectangle(frame, selection, Scalar(0, 0, 255), 2, 8);

            //sprintf 和平时我们常用的 printf 函数的功能很相似。
            //sprintf 函数打印到字符串中,而printf 函数打印输出到屏幕上
            sprintf_s(numText, "%d", count);
            putText(frame, numText, Point(selection.x, selection.y),
                CV_FONT_NORMAL, FONT_HERSHEY_PLAIN, Scalar(255, 0, 0), 1, 8);
        }

        imshow("Result", frame);
        char c = waitKey(50);
        if (c == 27)
        {
            break;
        }
    }
    capture.release();
}

int main(){
    test();
    waitKey(0);

    return 0;
}/<vec4i>/<vector>/<backgroundsubtractormog2>/<iostream>/<opencv2>/<code>

效果:

由于放视频会有一些问题,这里就只贴图片啦!

视频分析与对象跟踪-CamShift 算法


分享到:


相關文章: