1. 程式人生 > >OpenCV之視訊分析與物件跟蹤(五) CAMShift物件跟蹤

OpenCV之視訊分析與物件跟蹤(五) CAMShift物件跟蹤

MeanShift演算法(均值漂移):
假設右一堆資料,不均勻的在平面空間分佈,首先通過均值計算,評估中心點在哪裡,然後中心點向高密度的地方平移,最後找到密度最高的地方。
這裡寫圖片描述
數學描述:
這裡寫圖片描述
這裡寫圖片描述
找到的windows區域繪製成直方圖:
這裡寫圖片描述
通過直方圖比較(轉換為HSV空間,巴氏距離比較Hue),反向對映,跟蹤找到選擇的roi區域:
這裡寫圖片描述
這裡寫圖片描述
MeanShift演算法使用的直方圖比較與模板匹配類似,這兩種演算法只能匹配windows固定的roi區域,如果視訊中選擇的roi區域的移動忽遠忽近(甚至變形),這兩種演算法就都不能再準確的跟蹤到roi區域。這個時候使用CAMShift。

CAMShift跟蹤演算法:(連續自適應的MeanShift跟蹤演算法)
- 視窗尺寸自動變化
- 適合變形目標檢測
這裡寫圖片描述

程式碼

#include "../common/common.hpp"
#include <opencv2/tracking.hpp>

static int smin = 15; // 二值化時,s 最小值的閾值
static int vmin = 40; // 二值化時,v 最小值的閾值
static int vmax = 256; // 二值化時,s v 最大值的閾值
static int bins = 16; // 直方圖bins

void main(int argc, char** argv) 
{
    VideoCapture capture;
    capture.open(getCVImagesPath("videos/balltest.avi"));
    if (!capture.isOpened()) cout << "video not open.." << endl;

    bool firstRead = true;
    float hrange[] = { 0, 180 };
    const float* hranges = hrange;
    Rect selection; // roi區域
    Mat frame, hsv, hue, mask, hist, backprojection;
    Mat drawImg = Mat::zeros(300, 300, CV_8UC3); // 直方圖
    char roiName[] = "CAMShift Tracking";
    while (capture.read(frame)) 
    {
        if (firstRead) 
        {
            cout << "frame.size=" << frame.size() << endl;
            // 從frame中選擇roi區域,程式會阻斷,等待選擇。返回值為選擇的roi區域,引數windowName要與後面的imshow的name一致
            Rect2d first = selectROI(roiName, frame); // roi可以選擇整個黃色小球區域,也可以只選擇其內部一部分割槽域
            selection.x = first.x;
            selection.y = first.y;
            selection.width = first.width;
            selection.height = first.height;
            printf("ROI.x= %d, ROI.y= %d, width = %d, height= %d\n", selection.x, selection.y, selection.width, selection.height);
        }
        // convert to HSV
        cvtColor(frame, hsv, COLOR_BGR2HSV);
        inRange(hsv, Scalar(0, smin, vmin), Scalar(180, vmax, vmax), mask); // 二值化,為CAMShift跟蹤演算法去掉一定的干擾
        hue = Mat(hsv.size(), hsv.depth());
        int channels[] = { 0, 0 }; // hsv 的第0通道,輸出到hue的第0通道
        /*  
            mixChannels( // 將輸入陣列的指定通道複製到輸出陣列的指定通道
                const Mat* src, //輸入陣列或向量矩陣,所有矩陣的大小和深度必須相同。
                size_t nsrcs, //矩陣的數量
                Mat* dst, //輸出陣列或矩陣向量,大小和深度必須與src相同
                size_t ndsts,//矩陣的數量
                const int* fromTo,//指定被複制通道與要複製到的位置組成的索引對
                size_t npairs //fromTo中索引對的數目
            );
        */
        mixChannels(&hsv, 1, &hue, 1, channels, 1); // 將輸入陣列的指定通道複製到輸出陣列的指定通道

        if (firstRead) 
        {
            // ROI 直方圖計算
            Mat roi(hue, selection); // 摳出hue中roi區域
            Mat maskroi(mask, selection); // 摳出mask中roi區域
            /*
                calcHist(// 計算直方圖
                    const Mat* images,//輸入影象指標
                    int images,// 影象數目
                    const int* channels,// 要計算的通道數的下標,可以傳一個數組 {0, 1} 表示計算第0通道與第1通道的直方圖,此陣列長度要與histsize ranges 陣列長度一致
                    InputArray mask,// 輸入mask,可選,如有,則表示只計算mask元素值為255的位置的直方圖
                    OutputArray hist,//輸出的直方圖資料
                    int dims,// 維數
                    const int* histsize,// 直方圖級數, 對應 bins
                    const float** ranges,// 值域範圍
                    bool uniform,// true by default   是否歸一化到 0-1 之間
                    bool accumulate// false by defaut 多通道時true?
                )
            */
            calcHist(&roi, 1, 0, maskroi, hist, 1, &bins, &hranges); // 這裡直方圖只計算繪製一次第一幀上選擇的roi區域
            // hist size=[1 x 16], depth=5, type=5    CV_32F
            cout << "hist size=" << hist.size() << ", depth=" << hist.depth() << ", type=" << hist.type() << endl;
            normalize(hist, hist, 0, 255, NORM_MINMAX); // 歸一化,排除光強幹擾進行比較

            // show histogram
            int binw = drawImg.cols / bins;
            Mat colorIndex = Mat(1, bins, CV_8UC3);
            for (int i = 0; i < bins; i++) 
            {
                colorIndex.at<Vec3b>(0, i) = Vec3b(saturate_cast<uchar>(i * 180 / bins), 255, 255); // 按照HSV賦值,下面再轉換為BGR顏色空間
            }
            cvtColor(colorIndex, colorIndex, COLOR_HSV2BGR); // 不轉換為BGR也是可以的,只是直方圖顏色不一樣而已
            for (int i = 0; i < bins; i++) 
            {
                // 如果roi選擇的是黃色小球區域或其內部一部分割槽域,hist的值基本屬於兩值分化的狀態,0 或 255
                int  val = saturate_cast<int>(hist.at<float>(i)*drawImg.rows / 255); // val 等於 0-drawImg.rows 之間
                rectangle(drawImg, Point(i*binw, drawImg.rows), Point((i + 1)*binw, drawImg.rows - val), Scalar(colorIndex.at<Vec3b>(0, i)), -1, 8, 0); // 繪製直方圖
            }
        }

        /*
            calcBackProject ( // 反向投影
                const Mat * images, // 輸入影象,影象深度必須位CV_8U,CV_16U或CV_32F中的一種,尺寸相同,每一幅影象都可以有任意的通道數
                int nimages, // 輸入影象的數量
                const int * channels, // 用於計算反向投影的通道列表,通道數必須與直方圖維度相匹配,第一個陣列的通道是從0到image[0].channels()-1,
                                        第二個陣列通道從影象image[0].channels()到image[0].channels()+image[1].channels()-1計數
                InputArray hist, // 輸入的直方圖,直方圖的bin可以是密集(dense)或稀疏(sparse)
                OutputArray backProject, // 目標反向投影輸出影象,是一個單通道影象,與原影象有相同的尺寸和深度
                const float ** ranges, // 直方圖中每個維度bin的取值範圍
                double scale = 1, // 可選輸出反向投影的比例因子
                bool uniform = true // 直方圖是否均勻分佈(uniform)的識別符號,預設值true
            )
        */
        calcBackProject(&hue, 1, 0, hist, backprojection, &hranges); // 反響對映
        imshow("backprojection", backprojection);
        imshow("mask6-12", mask);
        // CAMShift tracking
        backprojection &= mask; // &=運算子過載,backprojection與mask必須同size,返回結果為它們相同位置的畫素值都為255的畫素保留,否則為0
        imshow("back&mask", backprojection);
        /*
            RotatedRect CamShift( // CAMShift跟蹤演算法
                InputArray probImage, // 反向投影影象
                CV_IN_OUT Rect& window, // 輸入和輸出的搜尋視窗/目標視窗,window的尺寸在跟蹤過程中會隨著跟蹤目標的大小變化自動調整
                TermCriteria criteria // 迭代收斂終止條件
            );
            返回值為跟蹤到的目標所在的旋轉了的矩形框
            CAMShift跟蹤演算法對於跟蹤的目標,即使發生遠近變換,形變,都能準確跟蹤到,甚至跟蹤目標離開了螢幕再回來也能繼續跟蹤到,
                    但是離開的期間也會產生 selection,所以這個時候的selection是不對的,當跟蹤目標回到屏幕後也不是立即就察覺到,需要一定時間
                    so CAMShift無法判斷目標是否離開螢幕,這段期間產生的誤差如何解決?
                    同時CAMShift跟蹤演算法適合比較簡單的影象,如果影象顏色資料很複雜,有很多大量與跟蹤目標顏色重複的畫素的話(也就是干擾很強),CAMShift效果就不太好了
        */
        // 引數 TermCriteria((TermCriteria::COUNT | TermCriteria::EPS), 10, 1) 表示MeanShift尋找中心點的演算法時,最多迭代10次,當中心點的距離插值小於1,立即結束
        RotatedRect trackBox = CamShift(backprojection, selection, TermCriteria((TermCriteria::COUNT | TermCriteria::EPS), 10, 1)); // 演算法速度很快,滿足實時性
        cout << "selection.size=" << selection.size() << ", ";

        // draw location on frame;
        ellipse(frame, trackBox, Scalar(0, 0, 255), 3, 8); // 在跟蹤的roi區域繪製橢圓

        if (firstRead) firstRead = false;
        imshow(roiName, frame);
        imshow("ROI Histogram", drawImg);
        if (waitKey(50) == 27) break;
    }
    capture.release();

    waitKey(0);
}

效果圖

這裡寫圖片描述