【C#】アフィン変換の相互座標変換

アフィン変換を用いて画像を拡大、縮小、回転などを行ってピクチャボックスへが画像の表示を行うと、逆にピクチャボックス上の座標から、元の画像の座標を知りたくなる場合がありますが、画像の表示をアフィン変換行列を用いて表示すると、意外と簡単に求まります。

 

 

画像上の座標を(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#】アフィン変換を用いて画像ビューアを作ろう!

 

画像処理のためのC#テクニックへ戻る

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください