项目实战笔记 | C++实现运动目标的追踪 实验楼项目

sudo apt-getupdate && sudo apt-get install gtk-recordmydesktop

首先安装屏幕录制工具

1#include<opencv2/opencv.hpp>要使用opencv得这个头文件,

2VideoCapture获取视频,有两个值,可以是路径或者是数字,数字表示使用哪个摄像头,通常只有一个摄像头的时候是0

3)要加cv::

4Mat是矩阵对象,开头大写,可以VideoCapture>>Mat,来将视频内容放入Mat对象里面,可以使用imshow("test",frame);来播放某个矩阵对象,

5int key = cv::waitKey(1000/15);这个可以让视频停顿相应的时间来等待键盘输入,这里帧率是15,这里的输入单位是毫秒,这种做法能够使得视频播放更加的流畅

6key==27表示获得的按键是esc按键

7)在代码结束的时候,要使用对象.release()来释放所有的对象,release貌似是opencv独有的函数,还需要使用destroyAllWindows();来消除掉播放框

8

        if( selectObject &&selection.width > 0 && selection.height > 0 ) {

            cv::Mat roi(image, selection);

            bitwise_not(roi, roi);

        }

 

        imshow( "CamShift atShiyanlou", image );

使用这两句可以实现选中区域的颜色反向,具体原理不清楚

9selection &=cv::Rect(0, 0, image.cols, image.rows);

可以使用这一句来保证鼠标选中的区域不会超出绘画范围,关键在于    &=这一句

运行效果图

项目实战笔记 | C++实现运动目标的追踪 实验楼项目

}红圈部分是鼠标选中的部分,可以看到自动追踪了绿色轨道上的的行星

//

// main.cpp

//

#include<opencv2/opencv.hpp> // OpenCV 头文件

 

int main() {

 

    // 创建一个视频捕获对象

    // OpenCV 提供了一个 VideoCapture 对象,它屏蔽了

    // 从文件读取视频流和从摄像头读取摄像头的差异,当构造

    // 函数参数为文件路径时,会从文件读取视频流;当构造函

    // 数参数为设备编号时(第几个摄像头, 通常只有一个摄像

    // 头时为0),会从摄像头处读取视频流。

    cv::VideoCapturevideo("video.ogv"); // 读取文件

    // cv::VideoCapture video(0);        // 使用摄像头

 

    // 捕获画面的容器,OpenCV 中的 Mat 对象

    // OpenCV 中最关键的 Mat 类,Mat 是 Matrix(矩阵)

    // 的缩写,OpenCV 中延续了像素图的概念,用矩阵来描述

    // 由像素构成的图像。

    cv::Mat frame;

    while(true) {

 

        // 将 video 中的内容写入到 frame 中,

        // 这里 >> 运算符是经过 OpenCV 重载的

        video >> frame;

 

        // 当没有帧可继续读取时,退出循环

        if(frame.empty()) break;

 

        // 显示当前帧

        cv::imshow("test", frame);

 

        // 录制视频帧率为 15, 等待 1000/15 保证视频播放流畅。

        // waitKey(int delay) 是 OpenCV 提供的一个等待函数,

        // 当运行到这个函数时会阻塞 delay 毫秒的时间来等待键盘输入

        int key = cv::waitKey(1000/15);

 

        // 当按键为 ESC 时,退出循环

        if (key == 27) break;

    }

    // 释放申请的相关内存

    cv::destroyAllWindows();

    video.release();

    return 0;

 

}

来源: 实验楼

链接: https://www.shiyanlou.com/courses/560

MeanShift算法:

简单来说就是当圆圈的圆心和质心不重合的时候,将圆圈向质心的位置移动,这个算法可以计算局部点最密集的地方

这个算法貌似可以用来捕捉运动的物体,在捕捉物体的时候,使用反向投影直方图,这个直方图能够反映物体的运动

如图所示,把蓝色的圆圈标记为 C1,蓝色的矩形为圆心标记为 C1_o。但这个圆的质心却为 C1_r,标记为蓝色的实心圆。

当C1_o和C1_r不重合时,将圆 C1移动到圆心位于 C1_r的位置,如此反复。最终会停留在密度最高的圆 C2 上。

但是meanshift只能对恒定大小的物体进行追踪,在实际运中,可能物体并不是恒定大小的

Camshift算法能够改进这个问题,它在每一次meanshift收敛之后用一个椭圆来更新窗口,然后在新的窗口下再次应用meanshift算法

Opencv里面实现了这个算法:

