第5讲 相机与图像
5.1 相机模型
5.1.1 针孔相机模型
内参矩阵K:
5.1.2 畸变
由透镜形状引起的畸变称为径向畸变
径向畸变分为:
- 桶形畸变
- 枕形畸变
在相机的组装过程中由于不能使透镜和成像面严格平行也会引起切向畸变
5.1.4 RGB-D相机模型
红外结构光法、飞行时间法
5.2 图像
unsigned char image[480][640]; unsigned char pixel = image[y][x];
- 彩色图像的表示则需要通道的概念。
- 最常见的彩色图像有三个通道,每个通道由8位整数表示,一个像素占据24位空间。
- 在OpenCV的彩色图像中,通道的默认顺序是B、G、R。
- 前8位表示蓝色数值,中间8位表示绿色,最后8位表示红色
## 5.3 实践:图像的存储与访问
(1)opencv2安装
https://blog.csdn.net/qq_39236499/article/details/122725012
(2)代码解析
CMakeLists.txt
cmake_minimum_required( VERSION 2.8 ) project( imageBasics ) # 添加c++ 11标准支持 set( CMAKE_CXX_FLAGS "-std=c++11" ) # 寻找OpenCV库 find_package( OpenCV REQUIRED ) #这里我去掉了3,因为我的版本是2 # 添加头文件 include_directories( ${OpenCV_INCLUDE_DIRS} ) add_executable( imageBasics imageBasics.cpp ) # 链接OpenCV库 target_link_libraries( imageBasics ${OpenCV_LIBS} )
imageBasics.cpp
#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]指定的图像 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++){ // 用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; // 关于 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); // 将左上角100*100的块置白色 cv::imshow ("image", image); cv::imshow ("image_clone", image_clone); cv::waitKey (0); // 对于图像还有很多基本的操作,如剪切,旋转,缩放等,限于篇幅就不一一介绍了,请参看OpenCV官方文档查询每个函数的调用方法. cv::destroyAllWindows(); return 0; }
(3)编译和运行
cd [imageBasics路径] mkdir build cd builcd cmake .. make cd .. build/imageBasics ubuntu.png
5.4 实践:拼接点云
1. 安装依赖项
你必须安装一个pcl,下面一句指令就行了,会自动安装依赖项,别看其他的博客一个一个依赖项安装,心真的累
sudo apt install libpcl-dev pcl-tools
2.(Ubuntu16.04执行,14.04不需要)CMakeLists.txt加一句代码
cmake_minimum_required( VERSION 2.8 ) project( joinMap ) set( CMAKE_BUILD_TYPE Release ) set( CMAKE_CXX_FLAGS "-std=c++11 -O3" ) # opencv find_package( OpenCV REQUIRED ) include_directories( ${OpenCV_INCLUDE_DIRS} ) # eigen include_directories( "/usr/include/eigen3/" ) # pcl find_package( PCL REQUIRED COMPONENT common io ) list(REMOVE_ITEM PCL_LIBRARIES "vtkproj4") #use this in ubuntu 16.04 加上这句 include_directories( ${PCL_INCLUDE_DIRS} ) add_definitions( ${PCL_DEFINITIONS} ) add_executable( joinMap joinMap.cpp ) target_link_libraries( joinMap ${OpenCV_LIBS} ${PCL_LIBRARIES} )
3. 正常编译
mkdir build cd build cmake .. make
4. 运行
记得回退一节目录,即cd…
cd .. build/joinMap
5. 查看点云图
pcl_viewer map.pcd
6. 代码解析
#include <iostream> #include <fstream> using namespace std; #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <Eigen/Geometry> #include <boost/format.hpp> // for formating strings #include <pcl/point_types.h> #include <pcl/io/pcd_io.h> #include <pcl/visualization/pcl_visualizer.h> int main( int argc, char** argv ) { vector<cv::Mat> colorImgs, depthImgs; // 彩色图和深度图 vector<Eigen::Isometry3d, Eigen::aligned_allocator<Eigen::Isometry3d>> 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}; //压入pose文件 for (auto& d : data){ fin >> d; } Eigen::Quaterniond q(data[6], data[3], data[4], data[5]); //旋转四元素 Eigen::Isometry3d T(q); T.pretranslate(Eigen::Vector3d(data[0], data[1], data[2])); //平移向量 poses.push_back(T); } // 计算点云并拼接 // 相机内参 double cx = 325.5; double cy = 253.5; double fx = 518.0; double fy = 519.0; double depthScale = 1000.0; cout<<"正在将图像转换为点云..."<<endl; // 定义点云使用的格式:这里用的是XYZRGB typedef pcl::PointXYZRGB PointT; typedef pcl::PointCloud<PointT> PointCloud; // 新建一个点云 PointCloud::Ptr pointCloud( new PointCloud ); for (int i = 0; i < 5; i++){ cout << "转换图像中: " << i+1 << endl; cv::Mat color = colorImgs[i]; //准备数据 cv::Mat depth = depthImgs[i]; Eigen::Isometry3d 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表示没有测量到 即该像素点深度为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; //世界坐标系下坐标 PointT p ; p.x = pointWorld[0]; p.y = pointWorld[1]; p.z = pointWorld[2]; p.b = color.data[ v*color.step + u*color.channels() ]; p.g = color.data[ v*color.step + u*color.channels() + 1 ]; p.r = color.data[ v*color.step + u*color.channels() + 2 ]; pointCloud->points.push_back(p); //存储到pcl点云库 } } } pointCloud->is_dense = false; cout << "点云共有" << pointCloud->size() << "个点." << endl; pcl::io::savePCDFileBinary("map.pcd", *pointCloud ); return 0; }