[英]How to stitch images with very little overlap?
I am trying to create a panorama using images with very little overlap, but I know the angle of the camera so I know exactly how much overlap there is and I know the order of the images so I know where each one belongs in the panorama. 我正在尝试使用几乎没有重叠的图像创建全景图,但我知道相机的角度,所以我确切知道有多少重叠,我知道图像的顺序,所以我知道每个人在全景图中的位置。 As a first pass I simply cocantenated the images together but the result is not good enough.
作为第一遍,我简单地将图像拼凑在一起,但结果不够好。 Is there a way to crop the Bitmaps to trapezoids to eliminate (most of the) the overlap, then stretch the Bitmaps back to Rectangles before the concantenation?
有没有办法将位图裁剪为梯形以消除(大部分)重叠,然后在缩放之前将位图拉回到矩形? I know this will produce distortion during the stretching, and that a trapezoid is just a close approximation to how the Bitmap actually needs to be cropped, but I am hoping this will be good enough.
我知道这会在拉伸过程中产生扭曲,并且梯形只是与Bitmap实际需要裁剪的方式非常接近,但我希望这将足够好。
The technique you're looking for is called Image Registration using an Affine Transform. 您正在寻找的技术称为使用仿射变换进行图像配准 。 This can be achieved in software by computing the matrix transform for image B that maps it on to image A. I assume you are trying to do this in software using Windows Forms & GDI+?
这可以通过计算将映射到图像A的图像B的矩阵变换在软件中实现。我假设您正在尝试使用Windows Forms和GDI +在软件中执行此操作? Then the matrices you have available are 3x3 matrices, capable of Scale, translate, Rotate and Skew.
那么你可用的矩阵是3x3矩阵,能够进行缩放,平移,旋转和偏斜。 This is often enough to create simple image registration and I used this technique successfully in a commercial software package (however was WPF).
这通常足以创建简单的图像注册,并且我在商业软件包中成功使用了这种技术(但是是WPF)。
To achieve Image registration using an Affine Transform, firstly you need a collection of control points in a pair of images to be registered. 要使用仿射变换实现图像配准,首先需要在要注册的一对图像中使用一组控制点。 From this we can compute the 2D transformation to register the images?
从这里我们可以计算2D变换来注册图像? I have done this in WPF, which has a 3x3 Matrix can be defined using the System.Windows.Media.Matrix class, which has the following constructor:
我在WPF中完成了这个,它有一个3x3 Matrix可以使用System.Windows.Media.Matrix类定义,该类具有以下构造函数:
Matrix(double m11, double m12, double m21, double m22,
double offsetX, double offsetY)
Note: GDI+ has a Matrix class whose constructor may differ but the principle is the same 注意:GDI +有一个Matrix类,其构造函数可能不同但原理相同
The constructor arguments form the matrix as follows: 构造函数参数构成矩阵,如下所示:
M11 M12 0 M21 M22 0 OffsetX OffsetY 1
Now if the input points are called X,Y and output U,V, the affine matrix transform, T, that maps X,Y onto U,V can be computed as follows: 现在,如果输入点被称为X,Y和输出U,V,则将X,Y映射到U,V上的仿射矩阵变换T可以如下计算:
U = X * T [U1 V1 1] = [X1 Y1 1] [A B 0] [U2 V2 1] = [X2 Y2 1] * [C D 0] [U3 V3 1] = [X3 Y3 1] [Tx Ty 1]
This can also be simplified as follows: 这也可以简化如下:
U = X * T [U1 V1] = [X1 Y1 1] [A B ] [U2 V2] = [X2 Y2 1] * [C D ] [U3 V3] = [X3 Y3 1] [Tx Ty]
or 要么
X^-1 * U = T [X1 Y1 1]^-1 [U1 V1] [A B ] [X2 Y2 1] * [U2 V2] = [C D ] [X3 Y3 1] [U3 V3] [Tx Ty]
In english what this means is, given a list of points X,Y in image 1 that correspond to image 2, the inverse of the matrix X containing XY points multiplied by the matrix of corresponding points in image 2 gives you your matrix transform from Image 1 to 2 在英语中这意味着, 给定图像1中对应于图像2的点X,Y的列表,包含XY点的矩阵X的倒数乘以图像2中的对应点的矩阵,给出了来自Image的矩阵变换1到2
The output transform T contains A,B,C,D and Tx,Ty which correspond to M11,M12,M21,M22,OffsetX,OffsetY in the 3x3 affine matrix class (constructor above). 输出变换T包含A,B,C,D和Tx,Ty,其对应于3×3仿射矩阵类(上面的构造函数)中的M11,M12,M21,M22,OffsetX,OffsetY。 However, if the X matrix and U matrix have more than 3 points, the solution is overdetermined and a least squares fit must be found.
然而,如果X矩阵和U矩阵具有多于3个点,则解决方案是超定的并且必须找到最小二乘拟合。 This is acheived using the Moores-Penrose Psuedo-inverse to find X^-1.
这是使用Moores-Penrose Psuedo-inverse实现的,以找到X ^ -1。
What does this mean in code? 这在代码中意味着什么? Well I coded my own Matrix3x3, Matrix3x2 classes and Control Point (x,y point) to handle the transformation then applied this to a WPF MatrixTransform on an element.
好吧,我编写了自己的Matrix3x3,Matrix3x2类和控制点(x,y点)来处理转换,然后将其应用于元素上的WPF MatrixTransform。 In GDI+ you can do the same by applying the Matrix to the graphics pipeline before calling Graphics.DrawImage.
在GDI +中,您可以在调用Graphics.DrawImage之前将Matrix应用于图形管道。 Let's see how we can compute the transformation matrix.
让我们看看我们如何计算变换矩阵。
The first class we need is the Matrix3x3 class: 我们需要的第一个类是Matrix3x3类:
public class Matrix3x3 : ICloneable
{
#region Local Variables
private double [] coeffs;
private const int _M11 = 0;
private const int _M12 = 1;
private const int _M13 = 2;
private const int _M21 = 3;
private const int _M22 = 4;
private const int _M23 = 5;
private const int _M31 = 6;
private const int _M32 = 7;
private const int _M33 = 8;
#endregion
#region Construction
/// <summary>
/// Initializes a new instance of the <see cref="Matrix3x3"/> class.
/// </summary>
public Matrix3x3()
{
coeffs = new double[9];
}
/// <summary>
/// Initializes a new instance of the <see cref="Matrix3x3"/> class.
/// </summary>
/// <param name="coefficients">The coefficients to initialise. The number of elements of the array should
/// be equal to 9, else an exception will be thrown</param>
public Matrix3x3(double[] coefficients)
{
if (coefficients.GetLength(0) != 9)
throw new Exception("Matrix3x3.Matrix3x3()", "The number of coefficients passed in to the constructor must be 9");
coeffs = coefficients;
}
/// <summary>
/// Initializes a new instance of the <see cref="Matrix3x3"/> class.
/// </summary>
/// <param name="m11">The M11 coefficient</param>
/// <param name="m12">The M12 coefficien</param>
/// <param name="m13">The M13 coefficien</param>
/// <param name="m21">The M21 coefficien</param>
/// <param name="m22">The M22 coefficien</param>
/// <param name="m23">The M23 coefficien</param>
/// <param name="m31">The M31 coefficien</param>
/// <param name="m32">The M32 coefficien</param>
/// <param name="m33">The M33 coefficien</param>
public Matrix3x3(double m11, double m12, double m13, double m21, double m22, double m23, double m31, double m32, double m33)
{
// The 3x3 matrix is constructed as follows
//
// | M11 M12 M13 |
// | M21 M22 M23 |
// | M31 M32 M33 |
coeffs = new double[] { m11, m12, m13, m21, m22, m23, m31, m32, m33 };
}
/// <summary>
/// Initializes a new instance of the <see cref="Matrix3x3"/> class. The IAffineTransformCoefficients
/// passed in is used to populate coefficients M11, M12, M21, M22, M31, M32. The remaining column (M13, M23, M33)
/// is populated with homogenous values 0 0 1.
/// </summary>
/// <param name="affineMatrix">The IAffineTransformCoefficients used to populate M11, M12, M21, M22, M31, M32</param>
public Matrix3x3(IAffineTransformCoefficients affineTransform)
{
coeffs = new double[] { affineTransform.M11, affineTransform.M12, 0,
affineTransform.M21, affineTransform.M22, 0,
affineTransform.OffsetX, affineTransform.OffsetY, 1};
}
#endregion
#region Public Properties
/// <summary>
/// Gets or sets the M11 coefficient
/// </summary>
/// <value>The M11</value>
public double M11
{
get
{
return coeffs[_M11];
}
set
{
coeffs[_M11] = value;
}
}
/// <summary>
/// Gets or sets the M12 coefficient
/// </summary>
/// <value>The M12</value>
public double M12
{
get
{
return coeffs[_M12];
}
set
{
coeffs[_M12] = value;
}
}
/// <summary>
/// Gets or sets the M13 coefficient
/// </summary>
/// <value>The M13</value>
public double M13
{
get
{
return coeffs[_M13];
}
set
{
coeffs[_M13] = value;
}
}
/// <summary>
/// Gets or sets the M21 coefficient
/// </summary>
/// <value>The M21</value>
public double M21
{
get
{
return coeffs[_M21];
}
set
{
coeffs[_M21] = value;
}
}
/// <summary>
/// Gets or sets the M22 coefficient
/// </summary>
/// <value>The M22</value>
public double M22
{
get
{
return coeffs[_M22];
}
set
{
coeffs[_M22] = value;
}
}
/// <summary>
/// Gets or sets the M23 coefficient
/// </summary>
/// <value>The M23</value>
public double M23
{
get
{
return coeffs[_M23];
}
set
{
coeffs[_M23] = value;
}
}
/// <summary>
/// Gets or sets the M31 coefficient
/// </summary>
/// <value>The M31</value>
public double M31
{
get
{
return coeffs[_M31];
}
set
{
coeffs[_M31] = value;
}
}
/// <summary>
/// Gets or sets the M32 coefficient
/// </summary>
/// <value>The M32</value>
public double M32
{
get
{
return coeffs[_M32];
}
set
{
coeffs[_M32] = value;
}
}
/// <summary>
/// Gets or sets the M33 coefficient
/// </summary>
/// <value>The M33</value>
public double M33
{
get
{
return coeffs[_M33];
}
set
{
coeffs[_M33] = value;
}
}
/// <summary>
/// Gets the determinant of the matrix
/// </summary>
/// <value>The determinant</value>
public double Determinant
{
get
{
// |a b c|
// In general, for a 3X3 matrix |d e f|
// |g h i|
//
// The determinant can be found as follows:
// a(ei-fh) - b(di-fg) + c(dh-eg)
// Get coeffs
double a = coeffs[_M11];
double b = coeffs[_M12];
double c = coeffs[_M13];
double d = coeffs[_M21];
double e = coeffs[_M22];
double f = coeffs[_M23];
double g = coeffs[_M31];
double h = coeffs[_M32];
double i = coeffs[_M33];
double ei = e * i;
double fh = f * h;
double di = d * i;
double fg = f * g;
double dh = d * h;
double eg = e * g;
// Compute the determinant
return (a * (ei - fh)) - (b * (di - fg)) + (c * (dh - eg));
}
}
/// <summary>
/// Gets a value indicating whether this matrix is singular. If it is singular, it cannot be inverted
/// </summary>
/// <value>
/// <c>true</c> if this instance is singular; otherwise, <c>false</c>.
/// </value>
public bool IsSingular
{
get
{
return Determinant == 0;
}
}
/// <summary>
/// Gets the inverse of this matrix. If the matrix is singular, this method will throw an exception
/// </summary>
/// <value>The inverse</value>
public Matrix3x3 Inverse
{
get
{
// Taken from http://everything2.com/index.pl?node_id=1271704
// a b c
//In general, the inverse matrix of a 3X3 matrix d e f
// g h i
//is
// 1 (ei-fh) (bi-ch) (bf-ce)
// ----------------------------- x (fg-di) (ai-cg) (cd-af)
// a(ei-fh) - b(di-fg) + c(dh-eg) (dh-eg) (bg-ah) (ae-bd)
// Get coeffs
double a = coeffs[_M11];
double b = coeffs[_M12];
double c = coeffs[_M13];
double d = coeffs[_M21];
double e = coeffs[_M22];
double f = coeffs[_M23];
double g = coeffs[_M31];
double h = coeffs[_M32];
double i = coeffs[_M33];
//// Compute often used components
double ei = e * i;
double fh = f * h;
double di = d * i;
double fg = f * g;
double dh = d * h;
double eg = e * g;
double bi = b * i;
double ch = c * h;
double ai = a * i;
double cg = c * g;
double cd = c * d;
double bg = b * g;
double ah = a * h;
double ae = a * e;
double bd = b * d;
double bf = b * f;
double ce = c * e;
double cf = c * d;
double af = a * f;
// Construct the matrix using these components
Matrix3x3 tempMat = new Matrix3x3(ei - fh, ch - bi, bf - ce, fg - di, ai - cg, cd - af, dh - eg, bg - ah, ae - bd);
// Compute the determinant
double det = Determinant;
if (det == 0.0)
{
throw new Exception("Matrix3x3.Inverse", "Unable to invert the matrix as it is singular");
}
// Scale the matrix by 1/determinant
tempMat.Scale(1.0 / det);
return tempMat;
}
}
/// <summary>
/// Gets a value indicating whether this matrix is affine. This will be true if the right column
/// (M13, M23, M33) is 0 0 1
/// </summary>
/// <value><c>true</c> if this instance is affine; otherwise, <c>false</c>.</value>
public bool IsAffine
{
get
{
return (coeffs[_M13] == 0 && coeffs[_M23] == 0 && coeffs[_M33] == 1);
}
}
#endregion
#region Public Methods
/// <summary>
/// Multiplies the current matrix by the 3x3 matrix passed in
/// </summary>
/// <param name="rhs"></param>
public void Multiply(Matrix3x3 rhs)
{
// Get coeffs
double a = coeffs[_M11];
double b = coeffs[_M12];
double c = coeffs[_M13];
double d = coeffs[_M21];
double e = coeffs[_M22];
double f = coeffs[_M23];
double g = coeffs[_M31];
double h = coeffs[_M32];
double i = coeffs[_M33];
double j = rhs.M11;
double k = rhs.M12;
double l = rhs.M13;
double m = rhs.M21;
double n = rhs.M22;
double o = rhs.M23;
double p = rhs.M31;
double q = rhs.M32;
double r = rhs.M33;
// Perform multiplication. Formula taken from
// http://www.maths.surrey.ac.uk/explore/emmaspages/option1.html
coeffs[_M11] = a * j + b * m + c * p;
coeffs[_M12] = a * k + b * n + c * q;
coeffs[_M13] = a * l + b * o + c * r;
coeffs[_M21] = d * j + e * m + f * p;
coeffs[_M22] = d * k + e * n + f * q;
coeffs[_M23] = d * l + e * o + f * r;
coeffs[_M31] = g * j + h * m + i * p;
coeffs[_M32] = g * k + h * n + i * q;
coeffs[_M33] = g * l + h * o + i * r;
}
/// <summary>
/// Scales the matrix by the specified scalar value
/// </summary>
/// <param name="scalar">The scalar.</param>
public void Scale(double scalar)
{
coeffs[0] *= scalar;
coeffs[1] *= scalar;
coeffs[2] *= scalar;
coeffs[3] *= scalar;
coeffs[4] *= scalar;
coeffs[5] *= scalar;
coeffs[6] *= scalar;
coeffs[7] *= scalar;
coeffs[8] *= scalar;
}
/// <summary>
/// Makes the matrix an affine matrix by setting the right column (M13, M23, M33) to 0 0 1
/// </summary>
public void MakeAffine()
{
coeffs[_M13] = 0;
coeffs[_M23] = 0;
coeffs[_M33] = 1;
}
#endregion
#region ICloneable Members
/// <summary>
/// Creates a new object that is a copy of the current instance.
/// </summary>
/// <returns>
/// A new object that is a copy of this instance.
/// </returns>
public object Clone()
{
double[] coeffCopy = (double[])coeffs.Clone();
return new Matrix3x3(coeffCopy);
}
#endregion
#region IAffineTransformCoefficients Members
//
// NB: M11, M12, M21, M22 members of IAffineTransformCoefficients are implemented within the
// #region Public Properties directive
//
/// <summary>
/// Gets or sets the Translation Offset in the X Direction
/// </summary>
/// <value>The M31</value>
public double OffsetX
{
get
{
return coeffs[_M31];
}
set
{
coeffs[_M31] = value;
}
}
/// <summary>
/// Gets or sets the Translation Offset in the Y Direction
/// </summary>
/// <value>The M32</value>
public double OffsetY
{
get
{
return coeffs[_M32];
}
set
{
coeffs[_M32] = value;
}
}
#endregion
}
And a Matrix3x2 class 还有一个Matrix3x2类
public class Matrix3x2 : ICloneable
{
#region Local Variables
private double[] coeffs;
private const int _M11 = 0;
private const int _M12 = 1;
private const int _M21 = 2;
private const int _M22 = 3;
private const int _M31 = 4;
private const int _M32 = 5;
#endregion
#region Construction
/// <summary>
/// Initializes a new instance of the <see cref="Matrix3x2"/> class.
/// </summary>
public Matrix3x2()
{
coeffs = new double[6];
}
/// <summary>
/// Initializes a new instance of the <see cref="Matrix3x2"/> class.
/// </summary>
/// <param name="coefficients">The coefficients to initialise. The number of elements of the array should
/// be equal to 6, else an exception will be thrown</param>
public Matrix3x2(double[] coefficients)
{
if (coefficients.GetLength(0) != 6)
throw new Exception("Matrix3x2.Matrix3x2()",
"The number of coefficients passed in to the constructor must be 6");
coeffs = coefficients;
}
public Matrix3x2(double m11, double m12, double m21, double m22, double m31, double m32)
{
coeffs = new double[] { m11, m12, m21, m22, m31, m32 };
}
/// <summary>
/// Initializes a new instance of the <see cref="Matrix3x2"/> class. The IAffineTransformCoefficients
/// passed in is used to populate coefficients M11, M12, M21, M22, M31, M32.
/// </summary>
/// <param name="affineMatrix">The IAffineTransformCoefficients used to populate M11, M12, M21, M22, M31, M32</param>
public Matrix3x2(IAffineTransformCoefficients affineTransform)
{
coeffs = new double[] { affineTransform.M11, affineTransform.M12,
affineTransform.M21, affineTransform.M22,
affineTransform.OffsetX, affineTransform.OffsetY};
}
#endregion
#region Public Properties
/// <summary>
/// Gets or sets the M11 coefficient
/// </summary>
/// <value>The M11</value>
public double M11
{
get
{
return coeffs[_M11];
}
set
{
coeffs[_M11] = value;
}
}
/// <summary>
/// Gets or sets the M12 coefficient
/// </summary>
/// <value>The M12</value>
public double M12
{
get
{
return coeffs[_M12];
}
set
{
coeffs[_M12] = value;
}
}
/// <summary>
/// Gets or sets the M21 coefficient
/// </summary>
/// <value>The M21</value>
public double M21
{
get
{
return coeffs[_M21];
}
set
{
coeffs[_M21] = value;
}
}
/// <summary>
/// Gets or sets the M22 coefficient
/// </summary>
/// <value>The M22</value>
public double M22
{
get
{
return coeffs[_M22];
}
set
{
coeffs[_M22] = value;
}
}
/// <summary>
/// Gets or sets the M31 coefficient
/// </summary>
/// <value>The M31</value>
public double M31
{
get
{
return coeffs[_M31];
}
set
{
coeffs[_M31] = value;
}
}
/// <summary>
/// Gets or sets the M32 coefficient
/// </summary>
/// <value>The M32</value>
public double M32
{
get
{
return coeffs[_M32];
}
set
{
coeffs[_M32] = value;
}
}
#endregion
#region Public Methods
/// <summary>
/// Transforms the the ILocation passed in and returns the result in a new ILocation
/// </summary>
/// <param name="location">The location to transform</param>
/// <returns>The transformed location</returns>
public ILocation Transform(ILocation location)
{
// Perform the following equation:
//
// | x y 1 | | M11 M12 | |(xM11 + yM21 + M31) (xM12 + yM22 + M32)|
// * | M21 M22 | =
// | M31 M32 |
double x = location.X * coeffs[_M11] + location.Y * coeffs[_M21] + coeffs[_M31];
double y = location.X * coeffs[_M12] + location.Y * coeffs[_M22] + coeffs[_M32];
return new Location(x, y);
}
/// <summary>
/// Multiplies the 3x3 matrix passed in with the current 3x2 matrix
/// </summary>
/// <param name="x">The 3x3 Matrix X</param>
public void Multiply(Matrix3x3 lhs)
{
// Multiply the 3x3 matrix with the 3x2 matrix and store inside the current 2x3 matrix
//
// [a b c] [j k] [(aj + bl + cn) (ak + bm + co)]
// [d e f] * [l m] = [(dj + el + fn) (dk + em + fo)]
// [g h i] [n o] [(gj + hl + in) (gk + hm + io)]
// Get coeffs
double a = lhs.M11;
double b = lhs.M12;
double c = lhs.M13;
double d = lhs.M21;
double e = lhs.M22;
double f = lhs.M23;
double g = lhs.M31;
double h = lhs.M32;
double i = lhs.M33;
double j = coeffs[_M11];
double k = coeffs[_M12];
double l = coeffs[_M21];
double m = coeffs[_M22];
double n = coeffs[_M31];
double o = coeffs[_M32];
coeffs[_M11] = a * j + b * l + c * n;
coeffs[_M12] = a * k + b * m + c * o;
coeffs[_M21] = d * j + e * l + f * n;
coeffs[_M22] = d * k + e * m + f * o;
coeffs[_M31] = g * j + h * l + i * n;
coeffs[_M32] = g * k + h * m + i * o;
}
#endregion
#region ICloneable Members
/// <summary>
/// Creates a new object that is a copy of the current instance.
/// </summary>
/// <returns>
/// A new object that is a copy of this instance.
/// </returns>
public object Clone()
{
double[] coeffCopy = (double[])coeffs.Clone();
return new Matrix3x2(coeffCopy);
}
#endregion
#region IAffineTransformCoefficients Members
//
// NB: M11, M12, M21, M22 members of IAffineTransformCoefficients are implemented within the
// #region Public Properties directive
//
/// <summary>
/// Gets or sets the Translation Offset in the X Direction
/// </summary>
/// <value>The M31</value>
public double OffsetX
{
get
{
return coeffs[_M31];
}
set
{
coeffs[_M31] = value;
}
}
/// <summary>
/// Gets or sets the Translation Offset in the Y Direction
/// </summary>
/// <value>The M32</value>
public double OffsetY
{
get
{
return coeffs[_M32];
}
set
{
coeffs[_M32] = value;
}
}
#endregion
}
From these we can perform image registration with a list of points that correspond to the two images. 从这些我们可以使用与两个图像对应的点列表来执行图像配准。 To clarify what this means, lets say your panorama shots have certain features that are the same.
为了澄清这意味着什么,让我们说你的全景照片具有相同的某些功能。 Both have a cathedral spire, both have a tree.
两者都有一座大教堂尖顶,都有一棵树。 The points that register images A to B would be the X,Y locations in each image that correspond, ie: the XY location of the spire in both images would be one pair of points.
记录图像A至B的点将是每个图像中对应的X,Y位置,即:两个图像中的尖顶的XY位置将是一对点。
Now with this list of points we can compute our transform: 现在有了这个点列表,我们可以计算出我们的变换:
public Matrix3x2 ComputeForwardTransform(IList<Point> baselineLocations, IList<Point> registerLocations)
{
if (baselineLocations.Count < 3 || registerLocations.Count < 3)
{
throw new Exception("ComputeForwardTransform()",
"Unable to compute the forward transform. A minimum of 3 control point pairs are required");
}
if (baselineLocations.Count != registerLocations.Count)
{
throw new Exception("ComputeForwardTransform()",
"Unable to compute the forward transform. The number of control point pairs in baseline and registration results must be equal");
}
// To compute
// Transform = ((X^T * X)^-1 * X^T)U = (X^T * X)^-1 (X^T * U)
// X^T * X =
// [ Sum(x_i^2) Sum(x_i*y_i) Sum(x_i) ]
// [ Sum(x_i*y_i) Sum(y_i^2) Sum(y_i) ]
// [ Sum(x_i) Sum(y_i) Sum(1)=n ]
// X^T * U =
// [ Sum(x_i*u_i) Sum(x_i*v_i) ]
// [ Sum(y_i*u_i) Sum(y_i*v_i) ]
// [ Sum(u_i) Sum(v_i) ]
IList<Point> xy = baselineLocations;
IList<Point> uv = registerLocations;
double a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0, h = 0, n = xy.Count;
double p = 0, q = 0, r = 0, s = 0, t = 0, u = 0;
for (int i = 0; i < n; i++)
{
// Compute sum of squares for X^T * X
a += xy[i].X * xy[i].X;
b += xy[i].X * xy[i].Y;
c += xy[i].X;
d += xy[i].X * xy[i].Y;
e += xy[i].Y * xy[i].Y;
f += xy[i].Y;
g += xy[i].X;
h += xy[i].Y;
// Compute sum of squares for X^T * U
p += xy[i].X * uv[i].X;
q += xy[i].X * uv[i].Y;
r += xy[i].Y * uv[i].X;
s += xy[i].Y * uv[i].Y;
t += uv[i].X;
u += uv[i].Y;
}
// Create matrices from the coefficients
Matrix3x2 uMat = new Matrix3x2(p, q, r, s, t, u);
Matrix3x3 xMat = new Matrix3x3(a, b, c, d, e, f, g, h, n);
// Invert X
Matrix3x3 xInv = xMat.Inverse;
// Perform the multiplication to get the transform
uMat.Multiply(xInv);
// Matrix uMat now holds the image registration transform to go from the current result to baseline
return uMat;
}
Finally, the above can be called as follows: 最后,上述内容可以如下调用:
// where xy1, xy2, xy3 are control points in first image, and uv1, uv2, uv3 are // corresponding pairs in the second image Matrix3x2 result = ComputeForwardTransform(new [] {xy1, xy2, xy3}. new [] {uv1, uv2, uv3}); //其中xy1,xy2,xy3是第一个图像中的控制点,uv1,uv2,uv3是//第二个图像中的对应对象Matrix3x2 result = ComputeForwardTransform(new [] {xy1,xy2,xy3} .new [] { uv1,uv2,uv3});
Anyway, I hope this is helpful to you. 无论如何,我希望这对你有所帮助。 I realise its not GDI+ specific but does discuss how to register images using 3x3 transforms in detail, which can be used both in GDI+ and WPF.
我意识到它不是GDI +特定的,但确实讨论了如何使用3x3变换详细注册图像,这可以在GDI +和WPF中使用。 I actually have a code example deep down somewhere on my hard drive and would be happy to talk more if you need clarification on the above.
我实际上在我的硬盘驱动器的某个地方有一个代码示例,如果你需要澄清上面的内容,我会很乐意谈谈。
Below: Demo showing stiched images 下图:演示显示图像
What you want is referred to as a matrix transformation. 你想要什么被称为矩阵变换。
Here are some simple examples in C#/GDI+. 以下是C#/ GDI +中的一些简单示例。
MSDN has some more in-depth descriptions. MSDN有一些更深入的描述。
I believe in the end you will be looking for a "Perspective Transformation", here is an SO question about that that might lead you down the right path. 我相信最终你会寻找一个“透视转型”, 这是一个关于那个可能引导你走上正确道路的问题。
I'm not worried about the bounty, this is a complex (and fun) topic and I don't have the time to work out a solution, I just hope this information is helpful. 我并不担心赏金,这是一个复杂(有趣)的话题,我没有时间制定解决方案,我只希望这些信息有用。 :)
:)
请参阅使用Accord.NET自动图像拼接 ,以及演示和源代码。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.