第5讲 相机与图像

目录

    • 1 操作OpenCV图像
    • 2 图像去畸变
    • 3 双目视觉

1 操作OpenCV图像

  cpp文件内容如下,

#include 
#include 
#include 

using namespace std;

#include 
#include 

using namespace cv;

string path = "../ubuntu.png";
clock_t start, last;

int main()
{
     
    start = clock();

    Mat image;
    image = imread(path);  //imread()函数读取指定路径下的图像

    //判断图像文件是否正确读取
    if(image.data == nullptr)  //数据不存在,可能是文件不存在
    {
     
        cerr << "文件" << path << "不存在!" << endl;
        return 0;
    }

    //文件顺利读取,首先输出一些基本信息
    cout << "图像宽为:" << image.cols << "像素,图像高为:" << image.rows << "像素,图像通道数为:" << image.channels() << "!" << endl;
    imshow("image", image);  //用imshow()函数显示图像
    waitKey(0);  //暂停程序,等待一个按键输入

    //判断图像image的类型
    if(image.type() != CV_8UC1 && image.type() != CV_8UC3)
    {
     
        cout << "请输入一张彩色图或灰度图!" << endl;
        return 0;
    }

    //使用指针遍历图像image中的像素
    //steady_clock是单调的时钟,相当于教练中的秒表,只会增长,适合用于记录程序耗时。
    chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
    //size_t全称是size_type,它表示sizeof()函数的返回值,是无符号整型unsigned int变量。
    //使用size_t的目的是提供一种可移植的方法来声明与系统中可寻址的内存区域一致的长度。
    for(size_t y = 0; y < image.rows; y++)
    {
     
        //用ptr获得图像的行指针
        unsigned char* row_ptr = image.ptr<unsigned char>(y);  //row_ptr是第y行的头指针
        for(size_t x = 0; x < image.cols; x++)
        {
     
            //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为image(x,y)第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;

    //直接赋值并不会拷贝数据,图像名image有点类似于指针
    Mat image_another = image;
    //修改image_another也会导致image发生变化
    image_another(Rect(0, 0, 100, 100)).setTo(0);  //将左上角100*100的块置为黑色
    imshow("image", image);
    waitKey(0);  //停止执行,等待一个按键输入
    cout << "直接赋值并不会拷贝数据,修改image_another也会导致image发生变化!" << endl;

    //使用clone()函数拷贝数据
    Mat image_clone = image.clone();
    image_clone(Rect(0, 0, 100, 100)).setTo(255);  //将左上角100*100的块置为白色
    imshow("image", image);
    imshow("image_clone", image_clone);
    waitKey(0);
    cout << "使用clone()函数可以拷贝数据,修改图像image_clone并不会改变图像image!" << endl;

    last = clock();
    cout << "运行程序花费的时间为:" << (double)(last - start) * 1000 / CLOCKS_PER_SEC << "毫秒!" << endl;
    return 0;
}

  CMakeLists.txt文件内容如下,

find_package(OpenCV REQUIRED)
include_directories(${
     OpenCV_INCLUDE_DIRECTORIES})
add_executable(main main.cpp)
target_link_libraries(main ${
     OpenCV_LIBRARIES})

  结果如下,

图像宽为:1200像素,图像高为:674像素,图像通道数为:3!
遍历图像用时:0.0128371秒!
直接赋值并不会拷贝数据,修改image_another也会导致image发生变化!
使用clone()函数可以拷贝数据,修改图像image_clone并不会改变图像image!
运行程序花费的时间为:192.463毫秒!

注:结果中省略了imshow()函数的显示!

2 图像去畸变

  cpp文件内容如下,

#include 
#include 
#include 
#include 

using namespace std;

#include 

using namespace cv;

string path = "../distorted.png";  //图片路径
double fx = 458.654, fy = 457.296, cx = 367.215, cy = 248.375;  //相机内参
double k1 = -0.28340811, k2 = 0.07395907, p1 = 0.00019359, p2 = 1.76187114e-5;  //畸变参数

clock_t start, last;

//本程序实现去畸变部分的代码。尽管我们可以调用OpenCV的去畸变函数,但自己去实现一遍有助于理解
int main()
{
     
    start = clock();

    Mat image = imread(path, 0);  //0表示返回一张灰度图
    int rows = image.rows, cols = image.cols;
    Mat image_undistort(rows, cols, CV_8UC1);  //构造一张与图像image相同尺寸和相同类型的图像image_undistort

    //计算去畸变后的内容
    for(int v = 0; v < rows; v++)
        for(int u = 0; u < cols; u++)
        {
     
            //依据公式,计算像素点image_undistort(v,u)去畸变后的坐标image(v_distorted,u_distorted)
            double x = (u - cx) / fx, y = (v - cy) / fy;
            double r = sqrt(x * x + y * y);
            double r2 = pow(r, 2), r4 = pow(r, 4);
            double x_distorted = x * (1 + k1 * r2 + k2 * r4) + 2 * p1 * x * y + p2 * (r2 + 2 * x * x);
            double y_distorted = y * (1 + k1 * r2 + k2 * r4) + p1 * (r2 + 2 * y * y) + 2 * p2 * x * y;
            double u_distorted = fx * x_distorted + cx;
            double v_distorted = fy * y_distorted + cy;

            //将image(v_distorted,u_distorted)处的颜色信息赋值到image_undistort(v,u)处
            if(u_distorted >= 0 && v_distorted >= 0 && u_distorted < cols && v_distorted < rows)
                image_undistort.at<uchar>(v, u) = image.at<uchar>((int)round(v_distorted), (int)round(u_distorted));
            else
                image_undistort.at<uchar>(v, u) = 0;  //超出范围像素点的颜色设置为黑色


        }

    //显示去畸变前后的图像
    imshow("原图", image);
    imshow("去畸变后的图像", image_undistort);
    waitKey(0);  //程序终止,等待一个按键输入

    last = clock();
    cout << "执行程序总共花费的时间为:" << double(last - start) * 1000 / CLOCKS_PER_SEC << "毫秒!" << endl;
    return 0;
}

  CMakeLists.txt文件内容如下,

find_package(OpenCV REQUIRED)
include_directories(${
     OpenCV_DIRECTORIES})
add_executable(main main.cpp)
target_link_libraries(main ${
     OpenCV_LIBRARIES})

  结果如下,
第5讲 相机与图像_第1张图片
第5讲 相机与图像_第2张图片

执行程序总共花费的时间为:80.934毫秒!

3 双目视觉

  cpp文件内容如下,

#include 
#include 
#include 
#include 

using namespace std;

#include 

using namespace cv;

#include   //Eigen核心模块

using namespace Eigen;

#include 

//相机内参
double fx = 718.856, fy = 718.856, cx = 607.1928, cy = 185.2157, b = 0.573;

//文件路径
string left_file = "../left.png";
string right_file = "../right.png";

//在pangolin中画图,已写好,无需调整
void showPointCloud(const vector<Vector4d, Eigen::aligned_allocator<Vector4d>>& pointcloud);

clock_t start, last;

int main()
{
     
    start = clock();

    Mat left = imread(left_file, 0);  //0表示返回一张灰度图
    Mat right = imread(right_file, 0);  //0表示返回一张灰度图

    //调用opencv中的sgbm算法来求解深度
    Ptr<StereoSGBM> sgbm = StereoSGBM::create(
            0, 96, 9, 8 * 9 * 9, 32 * 9 * 9, 1, 63, 10, 100, 32
            );  //来自网络上的关于sgbm算法的经典参数配置
    Mat disparity_sgbm, disparity;
    sgbm->compute(left, right, disparity_sgbm);
    disparity_sgbm.convertTo(disparity, CV_32F, 1.0 / 16.0f);  //注意disparity才是最后的视差图

    //生成点云
    vector<Vector4d, Eigen::aligned_allocator<Vector4d>> pointcloud;  //前三维为X,Y,Z,表示位置信息,后一维表示颜色信息,在此处为灰度
    for(int v = 0; v < left.rows; v++)
        for(int u = 0; u < left.cols; u++)
        {
     
            //设置一个Check,排除视差不在(10.0, 96.0)范围内的像素点
            if(disparity.at<float>(v, u) <= 10.0 || disparity.at<float>(v, u) >= 96.0)
                continue;
            Vector4d point(0, 0, 0, left.at<uchar>(v, u) / 255.0); //创建一个Vector4d类型的变量,前三维用来存储位置信息,后一维为归一化之后的灰度
            //根据双目模型恢复像素点的三维位置
            double Z = fx * b / (disparity.at<float>(v, u));
            double X = (u - cx) / fx * Z;
            double Y = (v - cy) / fy * Z;
            //将X,Y,Z赋值给point的前三维
            point[0] = X; point[1] = Y; point[2] = Z;

            pointcloud.push_back(point);


        }

    imshow("视差图", disparity / 96.0);  //disparity/96表示归一化之后的视差图像!
    waitKey(0);  //停止执行,等待一个按键输入

    //用pangolin画出点云
    showPointCloud(pointcloud);

    last = clock();
    cout << "执行程序总共花费的时间为:" << (double)(last - start) * 1000 / CLOCKS_PER_SEC << "毫秒!" << endl;
    return 0;

}


void showPointCloud(const vector<Vector4d, Eigen::aligned_allocator<Vector4d>>& pointcloud)
{
     
    if(pointcloud.empty())
    {
     
        cerr << "点云为空,返回!" << endl;
        return;
    }

    //创建一个pangolin窗口
    pangolin::CreateWindowAndBind("Point Cloud Viewer", 1024, 768);
    glEnable(GL_DEPTH_TEST);  //根据物体远近,实现遮挡效果
    glEnable(GL_BLEND);  //使用颜色混合模型,让物体显示半透明效果
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    //GL_SRC_ALPHA表示使用源颜色的alpha值作为权重因子,GL_ONE_MINUS_SRC_ALPHA表示使用(1-源颜色的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)
            );
    //ProjectionMatrix()中参数依次表示相机观察视图的宽度和高度,fx, fy, cx, cy,最小深度和最大深度
    //ModelViewLookAt()中参数依次为相机光心位置,被观察点位置,相机坐标系各坐标轴的正方向(000表示右下前)

    //创建交互视图,显示上一帧图像内容
    pangolin::Handler3D handler(s_cam);  //交互视图句柄
    pangolin::View &d_cam = pangolin::CreateDisplay()
            .SetBounds(0.0, 1.0, pangolin::Attach::Pix(175), 1.0, -1024.0f / 768.0f)
            .SetHandler(&handler);
    //SetBounds()中各参数表示交互视图范围、交互视图横纵比

    while(pangolin::ShouldQuit() == false)  //如果pangolin视图没有被关闭,则执行
    {
     
        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);  //设置线宽为2

        //绘制点云
        glBegin(GL_POINTS);
        for(auto &p : pointcloud)  //使用基于范围的for进行遍历
        {
     
            glColor3f(p[3], p[3], p[3]);  //设置颜色信息
            glVertex3d(p[0], p[1], p[2]);  //设置位置信息
        }
        glEnd();
        pangolin::FinishFrame();  //按照上面的设置执行渲染!
        usleep(5000);  //停止执行5毫秒
    }
    return;
}

  CMakeList.txt文件内容如下,

include_directories("/usr/include/eigen3")

find_package(OpenCV REQUIRED)
include_directories(${
     OpenCV_DIRECTORIES})

find_package(Pangolin REQUIRED)
include_directories(${
     Pangolin_DIRECTORIES})

add_executable(main main.cpp)
target_link_libraries(main ${
     OpenCV_LIBRARIES} 
                           ${
     Pangolin_LIBRARIES})

  结果如下,
第5讲 相机与图像_第3张图片

第5讲 相机与图像_第4张图片
上图为左图的视差图,黑色表示视差为0,白色表示视差为1。根据公式 Z = f x b / d Z = f_x b / d Z=fxb/d可知,深度越大,视差越小,对应上图就是离相机越远的地方越黑。左边小部分区域由于没有相同的观测信息,所以无法计算视差,表现为黑色。
第5讲 相机与图像_第5张图片注:上图为点云显示图,为了更好的展示三维立体效果,本文提供了点云显示相关视频,见(等bilibili审核视频通过之后再上传)

执行程序总共花费的时间为:1669.74毫秒!

更新中!

你可能感兴趣的:(《视觉SLAM十四讲》个人代码,slam,ubuntu,opencv)