LKflow

cmakelists:

cmake_minimum_required(VERSION 3.7)
project(LKFlow)

set(CMAKE_CXX_STANDARD 11)

find_package(OpenCV)
include_directories(${OpenCV_INCLUDE_DIRS})

set(SOURCE_FILES main.cpp)
add_executable(LKFlow ${SOURCE_FILES})
target_link_libraries(LKFlow ${OpenCV_LIBS})

source:

#include <iostream>
#include <fstream>
#include <list>//这里用到了list列表
#include <vector>
#include <chrono>//计时的东西都被省掉了,这里没写

using namespace std;

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/video/tracking.hpp>

int main(int argc, char** argv)
{
    //命令行防呆,如果命令行个数不对,直接跳出程序
    if (argc != 2)
    {
        cout<<"usage: useLK path_to_data_folder"<<endl;
        return 1;
    }

    //将命令行转存一下,转存至path_to_data_folder。
    //这里注意,这里的argv[1]是一个文件夹,而不是像以往用的都直接是文件,
    //因为这个程序需要用到data文件夹中好多个文件,所以这里直接通过命令行引入文件夹,然后再在文件夹中寻找文件
    string path_to_data_folder=argv[1];
    //这里就是寻址文件的一种方法,由于path_to_data_folder是文件夹路径,在文件夹中选文件,中间要有/,不要忘记。
    string associate_file=path_to_data_folder+"/associate.txt";
    //通过文件输入,将associate_file文件读进来。
    ifstream fin(associate_file);

    //读入防呆,如果没有读取成功直接跳出。
    if(!fin)
    {
        cerr<<"I cann't find associate.txt!"<<endl;
        return 1;
    }

    //这里定义用于承接associate.txt文件中的输入,依次是:rgb图的时间,rgb图名称,深度图时间,深度图
    string time_rgb, rgb_file, time_depth, depth_file;
    list<cv::Point2f> keypoints;//因为要删除跟踪失败的点,所以使用list
    //定义彩色图和深度图,以及用于逐帧操作的将color承接为last_color,然后color再去读取新的图,依次循环
    cv::Mat color, depth, last_color;

    //循环,这里index指的是每一帧对应一个index。为什么是100,后面解释。
    for(int index=0; index<100; index++)
    {
        //这里可以看出,虽然程序中并不需要time_rgb和time_depth这两个数据,
        //但是由于文件输入流associate_file中有这两个信息,所以还是将其从流中读出来,保证顺次能够正确读取到rgb_file和depth_file
        fin>>time_rgb>>rgb_file>>time_depth>>depth_file;

        //到这里,我们才真正开始读到一张color图和一张depth图,path_to_data_folder寻址到data文件夹,
        //rgb_file变量格式是这样:rgb/1305031453.359684.png,自带了进入rgb文件夹中寻址彩色图
        color=cv::imread(path_to_data_folder+"/"+rgb_file);
        //此程序中好像没有用到depth做什么事,因为本来LK光流就没深度什么事,纯粹是玩两张平面图
        depth=cv::imread(path_to_data_folder+"/"+depth_file, -1);


        //单独处理第一帧,对第一帧提取FAST特征点。第一帧不需要其他工作。
        if(index==0)
        {
            //用于承接关键点的数组
            vector<cv::KeyPoint> kps;
            //创建detector指针,注意这里是指针,注意创建格式
            cv::Ptr<cv::FastFeatureDetector> detector = cv::FastFeatureDetector::create();
            //调用detector的detect方法,对color进行角点检测,检测结果存储在kps关键点数组中
            detector->detect(color,kps);
            //遍历所有关键点,并把关键点的坐标放入keypoints列表中,
            //注意keypoints类型为list,元素类型为cv::Point2f,即是坐标
            //这里还是要注意注意注意,kp类型是KeyPoint,KeyPoint是KeyPoint,跟KeyPoint的坐标是两码事,
            //要访问坐标,用.pt成员进行访问!!!
            for (auto kp:kps)
                keypoints.push_back(kp.pt);

            //由于是第一帧,所以last_color也就是color了。
            last_color = color;
            //第一帧的工作就是检测角点,并把角点坐标存储在keypoints列表中,没有其他工作,所以处理完这些就直接continue跳出
            continue;
        }

        //这里就是上方index为100的原因。在不是第一帧(即index不为0)时候,上一句就是读取彩色图和深度图,
        //cv::imread的参数为一个图片名称字符串,即使没有那个文件时并不会报错,所以这里来检测。
        //associate.txt文件中提供了很多时间对齐的color和depth图,但是我们的color和depth文件夹中只有前9帧,其他都没有。
        //所以运行操作时,index=1,2,3,4,5,6,7,8,9.当在第十帧时,这一句就起作用了,因为imread去读了一个不存在的文件,
        //所以color.data和depth.data都为空指针(这里为了以防万一用了或),用continue将这一帧跳过,
        //由于后面都没有了,所以一直continue到index=99,然后程序结束
        //猜测一下这里为什么这么用,这样设置index其实就是设置了总帧数,
        //用这个if进行continue跳过仅是跳过这一帧,因为实际工况中极有可能某一帧丢失,
        //后面又有画面帧时还会继续工作,而不会丢失一帧就直接停掉
        if ( color.data==nullptr || depth.data==nullptr )
        {
            //加上这一句就会发现index=1,2,3,4,5,6,7,8,9时会有角点跟踪图,
            //后面就是直接秒输出到index直到99,然后循环结束,程序结束
            //也就是后面在没有帧画面时还是会一直imread,只是因为没有data就被一直continue掉了,很快index到99,程序结束了
            cout<<"index="<<index<<endl;
            continue;
        }

        //在index不为0时,对其他帧用LK跟踪特征点
        //首先定义前一帧和后一帧的关键点坐标数组,这里是在循环里面直接创建,所以每次出循环,这俩就没了,进循环到这里就被定义创建
        vector<cv::Point2f> prev_keypoints;
        vector<cv::Point2f> next_keypoints;

        //将从第一帧检测得到的关键点坐标列表,赋值给prev_keypoints数组,遍历并逐个元素push_back
        for (auto kp:keypoints)
            prev_keypoints.push_back(kp);

        //匹配状态承接数组,第一幅图中的关键点,在第二幅图中匹配到就给status赋1,没匹配到就赋0.
        vector<unsigned char> status;
        //匹配误差承接数组,在匹配到的关键点中,都会有一个匹配误差,用error承接出来。
        //想想LK光流原理,每一个像素带入都会得到一个方程,所以最后求解的超定方程解会让每个像素(这里就是关键点)的方程有一点误差。
        vector<float> error;

        //这就是LK光流跟踪函数,说一下参数:
        /*CV_EXPORTS_W void calcOpticalFlowPyrLK( InputArray prevImg, InputArray nextImg,
                                                InputArray prevPts, InputOutputArray nextPts,
                                                OutputArray status, OutputArray err,
                                                Size winSize = Size(21,21), int maxLevel = 3,
                                                TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01),
                                                int flags = 0, double minEigThreshold = 1e-4 );*/
        //InputArray prevImg 前一张图
        //InputArray nextImg 后一张要被跟踪的图
        //InputArray prevPts 前一张图中需要跟踪的角点坐标数组
        //InputOutputArray nextPts 后一张图中角点被跟踪出来后,承接放到此数组中,大小跟prevPts一样
        //OutputArray status 角点跟踪状态承接数组
        //OutputArray err 误差承接数组

        cv::calcOpticalFlowPyrLK( last_color, color, prev_keypoints, next_keypoints, status, error );

        //这一步是对keypoints列表进行更新,从前面的prev_keypoints更新到next_keypoints。
        int i=0;
        //这个循环中其实有两条遍历线,
        // 一条就是keypoints列表,通过iter指针控制,从keypoints.begin()开始到keypoints.end()结束。
        //遍历过程中通过判断status[]值是否为0,如果为0,则说明在跟踪过程中丢了,需要删掉。用.erase(iter)擦除
        //另外一条就是i,来遍历每两帧跟踪后得到的next_keypoints
        for ( auto iter=keypoints.begin(); iter!=keypoints.end(); i++)
        {
            if ( status[i] == 0 )
            {
                //用这一句看看那些跟丢的点,很明显在next_keypoints数组中也是有值的,只不过这里看输出发现好多负值的坐标。这就要在后面跳过了
                cout<<next_keypoints[i]<<endl;
                iter = keypoints.erase(iter);
                //keypoints.erase(iter);为什么这样不行??

                //这个continue,continue后下一次循环时,iter不会++,因为iter++为下面的语句,continue给跳过了。
                //所以在continue跳过跟丢的点时,iter还是保持当前的位置,不会++到下一个,
                //这是对的,keypoints列表保存的是跟踪到的关键点。跟丢了会等着一直到后面没跟丢的数据填充进来,而不会跳过
                //但是i就会被++了,因为i在for循环的()中,运行continue后就会被++。
                //也就是说跟踪失败的话,next_keypoints中的数据就被跳过了,至于为什么在最后面解释
                continue;
            }
            *iter = next_keypoints[i];
            iter++;
        }
        cout<<"tracked keypoints: "<<keypoints.size()<<endl;

        //所有跟踪都没了,就直接break了
        if (keypoints.size() == 0)
        {
            cout<<"all keypoints are lost."<<endl;
            break;
        }

        // 画出 keypoints
        cv::Mat img_show = color.clone();
        //将关键点坐标处画上圆圈
        for ( auto kp:keypoints )
            cv::circle(img_show, kp, 3, cv::Scalar(250, 0, 0));
        //显示画出跟踪点的图像
        cv::imshow("corners", img_show);

        cv::waitKey(500);
        //将color赋值给last_color,下一次循环将会用color读入新的帧
        last_color = color;
    }

    return 0;
}
//说一下程序中的list<cv::Point2f> keypoints、vector<cv::Point2f> prev_keypoints和vector<cv::Point2f> next_keypoints
//keypoints为list类型,定义在每帧跟踪循环之外,一直存在。被初始化是第一帧检测角点后,将所有角点坐标存入。他的更新只有减少,没有增加(只有跟丢的点进行删除)
//vector<cv::Point2f> prev_keypoints和vector<cv::Point2f> next_keypoints是在每帧跟踪循环内被定义的,也就是每次进循环被定义,出循环被释放
//循环中两两帧跟踪时,先将keypoints中点copy给prev_keypoints,然后调用追踪函数,生成next_keypoints。
//两两帧跟踪这个过程中,肯定是有关键点丢失的(因为视角变了,一些点可能出了视野范围),比如前帧100关键点,后帧98个了。
//但是丢失归丢失,整个跟踪过程没关系,prev_keypoints和next_keypoints大小是一样的,包括status数组,都是100。
//那两个跟丢的点在next_keypoints对应位置上依然存在,依然是个坐标值,只不过区分上是在status数组对应位置上,他们的状态是0,其他跟踪到的都是1。
//这样就好解释上面的i为什么被continue跳过了,next_keypoints数组中是有坐标的,只是这个坐标没啥用,因为它的状态已经被标志为0,所以就需要跳过。
//千万不要默认为跟丢之后,next_keypoints数组就自动将其舍掉不保存了。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值