OpenMVG 系列 (2):圖像和數值運算


     OpenMVG 的功能模塊由若干核心庫組成,本文主要介紹 image 和 numeric 兩個庫

1  圖像庫

    圖像庫包含圖像容器 Image<T>、圖像IO讀寫函數 ReadImage() 和 WriteImage()、基本繪圖操作 DrawLine()、DrawCircle() 和 DrawEllipse() 等

1.1  圖像容器

    Image<T> 是一個圖像類泛型容器,T 代表像素類型,可以是單通道的灰度圖

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

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

  Image<Rgb<unsigned char>>  rgb_img_8bit;   // 8bit RGB
  Image<Rgb<double> >        rgb_img_32bit;  // 32bit RGB

  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

  template <typename T>
  class Image : public Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>
  {
      // ...
  };  

    Image<T> 的完整類視圖如下,包含構造函數、析構函數、運算符重載函數、獲取高度(行)函數等

      

1.2  讀寫操作

    圖像的 IO 讀寫函數,使用比較簡單,如下:

  // Read a grayscale image
  Image<unit8_t> gray_img;
  bool bRet = ReadImage("Foo.imgExtension", &gray_img);

  // Read a color image
  Image<RGBColor> rgb_img;
  bool bRet = ReadImage("Foo.imgExtension", &rgb_img);  

    圖像 IO 讀寫函數的實現,稍微復雜,要根據不同的圖像格式 (如 jpeg、tiff、png等),調用各自的庫來實現 (如 libjpeg、libpng、libtiff 等),ReadImage() -> ReadJpg() -> ReadJpgStream() -> libjpeg

    筆者剛接觸圖像處理時,並不知道 libjpeg 等庫的存在,曾花了不少時間,嘗試用 c 語言讀寫 jpeg 圖片,現在看來是浪費了時間,並無多大的用處

    在此摘錄 OpenMVG 中 ReadJpgStream() 的實現代碼,僅供閱讀參考,希望不要投入過多精力

int ReadJpgStream(FILE * file, std::vector<unsigned char> * ptr, int * w, int * h, int * depth) 
{
  jpeg_decompress_struct cinfo;
  struct my_error_mgr jerr;
  cinfo.err = jpeg_std_error(&jerr.pub);
  jerr.pub.error_exit = &jpeg_error;

  if (setjmp(jerr.setjmp_buffer)) {
    std::cerr << "Error JPG: Failed to decompress.";
    jpeg_destroy_decompress(&cinfo);
    return 0;
  }

  jpeg_create_decompress(&cinfo);
  jpeg_stdio_src(&cinfo, file);
  jpeg_read_header(&cinfo, TRUE);
  jpeg_start_decompress(&cinfo);

  int row_stride = cinfo.output_width * cinfo.output_components;

  *h = cinfo.output_height;
  *w = cinfo.output_width;
  *depth = cinfo.output_components;
  ptr->resize((*h)*(*w)*(*depth));

  unsigned char *ptrCpy = &(*ptr)[0];

  while (cinfo.output_scanline < cinfo.output_height) {
    JSAMPROW scanline[1] = { ptrCpy };
    jpeg_read_scanlines(&cinfo, scanline, 1);
    ptrCpy += row_stride;
  }

  jpeg_finish_decompress(&cinfo);
  jpeg_destroy_decompress(&cinfo);
  return 1;
}  

 

2  數值運算

    numeric 的實現,主要是基於開源的 C++ 模板庫 Eigen,它包含了線性代數的基本運算:向量、矩陣、矩陣運算等

2.1  向量和矩陣

     Vec2f 和 Vec2 分別表示類型為 float 和 double 的 2d 點 (x, y)

  // 2d vector using float internal format
  using Vec2f = Eigen::Vector2f;

  // 2d vector using double internal format
  using Vec2 = Eigen::Vector2d;  

     Vec3f 和 Vec3 分別表示類型為 float 和 double 的 3d 點 (x, y, z)

  // 3d vector using float internal format
  using Vec3f =Eigen::Vector3f;

  // 3d vector using double internal format
  using Vec3 = Eigen::Vector3d;  

     Mat 表示一個通用的矩陣,Mat2X 是列存儲形式的一組 2d 點,Mat3X 則是列存儲形式的一組 3d 點

  // Unconstrained matrix using double internal format
  using Mat = Eigen::MatrixXd;

  // 2xN matrix using double internal format
  using Mat2X = Eigen::Matrix<double, 2, Eigen::Dynamic>;

  // 3xN matrix using double internal format
  using Mat3X = Eigen::Matrix<double, 3, Eigen::Dynamic>;  

2.2  奇異值分解 - SVD

    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個橢圓 

        

2.3  代碼示例

    Eigen 中的 SVD 分解:

  MatrixXf A = MatrixXf(3, 2);
  A << -1, -0.0827, -0.737, 0.0655, 0.511, -0.562;
  cout << "The matrix A:" << endl << A << endl;

  // SVD decomposition
  JacobiSVD<MatrixXf> svd(A, ComputeFullU | ComputeFullV);

  cout << "Singular values are:" << endl << svd.singularValues() << endl;
  cout << "Left singular vectors U :" << endl << svd.matrixU() << endl;
  cout << "Right singular vectors Vt :" << endl << svd.matrixV() << endl;  

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

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

    從結果來看,Eigen 和 OpenCV 的 SVD 分解,有兩處細微差異:一是小數點后位數不同,二是 U 和 Vt 中,部分元素的符號不同  

     

 

3  Image 與 cv::Mat

    OpenCV 中也有一個表示圖像容器的模板類 Mat,參見博文 OpenCV 之 Mat 類,二者的轉換關系如下:

    1)cv::Mat 轉換為 Image (灰度圖)     

  // cv Mat -> mvg Image
  cv::Mat img_cv = cv::imread("messi.jpg", cv::IMREAD_GRAYSCALE);

  Image<uint8_t> img_mvg;
  img_mvg.resize(img_cv.cols, img_cv.rows);

  // convert and save
  cv::cv2eigen(img_cv, *(Image<uint8_t>::Base*) &img_mvg);
  WriteImage("messi_mvg.jpg", img_mvg);  

    2)cv::Mat 轉換為 Image (彩色圖)

  cv::Mat img_cv;
  img_cv = cv::imread("messi.jpg");

  Image<RGBColor> img_mvg;
  img_mvg.resize(img_cv.cols, img_cv.rows);
  cv::cvtColor(img_cv, img_cv, cv::COLOR_BGR2RGB);

  // convert and save 
  memcpy(img_mvg.data(), static_cast<unsigned char*>(img_cv.data), img_cv.cols * img_cv.rows * 3);
  WriteImage("messi_mvg.jpg", img_mvg);  

    3)Image 轉換為 cv::Mat

  // Read a grayscale image
  Image<unsigned char> img_mvg;
  bool bRet = ReadImage("messi.jpg", &img_mvg);

  // mvg Image -> cv Mat
  cv::Mat img_cv;
  cv::eigen2cv(img_mvg.GetMat(), img_cv);

  // show image
  cv::imshow("messi", img_cv);
  cv::waitKey();  

    轉換后的圖片結果: 

   

  

 

 參考資料

  OpenMVG libraries

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

  Eigen::JacobiSVD 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM