C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[4]: 【C#】画像を台形にしたい(アフィン変換?)


(過去ログ 90 を表示中)

[トピック内 6 記事 (1 - 6 表示)]  << 0 >>

■53977 / inTopicNo.1)  【C#】画像を台形にしたい(アフィン変換?)
  
□投稿者/ どらごら (1回)-(2010/10/01(Fri) 13:33:53)

分類:[C#] 

★行いたいこと
2D画像を疑似3D(台形)にしたい

同じような質問は多々ありますが、質問者のイメージが伝わっていないのか、
解決しているのが見当たらないため質問のイメージを用意し質問します。

調べた限り「アフィン変換」というキーワードは多々出てきて、
実際C#で実装されているクラスも見つけましたが、サンプルを見ると平行四辺形になっています。
http://thorshammer.blog95.fc2.com/blog-entry-250.html

「線分BD < 線分AC」にしたいため、∠ACDと∠BDCは同じではありません。

1.90度回転(線分ABを上底、線分CDを下底)
2.台形に変換(頂点移動?)
3.−90度回転
すれば良いのだろうとは思うのですが、2の部分の実装方法がわかりません。
よろしくお願いします。

★調べたこと
「画像」「台形」「アフィン変換」などのキーワードでGoogleで検索

★イメージ(カツァクリアップローダ3)
http://www.katsakuri.sakura.ne.jp/src/up47701.png.html

★環境
VS2008(CLR2.0) C#

引用返信 編集キー/
■53981 / inTopicNo.2)  Re[1]: 【C#】画像を台形にしたい(アフィン変換?)
□投稿者/ ごう (187回)-(2010/10/01(Fri) 14:10:57)
No53977 (どらごら さん) に返信

※直接の回答ではありません。

C++で書かれたライブラリですが、「OpenCV」というライブラリに
希望のものと近いのでは、と思われる関数が用意されています。

URL中の「画像の透視投影変換 cvGetPerspectiveTransform + cvWarpPerspective」を参照
http://opencv.jp/sample/sampling_and_geometricaltransforms.html

OpenCVのDLLを活用してもいいですし、OpenCVのライブラリ自体ソースコードが公開されていますので
cvGetPerspectiveTransform関数やcvWarpPerspective関数でどのような処理が行われているか、
参考にされてみてはいかがでしょうか。
引用返信 編集キー/
■54002 / inTopicNo.3)  Re[1]: 【C#】画像を台形にしたい(アフィン変換?)
□投稿者/ すなふきぬ (57回)-(2010/10/01(Fri) 23:31:02)
No53977 (どらごら さん) に返信
> ★行いたいこと
> 2D画像を疑似3D(台形)にしたい
> 1.90度回転(線分ABを上底、線分CDを下底)
> 2.台形に変換(頂点移動?)
> 3.−90度回転
> すれば良いのだろうとは思うのですが、2の部分の実装方法がわかりません。
> よろしくお願いします。

台形に限らず、矩形を自由なアフィン変換を行うには共一次変換もしくは擬似アフィン変換を行ってあげると良いかもしれません。
ただし、このアルゴリズムは辺が1次式にならないため辺が曲線になる特徴があります。

直線で変形を行う場合は、座標から変換後の座標の比率をベクトルを用いて求めてあげればいいみたいです。
詳しい変換方法や理論は、

ISBN 978-4-7973-4437-0
「詳細 画像処理プログラミング」

を参考にすれば、ソースも交えて解説してあるので、勉強になると思います。
引用返信 編集キー/
■54004 / inTopicNo.4)  Re[2]: 【C#】画像を台形にしたい(アフィン変換?)
□投稿者/ れい (959回)-(2010/10/02(Sat) 01:38:02)
No54002 (すなふきぬ さん) に返信
> 台形に限らず、矩形を自由なアフィン変換を行うには共一次変換もしくは擬似アフィン変換を行ってあげると良いかもしれません。
> ただし、このアルゴリズムは辺が1次式にならないため辺が曲線になる特徴があります。

いやいや。
極めて当たり前な話ですが、
自由にアフィン変換を行うにはアフィン変換をすればよく、疑似アフィン変換をする必要はありません。

アフィン変換は線形変換と平行移動の2種でできていまして、一般に表すと、
x,bをベクトル、Aを行列として

x -> Ax + b

のような変換となります。
また、「回転」も線形変換ですので、

> 1.90度回転(線分ABを上底、線分CDを下底)
> 2.台形に変換(頂点移動?)
> 3.−90度回転

