視頻分析與對象跟蹤-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 算法


分享到:


相關文章: