SLAM十四讲-第五讲-ch5-相机与图像(源码详细解释)

一、opencv的简单使用

该程序实现的功能是输出图片的基本信息,同时将图片左上角的100x100区域用白色的100x100窗口覆盖掉
源码如下:

//
//
#include <iostream>
#include <chrono>
using namespace std;
#include<opencv2/core/core.hpp>
#include<opencv2//highgui//highgui.hpp>

int main(int argc,char **argv)
{
    
    
    //读取图片
    argv[1]="/home/nnz/data/slam_pratice/pratice_cv/ubuntu.png";
    cv::Mat image=cv::imread(argv[1]);
    //判断图片有没有读取成功
    if(image.data== nullptr)
    {
    
    
        cerr<<"文件不存在!"<<endl;
        return 0;
    }
    //文件读取成功,对该图片进行简单的操作
    //首先输出图片的基本信息,宽,高,通道数等等
    cout<<"图片的宽为:"<<image.cols<<"\t图片高为:"<<image.rows<<
    "图片的通道数为:"<<image.channels()<<endl;
    //判断image的类型
    //U代表unsigned C是通道,C后面的数字代表通道数
    if(image.type()!=CV_8UC1 && image.type()!=CV_8UC3)
    {
    
    
        cout<<"请输入一张灰度图或者彩色图!"<<endl;
    }
    chrono::steady_clock::time_point  t1=chrono::steady_clock::now();
    //遍历图像的像素
    for(size_t y=0;y<image.rows;y++)
    {
    
    
        //用cv::Ma::ptr获取行指针
        //定义行指针 row_ptr
        unsigned char *row_ptr=image.ptr<unsigned char>(y);
        for(size_t x=0;x<image.cols;x++)
        {
    
    
            //定义访问 (x ,y)像素的指针 data_ptr
            unsigned char *data_ptr=&row_ptr[x*image.channels()];
            for(int c=0;c<image.channels();c++)
            {
    
    
                unsigned char data=data_ptr[c];//data是第c个通道的像素
            }
        }
    }
    chrono::steady_clock::time_point  t2=chrono::steady_clock::now();
    chrono::duration<double> time_used=chrono::duration_cast <chrono::duration<double>>(t2-t1);
    cout<<"遍历图片用时:"<<time_used.count()<<endl;//算出遍历图片中每个像素的时间
    //特别注意图片的拷贝,CV::Mat image_clone=image 这样赋值的话,若改变了image_clone,则image也会改变
    //所以应该用image_clone=image.clone()
    cv::Mat image_clone=image.clone();//拷贝图片
    image_clone(cv::Rect(0,0,100,100)).setTo(255);//255代表RGB255 截取的区域是白色
    cv::imshow("image",image);
    cv::imshow("image_clone",image_clone);
    cv::waitKey(0);//按任意键清屏?结束
    //int cvWaitKey( int delay=0 )
    //返回值为int型,函数的参数为int型,当delay小于等于0的时候,如果没有键盘触发,则一直等待,此时的返回值为-1,否则返回值为键盘按下的码字;
    // 当delay大于0时,如果没有键盘的的触发,则等待delay的时间,此时的返回值是-1,否则返回值为键盘按下的码字。
    cv::destroyAllWindows();//销毁所有窗口
    //上面的Rect()函数中的参数是要切割掉的窗口的大小、位置,后面的setTo()中的255是

    return 0;
}

运行结果如下:

图片的宽为:1200	图片高为:674图片的通道数为:3
遍历图片用时:5.7e-08

可以看出东莞打工的
可以看出拷贝的图片左上角被覆盖了100*100的区域,覆盖的颜色为白色

二、图像去畸变

该程序是将畸变的图形进行去畸变的操作,并显示去畸变和未去畸变的图像(手写去畸变)
源码如下:

