OpenCV中的算法--透視和仿射變換

引言

仿射變換保證物體形狀的“平直性”和“平行性”。透視變換不能保證物體形狀的“平行性”。仿射變換是透視變換的特殊形式。

仿射變換,又稱仿射映射,是指在幾何中,一個向量空間進行一次線性變換並接上一個平移,變換爲另一個向量空間。仿射變換是在幾何上定義爲兩個向量空間之間的一個仿射變換或者仿射映射(來自拉丁語,affine,“和…相關”)由一個非奇異的線性變換(運用一次函數進行的變換)接上一個平移變換組成。

warpPerspective 和 affineTransform的轉換矩陣的區別

  1. affineTransform保持平行性,而warpPerspective不能保證
  2. warpPerspective至少4個點對,而 affineTransform至少三個點對 下面是opencv中關於這兩個變換矩陣的求解過程。
/* Calculates coefficients of perspective transformation
 * which maps (xi,yi) to (ui,vi), (i=1,2,3,4):
 *
 *      c00*xi + c01*yi + c02
 * ui = ---------------------
 *      c20*xi + c21*yi + c22
 *
 *      c10*xi + c11*yi + c12
 * vi = ---------------------
 *      c20*xi + c21*yi + c22
 *
 * Coefficients are calculated by solving linear system:
 * / x0 y0 1 0 0 0 -x0*u0 -y0*u0 \ /c00\ /u0\
 * | x1 y1 1 0 0 0 -x1*u1 -y1*u1 | |c01| |u1|
 * | x2 y2 1 0 0 0 -x2*u2 -y2*u2 | |c02| |u2|
 * | x3 y3 1 0 0 0 -x3*u3 -y3*u3 |.|c10|=|u3|,
 * | 0 0 0 x0 y0 1 -x0*v0 -y0*v0 | |c11| |v0|
 * | 0 0 0 x1 y1 1 -x1*v1 -y1*v1 | |c12| |v1|
 * | 0 0 0 x2 y2 1 -x2*v2 -y2*v2 | |c20| |v2|
 * \ 0 0 0 x3 y3 1 -x3*v3 -y3*v3 / \c21/ \v3/
 *
 * where:
 * cij - matrix coefficients, c22 = 1
 */
cv::Mat cv::getPerspectiveTransform( const Point2f src[], const Point2f dst[] )
{
  Mat M(3, 3, CV_64F), X(8, 1, CV_64F, M.data);
  double a[8][8], b[8];
  Mat A(8, 8, CV_64F, a), B(8, 1, CV_64F, b);

  for( int i = 0; i < 4; ++i )
  {
  a[i][0] = a[i+4][3] = src[i].x;
  a[i][1] = a[i+4][4] = src[i].y;
  a[i][2] = a[i+4][5] = 1;
  a[i][3] = a[i][4] = a[i][5] =
  a[i+4][0] = a[i+4][1] = a[i+4][2] = 0;
  a[i][6] = -src[i].x*dst[i].x;
  a[i][7] = -src[i].y*dst[i].x;
  a[i+4][6] = -src[i].x*dst[i].y;
  a[i+4][7] = -src[i].y*dst[i].y;
  b[i] = dst[i].x;
  b[i+4] = dst[i].y;
  }

  solve( A, B, X, DECOMP_SVD );
  ((double*)M.data)[8] = 1.;

  return M;
}


/* Calculates coefficients of affine transformation
 * which maps (xi,yi) to (ui,vi), (i=1,2,3):
 *
 * ui = c00*xi + c01*yi + c02
 *
 * vi = c10*xi + c11*yi + c12
 *
 * Coefficients are calculated by solving linear system:
 * / x0 y0 1 0 0 0 \ /c00\ /u0\
 * | x1 y1 1 0 0 0 | |c01| |u1|
 * | x2 y2 1 0 0 0 | |c02| |u2|
 * | 0 0 0 x0 y0 1 | |c10| |v0|
 * | 0 0 0 x1 y1 1 | |c11| |v1|
 * \ 0 0 0 x2 y2 1 / |c12| |v2|
 *
 * where:
 * cij - matrix coefficients
 */

cv::Mat cv::getAffineTransform( const Point2f src[], const Point2f dst[] )
{
  Mat M(2, 3, CV_64F), X(6, 1, CV_64F, M.data);
  double a[6*6], b[6];
  Mat A(6, 6, CV_64F, a), B(6, 1, CV_64F, b);

  for( int i = 0; i < 3; i++ )
  {
  int j = i*12;
  int k = i*12+6;
  a[j] = a[k+3] = src[i].x;
  a[j+1] = a[k+4] = src[i].y;
  a[j+2] = a[k+5] = 1;

  a[j+3] = a[j+4] = a[j+5] = 0;
  a[k] = a[k+1] = a[k+2] = 0;

  b[i*2] = dst[i].x;
  b[i*2+1] = dst[i].y;
  }

  solve( A, B, X );
  return M;
}

如果我們要自己實現這個函數,其實關鍵就是在於如何求解AX=B的問題。當然,我們可以直接調用庫函數,如eigen.

問題:這個函數如果要自己實現,如何測試正確性?

  • 方案1: 採用引入opencv作爲第三方庫,然後相同的輸入結果與opencv中進行對比。這種方法簡單,但是需要引入龐大的第三方庫opencv

  • 方案2: 採用兩次變換,例如測試warp_perspective其中第一次將src_img經過warp_perspective變換爲dst_img,其中轉換矩陣爲M; 然後將dst_img經過warp_perspective變換爲dst_warp,其中轉換矩陣爲M‘M的逆矩陣; 最後比較dst_warpsrc中進行逐個像素對照,統計diff的像素個數count, return count <= thresh_value. 這種方法的缺點就是需要設置thresh_value,同時需要求M的逆矩陣

  • 方案3: 如果不能將opencv作爲第三方庫引入,那麼我們可以這樣,將opencv的輸入參數和結果作爲hard code的方式,進行測試。這種方法尤其是 再嵌入式開發中很常見。

更多信息可以參考

[1] https://docs.opencv.org/2.4/doc/tutorials/imgproc/imgtrans/warp_affine/warp_affine.html

[2] gTest的原理: http://cwlseu.github.io/st-CMAKE-and-gTest/搜索

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章