1. 程式人生 > >目標跟蹤--CamShift

目標跟蹤--CamShift

矩形 顏色 hsv顏色空間 方法 邊界 攝像頭 stream i++ 可選

轉載請註明出處!

!!

http://blog.csdn.net/zhonghuan1992

目標跟蹤--CamShift


CamShift全稱是ContinuouslyAdaptive Mean Shift,即連續自適應的MeanShift算法。而MeanShift算法,首先得對MeanShift算法有個初步的了解,可以參考這裏。而CamShift是在MeanShift的基礎上,依據上一幀的結果。來調整下一幀的中心位置和窗體大小,所以。當跟蹤的目標在視頻中發生變化時,可以對這個變化有一定的調整。

OpenCV自帶樣例中的camShift算法,能夠分為三個部分:(引用自這裏 http://blog.csdn.net/carson2005/article/details/7439125)

一、計算色彩投影圖(反向投影):

(1)為了降低光照變化對目標跟蹤的影響,首先將圖像從RGB顏色空間轉換到HSV顏色空間。

(2)對H分量進行直方圖統計,直方圖代表了不同H分量取值出現的概率。或者說能夠據此查找出H分量的大小為x時的概率或像素個數。即,得到顏色概率查找表;

(3)將圖像中每一個像素的值用其顏色出現的概率進行替換,由此得到顏色概率分布圖;

以上三個步驟稱之為反向投影,須要提醒的是。顏色概率分布圖是一個灰度圖像。

二、meanShift尋優

前面提到過meanShift算法(http://blog.csdn.net/carson2005/article/details/7337432)是一種非參數概率密度預計方法,它通過不斷叠代計算得到最優搜索窗體的位置和大小。

三、camShift跟蹤算法

前面提到,camShift事實上就是在視頻序列的每一幀其中都運用meanShift,並將上一幀的meanShift結果作為下一幀的初始值,如此不斷循環叠代,就能夠實現目標的跟蹤了。

在openCV中自帶有camShift函數。老看一下實現,代碼中有部分解釋。(凝視功勞來自http://www.cnblogs.com/tornadomeet/archive/2012/03/15/2398769.html)

#include "opencv2/video/tracking.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
 
 
#include <iostream>
#include <ctype.h>
 
using namespace cv;
using namespace std;
 
Mat image;
 
bool backprojMode = false; //表示是否要進入反向投影模式。ture表示準備進入反向投影模式
bool selectObject = false;//代表是否在選要跟蹤的初始目標,true表示正在用鼠標選擇
int trackObject = 0; //代表跟蹤目標數目
bool showHist = true;//是否顯示直方圖
Point origin;//用於保存鼠標選擇第一次單擊時點的位置
Rect selection;//用於保存鼠標選擇的矩形框
int vmin = 10, vmax =256, smin = 30;
 
void onMouse(int event, int x, int y, int, void*)
{
    if (selectObject)//僅僅有當鼠標左鍵按下去時才有效,然後通過if裏面代碼就能夠確定所選擇的矩形區域selection了
    {
        selection.x = MIN(x, origin.x);//矩形左上角頂點坐標
        selection.y = MIN(y, origin.y);
        selection.width = std::abs(x - origin.x);//矩形寬
        selection.height = std::abs(y - origin.y);//矩形高
 
        selection &= Rect(0, 0, image.cols,image.rows);//用於確保所選的矩形區域在圖片範圍內
    }
 
    switch (event)
    {
    case CV_EVENT_LBUTTONDOWN:
        origin = Point(x, y);
        selection = Rect(x, y, 0, 0);//鼠標剛按下去時初始化了一個矩形區域
        selectObject = true;
        break;
    case CV_EVENT_LBUTTONUP:
        selectObject = false;
        if (selection.width> 0 && selection.height > 0)
            trackObject = -1;
        break;
    }
}
 
void help()
{
    cout << "\nThis is ademo that shows mean-shift based tracking\n"
        "You select acolor objects such as your face and it tracks it.\n"
        "This readsfrom video camera (0 by default, or the camera number the user enters\n"
        "Usage:\n"
        "    ./camshiftdemo [camera number]\n";
 
    cout << "\n\nHot keys:\n"
        "\tESC - quitthe program\n"
        "\tc - stop thetracking\n"
        "\tb - switchto/from backprojection view\n"
        "\th -show/hide object histogram\n"
        "\tp - pausevideo\n"
        "To initializetracking, select the object with mouse\n";
}
 
const char* keys =
{
    "{1|  | 0 |camera number}"
};
 
int main(int argc, const char** argv)
{
    help();
 
    VideoCapture cap; //定義一個攝像頭捕捉的類對象
    Rect trackWindow;
    RotatedRect trackBox;//定義一個旋轉的矩陣類對象
    int hsize = 16;
    float hranges[] = { 0, 180 };//hranges在後面的計算直方圖函數中要用到
    const float* phranges = hranges;
    CommandLineParser parser(argc, argv, keys);//命令解析器函數
    int camNum = parser.get<int>("0");
 
    cap.open(camNum);//直接調用成員函數打開攝像頭
 
    if (!cap.isOpened())
    {
        help();
        cout << "***Could notinitialize capturing...***\n";
        cout << "Currentparameter's value: \n";
        parser.printParams();
        return -1;
    }
 
    namedWindow("Histogram", 0);
    namedWindow("CamShiftDemo",0);
    setMouseCallback("CamShiftDemo",onMouse, 0);//消息響應機制
    createTrackbar("Vmin", "CamShiftDemo",&vmin, 256, 0);//createTrackbar函數的功能是在相應的窗體創建滑動條,滑動條Vmin,vmin表示滑動條的值,最大為256
    createTrackbar("Vmax", "CamShiftDemo",&vmax, 256, 0);//最後一個參數為0代表沒有調用滑動拖動的響應函數
    createTrackbar("Smin", "CamShift Demo", &smin, 256,0);//vmin,vmax,smin初始值分別為10,256,30
 
    Mat frame, hsv, hue, mask, hist, histimg = Mat::zeros(200, 320, CV_8UC3), backproj;
    bool paused = false;
 
    for (;;)
    {
        if (!paused)//沒有暫停
        {
            cap >> frame;//從攝像頭抓取一幀圖像並輸出到frame中
            if (frame.empty())
                break;
        }
 
        frame.copyTo(image);
 
        if (!paused)//沒有按暫停鍵
        {
            cvtColor(image, hsv, CV_BGR2HSV);//將rgb攝像頭幀轉化成hsv空間的
 
            if (trackObject)//trackObject初始化為0,或者按完鍵盤的'c'鍵後也為0,當鼠標單擊松開後為-1
            {
                int _vmin = vmin, _vmax= vmax;
 
                //inRange函數的功能是檢查輸入數組每個元素大小是否在2個給定數值之間。能夠有多通道,mask保存0通道的最小值,也就是h分量
                //這裏利用了hsv的3個通道,比較h,0~180,s,smin~256,v,min(vmin,vmax),max(vmin,vmax)。假設3個通道都在相應的範圍內,則
                //mask相應的那個點的值全為1(0xff),否則為0(0x00).
                inRange(hsv, Scalar(0, smin, MIN(_vmin, _vmax)),
                    Scalar(180, 256, MAX(_vmin, _vmax)),mask);
                int ch[] = { 0, 0 };
                hue.create(hsv.size(),hsv.depth());//hue初始化為與hsv大小深度一樣的矩陣,色調的度量是用角度表示的,紅綠藍之間相差120度,反色相差180度
                mixChannels(&hsv, 1,&hue, 1, ch, 1);//將hsv第一個通道(也就是色調)的數拷貝到hue中,0索引數組
 
                if (trackObject <0)//鼠標選擇區域松開後,該函數內部又將其賦值1
                {
                    //此處的構造函數roi用的是Mat hue的矩陣頭。且roi的數據指針指向hue,即共用同樣的數據,select為其感興趣的區域
                    Mat roi(hue,selection), maskroi(mask, selection);//mask保存的hsv的最小值
 
                    //calcHist()函數第一個參數為輸入矩陣序列,第2個參數表示輸入的矩陣數目。第3個參數表示將被計算直方圖維數通道的列表,第4個參數表示可選的掩碼函數
                    //第5個參數表示輸出直方圖,第6個參數表示直方圖的維數,第7個參數為每一維直方圖數組的大小。第8個參數為每一維直方圖bin的邊界
                    calcHist(&roi, 1, 0,maskroi, hist, 1, &hsize, &phranges);//將roi的0通道計算直方圖並通過mask放入hist中。hsize為每一維直方圖的大小
                    normalize(hist, hist, 0,255, CV_MINMAX);//將hist矩陣進行數組範圍歸一化,都歸一化到0~255
 
                    trackWindow = selection;
                    trackObject = 1;//僅僅要鼠標選完區域松開後。且沒有按鍵盤清0鍵'c',則trackObject一直保持為1。因此該if函數僅僅能運行一次,除非又一次選擇跟蹤區域
 
                    histimg = Scalar::all(0);//與按下'c'鍵是一樣的,這裏的all(0)表示的是標量所有清0
                    int binW = histimg.cols/ hsize;  //histing是一個200*300的矩陣。hsize應該是每個bin的寬度。也就是histing矩陣能分出幾個bin出來
                    Mat buf(1, hsize, CV_8UC3);//定義一個緩沖單bin矩陣
                    for (int i = 0; i <hsize; i++)//saturate_case函數為從一個初始類型準確變換到還有一個初始類型
                        buf.at<Vec3b>(i) = Vec3b(saturate_cast<uchar>(i*180. /hsize), 255, 255);//Vec3b為3個char值的向量
                    cvtColor(buf, buf, CV_HSV2BGR);//將hsv又轉換成bgr
 
                    for (int i = 0; i <hsize; i++)
                    {
                        int val =saturate_cast<int>(hist.at<float>(i)*histimg.rows/ 255);//at函數為返回一個指定數組元素的參考值
                        rectangle(histimg, Point(i*binW,histimg.rows),    //在一幅輸入圖像上畫一個簡單抽的矩形,指定左上角和右下角。並定義顏色,大小,線型等
                            Point((i + 1)*binW,histimg.rows - val),
                            Scalar(buf.at<Vec3b>(i)), -1, 8);
                    }
                }
 
                calcBackProject(&hue, 1, 0,hist, backproj, &phranges);//計算直方圖的反向投影。計算hue圖像0通道直方圖hist的反向投影,並讓入backproj中
                backproj &= mask;
 
                //opencv2.0以後的版本號函數命名前沒有cv兩字了,而且假設函數名是由2個意思的單詞片段組成的話。且前面那個片段不夠成單詞,則第一個字母要
                //大寫,比方Camshift。假設第一個字母是個單詞,則小寫。比方meanShift,可是第二個字母一定要大寫
                RotatedRect trackBox =CamShift(backproj, trackWindow,              //trackWindow為鼠標選擇的區域。TermCriteria為確定叠代終止的準則
                    TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1));//CV_TERMCRIT_EPS是通過forest_accuracy,CV_TERMCRIT_ITER
                if (trackWindow.area()<= 1)                                                  //是通過max_num_of_trees_in_the_forest 
                {
                    int cols =backproj.cols, rows = backproj.rows, r = (MIN(cols, rows) + 5) /6;
                    trackWindow = Rect(trackWindow.x - r,trackWindow.y - r,
                        trackWindow.x + r,trackWindow.y + r) &
                        Rect(0, 0, cols, rows);//Rect函數為矩陣的偏移和大小,即第一二個參數為矩陣的左上角點坐標。第三四個參數為矩陣的寬和高
                }
 
                if (backprojMode)
                    cvtColor(backproj, image, CV_GRAY2BGR);//因此投影模式下顯示的也是rgb圖?
                ellipse(image, trackBox, Scalar(0, 0, 255), 3, CV_AA);//跟蹤的時候以橢圓為代表目標
            }
        }
 
        //後面的代碼是無論pause為真還是為假都要運行的
        else if (trackObject <0)//同一時候也是在按了暫停字母以後
            paused = false;
 
        if (selectObject&& selection.width > 0 && selection.height > 0)
        {
            Mat roi(image,selection);
            bitwise_not(roi, roi);//bitwise_not為將每個bit位取反
        }
 
        imshow("CamShiftDemo",image);
        imshow("Histogram", histimg);
 
        char c = (char)waitKey(10);
        if (c == 27)              //退出鍵
            break;
        switch (c)
        {
        case 'b':             //反向投影模型交替
            backprojMode = !backprojMode;
            break;
        case 'c':            //清零跟蹤目標對象
            trackObject = 0;
            histimg = Scalar::all(0);
            break;
        case 'h':          //顯示直方圖交替
            showHist = !showHist;
            if (!showHist)
                destroyWindow("Histogram");
            else
                namedWindow("Histogram", 1);
            break;
        case 'p':       //暫停跟蹤交替
            paused = !paused;
            break;
        default:
            ;
        }
    }
 
    cap.release();
    return 0;
}


實驗結果:

以攝像頭中我的臉為目標,當他移動時,可以跟蹤到他。

技術分享

技術分享

技術分享

能夠看到我的臉的各色調密度圖。

技術分享



目標跟蹤--CamShift