これらは一つのアフィン変換で表せます。
アフィン変換のやり方はいろいろあり、すべてを説明するのはここでは無理です。
最も簡単に思いつく2例をあげると、
変換先が変換元より十分に小さい場合は
        For y As Integer = 0 To image.Height - 1
            For x As Integer = 0 To image.Width - 1
                Dim new_x As Integer
                Dim new_y As Integer
                new_x = x * a_a + y * a_b + b_x
                new_y = x * a_c + y * a_d + b_y

                newimage.SetPixel(new_x, new_y, image.GetPixel(x, y))

            Next
        Next
のように、元の式に忠実なプログラムでも変換することができます。
「元画像を一点一点アフィン変換して変換後の画像を埋めていく」
という作業になります。

これだと変換後の画像が大きい場合は埋まらない点が歯抜けになってしまいますので、
逆変換を使って
        For new_y As Integer = 0 To newimage.Height - 1
            For new_x As Integer = 0 To newimage.Width - 1
                Dim org_x As Integer
                Dim org_y As Integer
                org_x = ((new_x - b_x) * a_d - (new_y - b_y) * a_b) / (a_a * a_d - a_b * a_c)
                org_y = (-(new_x - b_x) * a_c + (new_y - b_y) * a_a) / (a_a * a_d - a_b * a_c)

                newimage.SetPixel(new_x, new_y, image.GetPixel(org_x, org_y))
            Next
        Next
のようにすることもできます。
これは「変換後の画像について、一点一点逆アフィン変換して元画像上での色を探していく」
というプログラムになります。

台形にするのか平行四辺形にするのか回転するのか拡大縮小するのか、といった情報は
パラメーターA(a_a,a_b,a_c,a_d)とb(b_x,b_y)に入っています。

実際いろいろやってみてもいいですし、線形代数を勉強してもいいですが…
元画像の四隅を、変換後の画像のどこに映したいのか、というのを考えて、
連立方程式を立てると計算できます。

式がやたらめんどくさいのでここには書けませんが…。

疑似アフィン変換が入ると非線形項が入るのでかなり計算が大変になります。
掲示板で書くのはちょっとめんどくさい。
ここではなく本で学んだ方がいいと思います。

それと。
> 「線分BD < 線分AC」にしたいため、∠ACDと∠BDCは同じではありません。
中学校で習うと思いますが、
四角形などの多角形を表現するとき、頂点は左回りもしくは右回りで記号を振る慣例になっています。
混乱を招きますので、特に問題がないのであれば慣例に従うようにしたほうがいいでしょう。

引用返信 編集キー/
■54010 / inTopicNo.5)  Re[3]: 【C#】画像を台形にしたい(アフィン変換?)
□投稿者/ どらごら (2回)-(2010/10/02(Sat) 11:49:43)
みなさん返信ありがとうございます。

■No53981 (ごう さん) に返信
「OpenCV」調べてきました。
インテルが作ってるんですね。
おまけに.NET用ラッパーもあるらしいのでよさそう。

■No54002 (すなふきぬ さん) に返信
「アフィン変換」でwikipediaやhttp://www.teu.ac.jp/clab/kondo/research/cadcgtext/Chap5/Chap502.html
を流し読みしましたが、まったく理解できませんでした。すみません。

■No54004 (れい さん) に返信
サンプルコードありがとうございます。
歯抜けは出るか出ないかわかりませんが、変換後座標から元画像の色を探すなら
確かに歯抜けは出ないと思うので、パラメータに色々渡して試してみます。
        For new_y As Integer = 0 To newimage.Height - 1
            For new_x As Integer = 0 To newimage.Width - 1
                Dim org_x As Integer
                Dim org_y As Integer
                org_x = ((new_x - b_x) * a_d - (new_y - b_y) * a_b) / (a_a * a_d - a_b * a_c)
                org_y = (-(new_x - b_x) * a_c + (new_y - b_y) * a_a) / (a_a * a_d - a_b * a_c)

                newimage.SetPixel(new_x, new_y, image.GetPixel(org_x, org_y))
            Next
        Next

> > 「線分BD < 線分AC」にしたいため、∠ACDと∠BDCは同じではありません。
> 中学校で習うと思いますが、
> 四角形などの多角形を表現するとき、頂点は左回りもしくは右回りで記号を振る慣例になっています。
> 混乱を招きますので、特に問題がないのであれば慣例に従うようにしたほうがいいでしょう。

ごめんなさい。掲示板に書き込む時点で気づいたのですが、イメージ画像を作る時点で
何も考えず頂点を書き込んでしまったので、こうなってしまいました。

