相机在投影到画布的过程中对Z坐标进行了归一化,意味着丢失了深度信息,因此常规的相机只能形成二维图像,而不是更加具体的三维位置信息,因此我们需要通过别的手段计算估计每个像素点的深度,常见的方法有双目相机和RGB-D相机
双目相机模型
类似人眼,双目相机由水平左右俩个相机构成,人可以根据左右人眼视角的不同轻易的判断所看到的事物的距离,这个视角差异称为视差。双目相机的原理也是如此,通过对同一时刻左右相机看到的图像,计算图像之间的视差,以便估计像素的深度信息
计算视差
如图:Ol和Or为左右成像中心,方框为成像范围,f为焦距,俩个相机之间的距离b称为基线,P是相机视野内一点,投影到俩个相机中为Pl和Pr,ul和ur为图像坐标(距离图像中心的x坐标),考虑到方向性,因此图中ur标记为负号
可以看出,俩个三角形构成相似
$$ \bigtriangleup PO_{L}O_{R}\sim \bigtriangleup PP_{L}P_{R} $$
可以得到
$$ \frac{Z-f}{Z} =\frac{b-u_{L}+u_{R}}{b} $$
整理后得到我们所需要的
$$ z=\frac{fb}{d} ,d= u_{L}-u_{R} $$
其中我们将d定义为俩张图片中同一点的横坐标之差,称为视差。显而易见的是,越近的点视差越大,视差与距离成反比。
缺点
即使有了计算方法,我们依然很难去计算一张图中的距离,因为我们在计算机中很难判断左侧相机中的某个像素点与右侧的哪个像素对应,与此同时,视差最小为一个像素,所以双目相机的计算深度存在理论最大值。
示例
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";
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;
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,第四维为颜色
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;
}
左右相机视角以及结果
左相机
右相机
深度图以及点云