//
// Created by wenbo on 2020/10/27.
//
#include<iostream>
#include<opencv2/opencv.hpp>
//首先确定5个畸变参数 k1,k2,k3,p1,p2
//k1,k2,k3纠正径向畸变
//p1,p2纠正切向畸变
//r=sqrt(x*x+y*y)
//x_d=x(1+k1r^2+k2r^4+k3r^6)+2p1xy+p2(r^2+2x^2) //归一化平面下的畸变点x_d
//y_d=y(1+k1r^2+k2r^4+k3r^6)+2p2xy+p1(r^2+2y^2) //归一化平面下的畸变点y_d
//最后再将畸变的归一化下的x_d 和 y_d 变为像素坐标即可
//u_d=fx*x_d+cx
//v_d=fy*y_d+cy
#include<string>
using namespace std;
string file="/home/nnz/data/slam_pratice/pratice_cv/distorted.png";
int main(int argc,char** argv)
{
    
    
    //定义畸变参数
    double k1 = -0.28340811, k2 = 0.07395907, p1 = 0.00019359, p2 = 1.76187114e-05;
    // 内参
    double fx = 458.654, fy = 457.296, cx = 367.215, cy = 248.375;
    cv::Mat image=cv::imread(file,0);//0表示是灰度图,CV_8UC1
    int rows=image.rows, cols=image.cols;
    cv::Mat image_undistort=cv::Mat(rows,cols,CV_8UC1);//定义去畸变图形
    //遍历每个像素,把每个像素进行去畸变
    for(int v=0;v<rows;v++)
    {
    
    
        for(int u=0;u<cols;u++)
        {
    
    
            //咱们应该先得到归一化下的坐标x,y 见P99 公式5.4
            double x=(u-cx)/fx, y=(v-cy)/fy;
            double r=sqrt(x*x+y*y);
            double x_d=x*(1+k1*r*r+k2*r*r*r*r)+2*p1*x*y+p2*(r*r+2*x*x);
            double y_d=y*(1+k1*r*r+k2*r*r*r*r)+2*p2*x*y+p1*(r*r+2*y*y);
            double u_d=fx*x_d+cx;
            double v_d=fy*y_d+cy;
            //赋值
            if(u_d>=0 && v_d>=0 && u_d<cols && v_d<rows)
            {
    
    
                image_undistort.at<uchar>(v,u)=image.at<uchar>((int)v_d,(int)u_d);
            }
            else
            {
    
    
                image_undistort.at<uchar>(v,u)=0;
            }
        }
    }
    //画出没有去畸变和去畸变的图片
    cv::imshow("undistorted_image",image_undistort);
    cv::imshow("distort_image",image);
    cv::waitKey(0);
    return 0;
}

运行结果如下:
在这里插入图片描述

三、stereo双目点云成像(重点是获取视差,得到深度信息)

利用pangolin绘制点云
源码如下:

//
//
#include<iostream>
#include<opencv2/opencv.hpp>
#include <vector>
#include<string>
#include<Eigen/Core>
#include<pangolin/pangolin.h>
#include<unistd.h>
using namespace std;
using namespace Eigen;
//path of file
string left_file="/home/nnz/data/slam_pratice/pratice_3dvision/left.png";
string right_file="/home/nnz/data/slam_pratice/pratice_3dvision/right.png";
void showPointCloud(const vector<Vector4d,Eigen::aligned_allocator<Vector4d>> &pointcloud);
int main(int argc,char** argv)
{
    
    
    // 内参
    double fx = 718.856, fy = 718.856, cx = 607.1928, cy = 185.2157;
    //base line
    double b = 0.573;
    //读图像
    cv::Mat image_left=cv::imread(left_file,0);
    cv::Mat image_right=cv::imread(right_file,0);
    //static Ptr<StereoSGBM> create(int minDisparity = 0, int numDisparities = 16,           int blockSize = 3,  int P1 = 0, int P2 = 0, int disp12MaxDiff = 0,   int preFilterCap = 0, int uniquenessRatio = 0,  int speckleWindowSize = 0, int //speckleRange = 0,int mode = StereoSGBM::MODE_SGBM);
    //semi-global matching(SGM)是一种用于计算双目视觉中视差(disparity)的半全局匹配算法,
    // 在OpenCV中的实现为semi-global block matching(SGBM)
    //minDisparity 最小的可能的视差值
    //numDisparity 是最大视差减去最小视差
    //disp12MaxDiff是左右图视差检查所允许的最大的不同值
    //P1, P2:控制视差变化平滑性的参数。P1、P2的值越大,视差越平滑。P1是相邻像素点视差增/减 1 时的惩罚系数;P2是相邻像素点视差变化值大于1时的惩罚系数。P2必须大于P1。
    //preFilterCap:预处理滤波器的截断值,预处理的输出值仅保留[-preFilterCap, preFilterCap]范围内的值,
    //speckleWindowSize:检查视差连通区域变化度的窗口大小, 值为 0 时取消 speckle 检查,int 型
    //speckleRange:视差变化阈值,当窗口内视差变化大于阈值时,该窗口内的视差清零,int 型
    //uniquenessRatio:视差唯一性百分比, 视差窗口范围内最低代价是次低代价的(1 + uniquenessRatio/100)倍时,最低代价对应的视差值才是该像素点的视差,否则该像素点的视差为 0
    cv::Ptr<cv::StereoSGBM> sgbm=cv::StereoSGBM::create(0, 96, 9, 8 * 9 * 9, 32 * 9 * 9, 1, 63, 10, 100, 32);
    cv::Mat disparity_sgbm, disparity;
    sgbm->compute(image_left, image_right, disparity_sgbm);
    //4-byte floating point (float)
    //CV_32F是 float -像素是在0-1.0之间的任意值,这对于一些数据集的计算很有用,但是它必须通过将每个像素乘以255来转换成8位来保存或显示
    disparity_sgbm.convertTo(disparity, CV_32F, 1.0 / 16.0f);// disparity is optical parallax (shi cha)
    // 生成点云
    vector<Vector4d, Eigen::aligned_allocator<Vector4d>> pointcloud;
    // 如果你的机器慢,请把后面的v++和u++改成v+=2, u+=2
    for (int v = 0; v < image_left.rows; v++)
        for (int u = 0; u < image_left.cols; u++)
        {
    
    
            if (disparity.at<float>(v, u) <= 0.0 || disparity.at<float>(v, u) >= 96.0) continue;

            Vector4d point(0, 0, 0, image_left.at<uchar>(v, u) / 255.0); // 前三维为xyz,第四维为颜色

            // 根据双目模型计算 point 的位置
            double x = (u - cx) / fx;//归一化坐标系下的x p99 5.5
            double y = (v - cy) / fy;//归一化坐标系下的y
            double depth = fx * b / (disparity.at<float>(v, u));// P104 5.15 get depth
            point[0] = x * depth;//
            point[1] = y * depth;
            point[2] = depth;//
            pointcloud.push_back(point);// get pointcloud
        }
    cv::imshow("disparity",disparity/96.0);
    cv::waitKey(0);
    showPointCloud(pointcloud);
    return 0;
}
void showPointCloud(const vector<Vector4d,Eigen::aligned_allocator<Vector4d>> &pointcloud)
{
    
    
    //pangolin的使用可以参考我之前的文章
    //https://blog.csdn.net/joun772/article/details/109246680
    判断点云存不存在
    if(pointcloud.empty())
    {
    
    
        cerr<<"point cloud is empty"<<endl;
        return;
    }
    //设置窗口
    pangolin::CreateWindowAndBind("Point Cloud Viewer",1024,768);
    //开启深度的测试?
    glEnable(GL_DEPTH_TEST);
    //开启融合
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
    pangolin::OpenGlRenderState s_cam(
            //
            pangolin::ProjectionMatrix(1024,768,500,500,512,389,0.1,1000),
            //
            pangolin::ModelViewLookAt(0,-0.1,-1.8,0,0,0,0.0,-1.0,0.0)
            );
    pangolin::View &d_cam=pangolin::CreateDisplay()
            .SetBounds(0.0,1.0,pangolin::Attach::Pix(175), 1.0, -1024.0f / 768.0f)
            .SetHandler(new pangolin::Handler3D(s_cam));
    while (pangolin::ShouldQuit()== false)
    {
    
    
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        d_cam.Activate(s_cam);
        glClearColor(1.0f,1.0f,1.0f,1.0f);
        glPointSize(2);//点的尺寸
        glBegin(GL_POINTS);//
        //
        for(auto &p: pointcloud)
        {
    
    
            glColor3f(p[3],p[3],p[3]);
            glVertex3d(p[0],p[1],p[2]);
        }
        glEnd();
        pangolin::FinishFrame();
        usleep(5000);//slepp 5ms
    }
    return;

}

运行结果如下:
在这里插入图片描述在这里插入图片描述

四、利用RGBD获得点云并显示点云图像

该程序主要将彩色图以及深度图的信息提取出来,得到具有深度信息的3d点,再通过已有的相机坐标文件(pose.txt),将相机坐标转换为世界坐标,利用Pangolin画出点云
源码如下:

//
//
#include<iostream>
#include<string>
#include <fstream>
#include<Eigen/Core>
#include <vector>
#include<boost/format.hpp>
#include<sophus/se3.hpp>
#include <pangolin/pangolin.h>
#include <opencv2/opencv.hpp>
#include<unistd.h>
using namespace std;
using namespace Eigen;
typedef vector<Sophus::SE3d,Eigen::aligned_allocator<Sophus::SE3d>> TrajectoryType;
typedef Eigen::Matrix<double,6,1> Vector6d;
void showPointCloud(const vector<Vector6d,Eigen::aligned_allocator<Vector6d>> &pointcloud);
int main()
{
    
    
    //判断是不是在有pose.txt的路径下运行
    ifstream fin("./pose.txt");
    if(!fin)
    {
    
    
        cout<<"提示,请在有pose.txt的目录下运行程序!"<<endl;
        return 1;//return 0  代表程序正常退出,return 1代表程序异常退出!
    }
    TrajectoryType poses;//定义装位姿的容器
    vector<cv::Mat> colorim,depthim;
    //读入深度图片和彩色图片以及位姿
    //该程序用到了模板格式 format
    for(int i=0;i<5;i++)
    {
    
    
        boost::format fmt("./%s/%d.%s");//图片文件路径格式
        colorim.push_back(cv::imread((fmt%"color"%(i+1)%"png").str()));
        //-1 读取原始图像
        depthim.push_back(cv::imread((fmt%"depth"%(i+1)%"pgm").str(),-1));
        //定义数组data[7]暂时接受 pose
        double data[7]={
    
    0};
        for(auto &d:data) fin>>d;//利用d 把pose.txt中的内容遍历传给data
        //调用Sophus库构建pose矩阵

        Eigen::Quaterniond q(data[6],data[3],data[4],data[5]);//得到pose的旋转(四元数)
        Vector3d t(data[0],data[1],data[2]);//得到平移向量
        Sophus::SE3d pose(q,t);//得到pose
        poses.push_back(pose);
    }
    // 计算点云并拼接
    // 相机内参
    double cx = 325.5;
    double cy = 253.5;
    double fx = 518.0;
    double fy = 519.0;
    double depthScale = 1000.0;
    //定义一个点云
    vector<Vector6d,Eigen::aligned_allocator<Vector6d>> pointcloud;
    //reserve为容器预先分配内存空间,并未初始化空间元素,因此不能用[]操作符访问元素,因为只是内存一块,
    // 只能先用push_back,insert等函数插入函数后后再访问。
    //其中size()函数是查看容器中有多少的元素,而不是内存;
    pointcloud.reserve(1000000);
    for(int i=0;i<5;i++)
    {
    
    
        cout<<"图像转换中:"<<i+1<<endl;
        cv::Mat color=colorim[i];//接收彩色图
        cv::Mat depth=depthim[i];//接收深度图
        Sophus::SE3d T=poses[i];//接收相机的位姿
        //遍历每个图片的像素,并将深度图的深度信息提取出来,将像素点变为归一化后,再乘上深度信息得到相机坐标下的点,之后再左乘T,变为世界坐标
        for(int v=0;v<color.rows;v++)
        {
    
    
            for(int u=0;u<color.cols;u++)
            {
    
    
                unsigned int d=depth.ptr<unsigned short >(v)[u];//得到对应像素点的深度信息,这里要注意过得深度信息的用法,利用ptr指针
                if(d==0)
                    continue;
                Eigen::Vector3d point;
                point[2]=double(d)/depthScale;
                point[1]=(v-cy)*point[2]/fy;
                point[0]=(u-cx)*point[2]/fx;
                Eigen::Vector3d w_point=T*point;//得到世界坐标下的3d点
                Vector6d p;//前三个是坐标,后三个是颜色
                p.head<3>()=w_point;
                p[5]=color.data[v*color.step+u*color.channels()];//BLUE
                p[4]=color.data[v*color.step+u*color.channels()+1];//GREEN
                p[3]=color.data[v*color.step+u*color.channels()+2];//RED
                pointcloud.push_back(p);
            }
        }
    }
    cout<<"点云总共有:"<<pointcloud.size()<<endl;
    showPointCloud(pointcloud);
    return 0;
}
void showPointCloud(const vector<Vector6d,Eigen::aligned_allocator<Vector6d>> &pointcloud)
{
    
    
    //pangolin的使用可以参考我之前的文章
    //https://blog.csdn.net/joun772/article/details/109246680
    判断点云存不存在
    if(pointcloud.empty())
    {
    
    
        cerr<<"point cloud is empty"<<endl;
        return;
    }
    //设置窗口
    pangolin::CreateWindowAndBind("Point Cloud Viewer",1024,768);
    //开启深度的测试?
    glEnable(GL_DEPTH_TEST);
    //开启融合
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
    pangolin::OpenGlRenderState s_cam(
            //
            pangolin::ProjectionMatrix(1024,768,500,500,512,389,0.1,1000),
            //
            pangolin::ModelViewLookAt(0,-0.1,-1.8,0,0,0,0.0,-1.0,0.0)
    );
    pangolin::View &d_cam=pangolin::CreateDisplay()
            .SetBounds(0.0,1.0,0, 1.0, -1024.0f / 768.0f)
            .SetHandler(new pangolin::Handler3D(s_cam));
    while (pangolin::ShouldQuit()== false)
    {
    
    
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        d_cam.Activate(s_cam);
        glClearColor(1.0f,1.0f,1.0f,1.0f);
        glPointSize(2);//点的尺寸
        glBegin(GL_POINTS);//
        //
        for(auto &p: pointcloud)
        {
    
    
            glColor3f(p[3]/255.0,p[4]/255.0,p[5]/255.0);
            glVertex3d(p[0],p[1],p[2]);
        }
        glEnd();
        pangolin::FinishFrame();
        usleep(5000);//slepp 5ms
    }
    return;

}

运行结果如下:
在这里插入图片描述

在这里插入图片描述

slam十四讲ch5利用RGBD获得点云图像

猜你喜欢

转载自blog.csdn.net/joun772/article/details/109304896