《视觉 SLAM 十四讲》V2 第 5 讲 相机与图像

news2024/12/24 5:46:27

文章目录

      • 相机 内参 && 外参
      • 5.1.2 畸变模型
      • 单目相机的成像过程
      • 5.1.3 双目相机模型
      • 5.1.4 RGB-D 相机模型
    • 实践
      • 5.3.1 OpenCV 基础操作 【Code】
        • OpenCV版本查看
      • 5.3.2 图像去畸变 【Code】
      • 5.4.1 双目视觉 视差图 点云 【Code】
      • 5.4.2 RGB-D 点云 拼合成 地图【Code】
    • 习题
      • 题1
      • √ 题2
      • 题3
      • √ 题4
      • 题5
      • 题6
      • 题7

在这里插入图片描述

空间点 投影到 相机成像平面

前面内容总结:
1、机器人如何表示自身位姿

视觉SLAM: 观测主要是指 相机成像 的过程

投影过程描述: 针孔 + 畸变

相机 内参 && 外参

在这里插入图片描述
在这里插入图片描述

像素坐标系 与 成像平面之间,相差了一个缩放 和一个原点的平移。

像素坐标系:
原点 o ′ o^{\prime} o 位于 图像 左上角
u u u 轴 向右 与 x x x 轴 平行
v v v 轴 向下 与 y y y 轴 平行

