您的位置:首页 > 编程语言 > C语言/C++

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

2018-03-25 18:25 489 查看
sudo apt-getupdate && sudo apt-get install gtk-recordmydesktop
首先安装屏幕录制工具
1)#include<opencv2/opencv.hpp>要使用opencv得这个头文件,
2)VideoCapture获取视频,有两个值,可以是路径或者是数字,数字表示使用哪个摄像头,通常只有一个摄像头的时候是0
3)要加cv::头
4)Mat是矩阵对象,开头大写,可以VideoCapture>>Mat,来将视频内容放入Mat对象里面,可以使用imshow("test",frame);来播放某个矩阵对象,
5)int key = cv::waitKey(1000/15);这个可以让视频停顿相应的时间来等待键盘输入,这里帧率是15,这里的输入单位是毫秒,这种做法能够使得视频播放更加的流畅
6)key==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 );
使用这两句可以实现选中区域的颜色反向,具体原理不清楚
9)selection &=cv::Rect(0, 0, image.cols, image.rows);
可以使用这一句来保证鼠标选中的区域不会超出绘画范围,关键在于    &=这一句
运行效果图



}红圈部分是鼠标选中的部分,可以看到自动追踪了绿色轨道上的的行星
//
// 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的交集
3)Mat有两个属性,rows和cols,分别表示行数和列数
 
第一步:选择追踪目标区域的鼠标回调函数:
 
 
 
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中的selection(rect类型)框住的一部分
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,只有一个输出矩阵,标记对于个数为1,ch为{0,0}表示把hsv的第一个通道给hue,也就是h通道!
5)       Cv::Mat roi(hue,selection);这个语句的功能是把hue的selection(这个为rect)部分复制给roi
6)       对calcHist的分析,calcHist(&roi,1,0,maskroi,hist,1,&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)
d65f
;
                // 将直方图归一
                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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