美文网首页
vins 的一点点理解(学习笔记)

vins 的一点点理解(学习笔记)

作者: 飘逸_灵魂 | 来源:发表于2018-03-19 11:40 被阅读0次
    vins流程

    和大多数的VSLAM的框架一致,都是分为前端的数据采集,视觉里程计,后端的数据优化与回环检测,当然,作者还设计了一个炫酷的AR与VINS的轨迹显示。下面,我们从程序的入口来分析下整个代码。

    程序的入口是在ViewController.mm和ViewController.h文件下,在里面,认为主要是通过前端UI和后端的核心代码给联系起来。

    下面是运行起来的图片:

    下面,我们从整个项目开始的流程(先后顺序)来讲解下文件ViewController.mm。

    项目的开始是:(void)viewDidLoad函数。

    第一步:Camera setup,作者调用OpenCV的函数来启动摄像头。

     /*******************************************Camera

    setup*******************************************/

        /*

        OpenCV的 highgui 模块中有个类,CvVideoCamera,它把 iPhone 的摄像机抽象出来,让我们的 app 通过一个代理函数- (void)processImage:(cv::Mat&)image 来获得视频流。

        */

        //调用OpenCV来启动摄像头。   

         self.videoCamera=[[CvVideoCamera alloc]

                           initWithParentView:imageView];

        self.videoCamera.delegate= self;

        self.videoCamera.defaultAVCaptureDevicePosition=

        AVCaptureDevicePositionBack;

        self.videoCamera.defaultAVCaptureVideoOrientation=AVCaptureVideoOrientationPortrait;

        self.videoCamera.defaultAVCaptureSessionPreset=

        AVCaptureSessionPreset640x480;

    #ifdef DATA_EXPORT

        self.videoCamera.defaultFPS = 1;

    #else

        self.videoCamera.defaultFPS = 30;

        //摄像头的频率被设置为30帧每秒,我们实现的processImage函数将每秒调用30次。

    #endif

    第二步:UI Congratulation,定义了一些按压手势操作。

        UIPanGestureRecognizer*resultPanGestureRecognizer = [[UIPanGestureRecognizer alloc]

                                                             initWithTarget:self

                                                             action:@selector(handlePan:)];

        resultPanGestureRecognizer.minimumNumberOfTouches = 1;

        resultPanGestureRecognizer.maximumNumberOfTouches = 2;

        [self.imageViewaddGestureRecognizer:resultPanGestureRecognizer];

        UIPinchGestureRecognizer*resultPinchGestureRecognizer = [[UIPinchGestureRecognizer alloc]

                                                                 initWithTarget:self

                                                                 action:@selector(handlePinch:)];

        [self.imageViewaddGestureRecognizer:resultPinchGestureRecognizer];

        UITapGestureRecognizer*resultTapGestureRecognizer = [[UITapGestureRecognizer alloc]

                                                             initWithTarget:self

                                                             action:@selector(handleTap:)];

        [self.imageViewaddGestureRecognizer:resultTapGestureRecognizer];

        UILongPressGestureRecognizer*resultLongPressGestureRecognizer = [[UILongPressGestureRecognizer alloc]

                                                                          initWithTarget:self

                                                                         action:@selector(handleLongPress:)];

        [self.imageViewaddGestureRecognizer:resultLongPressGestureRecognizer];

    //

    UIPinchGestureRecognizer 放大、缩小手势

    - (void) handlePinch:(UIPinchGestureRecognizer*)recognizer

    //

    UITapGestureRecognizer (给控件添加点击事件)

    - (void) handleTap:(UITapGestureRecognizer*)recognizer

    - (void) handleLongPress:(UILongPressGestureRecognizer*)recognizer

    上面的红字标示下面都有函数进行具体表示。UI的设置部分。

    第三步,Init all the thread,初始化所有的线程。

    在作者的代码中主要是几个部分,mainLoop,saveData,loop_thread,globalLoopThread。

        mainLoop=[[NSThreadalloc]initWithTarget:self selector:@selector(run) object:nil];

        [mainLoop setName:@"mainLoop"];

        saveData=[[NSThreadalloc]initWithTarget:self selector:@selector(saveData) object:nil];

        [saveData setName:@"saveData"];

        //上面类似于注册了下线程的意思,具体启动线程在下面。

        if(LOOP_CLOSURE)

        {

            //loop closure

    thread

            loop_thread = [[NSThreadalloc]initWithTarget:self selector:@selector(loop_thread) object:nil];

            [loop_thread setName:@"loop_thread"];

            [loop_thread start];

            // [loop_thread

    start]; 表示线程正式开始启动

            globalLoopThread=[[NSThreadalloc]initWithTarget:self selector:@selector(globalLoopThread) object:nil];

            [globalLoopThread setName:@"globalLoopThread"];

            [globalLoopThread start];

        }

    上面的几个thread中,mainLoop,saveData两个线程仅仅是注册了一下,并没有正式启动,mainLoop的启动在下面。但是saveData仅仅是为了调试而写的,所以在实际中被作者给注释了。

    loop_thread,globalLoopThread在注册的时候就直接启动。

    再有,几个线程都分别对应函数runsaveDataloop_threadglobalLoopThread,作者在代码下面均给出了实现的方法。

    第四步,Device and iOS version check,检查手机设备和iOS版本。

    主要函数:

        bool deviceCheck = setGlobalParam(deviceName());

        bool versionCheck = iosVersion();

    第五步,Start VINS,启动VINS,主要是启动主线程mainLoop。

        if(versionCheck&& deviceCheck)

        {

            [self imuStartUpdate];

            isCapturing =YES;

            [mainLoop start];

            motionManager = [[CMMotionManageralloc] init];

            frameSize =cv::Size(videoCamera.imageWidth,

                                 videoCamera.imageHeight);

        }

    到此完成VINS的运行。

    下面我们来解释下mainLoop线程运行的run函数。(个人认为run函数是一个前端的视觉里程计)

    /*

     VINS thread: this thread tightly fuses thevisual measurements and imu data and solves pose, velocity, IMU bias, 3Dfeature for all frame in WINNDOW

     If the newest frame is keyframe, then push

    it into keyframe database

     */

    -(void)run{

        [_condition lock];

        //表示线程只要不被停止,该线程就会一直被运行。

        while (![[NSThreadcurrentThread]isCancelled])

        {

            [selfprocess];

            [NSThreadsleepForTimeInterval:0.01];

            // [NSThread

    sleepForTimeInterval:0.01] 来设置开机动画停留的时间

        }

        [_condition unlock];

    }

    接上process函数,让线程一直运行,在process函数中实现一帧图像的处理。

            [self performSelectorOnMainThread:@selector(showInputView) withObject:nil waitUntilDone:YES];

            //go back to the

    main thread to reflesh the thread

    处理完一帧(关键帧)后回到main thread然后更新视图,使用函数showInputView。

    通过performSelectorOnMainThread在不同线程中更新界面

    下面,我们来看showInputView是怎样更新显示图像的(UI部分)。

    /*************************************************UI View Controler**********************************

    - (void)showInputView

    //专门用来显示的函数,分两层,第一层是没有初始化,第二层是初始化已经完成。

    {

        NSString*stringView;

        static bool finish_init = false;

        if(vins.solver_flag

    != vins.NON_LINEAR)

        {

           //主要是没有初始化的UI

        }

        else

        {

            if(finish_init == false)

            {

                //Hide init UI

                [_feature_label2 setHidden:YES];

                [_feature_label3 setHidden:YES];

                [indicator setHidden:YES];

                [featureImageView setHidden:YES];

                start_show =true;

                finish_init =true;

            }

            float x_view = (float)vins.correct_Ps[frame_cnt][0];

            float y_view = (float)vins.correct_Ps[frame_cnt][1];

            float z_view = (float)vins.correct_Ps[frame_cnt][2];

            if(x_view_last ==

    -5000)

            {

                x_view_last = x_view;

                y_view_last = y_view;

                z_view_last = z_view;

            }

            total_odom +=sqrt(pow((x_view -

    x_view_last), 2) +

                               pow((y_view -

    y_view_last), 2) +

                               pow((z_view -

    z_view_last), 2));

            // total_odom 应该表示总路程 

            // odometer  总里程            

            x_view_last = x_view;

            y_view_last = y_view;

            z_view_last = z_view;

            stringView = [NSStringstringWithFormat:@"X:%.2f",x_view];

            [_X_label setText:stringView];

            stringView = [NSStringstringWithFormat:@"TOTAL:%.2f",total_odom];

            //stringView =

    [NSString stringWithFormat:@"COST:%.2lf",vins.final_cost];

            //stringView =

    [NSString stringWithFormat:@"COST: %d, %.2lf",vins.visual_factor_num,

    vins.visual_cost];

            [_total_odom_label setText:stringView];

            stringView = [NSStringstringWithFormat:@"Y:%.2f",y_view];

            [_Y_label setText:stringView];

            stringView = [NSStringstringWithFormat:@"Z:%.2f",z_view];

            [_Z_label setText:stringView];

        }

        stringView = [NSStringstringWithFormat:@"BUF:%d",waiting_lists];

        [_buf_label setText:stringView];

        //NSString *stringZ = [NSString stringWithFormat:@"Z:%.2f",z_view,

    vins.f_manager.getFeatureCount()];

        if(loop_old_index

    != -1)

        {

            stringView = [NSStringstringWithFormat:@"LOOP with %d",loop_old_index];

            [_loop_label setText:stringView];

        }

        stringView = [NSStringstringWithFormat:@"FEATURE: %d",vins.feature_num];

        [_feature_label setText:stringView];

    }

    -(void)showOutputImage:(UIImage*)image(这个函数不明白是什么意思?)

    {

        [featureImageView setImage:image];

    }

    - (void)showInputView函数中分为两个部分,一个部分是初始化UI,一个部分是初始化成功后的UI,构成所谓的if-else的结构。显示的图像分别为:

            初始化后的UI,在上面可以看到total odom表示总里程数,单位是m,x_view,y_view,z_view表示的是距离坐标原点的距离,也就是相机的位置的世界坐标。单位同样是m。

    非常乐意和大家一起交流,学习,共同进步。如有认知错误,希望大家指出,谢谢。

    文档格式有差异,如果需要PDF版原稿,请邮件至814810290@qq.com。

    参考文献:

    Monocular Visual-Inertial State Estimation for Mobile Augmented Reality,P.Li et al (ISMAR 2017)

    相关文章

      网友评论

          本文标题:vins 的一点点理解(学习笔记)

          本文链接:https://www.haomeiwen.com/subject/wywuqftx.html