设像素坐标在 u u u 轴 上缩放了 α \alpha α 倍 , 在 v v v 轴 上缩放了 β \beta β 倍。同时原点 平移了 [ c x , c y ] T [c_x, c_y]^T [cx,cy]T
则 点 p ′ p^{\prime} p 的坐标 与像素坐标 [ u , v ] T [u, v]^T [u,v]T 之间的关系
{ u = α X ′ + c x = 由式 5.2 α ⋅ f X Z + c x = 令 f x = α f f x X Z + c x v = β Y ′ + c y = 由式 5.2 β ⋅ f Y Z + c x = 令 f y = β f f y Y Z + c y \begin{equation*} \begin{cases} u = \alpha X^{\prime} + c_x \overset{由式5.2}{=} \alpha ·f\frac{X}{Z} + c_x \overset{令f_x = \alpha f}{=} f_x\frac{X}{Z} + c_x \\ v = \beta Y^{\prime} + c_y \overset{由式5.2}{=} \beta·f\frac{Y}{Z} + c_x \overset{令f_y = \beta f}{=} f_y\frac{Y}{Z} + c_y \end{cases} \end{equation*} {u=αX+cx=由式5.2αfZX+cx=fx=αffxZX+cxv=βY+cy=由式5.2βfZY+cx=fy=βffyZY+cy

其中 f x = α f , f y = β f f_x = \alpha f, f_y=\beta f fx=αf,fy=βf
f f f 的单位 为
α , β \alpha, \beta α,β 的单位为 像素/米
f x , f y f_x, f_y fx,fy c x , c y c_x, c_y cx,cy 的单位为 像素

[ u v 1 ] = [ f x 0 c x 0 f y c y 0 0 1 ] [ X Z Y Z 1 ] = 1 Z [ f x 0 c x 0 f y c y 0 0 1 ] [ X Y Z ] = d e f 1 Z K P \begin{align*}\begin{bmatrix}u\\ v\\ 1\end{bmatrix} &=\begin{bmatrix}f_x & 0 & c_x\\ 0 & f_y & c_y\\ 0 & 0 &1\end{bmatrix}\begin{bmatrix}\frac{X}{Z}\\ \frac{Y}{Z}\\ 1\end{bmatrix}\\ &=\frac{1}{Z}\begin{bmatrix}f_x & 0 & c_x\\ 0 & f_y & c_y\\ 0 & 0 &1\end{bmatrix}\begin{bmatrix}X\\ Y\\ Z\end{bmatrix}\\ &\overset{\mathrm{def}}{=} \frac{1}{Z}\bm{KP} \end{align*} uv1 = fx000fy0cxcy1 ZXZY1 =Z1 fx000fy0cxcy1 XYZ =defZ1KP

相机的内参数(Camera Intrinsics) 矩阵 K \bm{K} K

K = [ f x 0 c x 0 f y c y 0 0 1 ] \bm{K} = \begin{bmatrix}f_x & 0 & c_x\\ 0 & f_y & c_y\\ 0 & 0 &1\end{bmatrix} K= fx000fy0cxcy1

标定:自己确定相机的内参【相机生产厂商未告知相机内参的情形】

  • 标定算法: 单目棋盘格张正友标定法

相机在运动 ——> P P P 的相机坐标 = 其世界坐标 P w \bm{P_\mathrm{w}} Pw 根据相机位姿转换到 相机坐标系下。

Z P u v = Z [ u v 1 ] = K ( R P w + t ) = K T P w Z\bm{P}_{uv}=Z\begin{bmatrix} u \\v \\1\end{bmatrix}=\bm{K(RP_{\mathrm{w}}+t)=KTP_\mathrm{w}} ZPuv=Z uv1 =K(RPw+t)=KTPw

相机的外参数(Camera Extrinsics):相机的位姿 R \bm{R} R t \bm{t} t

机器人 或 自动驾驶: 外参 = 相机坐标系 到机器人本体坐标系 之间的 变换。

  • 描述 相机安装在什么地方

5.1.2 畸变模型

径向畸变透镜形状引起的畸变(失真)。坐标点 距离原点的长度发生了变化。
在这里插入图片描述

桶形畸变:图像放大率 随着 与光轴之间的距离 增加 而减小
枕型畸变:图像放大率 随着 与光轴之间的距离 增加 而增加。

  • 穿过图像中心和光轴有交点的直线还能保持形状不变。

切向畸变:相机在在组装过程中能使 透镜和成像面 严格平行水平夹角发行了变化。

在这里插入图片描述

通过5个畸变系数( k 1 , k 2 , k 3 , p 1 , p 2 k_1,k_2,k_3,p_1,p_2 k1,k2,k3,p1,p2)找到某个点在像素平面的正确位置:
在这里插入图片描述

单目相机的成像过程

在这里插入图片描述

5.1.3 双目相机模型

在这里插入图片描述
z − f z = b − u L + u R b \frac{z-f}{z}=\frac{b-u_L+u_R}{b} zzf=bbuL+uR
令 d = u L − u R 令d = u_L-u_R d=uLuR 视差

z − f z = b − d b \frac{z-f}{z}=\frac{b-d}{b} zzf=bbd

1 − f z = 1 − d b 1-\frac{f}{z}=1-\frac{d}{b} 1zf=1bd

f z = d b \frac{f}{z}=\frac{d}{b} zf=bd

z = f b d z=\frac{fb}{d} z=dfb

由于计算量的原因,双目深度估计需要使用 GPU 或 FPGA 来实时计算。

5.1.4 RGB-D 相机模型

在这里插入图片描述
在这里插入图片描述
RGB-D 相机: 向探测目标 发射一束 光线(通常是红外光)。

RGB-D 不足:
1、用红外光进行深度测量,容易受到 日光或其他传感器发射的红外光干扰。不能在室外使用。
2、多个RGB-D相机之间也会相互干扰。
3、透射材质因为接收不到反射光,无法测量。

在这里插入图片描述
h h h 对应 行数
w w w 对应 列数

在这里插入图片描述
OpenCV: 通道顺序为 BGR

在这里插入图片描述

Eigen对于固定大小的矩阵使用起来效率更高。

实践

5.3.1 OpenCV 基础操作 【Code】

OpenCV版本查看
python3 -c "import cv2; print(cv2.__version__)"

可能报错

/home/xixi/Downloads/slambook2-master/ch5/basicuse/basicuse.cpp:6:9: fatal error: opencv2/core/core.cpp: No such file or directory
    6 | #include<opencv2/core/core.cpp>

OpenCV没安装好
gtk/gtk.h报错链接
到 OpenCV 安装包

mkdir build && cd build
cmake ..
make -j4  # 之前 -j8有误,改4试试
sudo make install

——————————————————

mkdir build && cd build 
cmake ..
make 
./basicuse ubuntu.png   ## ubuntu.png 要放在 build文件夹里; 或者提供该图片的绝对路径;或相对于build文件夹的相对路径

CMakeLists.txt

cmake_minimum_required(VERSION 2.8)

project(basicuse)

# 添加C++ 11 标准支持  nullptr  chrono
set( CMAKE_BUILD_TYPE "Release" )
set( CMAKE_CXX_FLAGS "-std=c++11 -O3" )


# 寻找 OpenCV 库
find_package(OpenCV 4.2.0 REQUIRED)
#添加头文件
include_directories(${OpenCV_INCLUDE_DIRS})


add_executable(basicuse basicuse.cpp)
# 链接OpenCV库
target_link_libraries(basicuse ${OpenCV_LIBS})

basicuse.cpp

#include<iostream>
#include<chrono> // 计时

using namespace std;

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>// high-level graphical user interface

using namespace cv;

int main(int argc, char **argv){
    // 读取argv[1] 指定的图像
    cv::Mat image;
    image = cv::imread(argv[1]);  // 从命令行的第一个参数中 读取图像位置

    // 判断图像是否 正确读取
    if (image.data == nullptr){
        cerr << "文件" << argv[1] << "不存在。" << endl;
        return 0; 
    }

    // 输出文件的基本信息
    cout << "图像宽为" << image.cols << ",高为" << image.rows
    << ", 通道数为" << image.channels()  << endl;
    cv::imshow("image", image);
    cv::waitKey(0);  // 暂停程序,等待一个按键输入


    cv::destroyAllWindows();
    return 0;
}

在这里插入图片描述
在这里插入图片描述

#include<iostream>
#include<chrono> // 计时

using namespace std;

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>// high-level graphical user interface

using namespace cv;

int main(int argc, char **argv){
    // 读取argv[1] 指定的图像
    cv::Mat image;
    image = cv::imread(argv[1]);  // 从命令行的第一个参数中 读取图像位置

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

    // 遍历图像, 请注意以下遍历方式亦可使用于随机像素访问
    // 使用 std::chrono 来给算法计时
    chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
    for (size_t y = 0; y < image.rows; y++) {
        // 用cv::Mat::ptr获得图像的行指针
        unsigned char *row_ptr = image.ptr<unsigned char>(y);  // row_ptr是第y行的头指针
        for (size_t x = 0; x < image.cols; x++) {
        // 访问位于 x,y 处的像素
        unsigned char *data_ptr = &row_ptr[x * image.channels()]; // data_ptr 指向待访问的像素数据
        // 输出该像素的每个通道,如果是灰度图就只有一个通道
        for (int c = 0; c != image.channels(); c++) {
            unsigned char data = data_ptr[c]; // data为I(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;
    
    return 0;
}

在这里插入图片描述

#include<iostream>
#include<chrono> // 计时

using namespace std;

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>// high-level graphical user interface

using namespace cv;

int main(int argc, char **argv){
    // 读取argv[1] 指定的图像
    cv::Mat image;
    image = cv::imread(argv[1]);  // 从命令行的第一个参数中 读取图像位置

    // 关于 cv::Mat 的拷贝
    // 直接赋值并不会拷贝数据   浅拷贝 会 同时修改原始数据
    cv::Mat image_another = image;
    // 修改 image_another 会导致 image 发生变化
    image_another(cv::Rect(0, 0, 100, 100)).setTo(0); // 将左上角100*100的块置零
    cv::imshow("image", image);
    cv::waitKey(0);

    // 使用clone函数来拷贝数据
    cv::Mat image_clone = image.clone();
    image_clone(cv::Rect(0, 0, 100, 100)).setTo(255);
    cv::imshow("image", image);
    cv::imshow("image_clone", image_clone);
    cv::waitKey(0);

    // 对于图像还有很多基本的操作,如剪切,旋转,缩放等,限于篇幅就不一一介绍了,请参看OpenCV官方文档查询每个函数的调用方法.
    cv::destroyAllWindows();

    return 0;
}

5.3.2 图像去畸变 【Code】

cv::Undistort()

CMakeLists.txt

cmake_minimum_required(VERSION 2.8)

project(myOpenCV)

# 添加C++ 11 标准支持  nullptr  chrono
set( CMAKE_BUILD_TYPE "Release" )
set( CMAKE_CXX_FLAGS "-std=c++11 -O3" )


# 寻找 OpenCV 库
find_package(OpenCV 4.2.0 REQUIRED)
#添加头文件
include_directories(${OpenCV_INCLUDE_DIRS})


add_executable(myOpenCV undistortImage.cpp)
# 链接OpenCV库
target_link_libraries(myOpenCV ${OpenCV_LIBS})


undistortImage.cpp

#include <opencv2/opencv.hpp>
#include <string>

using namespace std;

string image_file = "../distorted.png";   // 请确保路径正确 

int main(int argc, char **argv) {

  // 本程序实现去畸变部分的代码。尽管我们可以调用OpenCV的去畸变,但自己实现一遍有助于理解。
  // 畸变参数
  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(image_file, 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++) {
      // 按照公式,计算点(u,v)对应到畸变图像中的坐标(u_distorted, v_distorted)
      double x = (u - cx) / fx, y = (v - cy) / fy;
      double r = sqrt(x * x + y * y);
      double x_distorted = x * (1 + k1 * r * r + k2 * r * r * r * r) + 2 * p1 * x * y + p2 * (r * r + 2 * x * x);
      double y_distorted = y * (1 + k1 * r * r + k2 * r * r * r * r) + p1 * (r * r + 2 * y * y) + 2 * p2 * x * y;
      double u_distorted = fx * x_distorted + cx;
      double v_distorted = fy * y_distorted + cy;

      // 赋值 (最近邻插值)
      if (u_distorted >= 0 && v_distorted >= 0 && u_distorted < cols && v_distorted < rows) {
        image_undistort.at<uchar>(v, u) = image.at<uchar>((int) v_distorted, (int) u_distorted);
      } else {
        image_undistort.at<uchar>(v, u) = 0;
      }
    }
  }

  // 画图去畸变后图像
  cv::imshow("distorted", image);
  cv::imshow("undistorted", image_undistort);
  cv::waitKey();
  return 0;
}

在这里插入图片描述

5.4.1 双目视觉 视差图 点云 【Code】

在这里插入图片描述
在这里插入图片描述
CMakeLists.txt

cmake_minimum_required(VERSION 2.8)

project(stereoVision)

# 添加C++ 11 标准支持  nullptr  chrono
set( CMAKE_BUILD_TYPE "Release" )
set( CMAKE_CXX_FLAGS "-std=c++11 -O3" )


# 寻找 OpenCV 库
find_package(OpenCV 4.2.0 REQUIRED)
#添加头文件
include_directories(${OpenCV_INCLUDE_DIRS})

find_package(Pangolin REQUIRED)

add_executable(stereoVision stereoVision.cpp)
target_link_libraries(stereoVision ${OpenCV_LIBS} ${Pangolin_LIBRARIES})

stereoVision.cpp

#include <opencv2/opencv.hpp>
#include <vector>
#include <string>
#include <Eigen/Core>
#include <pangolin/pangolin.h>
#include <unistd.h>

using namespace std;
using namespace Eigen;

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

// 在pangolin中画图,已写好,无需调整
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;
    // 基线
    double b = 0.573;

    // 读取图像
    cv::Mat left = cv::imread(left_file, 0);
    cv::Mat right = cv::imread(right_file, 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(left, right, disparity_sgbm);
    disparity_sgbm.convertTo(disparity, CV_32F, 1.0 / 16.0f);

    // 生成点云
    vector<Vector4d, Eigen::aligned_allocator<Vector4d>> pointcloud;

    // 如果你的机器慢,请把后面的v++和u++改成v+=2, u+=2
    for (int v = 0; v < left.rows; v++)
        for (int u = 0; u < left.cols; u++) {
            if (disparity.at<float>(v, u) <= 0.0 || disparity.at<float>(v, u) >= 96.0) continue;

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

            // 根据双目模型计算 point 的位置
            double x = (u - cx) / fx;
            double y = (v - cy) / fy;
            double depth = fx * b / (disparity.at<float>(v, u));
            point[0] = x * depth;
            point[1] = y * depth;
            point[2] = depth;

            pointcloud.push_back(point);
        }

    cv::imshow("disparity", disparity / 96.0);
    cv::waitKey(0);
    // 画出点云
    showPointCloud(pointcloud);
    return 0;
}

void showPointCloud(const vector<Vector4d, Eigen::aligned_allocator<Vector4d>> &pointcloud) {

    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);   // sleep 5 ms
    }
    return;
}

视差图:
在这里插入图片描述

byzanz-record -x 147 -y 76 -w 1386 -h 768  -d 15 --delay=5 -c  /home/xixi/myGIF/test.gif

在这里插入图片描述

在这里插入图片描述

5.4.2 RGB-D 点云 拼合成 地图【Code】

通过物理方法 获得 像素深度信息
在这里插入图片描述
在这里插入图片描述

mkdir build && cd build
cmake ..
make 
./joinMap

CMakeLists.txt

cmake_minimum_required(VERSION 2.8)

project(joinMap)

# 添加C++ 11 标准支持  nullptr  chrono
set( CMAKE_BUILD_TYPE "Release" )
set( CMAKE_CXX_FLAGS "-std=c++11 -O3" )

# 寻找 OpenCV 库
find_package(OpenCV 4.2.0 REQUIRED)
#添加头文件
include_directories(${OpenCV_INCLUDE_DIRS})

# Sophus 库
find_package(Sophus REQUIRED)
include_directories(${Sophus_INCLUDE_DIRS})

#  Pangolin 库
find_package(Pangolin REQUIRED)
include_directories(${Pangolin_INCLUDE_DIRS})

add_executable(joinMap joinMap.cpp)
target_link_libraries(joinMap ${OpenCV_LIBS} ${Pangolin_LIBRARIES} ${Sophus_LIBRARIES}) 
# 上面这句 一定要 链接到  Sophus

joinMap.cpp

#include <iostream>
#include <fstream>
#include <opencv2/opencv.hpp>
#include <boost/format.hpp>  // for formating strings
#include <pangolin/pangolin.h>
#include <sophus/se3.h>

using namespace Sophus;  // 原代码少了 这句
using namespace std;
typedef vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>> TrajectoryType;
typedef Eigen::Matrix<double, 6, 1> Vector6d;

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

int main(int argc, char **argv) {
    vector<cv::Mat> colorImgs, depthImgs;    // 彩色图和深度图
    TrajectoryType poses;         // 相机位姿

    ifstream fin("../pose.txt");
    if (!fin) {
        cerr << "请在有pose.txt的目录下运行此程序" << endl;
        return 1;
    }

    for (int i = 0; i < 5; i++) {
        boost::format fmt("../%s/%d.%s"); //图像文件格式  // !! 这里的路径也要改
        colorImgs.push_back(cv::imread((fmt % "color" % (i + 1) % "png").str()));
        depthImgs.push_back(cv::imread((fmt % "depth" % (i + 1) % "pgm").str(), -1)); // 使用-1读取原始图像

        double data[7] = {0};
        for (auto &d:data)
            fin >> d;
        Sophus::SE3 pose(Eigen::Quaterniond(data[6], data[3], data[4], data[5]),
                          Eigen::Vector3d(data[0], data[1], data[2]));
        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;
    pointcloud.reserve(1000000);

    for (int i = 0; i < 5; i++) {
        cout << "转换图像中: " << i + 1 << endl;
        cv::Mat color = colorImgs[i];
        cv::Mat depth = depthImgs[i];
        Sophus::SE3 T = poses[i];
        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]; // 深度值
                if (d == 0) continue; // 为0表示没有测量到
                Eigen::Vector3d point;
                point[2] = double(d) / depthScale;
                point[0] = (u - cx) * point[2] / fx;
                point[1] = (v - cy) * point[2] / fy;
                Eigen::Vector3d pointWorld = T * point;

                Vector6d p;
                p.head<3>() = pointWorld;
                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) {

    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) {
            glColor3d(p[3] / 255.0, p[4] / 255.0, p[5] / 255.0);
            glVertex3d(p[0], p[1], p[2]);
        }
        glEnd();
        pangolin::FinishFrame();
        usleep(5000);   // sleep 5 ms
    }
    return;
}

在这里插入图片描述

在这里插入图片描述

byzanz-record -x 72 -y 64 -w 998 -h 605  -d 15 --delay=5 -c  /home/xixi/myGIF/test.gif

在这里插入图片描述

习题

待做:

  • 找OpenCV里的标定 方法
  • 整理链接里的内容

在这里插入图片描述

题1

相机内参标定

√ 题2

相机内参 K \bm{K} K 的物理意义:可将世界坐标系某点 P P P归一化坐标 转成 像素坐标 P u v = K [ X / Z , Y / Z , 1 ] T \bm{P_{uv}=K}[X/Z,Y/Z, 1]^T Puv=K[X/Z,Y/Z,1]T

图像分辨率指图像中存储的信息量,是每英寸图像内有多少个像素点,分辨率的单位为PPI(Pixels Per Inch),通常叫做像素每英寸
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当分辨率变为原来的两倍时, 显然对于同一位置,以像素为单位的 c x c_x cx c y c_y cy 均变为原来的2倍。而以 像素/每米 为单位的 α \alpha α β \beta β 变成原来的 2 倍。 f f f 不变,则 f x = α f f_x = \alpha f fx=αf f y = β f f_y = \beta f fy=βf 也变为原来的 2 倍。
综上:当相机的分辨率变为原来的2倍时, c x c_x cx c y c_y cy f x f_x fx f y f_y fy 均变为原来的 2 倍。

题3

鱼眼或全景相机 标定
链接1
链接2
————————————

√ 题4

异同:
工业相机常见的曝光方式:
1、全局曝光(Global shutter,也称全局快门、帧曝光

  • 光圈打开时,工业相机中的图像传感器上所有像素点可以在同一时刻曝光,当光圈关闭后,所有像素同时结束曝光,然后输出像素数据。全局曝光的工业相机可以一次拍摄物体的整体图像后再输出,因此在拍摄高速运动物体时图像不会偏移,能够达到无失真的效果。
  • CCD(电荷耦合)元件 为这种曝光 方式

2、卷帘曝光(Rolling shutter,也称卷帘快门、行曝光

  • 采用的是逐行扫描逐行曝光的方式,当上一行的所有像素同时曝光后,下一行的所有像素再同时曝光,直至所有行曝光完成。
  • 当曝光不当或物体移动较快时,会出现部分曝光(partial exposure)、斜坡图形(skew)、晃动(wobble) 等现象。这种Rolling shutter方式拍摄出现的现象,称为“果冻效应”。
  • 大部分CMOS相机使用卷帘快门(rolling shutter)

3、基于卷帘曝光并结合全局曝光优势的全局复位释放曝光(Global Reset Release Shutter,GRR)

优缺点:
Global shutter适用于拍摄高速运动物体;且在光线有明暗变化的时候,Global shutter sensor不会有明暗瑕疵。
Global shutter需要对每个像素都要增加一个存储单元,这样增加了sensor的生产难度以及成本
Rolling Shutter sensor适用于拍摄运动速度相对较的物体或场景,可获得更高的成像信噪比。 Rolling Shutter 在低噪、像素损失、高感、动态范围等有优势。

————————

题5

RGB-D 相机标定
在这里插入图片描述

链接
链接2

题6

遍历图像的方法
链接
链接2

题7

OpenCV官方教程学习
官方文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1061764.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

美妆护肤品商城小程序的作用是什么?

化妆品几乎可以覆盖所有人群&#xff0c;各式各样的品牌及经销商非常多&#xff0c;主要销售模式为门店零售、线上入驻电商平台售卖、批发等&#xff0c;近些年随着电商发展迭代以及消费升级&#xff0c;对品牌或经销商来说&#xff0c;传统经营模式变得低效&#xff0c;每个人…

华为云云耀云服务器L实例评测|Uniapp开发部署茶叶商城小程序、H5

1、华为云云耀云服务器L实例评测&#xff5c;Uniapp开发茶叶商城小程序、H5 华为云耀云服务器L实例是新一代开箱即用、面向中小企业和开发者打造的全新轻量应用云服务器。多种产品规格&#xff0c;满足您对成本、性能及技术创新的诉求。云耀云服务器L实例提供丰富严选的应用镜像…

Spring Security 6.1.x 系列 (1)—— 初识Spring Security

一、 Spring Security 概述 Spring Security是Spring组织提供的一个开源安全框架&#xff0c;基于Spring开发&#xff0c;所以非常适合在Spring Boot中使用。 官方文档地址&#xff1a;https://docs.spring.io/spring-security/reference/index.html GitHub地址&#xff1a;…

抖音小店怎么运营?运营全流程,不懂的快来看!

我是电商珠珠 抖音小店是抖音旗下的电商平台&#xff0c;对于抖音来说流量是充足的&#xff0c;所以很多人想要在抖音小店上下功夫。 但是关于抖音小店的流程很多人还不太熟悉&#xff0c;今天我就来给大家详细的讲一下。 一、入驻准备 在入驻的时候&#xff0c;需要办理一…

lv7 嵌入式开发-网络编程开发 09 UDP通信

目录 1 用到的相关API 1.1 write/read到send/recv 1.2 sendto与recvfrom 2 UDP通信的实现过程 3 服务端代码、客户端、makefile代码实现 1 用到的相关API 1.1 write/read到send/recv send函数原型&#xff1a; ssize_t send(int sockfd, const void *buf, size_t len, …

Ubuntu MySQL

在安装前&#xff0c;首先看你之前是否安装过&#xff0c;如果安装过&#xff0c;但是没成功&#xff0c;就要先卸载。 一、卸载 1.查看安装 dpkg --list | grep mysql 有东西&#xff0c;就说明您之前安装过mysql。 2.卸载 先停掉server sudo systemctl stop mysql.servic…

基于回溯搜索优化的BP神经网络(分类应用) - 附代码

基于回溯搜索优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于回溯搜索优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.回溯搜索优化BP神经网络3.1 BP神经网络参数设置3.2 回溯搜索算法应用 4.测试结果…

基于Java的飞机航班订票购票管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

使用appscan定时批量扫描方法

文章目录 一、批量扫描设置二、定时扫描设置 一、批量扫描设置 appscan的窗口模式中允许用户一次只能选择一个扫描目标&#xff0c;但是如果想批量扫描多个网站的时候可以通过appscan安装文件夹下的AppScanCMD.exe工具来操作&#xff0c;具体操作如下。 首先按常规操作添加2个…

2.2.2搭建交叉编译器

1 交叉编译器 交叉编译的存在,有2个原因,1个是不同的平台,架构不同,使用的指令集不同,ARM和MIPS的CPU无法运行X86指令休编码的程序,1个是一般arm平台上的存储/性能有限,无法提供一个可靠的编译环境。所以就出现了在x86上编译,在arm上运行的镜像,即交叉编译。在交叉编…

git 同时配置 gitee github

git 同时配置 gitee github 1、 删除C:\Users\dell\.ssh目录。 在任意目录右击——》Git Bash Here&#xff0c;打开Git Bash窗口&#xff0c;下方命令在Git Bash窗口输入。 2、添加git全局范围的用户名和邮箱 git config --global user.email "609612189qq.com" …

【Spring篇】简述IoC入门案例,DI入门案例

&#x1f38a;专栏【Spring】 &#x1f354;喜欢的诗句&#xff1a;天行健&#xff0c;君子以自强不息。 &#x1f386;音乐分享【如愿】 &#x1f384;欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f384;Spring Framework系统架构&#x1f386;Spring核心概…

C10K问题:高并发模型设计

一、循环服务器模型 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/socket.h> //*******// #include &l…

DeckGL MVTLayer+Mapbox注入shader实现简单效果

1.数据获取与处理 可以从OSM下载相应的带高度的矢量建筑物轮廓数据&#xff0c;下载后进行数据处理&#xff0c;保留高度字段&#xff0c;其他字段根据需求选择&#xff08;比如说想实现选中建筑物弹出相应建筑物的信息&#xff0c;就把这些信息字段保留下来&#xff09;&…

腾讯云2023年双十一优惠活动整理

随着双十一的到来&#xff0c;各大电商平台纷纷推出优惠活动。作为国内领先的云计算服务提供商&#xff0c;腾讯云自然也不会错过。以下是对腾讯云2023年双十一优惠活动的整理&#xff0c;希望能够帮助大家轻松上云&#xff01; 一、腾讯云双十一活动入口 直达腾讯云双十一活动…

使用Pytorch从零实现Vision Transformer

在这篇文章中,我们将基于Pytorch框架从头实现Vision Transformer模型,并附录完整代码。 Vision Transformer(ViT)是一种基于Transformer架构的深度学习模型,用于处理计算机视觉任务。它将图像分割成小的图像块(patches),然后使用Transformer编码器来处理这些图像块。V…

基于Java的剧本杀预约系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

2023年CSP-J真题详解+分析数据

目录 亲身体验 江苏卷 选择题 阅读程序题 阅读程序(1&#xff09; 判断题 单选题 阅读程序(2) 判断题 单选题 阅读程序(3) 判断题 单选题 完善程序题 完善程序(1) 完善程序(2) 2023CSP-J江苏卷详解 小结 亲身体验 2023年的CSP-J是在9月16日9:30--11:30进行…

x64内核实验3-页机制的研究(1)

x64内核实验3-页机制的研究&#xff08;1&#xff09; CR4寄存器 在白皮书的第三卷2.5章对控制寄存器有详细的介绍&#xff0c;下面是白皮书中CR寄存器的结构图&#xff08;这里要说明一下cr4的第12位是由被使用的这个位在2.5章的后半部分有介绍是控制是否开启5级分页的位否则…

[Spring] Spring5——JdbcTemplate 简介

目录 一、JdbcTemplate 概述 1、什么是 JdbcTemplate 2、准备运行环境 二、添加功能 1、示例 三、修改和删除功能 1、示例 四、查询功能 1、查询记录数 2、查询返回 bean 对象 3、查询返回集合 4、测试 五、批量增删改功能 1、批量添加 2、批量修改 3、批量删除…