■ サンプル
* 以下で実装してみる + 補正方法 : 擬似2次元アフィン変換 + 補間方法 : 最近傍補間(ニアレストネイバー) * ベースは、以下の関連記事の2次元アフィン変換と変わらない => 変わるのは、係数の数と等式のみ。https://blogs.yahoo.co.jp/dk521123/38069294.html
* 等式については、以下のサイトを参照http://www.civil.kumamoto-u.ac.jp/matsu/geome.pdf
Form1.cs
using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; namespace SampleForm { public partial class Form1 : Form { private const int DataNumberForPseudoAffineCoefficients = 8; private Bitmap originalBitmap = null; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { // 画像ファイルのImageオブジェクトを作成する this.originalBitmap = new Bitmap(@"C:\temp\20161215052204.gif"); this.pictureBox1.Image = this.originalBitmap; } private void button1_Click(object sender, EventArgs e) { var inputs = new List<PointF>() { new PointF(0, 0), new PointF(this.originalBitmap.Width, this.originalBitmap.Height), new PointF(0, this.originalBitmap.Height), new PointF(this.originalBitmap.Width, 0), }; var outputs = new List<PointF>() { new PointF(0, 0), new PointF((float)(this.originalBitmap.Width * 1.5), (float)(this.originalBitmap.Height * 1.5)), new PointF(0, (float)(this.originalBitmap.Height * 1.5)), new PointF((float)(this.originalBitmap.Width * 1.5), 0), }; var pseudoAffineCoefficients = CalculatePseudoAffineCoefficients(inputs, outputs); if (pseudoAffineCoefficients == null) { MessageBox.Show("Error...", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } // 逆変換 var transferedBitmap = new Bitmap(this.originalBitmap.Width, this.originalBitmap.Height); for (int x = 0; x < this.originalBitmap.Width; ++x) { for (int y = 0; y < this.originalBitmap.Height; ++y) { Point correctedPoint = GetCorrectPointByPseudoAffineTransfer(pseudoAffineCoefficients, x, y); Color color; if (correctedPoint.X >= 0 && correctedPoint.X < this.originalBitmap.Width && correctedPoint.Y >= 0 && correctedPoint.Y < this.originalBitmap.Height) { color = this.originalBitmap.GetPixel(correctedPoint.X, correctedPoint.Y); } else { color = Color.Black; } transferedBitmap.SetPixel(x, y, color); } } this.pictureBox2.Image = transferedBitmap; } /// <summary> /// 擬似アフィン変換係数を求める /// </summary> /// <param name="observedPoints">観測点</param> /// <param name="transferedPoints">変換点</param> /// <returns></returns> private double[] CalculatePseudoAffineCoefficients( List<PointF> observedPoints, List<PointF> transferedPoints) { if (observedPoints.Count < 4 || observedPoints.Count != transferedPoints.Count) { // 4点未満の際は、計算不可なので、nullを返す return null; } var matrixValues = new double[DataNumberForPseudoAffineCoefficients / 2][] { new double[] { 0.0, 0.0, 0.0, 0.0 }, new double[] { 0.0, 0.0, 0.0, 0.0 }, new double[] { 0.0, 0.0, 0.0, 0.0 }, new double[] { 0.0, 0.0, 0.0, 0.0 } }; var vectors = new double[DataNumberForPseudoAffineCoefficients] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; int index = 0; foreach (var observedPoint in observedPoints) { var observedX = (double)observedPoint.X; var observedY = (double)observedPoint.Y; var transferedX = (double)transferedPoints[index].X; var transferedY = (double)transferedPoints[index].Y; var xy = observedX * observedY; var x2 = observedX * observedX; var y2 = observedY * observedY; var x2y = x2 * observedY; var xy2 = observedX * y2; var x2y2 = x2 * y2; matrixValues[0][0] += 1; matrixValues[0][1] += observedX; matrixValues[0][2] += observedY; matrixValues[0][3] += xy; matrixValues[1][0] += observedX; matrixValues[1][1] += x2; matrixValues[1][2] += xy; matrixValues[1][3] += x2y; matrixValues[2][0] += observedY; matrixValues[2][1] += xy; matrixValues[2][2] += y2; matrixValues[2][3] += xy2; matrixValues[3][0] += xy; matrixValues[3][1] += x2y; matrixValues[3][2] += xy2; matrixValues[3][3] += x2y2; vectors[0] += transferedX; vectors[1] += transferedX * observedX; vectors[2] += transferedX * observedY; vectors[3] += transferedX * observedX * observedY; vectors[4] += transferedY; vectors[5] += transferedY * observedX; vectors[6] += transferedY * observedY; vectors[7] += transferedY * observedX * observedY; index++; } // 逆行列を計算する if (!CalculateInverseMatrix(matrixValues, out double[][] inverseMatrixValues)) { // 逆行列算出に失敗したため、null return null; } var pseudoAffineCoefficients = new double[DataNumberForPseudoAffineCoefficients]; pseudoAffineCoefficients[0] = inverseMatrixValues[0][0] * vectors[0] + inverseMatrixValues[0][1] * vectors[1] + inverseMatrixValues[0][2] * vectors[2] + inverseMatrixValues[0][3] * vectors[3]; pseudoAffineCoefficients[1] = inverseMatrixValues[1][0] * vectors[0] + inverseMatrixValues[1][1] * vectors[1] + inverseMatrixValues[1][2] * vectors[2] + inverseMatrixValues[1][3] * vectors[3]; pseudoAffineCoefficients[2] = inverseMatrixValues[2][0] * vectors[0] + inverseMatrixValues[2][1] * vectors[1] + inverseMatrixValues[2][2] * vectors[2] + inverseMatrixValues[2][3] * vectors[3]; pseudoAffineCoefficients[3] = inverseMatrixValues[3][0] * vectors[0] + inverseMatrixValues[3][1] * vectors[1] + inverseMatrixValues[3][2] * vectors[2] + inverseMatrixValues[3][3] * vectors[3]; pseudoAffineCoefficients[4] = inverseMatrixValues[0][0] * vectors[4] + inverseMatrixValues[0][1] * vectors[5] + inverseMatrixValues[0][2] * vectors[6] + inverseMatrixValues[0][3] * vectors[7]; pseudoAffineCoefficients[5] = inverseMatrixValues[1][0] * vectors[4] + inverseMatrixValues[1][1] * vectors[5] + inverseMatrixValues[1][2] * vectors[6] + inverseMatrixValues[1][3] * vectors[7]; pseudoAffineCoefficients[6] = inverseMatrixValues[2][0] * vectors[4] + inverseMatrixValues[2][1] * vectors[5] + inverseMatrixValues[2][2] * vectors[6] + inverseMatrixValues[2][3] * vectors[7]; pseudoAffineCoefficients[7] = inverseMatrixValues[3][0] * vectors[4] + inverseMatrixValues[3][1] * vectors[5] + inverseMatrixValues[3][2] * vectors[6] + inverseMatrixValues[3][3] * vectors[7]; return pseudoAffineCoefficients; } // 逆行列算出。詳細は、以下の関連記事を参照 // https://blogs.yahoo.co.jp/dk521123/38068366.html private static bool CalculateInverseMatrix( double[][] targetMatrixes, out double[][] outInverseMatrixes) { var dataNumber = targetMatrixes.GetLength(0); outInverseMatrixes = new double[dataNumber][]; //単位行列を作る for (int i = 0; i < dataNumber; i++) { outInverseMatrixes[i] = new double[dataNumber]; for (int j = 0; j < dataNumber; j++) { outInverseMatrixes[i][j] = (i == j) ? 1.0f : 0.0f; } } // 掃き出し法 for (int i = 0; i < dataNumber; i++) { if (targetMatrixes[i][i] == 0) { return false; } var tempValue = 1.0 / targetMatrixes[i][i]; for (int j = 0; j < dataNumber; j++) { targetMatrixes[i][j] *= tempValue; outInverseMatrixes[i][j] *= tempValue; } for (int j = 0; j < dataNumber; j++) { if (i != j) { tempValue = targetMatrixes[j][i]; for (int k = 0; k < dataNumber; k++) { targetMatrixes[j][k] -= targetMatrixes[i][k] * tempValue; outInverseMatrixes[j][k] -= outInverseMatrixes[i][k] * tempValue; } } } } return true; } /// <summary> /// 擬似アフィン変換による補正座標を返す /// </summary> /// <param name="pseudoAffineCoefficients">擬似アフィン係数</param> /// <param name="targetPoint">対象座標</param> /// <returns>擬似アフィン変換による補正座標</returns> private Point GetCorrectPointByPseudoAffineTransfer( double[] pseudoAffineCoefficients, int x, int y) { // u = a0xy + a1x + a2y + a4 var correctedX = pseudoAffineCoefficients[0] * x * y + pseudoAffineCoefficients[1] * x + pseudoAffineCoefficients[2] * y + pseudoAffineCoefficients[3]; // v = b0xy + b1x + b2y + b4 var correctedY = pseudoAffineCoefficients[4] * x * y + pseudoAffineCoefficients[5] * x + pseudoAffineCoefficients[6] * y + pseudoAffineCoefficients[7]; // 座標にするので四捨五入 return new Point( (int)Math.Round(correctedX, MidpointRounding.AwayFromZero), (int)Math.Round(correctedY, MidpointRounding.AwayFromZero)); } } }