OpenCVSharpの公式サイトに記載されているので、ご存じかもしれませんが。
< デバッグ中にIplImageの中を確認する方法をメモしておきます。

DebuggerVisualizer - opencvsharp http://code.google.com/p/opencvsharp/wiki/DebuggerVisualizer

導入方法

OpenCVSharpの中に含まれているdllの中に

OpenCvSharp.DebuggerVisualizers.dll

があります。
それを、My DocumentのVisual StudioのVisualizersというフォルダにコピーします。

VistaでVisual Studio2010の例ですと。
C:\Users\XXX\Documents\Visual Studio 2010\Visualizers
になります。


使い方

デバッグ中にIplImageの変数にフォーカスを当てると、虫メガネのマークが表示されるので
それをクリックすると現在の状況の画像が表示されます。

以前、Matlab(Octave)で実装した。
位相限定相関法(POC)




位相画像をOpenCVSharpで実装した
位相画像をC#とOpenCVSharpで実装

の続きです。

ソース

ソースはクラスもきちんと作れていないし、汚いソースなので紙面の都合上、肝の部分だけ抜粋します。

あとバグがありそうですので・・・見つけたらオシエテクダサイ。

// DFT用の最適サイズを計算し,そのサイズを返却する。
public static int GetOptimalDFTSize(int size)
{
    return Cv.GetOptimalDFTSize(size);
}

public static CvMat dft(IplImage img)
{
    IplImage reimg = Cv.CreateImage(img.Size, BitDepth.F64, 1);
    IplImage imimg = Cv.CreateImage(img.Size, BitDepth.F64, 1);
    IplImage cmpimg = Cv.CreateImage(img.Size, BitDepth.F64, 2);

    // (1)入力画像を実数配列にコピーし,虚数配列とマージして複素数平面を構成
    Cv.Scale(img, reimg, 1.0, 0.0);
    Cv.Zero(imimg);
    Cv.Merge(reimg, imimg, null, null, cmpimg);

    // (2)DFT用の最適サイズを計算し,そのサイズで行列を確保する
    int dft_M = GetOptimalDFTSize(img.Height);
    int dft_N = GetOptimalDFTSize(img.Width);

    CvMat dft_A = Cv.CreateMat(dft_M, dft_N, MatrixType.F64C2);

    // (3)複素数平面をdft_Aにコピーし,残りの行列右側部分を0で埋める
    CvMat tmp;
    Cv.GetSubRect(dft_A, out tmp, new CvRect(0, 0, img.Width, img.Height));
    Cv.Copy(cmpimg, tmp, null);
    if (dft_A.Cols > img.Width)
    {
        Cv.GetSubRect(dft_A, out tmp, new CvRect(img.Width, 0, dft_A.Cols - img.Width, img.Height));
        Cv.Zero(tmp);
    }

    // (4)離散フーリエ変換を行う
    Cv.DFT(dft_A, dft_A, DFTFlag.Forward, cmpimg.Height);
    //Cv.Split(dft_A, image_Re, image_Im, null, null);

    return dft_A;
}

public static IplImage idft(CvMat dft)
{
    // DFT用の最適サイズを計算し,そのサイズで行列を確保する
    int dft_M = GetOptimalDFTSize(dft.Height);
    int dft_N = GetOptimalDFTSize(dft.Width);
    IplImage img_Re = Cv.CreateImage(new CvSize(dft_N, dft_M), BitDepth.F64, 1);
    IplImage img_Im = Cv.CreateImage(new CvSize(dft_N, dft_M), BitDepth.F64, 1);

    // 逆変換
    Cv.DFT(dft, dft, DFTFlag.Inverse, dft_N);

    // 逆変換した画像を表示用の変数に格納
    Cv.Split(dft, img_Re, img_Im, null, null);

    return img_Re;
}

public static CvMat Conj(CvMat dft)
{
    int dft_M = Image.GetOptimalDFTSize(dft.Height);
    int dft_N = Image.GetOptimalDFTSize(dft.Width);

    using (CvMat srcRe = Cv.CreateMat(dft_M, dft_N, MatrixType.F64C1))
    using (CvMat srcIm = Cv.CreateMat(dft_M, dft_N, MatrixType.F64C1))
    {
        // 実部と虚部に分解
        Cv.Split(dft, srcRe, srcIm, null, null);
        for (int i = 0; i < dft_M; i++)
        {
            for (int j = 0; j < dft_N; j++)
            {
                srcIm[i, j] = -1 * srcIm[i, j];
            }
        }
        // 実部・虚部で計算した値をマージする
        Cv.Merge(srcRe, srcIm, null, null, dft);
    }
    return dft;
}

public static IplImage PhaseOnlyCorrelation(IplImage img1, IplImage img2)
{
    int dft_M = Image.GetOptimalDFTSize(img1.Height);
    int dft_N = Image.GetOptimalDFTSize(img1.Width);

    IplImage img = Cv.CreateImage(new CvSize(dft_M, dft_N), BitDepth.F64, 1);
    CvMat dft1 = Cv.CreateMat(dft_M, dft_N, MatrixType.F64C2);
    CvMat dft2 = Cv.CreateMat(dft_M, dft_N, MatrixType.F64C2);
    CvMat result = Cv.CreateMat(dft_M, dft_N, MatrixType.F64C2);

    // フーリエ変換
    dft1 = Image.dft(img1);
    // フーリエ変換後に複素共役にする
    dft2 = Image.Conj(Image.dft(img2));

    using (IplImage srcRe1 = Cv.CreateImage(new CvSize(dft_N, dft_M), BitDepth.F64, 1))
    using (IplImage srcIm1 = Cv.CreateImage(new CvSize(dft_N, dft_M), BitDepth.F64, 1))
    using (IplImage srcRe2 = Cv.CreateImage(new CvSize(dft_N, dft_M), BitDepth.F64, 1))
    using (IplImage srcIm2 = Cv.CreateImage(new CvSize(dft_N, dft_M), BitDepth.F64, 1))
    using (IplImage dstRe = Cv.CreateImage(new CvSize(dft_N, dft_M), BitDepth.F64, 1))
    using (IplImage dstIm = Cv.CreateImage(new CvSize(dft_N, dft_M), BitDepth.F64, 1))
    {
        // フーリエ変換した画像を実部と虚部に分解
        Cv.Split(dft1, srcRe1, srcIm1, null, null);
        Cv.Split(dft2, srcRe2, srcIm2, null, null);

        for (int i = 0; i < dft_M; i++)
        {
            for (int j = 0; j < dft_N; j++)
            {
                // 画像をかけあわせる F×G*
                dstRe[i, j] = srcRe1[i, j] * srcRe2[i, j] - srcIm1[i, j] * srcIm2[i, j];
                dstIm[i, j] = srcRe1[i, j] * srcIm2[i, j] + srcRe2[i, j] * srcIm1[i, j];

                // 絶対値を計算しその値で割る |F×G*|
                double spectrum = Math.Sqrt(dstRe[i, j] * dstRe[i, j] + dstIm[i, j] * dstIm[i, j]);
                dstRe[i, j] = dstRe[i, j] / spectrum;
                dstIm[i, j] = dstIm[i, j] / spectrum;
            }
        }
        // 計算結果を統合する
        Cv.Merge(dstRe, dstIm, null, null, result);
    }

    // 逆フーリエ変換
    img = idft(result);

    // データを解放
    dft1.Dispose();
    dft2.Dispose();
    result.Dispose();

    return img;
}



しかし、OpenCVって複素数をサポートしていないのかなぁ・・・
チャンネル毎に行うのはちょっと面倒で計算ミスが起こりそう・・・


結果

今回も画像はnashruddin.com - Phase Correlation in OpenCVさんから拝借したもので、行いました。


結果は以下のようになるようです。

相関値:0.6344
移動距離:(6,15)



1.jpg 2.jpg

POCを行ったグラフ
左上に白い点がありますが、そこがピーク値です。
POC1.png


ちなみに、私の計算結果は・・・
相関値:0.5904
移動距離:(6,15)



・・・あれ?ちょっと相関値が違う・・・
移動させた距離はあっているのだけどなぁ・・・


以前行った。射影変換lでは変換したい座標をあらかじめ配列に格納して実行していましたが、
Pictureboxでクリックした座標を取得して変換を行います。

C#とOpenCVSharpを使って行います。

ソース

ソースは以下のようになります。

pictureBox1_MouseDownの引数「MouseEventArgs e」にてpictureboxをクリックした座標を取得できます。

プログラムとしては、4点pictureboxをクリックして、その後buttunを押すと、クリックした座標を使用して射影変換をします。
特にガードなど細かいことはしていませんのでご了承を・・・m(_ _)m
button1_Click、pictureBox1_MouseDownの部分が処理のメイン部分です。



using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using OpenCvSharp;
using OpenCvSharp.CPlusPlus;

namespace HomographyImage
{
    public partial class Form1 : Form
    {
        // 表示するBitmap  
        private Bitmap bmp = null;
        // 描画用Graphicsオブジェクト  
        private Graphics g = null;
        // クリック位置の描画用座標
        private Point[] point = new Point[4];
        private int clickcnt = 0;

        public Form1()
        {
            InitializeComponent();
        }

        private void UpdateForm(Bitmap bmp)
        {
            int margin = 50;
            int maxsize = 800;
            int width = maxsize;
            int height = maxsize;

            if (maxsize > bmp.Width)
            {
                width = bmp.Width;
            }
            if (maxsize > bmp.Height)
            {
                height = bmp.Height;
            }

            // Formのサイズを画像に応じて変更
            this.Width = pictureBox1.Location.X + width + margin;
            this.Height = pictureBox1.Location.Y + height + margin;

            pictureBox1.Height = height;
            pictureBox1.Width = width;

            // 画像を表示
            pictureBox1.Image = bmp;
        }


        private void 開くOToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //「ファイルの種類」を設定
            openFileDialog1.Filter = "画像ファイル(BMP,JPEG,GIF,PNG)|*.bmp;*.jpg;*gif;*.png|すべてのファイル(*.*)|*.*";
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                bmp = new Bitmap(openFileDialog1.FileName);

                // 表示を更新
                UpdateForm(bmp);
            }
        }

        private void 保存SToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // ファイルのフィルタを設定する
            saveFileDialog1.Filter = "Jpeg Image|*.jpg|Bitmap Image|*.bmp|Gif Image|*.gif|PNG Image|*.png";

            saveFileDialog1.Title = "画像を保存";

            // 初期表示するファイル名を設定する
            saveFileDialog1.FileName = "";

            if (saveFileDialog1.ShowDialog() == DialogResult.OK)
            {
                Console.WriteLine(saveFileDialog1.FileName);
                string extension = System.IO.Path.GetExtension(saveFileDialog1.FileName);
                switch (extension.ToUpper())
                {
                    case ".JPEG":
                    case ".JPG":
                        pictureBox1.Image.Save(saveFileDialog1.FileName, System.Drawing.Imaging.ImageFormat.Jpeg);
                        break;
                    case ".BMP":
                        pictureBox1.Image.Save(saveFileDialog1.FileName, System.Drawing.Imaging.ImageFormat.Bmp);
                        break;
                    case ".GIF":
                        pictureBox1.Image.Save(saveFileDialog1.FileName, System.Drawing.Imaging.ImageFormat.Gif);
                        break;
                    case ".PNG":
                        pictureBox1.Image.Save(saveFileDialog1.FileName, System.Drawing.Imaging.ImageFormat.Png);
                        break;
                    default:
                        break;
                }
            }
        }

        private void 終了CToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Environment.Exit(0);
        }

        private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
        {
            if (bmp == null)
            {
                return;
            }

            // クリックした座標を取得
            point[clickcnt].X = e.X;
            point[clickcnt].Y = e.Y;

            //System.Diagnostics.Debug.WriteLine("({0},{1}) {2}", point[clickcnt].X, point[clickcnt].Y, clickcnt);

            // クリックした位置に点を描画
            g = pictureBox1.CreateGraphics();
            g.FillEllipse(Brushes.Aqua, point[clickcnt].X, point[clickcnt].Y, 10, 10);

            // 点の間でLineを引く
            if (clickcnt != 0)
            {
                Pen p = new Pen(Color.Aqua, 3);
                g.DrawLine(p, point[clickcnt], point[clickcnt - 1]);
                p.Dispose();
            }
            if (clickcnt == point.Length-1)
            {
                Pen p = new Pen(Color.Aqua, 3);
                g.DrawLine(p, point[clickcnt], point[(clickcnt + 1) % point.Length]);
                p.Dispose();
            }

            g.Dispose();

            clickcnt = (clickcnt + 1) % point.Length;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // クリックで取得した座標
            double[] a = new double[]{
                                point[0].X,point[0].Y,
                                point[1].X,point[1].Y,
                                point[2].X,point[2].Y,
                                point[3].X,point[3].Y
                                };

            // 変換先の頂点の座標
            double[] b = new double[]{
                                0, 0,
                                0, 511,
                                511, 511,
                                511, 0
                                };


            CvMat Mata = new CvMat(4, 2, MatrixType.F64C1, a);
            CvMat Matb = new CvMat(4, 2, MatrixType.F64C1, b);

            IplImage src = BitmapConverter.ToIplImage(bmp);
            IplImage dst = Cv.CreateImage(Cv.Size(512, 512), src.Depth, src.NChannels);

            //homography matrix
            CvMat homography = new CvMat(3, 3, MatrixType.F64C1);

            Cv.FindHomography(Mata, Matb, homography);
            Cv.WarpPerspective(src, dst, homography, Interpolation.Cubic);

            // pictureboxを更新
            UpdateForm(BitmapConverter.ToBitmap(dst));
        }

    }
}



結果

結果は以下のようになります。
クリックしていくたびに点が描画され領域が表示されます。


1_20121124221003.png


2_20121124221002.png


3_20121124221001.png


4_20121124221000.png


5.png


IplImage()やCv.CreateImage()で使うEnum等がいまいち覚えられなくて、
その都度調べているという状態がもったいないのでまとめてみました。

とりあえず、今回はLoadModeとBitDepthについて簡単にまとめてみました。

LoadMode

IplImage()の第2引数等で使用するLoadModeについてまとめたのが以下の表です。
実際に定義されているソースはhttp://opencvsharp.googlecode.com/svn/trunk/2.4/OpenCvSharp/Src/Enum/LoadMode.csになります。



OpenCV
OpenCVSharp
備考
CV_LOAD_IMAGE_UNCHANGED
LoadMode.Unchanged
-1
CV_LOAD_IMAGE_GRAYSCALE
LoadMode.GrayScale
0
CV_LOAD_IMAGE_COLOR
LoadMode.Color
1
CV_LOAD_IMAGE_ANYDEPTH
LoadMode.AnyDepth
2
CV_LOAD_IMAGE_ANYCOLOR
LoadMode.AnyColor
4



BitDepth

Cv.CreateImage()等の第2引数で使用する、BitDepthについてOpenCVとOpenCVSharpの差異についてまとめたものが以下です。
実際に定義されているソースがhttp://opencvsharp.googlecode.com/svn/trunk/2.4/OpenCvSharp/Src/Enum/BitDepth.csになります。



OpenCV
OpenCVSharp
備考
IPL_DEPTH_1U
BitDepth.U1
2値画像
IPL_DEPTH_8U
BitDepth.U8
通常のグレースケール画像
IPL_DEPTH_16U
BitDepth.U16
単精度画像
IPL_DEPTH_32F
BitDepth.F32
倍精度画像
IPL_DEPTH_8S BitDepth.S8
符号付きグレースケール画像
IPL_DEPTH_16S
BitDepth.S16 符号付き単精度画像
IPL_DEPTH_32S
BitDepth.S32 符号付き倍精度画像


自分のメモ程度ですが、今後必要になればまた作成しようと思います。

※数年ぶりにtableのhtmlを書いたらすっかり忘れています・・・汗
以前、Octaveで求めていた位相画像をC#とOpenCVSharpにて実装してみたいと思います。



ソース

以下がソースコードになります。
Form1_Paint()内にてフーリエ変換などを行っていますが、 途中まではOpenCVのサンプルをまねしています。

離散変換 - http://opencv.jp/sample/discrete_transforms.html

また、
DFT - http://opencv.jp/opencv-1.0.0/document/opencvref_cxcore_discrete.htmlにて記載されているように離散フーリエ変換であるcvDFT(Cv.DFT)の変換・逆変換は3つ目の引数であるflagsにより決まります。

つまり、以下のようになります。
CV_DXT_FORWARD(DFTFlag.Forward)
→フーリエ変換
CV_DXT_INVERSE(DFTFlag.Inverse)
→フーリエ逆変換


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using OpenCvSharp;

namespace PhaseImage
{
    public partial class Form1 : Form
    {
        Bitmap bitmap;
        String filename = "Lenna.bmp";

