アフィン変換を用いて画像を拡大、縮小、回転などを行ってピクチャボックスへが画像の表示を行うと、逆にピクチャボックス上の座標から、元の画像の座標を知りたくなる場合がありますが、画像の表示をアフィン変換行列を用いて表示すると、意外と簡単に求まります。
画像上の座標を(x y)、コントロール上の座標を(x’ y’)、アフィン変換の行列をMatとすると
となります。 ※マイクロソフト仕様の表示です。
上記の式の右側からアフィン変換の行列の逆行列を掛けると
となり、元の座標(元画像上の座標)を求める事ができます。
これをC#でプログラムすると、
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
// アフィン変換行列
System.Drawing.Drawing2D.Matrix _matAffine;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
// アフィン変換行列
_matAffine = new System.Drawing.Drawing2D.Matrix();
// 30°回転
_matAffine.Rotate(30, System.Drawing.Drawing2D.MatrixOrder.Append);
// 20倍
_matAffine.Scale(20.0f, 20.0f, System.Drawing.Drawing2D.MatrixOrder.Append);
// 平行移動
_matAffine.Translate(-4000f, -2300f, System.Drawing.Drawing2D.MatrixOrder.Append);
pictureBox1.Image = new Bitmap(pictureBox1.Width, pictureBox1.Height);
using (var bmp = new Bitmap(@"Mandrill.bmp"))
using (var g = Graphics.FromImage(pictureBox1.Image))
{
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
DrawImageLocal(g, _matAffine, bmp);
}
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
// マウスポインタの座標(コントロールの座標)
lblSrc.Text = String.Format("(x', y') = ({0}, {1})", e.X, e.Y);
using (var matInvert = _matAffine.Clone())
{
// アフィン変換行列の逆行列を求める
matInvert.Invert();
// コントロールの座標
var points = new PointF[]
{
new PointF(e.X, e.Y)
};
// 元の座標(画像上の座標)を求める
matInvert.TransformPoints(points);
lblDst.Text = String.Format("(x, y) = ({0:#.#}, {1:0.#})", points[0].X, points[0].Y);
}
}
/// <summary>
/// アフィン変換行列に基づき画像を表示する
/// </summary>
/// <param name="g">グラフィックスオブジェクト</param>
/// <param name="mat">アフィン変換行列</param>
/// <param name="bmp">表示するBitmapオブジェクト</param>
private void DrawImageLocal(
Graphics g,
System.Drawing.Drawing2D.Matrix mat,
Bitmap bmp)
{
if (bmp == null) return;
// 描画元の領域
var srcRect = new RectangleF(-0.5f, -0.5f, bmp.Width, bmp.Height);
// 描画先の座標(描画元に合わせる、左上、右上、左下の順)
var points = new PointF[]
{
new PointF(srcRect.Left, srcRect.Top),
new PointF(srcRect.Right, srcRect.Top),
new PointF(srcRect.Left, srcRect.Bottom),
};
// 描画先の座標をアフィン変換で求める(変換後の座標は上書きされる)
mat.TransformPoints(points);
// 描画
g.DrawImage(
bmp,
points,
srcRect,
GraphicsUnit.Pixel
);
}
}
}
(実行結果)
行列に慣れていないと、アフィン変換って、なんか面倒くさい!!
って言われる事もあるのですが、上記のような 画像座標 ⇔ コントロール座標 の相互変換を行う場合は、アフィン変換を用いると、いかに簡単に求まるか?を実感して頂けたでしょうか?
より詳細なサンプルプログラムはこちらのページ↓で公開しています。
←画像処理のためのC#テクニックへ戻る