RotatedRectCamShift(InputArray probImage, Rect& window, TermCriteria criteria)

其中第一个参数 probImage 为目标直方图的反向投影,第二个参数 window 为执行 Camshift 算法的搜索窗口,第三个参数为算法结束的条件。

接下来实现

1)这里通过event的值我们能够获得鼠标点击的具体是什么,比如CV_EVENT_LBUTTONDOWN是左键被按下,CV_EVENT_LBUTTONUP

 是右键放开

2&=rect重载,可以用来计算两个rect的交集

3Mat有两个属性,rowscols,分别表示行数和列数

 

第一步:选择追踪目标区域的鼠标回调函数:

 

 

 

boolselectObject = false; // 用于标记是否有选取目标

int trackObject= 0;       // 1 表示有追踪对象 0 表示无追踪对象 -1 表示追踪对象尚未计算Camshift 所需的属性

cv::Rectselection;        // 保存鼠标选择的区域

cv::Matimage;             // 用于缓存读取到的视频帧

 

// OpenCV 对所注册的鼠标回调函数定义为:

// voidonMouse(int event, int x, int y, int flag, void *param)

// 其中第四个参数 flag 为 event 下的附加状态,param 是用户传入的参数,我们都不需要使用

// 故不填写其参数名

void onMouse(int event, int x, int y, int, void* ) {

    static cv::Point origin;

    if(selectObject) {

        // 确定鼠标选定区域的左上角坐标以及区域的长和宽

        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);

 

        // & 运算符被 cv::Rect 重载

        // 表示两个区域取交集, 主要目的是为了处理当鼠标在选择区域时移除画面外

        selection &= cv::Rect(0, 0,image.cols, image.rows);

    }

 

    switch(event) {

        // 处理鼠标左键被按下

        case CV_EVENT_LBUTTONDOWN:

            origin = cv::Point(x, y);

            selection = cv::Rect(x, y, 0, 0);

            selectObject = true;

            break;

        // 处理鼠标左键被抬起

        case CV_EVENT_LBUTTONUP:

            selectObject = false;

            if( selection.width > 0&& selection.height > 0 )

                trackObject = -1; // 追踪的目标还未计算 Camshift 所需要的属性

            break;

    }

}

第二步:从视频流中读取图像

下面是对main函数进一步细化:

1)   使用namedWindow(string)来注册一个窗口,接下来可能是在这个窗口里面播放内容

2)   setMouseCallback(string,onMouse,NULL);第一个参数是类似于注释,第二个参数为回掉函数指针,第三个参数为用户提供给回调函数的

3)   Mat.copyTo(Mat)将一个mat中的数据拷贝到另外一个Mat里面,这里一般是视频的一个帧或者是一副画面

4)   这里的Mat ort(image,selection),意思是初始化一个mat,使用image中的selectionrect类型)框住的一部分

5)   bitwise_not(mat1,mat2),意思是将mat1的全部数据取反之后放入mat2

int main() {

    cv::VideoCapturevideo("video.ogv");

    cv::namedWindow("CamShift atShiyanlou");

 

    // 1. 注册鼠标事件的回调函数, 第三个参数是用户提供给回调函数的,也就是回调函数中最后的 param 参数

    cv::setMouseCallback("CamShift atShiyanlou", onMouse, NULL);

 

    cv::Mat frame; // 接收来自 video 视频流中的图像帧

 

    // 2. 从视频流中读取图像

    while(true) {

        video >> frame;

        if(frame.empty()) break;

 

        // 将frame 中的图像写入全局变量 image 作为进行 Camshift 的缓存

        frame.copyTo(image);

 

        // 如果正在选择追踪目标,则画出选择框

        if( selectObject &&selection.width > 0 && selection.height > 0 ) {

            cv::Mat roi(image, selection);

            bitwise_not(roi, roi);  // 对选择的区域图像反色

        }

        imshow("CamShift atShiyanlou", image);

        int key = cv::waitKey(1000/15.0);

        if(key == 27) break;

    }

    // 释放申请的相关内存

    cv::destroyAllWindows();

    video.release();

    return 0;

}

来源: 实验楼

链接:https://www.shiyanlou.com/courses/560

三步:实现 Camshift 过程

1)       cvtColor(image,hsv,COLOR_BGR2HSV);这个函数可以将rgb颜色空间的矩阵转换成hsv颜色空间的颜色矩阵,将image转换后存储在hsv

2)       inRange(hsv,cv::Scalar(0,30,10),cv::Scalar(180,256,256),mask);这个函数的功能是将MatHsv中的符合三个通道的相应的范围复制到mask