        public Form1()
        {
            InitializeComponent();
            bitmap = new Bitmap(filename);

            //左側のpictureBoxの設定
            pictureBox1.Height = bitmap.Height;
            pictureBox1.Width = bitmap.Width;
            pictureBox1.Left = 0;
            pictureBox1.Top = 0;
            pictureBox1.Image = bitmap;

            //右側のpictureBoxの設定
            pictureBox2.Height = bitmap.Height;
            pictureBox2.Width = bitmap.Width;
            pictureBox2.Left = pictureBox1.Width;
            pictureBox2.Top = 0;

            this.ClientSize = new Size(pictureBox1.Width + pictureBox2.Width, pictureBox1.Height);
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            IplImage img = new IplImage(filename, LoadMode.GrayScale);
            IplImage phase;
            using (IplImage reimg = Cv.CreateImage(img.Size, BitDepth.F64, 1))
            using (IplImage imimg = Cv.CreateImage(img.Size, BitDepth.F64, 1))
            using (IplImage cmpimg = Cv.CreateImage(img.Size, BitDepth.F64, 2))
            {
                // (1)入力画像を実数配列にコピーし,虚数配列とマージして複素数平面を構成
                Cv.Scale(img, reimg, 1.0, 0.0);
                Cv.Zero(imimg);
                Cv.Merge(reimg, imimg, null, null, cmpimg);

                // (2)DFT用の最適サイズを計算し,そのサイズで行列を確保する
                int dft_M = Cv.GetOptimalDFTSize(img.Height - 1);
                int dft_N = Cv.GetOptimalDFTSize(img.Width - 1);

                using (CvMat dft_A = Cv.CreateMat(dft_M, dft_N, MatrixType.F64C2))
                using (IplImage image_Re = Cv.CreateImage(new CvSize(dft_N, dft_M), BitDepth.F64, 1))
                using (IplImage image_Im = Cv.CreateImage(new CvSize(dft_N, dft_M), BitDepth.F64, 1))
                using (IplImage image_Re_Norm = Cv.CreateImage(new CvSize(dft_N, dft_M), BitDepth.F64, 1))
                using (IplImage image_Im_Norm = Cv.CreateImage(new CvSize(dft_N, dft_M), BitDepth.F64, 1))
                {
                    // (3)複素数平面をdft_Aにコピーし,残りの行列右側部分を0で埋める
                    CvMat tmp;
                    Cv.GetSubRect(dft_A, out tmp, new CvRect(0, 0, img.Width, img.Height));
                    Cv.Copy(cmpimg, tmp, null);
                    if (dft_A.Cols > img.Width)
                    {
                        Cv.GetSubRect(dft_A, out tmp, new CvRect(img.Width, 0, dft_A.Cols - img.Width, img.Height));
                        Cv.Zero(tmp);
                    }

                    // (4)離散フーリエ変換を行い,その結果を実数部分と虚数部分に分解
                    Cv.DFT(dft_A, dft_A, DFTFlag.Forward, cmpimg.Height);
                    Cv.Split(dft_A, image_Re, image_Im, null, null);

                    // 正規化用に複素数の値を保持しておく
                    Cv.Split(dft_A, image_Re_Norm, image_Im_Norm, null, null);

                    // (5)スペクトルの振幅を計算 Mag = sqrt(Re^2 + Im^2)
                    Cv.Pow(image_Re, image_Re, 2.0);
                    Cv.Pow(image_Im, image_Im, 2.0);
                    Cv.Add(image_Re, image_Im, image_Re, null);
                    Cv.Pow(image_Re, image_Re, 0.5);

                    // 正規化する
                    for (int j = 0; j < image_Re.Height; j++)
                    {
                        for (int i = 0; i < image_Re.Width; i++)
                        {
                            image_Re_Norm[j, i] = image_Re_Norm[j, i] / image_Re[j, i];
                            image_Im_Norm[j, i] = image_Im_Norm[j, i] / image_Re[j, i];
                        }
                    }
                    // 正規化した複素数平面をdft_Aにコピーする
                    Cv.Merge(image_Re_Norm, image_Im_Norm, null, null, dft_A);
                    Cv.DFT(dft_A, dft_A, DFTFlag.Inverse, cmpimg.Height);
                    
                    // 逆変換した画像を表示用の変数に格納
                    Cv.Split(dft_A, image_Re, image_Im, null, null);
                    phase = image_Re.Clone();
                }
            }

            //描画
            pictureBox1.Image = bitmap;
            pictureBox2.Image = phase.ToBitmap();
        }
    }
}

結果

左が元画像、右が位相画像です。





どうやらうまくいっているようです(;´Д`)=3

FC2カウンター
プロフィール

詠み人知らず

Author:詠み人知らず
プログラム好きな名もなき凡人がお送りしています。(得意とは言っていない
最近の興味はPython、C#、Matlab(Octave)、画像処理、AR(拡張現実)、統計などなど・・・

気分で思いついたことを書くため話題に一貫性がないかもしれません。

カレンダー
10 | 2017/11 | 12
- - - 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 - -
最新記事
タグクラウドとサーチ

カテゴリ
最新コメント
最新トラックバック
月別アーカイブ