OpenMVG 的功能模块由若干核心库组成,本文主要介绍 image 和 numeric 两个库

    图像库包含图像容器 Image<T>、图像IO读写函数 ReadImage() 和 WriteImage()、基本绘图操作 DrawLine()、DrawCircle() 和 DrawEllipse() 等

    Image<T> 是一个图像类泛型容器,T 代表像素类型,可以是单通道的灰度图

  1. // 8bit and 32bit gray images
  2. Image<unsigned char> gray_img_8bit;
  3. Image<double> gray_img_32bit;   

    也可以是 RGB 和 RGBA 等多通道的彩色图

  1. Image<Rgb<unsigned char>> rgb_img_8bit; // 8bit RGB
  2. Image<Rgb<double> > rgb_img_32bit; // 32bit RGB
  3.  
  4. Image<Rgba<unsigned char> > rgba_img_8bit; // 8bit RGBA  

     Image<T> 也是一个模板类,继承自 Eigen 中的“行优先”模板类 Matrix<T, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>,所谓“行优先”,指的是矩阵内元素的存储顺序

    以  $A=\begin{bmatrix} 1 & 2 & 3  \\ 4 & 5 & 6 \end{bmatrix}$ 为例,行优先时元素在内存中的存储顺序为 1-2-3-4-5-6,列优先为 1-4-2-5-3-6

  1. template <typename T>
  2. class Image : public Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>
  3. {
  4. // ...
  5. };  

    Image<T> 的完整类视图如下,包含构造函数、析构函数、运算符重载函数、获取高度(行)函数等

      

    图像的 IO 读写函数,使用比较简单,如下:

  1. // Read a grayscale image
  2. Image<unit8_t> gray_img;
  3. bool bRet = ReadImage("Foo.imgExtension", &gray_img);
  4.  
  5. // Read a color image
  6. Image<RGBColor> rgb_img;
  7. bool bRet = ReadImage("Foo.imgExtension", &rgb_img);  

    图像 IO 读写函数的实现,稍微复杂,要根据不同的图像格式 (如 jpeg、tiff、png等),调用各自的库来实现 (如 libjpeg、libpng、libtiff 等),ReadImage() -> ReadJpg() -> ReadJpgStream() -> libjpeg

    笔者刚接触图像处理时,并不知道 libjpeg 等库的存在,曾花了不少时间,尝试用 c 语言读写 jpeg 图片,现在看来是浪费了时间,并无多大的用处

    在此摘录 OpenMVG 中 ReadJpgStream() 的实现代码,仅供阅读参考,希望不要投入过多精力

  1. int ReadJpgStream(FILE * file, std::vector<unsigned char> * ptr, int * w, int * h, int * depth)
  2. {
  3. jpeg_decompress_struct cinfo;
  4. struct my_error_mgr jerr;
  5. cinfo.err = jpeg_std_error(&jerr.pub);
  6. jerr.pub.error_exit = &jpeg_error;
  7.  
  8. if (setjmp(jerr.setjmp_buffer)) {
  9. std::cerr << "Error JPG: Failed to decompress.";
  10. jpeg_destroy_decompress(&cinfo);
  11. return 0;
  12. }
  13.  
  14. jpeg_create_decompress(&cinfo);
  15. jpeg_stdio_src(&cinfo, file);
  16. jpeg_read_header(&cinfo, TRUE);
  17. jpeg_start_decompress(&cinfo);
  18.  
  19. int row_stride = cinfo.output_width * cinfo.output_components;
  20.  
  21. *h = cinfo.output_height;
  22. *w = cinfo.output_width;
  23. *depth = cinfo.output_components;
  24. ptr->resize((*h)*(*w)*(*depth));
  25.  
  26. unsigned char *ptrCpy = &(*ptr)[0];
  27.  
  28. while (cinfo.output_scanline < cinfo.output_height) {
  29. JSAMPROW scanline[1] = { ptrCpy };
  30. jpeg_read_scanlines(&cinfo, scanline, 1);
  31. ptrCpy += row_stride;
  32. }
  33.  
  34. jpeg_finish_decompress(&cinfo);
  35. jpeg_destroy_decompress(&cinfo);
  36. return 1;
  37. }  

 

    numeric 的实现,主要是基于开源的 C++ 模板库 Eigen,它包含了线性代数的基本运算:向量、矩阵、矩阵运算等

     Vec2f 和 Vec2 分别表示类型为 float 和 double 的 2d 点 (x, y)

  1. // 2d vector using float internal format
  2. using Vec2f = Eigen::Vector2f;
  3.  
  4. // 2d vector using double internal format
  5. using Vec2 = Eigen::Vector2d;  

     Vec3f 和 Vec3 分别表示类型为 float 和 double 的 3d 点 (x, y, z)

  1. // 3d vector using float internal format
  2. using Vec3f =Eigen::Vector3f;
  3.  
  4. // 3d vector using double internal format
  5. using Vec3 = Eigen::Vector3d;  

     Mat 表示一个通用的矩阵,Mat2X 是列存储形式的一组 2d 点,Mat3X 则是列存储形式的一组 3d 点

  1. // Unconstrained matrix using double internal format
  2. using Mat = Eigen::MatrixXd;
  3.  
  4. // 2xN matrix using double internal format
  5. using Mat2X = Eigen::Matrix<double, 2, Eigen::Dynamic>;
  6.  
  7. // 3xN matrix using double internal format
  8. using Mat3X = Eigen::Matrix<double, 3, Eigen::Dynamic>;  

    SVD 将一个矩阵分解成三个矩阵的乘积 $ A_{m \times n} = UDV^T$,其中,$U_{m\times m}$ 和 $V_{n \times n}$ 都是正交矩阵, $D_{m \times n}$ 是对角矩阵

    在图像的几何变换中,仿射变换可视为一个奇异值分解的过程,参见博文 OpenCV 之 图像几何变换

    变换过程如下:

     $\begin{bmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{bmatrix} = \begin{bmatrix} \cos \theta & -\sin \theta \\ \sin \theta & \cos \theta \end{bmatrix} \begin{bmatrix} \sigma_{1} & \\ & \sigma_2 \end{bmatrix} \begin{bmatrix} \cos \phi & sin \phi \\ -\sin \phi & \cos \phi \end{bmatrix} = UDV^T$

    更为形象的描述:第1个圆旋转 $V^T$得到第2个圆,再经过 $D$ 的拉伸得到第3个椭圆,最后旋转 $U$ 得到第4个椭圆 

        

    Eigen 中的 SVD 分解:

  1. MatrixXf A = MatrixXf(3, 2);
  2. A << -1, -0.0827, -0.737, 0.0655, 0.511, -0.562;
  3. cout << "The matrix A:" << endl << A << endl;
  4.  
  5. // SVD decomposition
  6. JacobiSVD<MatrixXf> svd(A, ComputeFullU | ComputeFullV);
  7.  
  8. cout << "Singular values are:" << endl << svd.singularValues() << endl;
  9. cout << "Left singular vectors U :" << endl << svd.matrixU() << endl;
  10. cout << "Right singular vectors Vt :" << endl << svd.matrixV() << endl;  

    OpenCV 中的 SVD 分解:SVDecomp (InputArray src, OutputArray w, OutputArray u, OutputArray vt, int flags = 0)

  1. Mat W, U, Vt;
  2. Mat A = (Mat_<float>(3, 2) << -1, - 0.0827, -0.737, 0.0655, 0.511, -0.562);
  3. SVDecomp(A, W, U, Vt, SVD::FULL_UV);   

    从结果来看,Eigen 和 OpenCV 的 SVD 分解,有两处细微差异:一是小数点后位数不同,二是 U 和 Vt 中,部分元素的符号不同  

     

 

    OpenCV 中也有一个表示图像容器的模板类 Mat,参见博文 OpenCV 之 Mat 类,二者的转换关系如下:

    1)cv::Mat 转换为 Image (灰度图)     

  1. // cv Mat -> mvg Image
  2. cv::Mat img_cv = cv::imread("messi.jpg", cv::IMREAD_GRAYSCALE);
  3.  
  4. Image<uint8_t> img_mvg;
  5. img_mvg.resize(img_cv.cols, img_cv.rows);
  6.  
  7. // convert and save
  8. cv::cv2eigen(img_cv, *(Image<uint8_t>::Base*) &img_mvg);
  9. WriteImage("messi_mvg.jpg", img_mvg);  

    2)cv::Mat 转换为 Image (彩色图)

  1. cv::Mat img_cv;
  2. img_cv = cv::imread("messi.jpg");
  3.  
  4. Image<RGBColor> img_mvg;
  5. img_mvg.resize(img_cv.cols, img_cv.rows);
  6. cv::cvtColor(img_cv, img_cv, cv::COLOR_BGR2RGB);
  7.  
  8. // convert and save
  9. memcpy(img_mvg.data(), static_cast<unsigned char*>(img_cv.data), img_cv.cols * img_cv.rows * 3);
  10. WriteImage("messi_mvg.jpg", img_mvg);  

    3)Image 转换为 cv::Mat

  1. // Read a grayscale image
  2. Image<unsigned char> img_mvg;
  3. bool bRet = ReadImage("messi.jpg", &img_mvg);
  4.  
  5. // mvg Image -> cv Mat
  6. cv::Mat img_cv;
  7. cv::eigen2cv(img_mvg.GetMat(), img_cv);
  8.  
  9. // show image
  10. cv::imshow("messi", img_cv);
  11. cv::waitKey();  

    转换后的图片结果: 

   

  

 

  OpenMVG libraries

 《Introduction to Linear Algebra》 7.4  The Geometry of the SVD

  Eigen::JacobiSVD 

 

版权声明:本文为xinxue原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/xinxue/p/14932419.html