3)       hue.create(hsv.size(),hsv.depth());这个函数功能是给hue创造矩阵,使用hsv的大小和hsv的像素深度!

4)       mixChannels(&hsv,1.&hue,1,ch,1)这个函数的功能是将指定的矩阵的相应的通道复制给输出矩阵,这里的输入矩阵为hsv,只有一个输入矩阵,输出矩阵为hue,只有一个输出矩阵,标记对于个数为1ch{0,0}表示把hsv的第一个通道给hue,也就是h通道!

5)       Cv::Mat roi(hue,selection);这个语句的功能是把hueselection(这个为rect)部分复制给roi

6)       calcHist的分析,calcHist(&roi,1,0maskroi,hist1&hsize&phranges)

第一个参数表示输入的矩阵,是constMat *类型,所以要输入地址,第二个参数表示输入图像的个数,第三个参数是const int *类型,表示输入图像使用哪些channels来计算直方图,一般是输入一个数组,这里输入一个0,表示空指针,这里应该是表示使用第一幅图像的第一个通道,也就是经过inRange过的H通道,第四个参数表示使用哪个掩码来过滤原图像,这里可以是矩阵要求和输入矩阵一样大,这里掩码有3个通道,但是输入图像只有一个通道,这里应该是使用默认的掩码的第一个通道来过滤输入图像的矩阵,过滤的方法为,当掩码的某个值为0的时候,输入图像的相应位置会被过滤,但是当值大于0的时候输入图像的相应值才会被用来计算直方图

Hist,表示输出直方图,这里用矩阵Mat来接受直方图数据,也不知道是怎么存储的,

6个参数表示输出的直方图是几维的,这里当然是一维的

7个参数表示在每一维上竖条的个数,这里为16,参数类型为const int *,可以是数组也可以是&int类型,这里只有1维所以是&int

8个参数表示直方图统计的原图的值的范围,是一个const float **的类型,也就是二维数组,比如float a[]={0,10} float b[]={20,30},float * c[]={a,b};这里的c就是表示输入的范围为0~10,10~20

这里是使用floathranges[] = {0,180}const float *phranges = hranges,来实现,这里需要输入指针的地址,这里可以直接输入&phranges来实现功能,

7)       normalize(inputArray,outputArray,doublealpha,double beta,normalizeType)

最后一个参数可以为CV_MINMAX,这里表示把矩阵归一化到指定范围

 

int main() {

    cv::VideoCapturevideo("video.ogv");

    cv::namedWindow("CamShift atShiyanlou");

    cv::setMouseCallback("CamShift atShiyanlou", onMouse, NULL);

 

    cv::Mat frame;

    cv::Mat hsv, hue, mask, hist, backproj;

    cv::Rect trackWindow;             // 追踪到的窗口

    int hsize = 16;                   // 计算直方图所必备的内容

    float hranges[] = {0,180};        // 计算直方图所必备的内容

    const float* phranges = hranges;  // 计算直方图所必备的内容

 

    while(true) {

        video >> frame;

        if(frame.empty()) break;

        frame.copyTo(image);

 

        // 转换到 HSV 空间

        cv::cvtColor(image, hsv,cv::COLOR_BGR2HSV);

        // 当有目标时开始处理

        if(trackObject) {

 

            // 只处理像素值为H:0~180,S:30~256,V:10~256之间的部分,过滤掉其他的部分并复制给 mask

            cv::inRange(hsv, cv::Scalar(0, 30,10), cv::Scalar(180, 256, 10), mask);

            // 下面三句将 hsv 图像中的 H 通道分离出来

            int ch[] = {0, 0};

            hue.create(hsv.size(),hsv.depth());

            cv::mixChannels(&hsv, 1,&hue, 1, ch, 1);

 

            // 如果需要追踪的物体还没有进行属性提取,则对选择的目标中的图像属性提取

            if( trackObject < 0 ) {

 

                // 设置 H 通道和 mask 图像的 ROI

                cv::Mat roi(hue, selection),maskroi(mask, selection);

                // 计算 ROI所在区域的直方图

                calcHist(&roi, 1, 0,maskroi, hist, 1, &hsize, &phranges);

                // 将直方图归一

                normalize(hist, hist, 0, 255,CV_MINMAX);

 

                // 设置追踪的窗口

                trackWindow = selection;

 

                // 标记追踪的目标已经计算过直方图属性

                trackObject = 1;

            }

            // 将直方图进行反向投影

            calcBackProject(&hue, 1, 0,hist, backproj, &phranges);

            // 取公共部分

            backproj &= mask;

            // 调用 Camshift 算法的接口

            cv::RotatedRect trackBox =CamShift(backproj, trackWindow, cv::TermCriteria( CV_TERMCRIT_EPS |CV_TERMCRIT_ITER, 10, 1 ));

            // 处理追踪面积过小的情况

            if( trackWindow.area() <= 1 ) {

                int cols = backproj.cols, rows= backproj.rows, r = (MIN(cols, rows) + 5)/6;

                trackWindow =cv::Rect(trackWindow.x - r, trackWindow.y - r,

                                  trackWindow.x + r, trackWindow.y + r) & cv::Rect(0, 0, cols, rows);

            }

            // 绘制追踪区域

            ellipse( image, trackBox,cv::Scalar(0,0,255), 3, CV_AA );

 

        }

 

 

        if( selectObject &&selection.width > 0 && selection.height > 0 ) {

            cv::Mat roi(image, selection);

            bitwise_not(roi, roi);

        }

        imshow("CamShift atShiyanlou", image);

        int key = cv::waitKey(1000/15.0);

        if(key == 27) break;

    }

    cv::destroyAllWindows();

    video.release();

    return 0;

}

接下来是所有的代码总结

 

#include<opencv2/opencv.hpp>

 

boolselectObject = false;

int trackObject= 0;

cv::Rectselection;

cv::Mat image;

 

void onMouse(int event, int x, int y, int, void* ) {

    static cv::Point origin;

    if(selectObject) {

        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 &= cv::Rect(0, 0,image.cols, image.rows);

    }

    switch(event) {

        case CV_EVENT_LBUTTONDOWN:

            origin = cv::Point(x, y);

            selection = cv::Rect(x, y, 0, 0);

            selectObject = true;

            break;

        case CV_EVENT_LBUTTONUP:

            selectObject = false;

            if( selection.width > 0&& selection.height > 0 )

                trackObject = -1;

            break;

    }

}

 

int main( intargc, const char** argv )

{

    cv::VideoCapturevideo("video.ogv");

    cv::namedWindow( "CamShift atShiyanlou" );

    cv::setMouseCallback( "CamShift atShiyanlou", onMouse, 0 );

 

    cv::Mat frame, hsv, hue, mask, hist,backproj;

    cv::Rect trackWindow;

    int hsize = 16;

    float hranges[] = {0,180};

    const float* phranges = hranges;

 

    while(true) {

        video >> frame;

        if( frame.empty() )

            break;

 

        frame.copyTo(image);

 

        cv::cvtColor(image, hsv,cv::COLOR_BGR2HSV);

 

        if( trackObject ) {

 

            cv::inRange(hsv, cv::Scalar(0, 30,10), cv::Scalar(180, 256, 256), mask);

            int ch[] = {0, 0};

            hue.create(hsv.size(),hsv.depth());

            cv::mixChannels(&hsv, 1,&hue, 1, ch, 1);

 

            if( trackObject < 0 ) {

                cv::Mat roi(hue, selection),maskroi(mask, selection);

                calcHist(&roi, 1, 0,maskroi, hist, 1, &hsize, &phranges);

                normalize(hist, hist, 0, 255,CV_MINMAX);

 

                trackWindow = selection;

                trackObject = 1;

            }

 

            calcBackProject(&hue, 1, 0,hist, backproj, &phranges);

            backproj &= mask;

            cv::RotatedRect trackBox =CamShift(backproj, trackWindow, cv::TermCriteria( CV_TERMCRIT_EPS |CV_TERMCRIT_ITER, 10, 1 ));

            if( trackWindow.area() <= 1 ) {

                int cols = backproj.cols, rows= backproj.rows, r = (MIN(cols, rows) + 5)/6;

                trackWindow =cv::Rect(trackWindow.x - r, trackWindow.y - r,

                                   trackWindow.x + r,trackWindow.y + r) &

                cv::Rect(0, 0, cols, rows);

            }

            ellipse( image, trackBox,cv::Scalar(0,0,255), 3, CV_AA );

 

        }

 

        if( selectObject &&selection.width > 0 && selection.height > 0 ) {

            cv::Mat roi(image, selection);

            bitwise_not(roi, roi);

        }

 

        imshow( "CamShift atShiyanlou", image );

        char c = (char)cv::waitKey(1000/15.0);

        if( c == 27 )

            break;

    }

    cv::destroyAllWindows();

    video.release();

    return 0;

}

来源: 实验楼

链接: https://www.shiyanlou.com/courses/560