视觉SLAM十四讲-高翔 第5讲 相机和图像

相机和图像

5.1 相机模型

我们使用针孔和畸变两个模型来描述整个投影过程,这两个模型能够把外部的三维点投影到相机内部成像平面,构成了相机的内参数

5.1.1 针孔相机模型

视觉SLAM十四讲-高翔 第5讲 相机和图像_第1张图片视觉SLAM十四讲-高翔 第5讲 相机和图像_第2张图片

视觉SLAM十四讲-高翔 第5讲 相机和图像_第3张图片
按照习惯 Z Z Z移到左侧
视觉SLAM十四讲-高翔 第5讲 相机和图像_第4张图片
我们把中间的量组成的矩阵称为相机的内参数矩阵(Camera Intrinsics)K
我们使用的是P 在相机坐标系下的坐标。由于相机在运动,所以P 的相机坐标应该是它的世界坐标(记为 P w \bm{P_w} Pw),根据相机的当前位姿,变换到相机坐标系下的结果。相机的位姿由它的旋转矩阵 R \bm{R} R 和平移向量 t \bm{t} t 来描述。那么有:
视觉SLAM十四讲-高翔 第5讲 相机和图像_第5张图片
上式两侧都是齐次坐标。因为齐次坐标乘上非零常数后表达同样的含义,所以可以简单地把 Z Z Z 去掉: P u v = K T P w \bm{P_{uv}=KTP_w} Puv=KTPw
视觉SLAM十四讲-高翔 第5讲 相机和图像_第6张图片

5.1.2 畸变

由透镜形状引起的畸变称之为径向畸变。它们主要分为两大类,桶形畸变和枕形畸变。
视觉SLAM十四讲-高翔 第5讲 相机和图像_第7张图片
桶形畸变是由于图像放大率随着离光轴的距离增加而减小,而枕形畸变却恰好相反。在这两种畸变中,穿过图像中心和光轴有交点的直线还能保持形状不变。
除了透镜的形状会引入径向畸变外,在相机的组装过程中由于不能使得透镜和成像面严格平行也会引入切向畸变。
视觉SLAM十四讲-高翔 第5讲 相机和图像_第8张图片
径向畸变可看成坐标点沿着长度方向发生了变化 , 也就是其距离原点的长度发生了变化。切向畸变可以看成坐标点沿着切线方向发生了变化,也就是水平夹角发生了变化 δ θ \delta \theta δθ

视觉SLAM十四讲-高翔 第5讲 相机和图像_第9张图片
视觉SLAM十四讲-高翔 第5讲 相机和图像_第10张图片
视觉SLAM十四讲-高翔 第5讲 相机和图像_第11张图片

注:因为项目不需要,暂不记录双目相机和RGB-D相机模型

2 图像

2.1 计算机中图像的表示

传统像素坐标系的定义方式。
视觉SLAM十四讲-高翔 第5讲 相机和图像_第12张图片

3 实践:图像的存取与访问

3.1 安装OpenCV

参考此链接:

ubuntu20 安装OpenCV(c++版)

3.2 操作OpenCV图像

imageBasic.cpp

#include 
#include 
using namespace std;

#include 
#include 

int main ( int argc, char** argv )
{
	// 读取 argv[1] 指定的图像
	cv::Mat image;
	image = cv::imread ( argv[1] ); // cv::imread 函数读取指定路径下的图像
	// 判断图像文件是否正确读取
	if ( image.data == nullptr ) // 数据不存在,可能是文件不存在
	{
		cerr<<"文件"<<argv[1]<<"不存在."<<endl;
		return 0;
	}

	// 文件顺利读取, 首先输出一些基本信息
	cout<<"图像宽为"<<image.cols<<",高为"<<image.rows<<",通道数为"<<image.channels()<<endl;
	cv::imshow ( "image", image ); // 用 cv::imshow 显示图像
	cv::waitKey ( 0 ); // 暂停程序,等待一个按键输入
	// 判断 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++ )
	{
		for ( size_t x=0; x<image.cols; x++ )
		{
			// 访问位于 x,y 处的像素
			// 用 cv::Mat::ptr 获得图像的行指针
			unsigned char* row_ptr = image.ptr<unsigned char> ( y ); // row_ptr 是第 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;

	// 关于 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;

}

CMakeLists.txt

cmake_minimum_required(VERSION 3.5) 
project(imageBasic)
find_package(OpenCV REQUIRED) 
include_directories(${OpenCV_INCLUDE_DIRS})
set(CMAKE_CXX_STANDARD 11)
add_executable(imageBasic imageBasic.cpp) 
target_link_libraries(imageBasic ${OpenCV_LIBS}) 

最后运行的时候记得加上图片位置(例如)

./imageBasic "../test1.png"

你可能感兴趣的:(VSLAM,计算机视觉,人工智能,机器学习)