2D で遠近補正グリッドを描画する方法
-
22-08-2019 - |
質問
画像/写真の上に現実世界の四角形を定義するアプリケーションがあります。もちろん、2D では斜めから見ているため、四角形ではない可能性があります。
問題は、長方形にグリッド線を引く必要があるとします。たとえば、長方形が 3x5 の場合、辺 1 から辺 3 までは 2 本の線を、辺 2 から辺 4 までは 4 本の線を引く必要があります。
現時点では、すべてのグリッド線の始点と終点を取得するために、各線を等距離の部分に分割しています。ただし、長方形の角度が大きくなるほど、これらの線はより「不正確」になります。これは、遠くにある水平線は互いに近づくはずであるためです。
検索すべきアルゴリズムの名前を知っている人はいますか?
はい、3D でこれを実行できることは知っていますが、この特定のアプリケーションでは 2D に限定されています。
解決
ここでのソリューションをします。
基本的な考え方は、あなたが斜めにコーナーを接続することで、あなたの長方形の視点正しい「センター」を見つけることができます。得られた二つの線の交点は、あなたの視点正しいセンターです。そこからは、4つの小さな長方形にあなたの四角形を細分化し、そしてあなたは、このプロセスを繰り返します。回数は、あなたがそれをする方法を正確に依存します。あなたが効果的に完璧な視点用のピクセルのすぐ下のサイズに細分化することができます。
次に、あなたの副矩形にあなたは自分の標準補正されていない「テクスチャ」の三角形、または長方形または何を適用します。
あなたは「本当の」3Dの世界を構築するための複雑なトラブルに行かなくても、このアルゴリズムを実行することができます。あなたがをを行う場合は、実際の3Dの世界をモデル化したため、それも良いですが、あなたのtextrianglesは、視点は、ハードウェアで修正されていないか、ピクセルレンダリング策略あたりなし視点正しい面を得るためのパフォーマンスの方法が必要です。
他のヒント
画像:双線形&視点の例(注:トップ&ボトム水平格子線の高さは、両方の図面に、実際残り線高さの半分である)を形質転換
========================================
私は、これは古い質問ですけど、私はそれが将来の読者に有用であろうホッピング、それを公開することを決めたので、私は、一般的な解決策を持っています。 コードベローズは、反復計算を必要とせず、任意の遠近グリッドを描くことができます。
私は同様の問題で実際に始まる:2D透視グリッドを描画して、視点を復元するために、下線画像を変換するために、
。私はここで読むことを開始しました: http://www.imagemagick.org/Usage/distorts/#bilinear_forwardする
その後、ここ(Leptonicaライブラリ): http://www.leptonica.com/affine.htmlする
は、私はこれを発見された。
多くの人は既にこのスレッドで指摘したように、あなたには、いくつかの任意の方向での面内でオブジェクトを見てみると 有限距離は、あなたは、追加の「キーストーン」歪みを取得します 画像。これは、直線を維持しており、射影変換であります ストレートが、線の間の角度を保持されません。この反り 線形アフィン変換によって記述され、実際にはできません 分母のx及びy依存用語によって異なります。
変換は、直線的ではありません。それは8つの必要な係数を計算するために(1回)8次方程式の線形システムを解く必要がして、あなたが望むように多くのポイントを変換するためにそれらを使用することができます。
私のプロジェクト内のすべてのLeptonicaライブラリを含めないようにするには、私はそれからのコードのいくつかの作品を取った、私はいくつかのメモリリークが修正され、すべての特殊Leptonicaデータ・タイプ&マクロを削除し、私は主にカプセル化のために(C ++のクラスにそれを変換しますただ一つのことを行うの理由): これは、(Qtの)QPointFフロート(x、y)は、対応する視点座標に座標をマッピングする。
あなたが別のC ++ライブラリにコードを適応したい場合は、、/代替を再定義する唯一のものはQPointFクラスを座標ます。
私はいくつかの将来の読者はそれが役立つでしょう願っています。 コード蛇腹を3つの部分に分割される:
A。 2D透視グリッド
を描画するgenImageProjective C ++クラスを使用する方法の例B。 genImageProjective.hファイル
C。 genImageProjective.cppファイル
//============================================================
// C++ Code Example on how to use the
// genImageProjective class to draw a perspective 2D Grid
//============================================================
#include "genImageProjective.h"
// Input: 4 Perspective-Tranformed points:
// perspPoints[0] = top-left
// perspPoints[1] = top-right
// perspPoints[2] = bottom-right
// perspPoints[3] = bottom-left
void drawGrid(QPointF *perspPoints)
{
(...)
// Setup a non-transformed area rectangle
// I use a simple square rectangle here because in this case we are not interested in the source-rectangle,
// (we want to just draw a grid on the perspPoints[] area)
// but you can use any arbitrary rectangle to perform a real mapping to the perspPoints[] area
QPointF topLeft = QPointF(0,0);
QPointF topRight = QPointF(1000,0);
QPointF bottomRight = QPointF(1000,1000);
QPointF bottomLeft = QPointF(0,1000);
float width = topRight.x() - topLeft.x();
float height = bottomLeft.y() - topLeft.y();
// Setup Projective trasform object
genImageProjective imageProjective;
imageProjective.sourceArea[0] = topLeft;
imageProjective.sourceArea[1] = topRight;
imageProjective.sourceArea[2] = bottomRight;
imageProjective.sourceArea[3] = bottomLeft;
imageProjective.destArea[0] = perspPoints[0];
imageProjective.destArea[1] = perspPoints[1];
imageProjective.destArea[2] = perspPoints[2];
imageProjective.destArea[3] = perspPoints[3];
// Compute projective transform coefficients
if (imageProjective.computeCoeefficients() != 0)
return; // This can actually fail if any 3 points of Source or Dest are colinear
// Initialize Grid parameters (without transform)
float gridFirstLine = 0.1f; // The normalized position of first Grid Line (0.0 to 1.0)
float gridStep = 0.1f; // The normalized Grd size (=distance between grid lines: 0.0 to 1.0)
// Draw Horizonal Grid lines
QPointF lineStart, lineEnd, tempPnt;
for (float pos = gridFirstLine; pos <= 1.0f; pos += gridStep)
{
// Compute Grid Line Start
tempPnt = QPointF(topLeft.x(), topLeft.y() + pos*width);
imageProjective.mapSourceToDestPoint(tempPnt, lineStart);
// Compute Grid Line End
tempPnt = QPointF(topRight.x(), topLeft.y() + pos*width);
imageProjective.mapSourceToDestPoint(tempPnt, lineEnd);
// Draw Horizontal Line (use your prefered method to draw the line)
(...)
}
// Draw Vertical Grid lines
for (float pos = gridFirstLine; pos <= 1.0f; pos += gridStep)
{
// Compute Grid Line Start
tempPnt = QPointF(topLeft.x() + pos*height, topLeft.y());
imageProjective.mapSourceToDestPoint(tempPnt, lineStart);
// Compute Grid Line End
tempPnt = QPointF(topLeft.x() + pos*height, bottomLeft.y());
imageProjective.mapSourceToDestPoint(tempPnt, lineEnd);
// Draw Vertical Line (use your prefered method to draw the line)
(...)
}
(...)
}
==========================================
//========================================
//C++ Header File: genImageProjective.h
//========================================
#ifndef GENIMAGE_H
#define GENIMAGE_H
#include <QPointF>
// Class to transform an Image Point using Perspective transformation
class genImageProjective
{
public:
genImageProjective();
int computeCoeefficients(void);
int mapSourceToDestPoint(QPointF& sourcePoint, QPointF& destPoint);
public:
QPointF sourceArea[4]; // Source Image area limits (Rectangular)
QPointF destArea[4]; // Destination Image area limits (Perspectivelly Transformed)
private:
static int gaussjordan(float **a, float *b, int n);
bool coefficientsComputed;
float vc[8]; // Vector of Transform Coefficients
};
#endif // GENIMAGE_H
//========================================
//========================================
//C++ CPP File: genImageProjective.cpp
//========================================
#include <math.h>
#include "genImageProjective.h"
// ----------------------------------------------------
// class genImageProjective
// ----------------------------------------------------
genImageProjective::genImageProjective()
{
sourceArea[0] = sourceArea[1] = sourceArea[2] = sourceArea[3] = QPointF(0,0);
destArea[0] = destArea[1] = destArea[2] = destArea[3] = QPointF(0,0);
coefficientsComputed = false;
}
// --------------------------------------------------------------
// Compute projective transform coeeeficients
// RetValue: 0: Success, !=0: Error
/*-------------------------------------------------------------*
* Projective coordinate transformation *
*-------------------------------------------------------------*/
/*!
* computeCoeefficients()
*
* Input: this->sourceArea[4]: (source 4 points; unprimed)
* this->destArea[4]: (transformed 4 points; primed)
* this->vc (computed vector of transform coefficients)
* Return: 0 if OK; <0 on error
*
* We have a set of 8 equations, describing the projective
* transformation that takes 4 points (sourceArea) into 4 other
* points (destArea). These equations are:
*
* x1' = (c[0]*x1 + c[1]*y1 + c[2]) / (c[6]*x1 + c[7]*y1 + 1)
* y1' = (c[3]*x1 + c[4]*y1 + c[5]) / (c[6]*x1 + c[7]*y1 + 1)
* x2' = (c[0]*x2 + c[1]*y2 + c[2]) / (c[6]*x2 + c[7]*y2 + 1)
* y2' = (c[3]*x2 + c[4]*y2 + c[5]) / (c[6]*x2 + c[7]*y2 + 1)
* x3' = (c[0]*x3 + c[1]*y3 + c[2]) / (c[6]*x3 + c[7]*y3 + 1)
* y3' = (c[3]*x3 + c[4]*y3 + c[5]) / (c[6]*x3 + c[7]*y3 + 1)
* x4' = (c[0]*x4 + c[1]*y4 + c[2]) / (c[6]*x4 + c[7]*y4 + 1)
* y4' = (c[3]*x4 + c[4]*y4 + c[5]) / (c[6]*x4 + c[7]*y4 + 1)
*
* Multiplying both sides of each eqn by the denominator, we get
*
* AC = B
*
* where B and C are column vectors
*
* B = [ x1' y1' x2' y2' x3' y3' x4' y4' ]
* C = [ c[0] c[1] c[2] c[3] c[4] c[5] c[6] c[7] ]
*
* and A is the 8x8 matrix
*
* x1 y1 1 0 0 0 -x1*x1' -y1*x1'
* 0 0 0 x1 y1 1 -x1*y1' -y1*y1'
* x2 y2 1 0 0 0 -x2*x2' -y2*x2'
* 0 0 0 x2 y2 1 -x2*y2' -y2*y2'
* x3 y3 1 0 0 0 -x3*x3' -y3*x3'
* 0 0 0 x3 y3 1 -x3*y3' -y3*y3'
* x4 y4 1 0 0 0 -x4*x4' -y4*x4'
* 0 0 0 x4 y4 1 -x4*y4' -y4*y4'
*
* These eight equations are solved here for the coefficients C.
*
* These eight coefficients can then be used to find the mapping
* (x,y) --> (x',y'):
*
* x' = (c[0]x + c[1]y + c[2]) / (c[6]x + c[7]y + 1)
* y' = (c[3]x + c[4]y + c[5]) / (c[6]x + c[7]y + 1)
*
*/
int genImageProjective::computeCoeefficients(void)
{
int retValue = 0;
int i;
float *a[8]; /* 8x8 matrix A */
float *b = this->vc; /* rhs vector of primed coords X'; coeffs returned in vc[] */
b[0] = destArea[0].x();
b[1] = destArea[0].y();
b[2] = destArea[1].x();
b[3] = destArea[1].y();
b[4] = destArea[2].x();
b[5] = destArea[2].y();
b[6] = destArea[3].x();
b[7] = destArea[3].y();
for (i = 0; i < 8; i++)
a[i] = NULL;
for (i = 0; i < 8; i++)
{
if ((a[i] = (float *)calloc(8, sizeof(float))) == NULL)
{
retValue = -100; // ERROR_INT("a[i] not made", procName, 1);
goto Terminate;
}
}
a[0][0] = sourceArea[0].x();
a[0][1] = sourceArea[0].y();
a[0][2] = 1.;
a[0][6] = -sourceArea[0].x() * b[0];
a[0][7] = -sourceArea[0].y() * b[0];
a[1][3] = sourceArea[0].x();
a[1][4] = sourceArea[0].y();
a[1][5] = 1;
a[1][6] = -sourceArea[0].x() * b[1];
a[1][7] = -sourceArea[0].y() * b[1];
a[2][0] = sourceArea[1].x();
a[2][1] = sourceArea[1].y();
a[2][2] = 1.;
a[2][6] = -sourceArea[1].x() * b[2];
a[2][7] = -sourceArea[1].y() * b[2];
a[3][3] = sourceArea[1].x();
a[3][4] = sourceArea[1].y();
a[3][5] = 1;
a[3][6] = -sourceArea[1].x() * b[3];
a[3][7] = -sourceArea[1].y() * b[3];
a[4][0] = sourceArea[2].x();
a[4][1] = sourceArea[2].y();
a[4][2] = 1.;
a[4][6] = -sourceArea[2].x() * b[4];
a[4][7] = -sourceArea[2].y() * b[4];
a[5][3] = sourceArea[2].x();
a[5][4] = sourceArea[2].y();
a[5][5] = 1;
a[5][6] = -sourceArea[2].x() * b[5];
a[5][7] = -sourceArea[2].y() * b[5];
a[6][0] = sourceArea[3].x();
a[6][1] = sourceArea[3].y();
a[6][2] = 1.;
a[6][6] = -sourceArea[3].x() * b[6];
a[6][7] = -sourceArea[3].y() * b[6];
a[7][3] = sourceArea[3].x();
a[7][4] = sourceArea[3].y();
a[7][5] = 1;
a[7][6] = -sourceArea[3].x() * b[7];
a[7][7] = -sourceArea[3].y() * b[7];
retValue = gaussjordan(a, b, 8);
Terminate:
// Clean up
for (i = 0; i < 8; i++)
{
if (a[i])
free(a[i]);
}
this->coefficientsComputed = (retValue == 0);
return retValue;
}
/*-------------------------------------------------------------*
* Gauss-jordan linear equation solver *
*-------------------------------------------------------------*/
/*
* gaussjordan()
*
* Input: a (n x n matrix)
* b (rhs column vector)
* n (dimension)
* Return: 0 if ok, 1 on error
*
* Note side effects:
* (1) the matrix a is transformed to its inverse
* (2) the vector b is transformed to the solution X to the
* linear equation AX = B
*
* Adapted from "Numerical Recipes in C, Second Edition", 1992
* pp. 36-41 (gauss-jordan elimination)
*/
#define SWAP(a,b) {temp = (a); (a) = (b); (b) = temp;}
int genImageProjective::gaussjordan(float **a, float *b, int n)
{
int retValue = 0;
int i, icol=0, irow=0, j, k, l, ll;
int *indexc = NULL, *indexr = NULL, *ipiv = NULL;
float big, dum, pivinv, temp;
if (!a)
{
retValue = -1; // ERROR_INT("a not defined", procName, 1);
goto Terminate;
}
if (!b)
{
retValue = -2; // ERROR_INT("b not defined", procName, 1);
goto Terminate;
}
if ((indexc = (int *)calloc(n, sizeof(int))) == NULL)
{
retValue = -3; // ERROR_INT("indexc not made", procName, 1);
goto Terminate;
}
if ((indexr = (int *)calloc(n, sizeof(int))) == NULL)
{
retValue = -4; // ERROR_INT("indexr not made", procName, 1);
goto Terminate;
}
if ((ipiv = (int *)calloc(n, sizeof(int))) == NULL)
{
retValue = -5; // ERROR_INT("ipiv not made", procName, 1);
goto Terminate;
}
for (i = 0; i < n; i++)
{
big = 0.0;
for (j = 0; j < n; j++)
{
if (ipiv[j] != 1)
{
for (k = 0; k < n; k++)
{
if (ipiv[k] == 0)
{
if (fabs(a[j][k]) >= big)
{
big = fabs(a[j][k]);
irow = j;
icol = k;
}
}
else if (ipiv[k] > 1)
{
retValue = -6; // ERROR_INT("singular matrix", procName, 1);
goto Terminate;
}
}
}
}
++(ipiv[icol]);
if (irow != icol)
{
for (l = 0; l < n; l++)
SWAP(a[irow][l], a[icol][l]);
SWAP(b[irow], b[icol]);
}
indexr[i] = irow;
indexc[i] = icol;
if (a[icol][icol] == 0.0)
{
retValue = -7; // ERROR_INT("singular matrix", procName, 1);
goto Terminate;
}
pivinv = 1.0 / a[icol][icol];
a[icol][icol] = 1.0;
for (l = 0; l < n; l++)
a[icol][l] *= pivinv;
b[icol] *= pivinv;
for (ll = 0; ll < n; ll++)
{
if (ll != icol)
{
dum = a[ll][icol];
a[ll][icol] = 0.0;
for (l = 0; l < n; l++)
a[ll][l] -= a[icol][l] * dum;
b[ll] -= b[icol] * dum;
}
}
}
for (l = n - 1; l >= 0; l--)
{
if (indexr[l] != indexc[l])
{
for (k = 0; k < n; k++)
SWAP(a[k][indexr[l]], a[k][indexc[l]]);
}
}
Terminate:
if (indexr)
free(indexr);
if (indexc)
free(indexc);
if (ipiv)
free(ipiv);
return retValue;
}
// --------------------------------------------------------------
// Map a source point to destination using projective transform
// --------------------------------------------------------------
// Params:
// sourcePoint: initial point
// destPoint: transformed point
// RetValue: 0: Success, !=0: Error
// --------------------------------------------------------------
// Notes:
// 1. You must call once computeCoeefficients() to compute
// the this->vc[] vector of 8 coefficients, before you call
// mapSourceToDestPoint().
// 2. If there was an error or the 8 coefficients were not computed,
// a -1 is returned and destPoint is just set to sourcePoint value.
// --------------------------------------------------------------
int genImageProjective::mapSourceToDestPoint(QPointF& sourcePoint, QPointF& destPoint)
{
if (coefficientsComputed)
{
float factor = 1.0f / (vc[6] * sourcePoint.x() + vc[7] * sourcePoint.y() + 1.);
destPoint.setX( factor * (vc[0] * sourcePoint.x() + vc[1] * sourcePoint.y() + vc[2]) );
destPoint.setY( factor * (vc[3] * sourcePoint.x() + vc[4] * sourcePoint.y() + vc[5]) );
return 0;
}
else // There was an error while computing coefficients
{
destPoint = sourcePoint; // just copy the source to destination...
return -1; // ...and return an error
}
}
//========================================
私のGoogle-FUは、任意の堅実な数学的解決策、私は少しを助けることができたかもしれない。この図を作ることができなかったが。
http://studiochalkboard.evansville.edu/lp-diminish.htmlする
私はそれはおそらく、対数または合計表現のいくつかの並べ替えですが、実際に自分で正しい数学を思い付くことはかなり難しいかもしれないと思います。うまくいけば、そのリンクでの描画との用語は、あなたのために少しより多くの検索可能なものを提供することがあります。
Breton の細分割法 (Mongo の拡張法に関連する) を使用すると、正確な任意の 2 のべき乗の除算が得られます。これらの方法を使用して 2 のべき乗以外の除算に分割するには、サブピクセル間隔まで細分化する必要があり、計算コストが高くなる可能性があります。
ただし、次のバリエーションを適用できるかもしれないと思います。 芳賀の定理 (折り紙では、辺が (N-1) 番目に分割されている場合に、辺を N 番目に分割するために使用されます) をパースペクティブ正方形の分割に適用して、分割を続行することなく、最も近い 2 のべき乗から任意の分割を生成します。
最もエレガントで最速の解決策は、写真座標に矩形座標をマップするホモグラフィ行列を、見つけることになります。
まともな行列ライブラリで、それは限り、あなたはあなたの数学を知っているように、困難な作業ではありません。
キーワード:共線、ホモグラフィ、直接線形変換
しかし、上記の再帰的なアルゴリズムが動作するはずですが、あなたのリソースが限られている場合は、おそらく、射影幾何学は、行くための唯一の方法である。
特殊ケースにあなたが側1および3に垂直に見えるときは、等しい部分にそれらの側面を分割することができます。次に対角線を引き、そして対角線以前の描かれた分割線の各交点を介してサイド1に平行線を描く。
この私が思った幾何学的なソリューションを提供します。私は「アルゴリズム」は名前を持っているかどうかわからない。
あなたが最初の縦線とn個に「四角形」を分割することによって開始したいとします。
の目標は、私たちは、そのようなポイントが存在しない場合、それらに左右のラインが出会うまたはパラレルポイントにそれらを介して線を描画するために使用することができ、トップライン上の点にP1..Pn-1を配置することです。
上部と下部の行は互いに平行である場合、ちょうど等距離コーナー間のトップラインを分割するthooseポイントを配置します。
他の場所のn theeseと左上隅は等距離であり、I
水平線のために、このアナログを行います。
回転面が平面である場合は特に、視点が垂直勾配によって生成され、Y軸周りの回転が与えられます。これらは、視点に徐々に近づきます。代わりに、2の与えられた力を働かせることができる4つの長方形を定義するために対角線を使用するのでは...左と右の2つの長方形を定義します。一つは狭い垂直セグメントに表面を分割し続ける場合、それらは、最終的には、幅の広いよりも高くなるだろう。これは、正方形のない表面を収容することができます。回転はX軸周りである場合には、水平方向の勾配が必要とされている。
私は、選択した答えは利用可能な最善の解決策ではないと思います。よりよい解決策は、MATLABスクリプト及び映像ショーを以下のように単純なグリッドに矩形の視点(射影)変換を適用することです。あなたにもC ++とOpenCVので、このアルゴリズムを実装することができます。
function drawpersgrid
sz = [ 24, 16 ]; % [x y]
srcpt = [ 0 0; sz(1) 0; 0 sz(2); sz(1) sz(2)];
destpt = [ 20 50; 100 60; 0 150; 200 200;];
% make rectangular grid
[X,Y] = meshgrid(0:sz(1),0:sz(2));
% find projective transform matching corner points
tform = maketform('projective',srcpt,destpt);
% apply the projective transform to the grid
[X1,Y1] = tformfwd(tform,X,Y);
hold on;
%% find grid
for i=1:sz(2)
for j=1:sz(1)
x = [ X1(i,j);X1(i,j+1);X1(i+1,j+1);X1(i+1,j);X1(i,j)];
y = [ Y1(i,j);Y1(i,j+1);Y1(i+1,j+1);Y1(i+1,j);Y1(i,j)];
plot(x,y,'b');
end
end
hold off;
問題は、それはあなたになってきた3Dから2Dへの変換であるということです。
何をする必要がある3D(世界)でそれを表現して、2D(画面)にそれをダウンしたプロジェクトです。
これは、次いで、2Dスクリーン空間ベクトルにダウンコンバートすることができる3D均質ベクトルにダウン4D上の突起は、均質ない4D変換行列を使用する必要があろう。
私はどちらかのグーグルでそれを見つけることができませんでしたが、良いコンピュータグラフィックスの本は詳細を持っています。
キーワード射影行列、射影変換、アフィン変換、均質ベクトル、ワールド空間、スクリーン空間、透視変換、3D変換
でありますところで、これは通常、そのすべてを説明するために、いくつかの講義を取ります。だから、幸運ます。