ひとまず、すなふきぬさんのサンプルコードとOpenCvSharpで試してみようと思います。

引用返信 編集キー/
■54027 / inTopicNo.6)  Re[4]: 【C#】画像を台形にしたい(アフィン変換?)
□投稿者/ どらごら (3回)-(2010/10/03(Sun) 01:10:26)
れいさんのサンプルを色々試したのですが、うまくいきませんでした。
            int src_x = 0;
            int src_y = 0;
            try
            {
                int a_a = int.Parse(txtAA.Text);
                int a_b = int.Parse(txtAB.Text);
                int a_c = int.Parse(txtAC.Text);
                int a_d = int.Parse(txtAD.Text);
                int b_x = int.Parse(txtBX.Text);
                int b_y = int.Parse(txtBY.Text);
                Bitmap src_bmp = (Bitmap)picSrc.Image;
                Bitmap new_bmp = (Bitmap)picNew.Image;

                {
                    for (int new_y = 0; new_y < picNew.Height - 1; new_y++)
                    {
                        for (int new_x = 0; new_x < picNew.Width - 1; new_x++)
                        {

                            if (new_bmp.GetPixel(new_x, new_y).ToArgb() != Color.Red.ToArgb())
                                continue;

                            int div_x = (a_a * a_d - a_b * a_c);
                            int div_y = (a_a * a_d - a_b * a_c);

                            if (div_x == 0 || div_y == 0)
                                continue;

                            src_x = ((new_x - b_x) * a_d - (new_y - b_y) * a_b) / div_x;
                            src_y = (-(new_x - b_x) * a_c + (new_y - b_y) * a_a) / div_y;

                            if (src_x < 0 || src_y < 0)
                                continue;

                            new_bmp.SetPixel(new_x, new_y, src_bmp.GetPixel(src_x, src_y));
                        }
                    }
                    picNew.Image = new_bmp;
                }

            }
            catch (Exception ex)
            {
                MessageBox.Show("(" + src_x + "," + src_y + ")\n\n" + ex.Message);
            }

そこで「OpenCvSharp」でごうさん紹介のサンプルを移植したら、簡単にできました。
#ライブラリの準備に時間掛かりましたが、移植は簡単でした。

            CvPoint2D32f[] src_pnt = new CvPoint2D32f[4];
            CvPoint2D32f[] dst_pnt = new CvPoint2D32f[4];
            IplImage src_img = null;
            IplImage dst_img = null;
            CvMat map_matrix = null;

            try
            {
                // (1)画像の読み込み,出力用画像領域の確保を行なう
                Bitmap bmp = (Bitmap)picSrc.Image;
                src_img = BitmapConverter.ToIplImage(bmp);
                dst_img = Cv.CloneImage(src_img);

                // (2)四角形の変換前と変換後の対応する頂点をそれぞれセットし
                src_pnt[0] = new CvPoint2D32f(src_img.Width - 1, 0);
                src_pnt[1] = new CvPoint2D32f(src_img.Width - 1, src_img.Height - 1);
                src_pnt[2] = new CvPoint2D32f(0, src_img.Height - 1);
                src_pnt[3] = new CvPoint2D32f(0, 0);

                dst_pnt[0] = new CvPoint2D32f(points[0].X, points[0].Y);
                dst_pnt[1] = new CvPoint2D32f(points[1].X, points[1].Y);
                dst_pnt[2] = new CvPoint2D32f(points[2].X, points[2].Y);
                dst_pnt[3] = new CvPoint2D32f(points[3].X, points[3].Y);

                map_matrix = new CvMat(3, 3, MatrixType.F32C1);
                map_matrix = Cv.GetPerspectiveTransform(src_pnt, dst_pnt);

                // (3)指定された透視投影変換行列により,cvWarpPerspectiveを用いて画像を変換させる
                Cv.WarpPerspective(src_img, dst_img, map_matrix, 
                    Interpolation.Linear | Interpolation.FillOutliers, Cv.ScalarAll(100));


                // (4)結果を表示する
                picNew.Image = (dst_img.ToBitmap());
            }
            finally
            {
                if(src_img!= null)
                    Cv.ReleaseImage(src_img);
                if (dst_img != null)
                    Cv.ReleaseImage(dst_img);
                if (map_matrix != null)
                    Cv.ReleaseMat(map_matrix);
            }

ありがとうございました。
テストApp
http://www.katsakuri.sakura.ne.jp/src/up47733.png.html

解決済み
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -