none
C言語の文⇒VBへのコード変換で悩んでます。 RRS feed

  • 質問

  • 開発環境:Visual Studio 2013 Pro
    開発言語:Visual Basic

    OpenCVのキャリブレーションをVb.netでやりたく、C言語で書かれたソースをVB.netにしています。

    CvPoint2D32f *corners = (CvPoint2D32f *) cvAlloc (sizeof (CvPoint2D32f) * ALL_POINTS);

    ↑の文を以下にVBでは下のように書きました。

    Dim corners As CvPoint2D32f = DirectCast(Cv.Alloc(Marshal.SizeOf(GetType(CvPoint2D32f)) * imageData.ALL_POINTS), CvPoint2D32f)

    型’System.IntPtr’の値を’OpenCVSharp.CvPoint2D32f’に変換できませんと表示されていて
    どう値を入れれば良いのか悩んでいます。

    ご教授頂けると助かります。

    • 移動 星 睦美 2016年7月13日 5:05 .NET Framework から
    2016年7月13日 4:55

回答

  • 外池と申します。

    http://schima.hatenablog.com/entry/2013/11/10/235351
    を見る限り、Marshal云々の難しいことを介することは不要なように見えるのですが・・・。
    普通に、CvPoint2D32fの配列としての、CvPoint2D32f[]型が定義、確保されてます。(C#風の表現)

    VBだったら、
    Dim
    corners As CvPoint2D32f= CvPoint2D32f(imageData.ALL_POINTS-1)

    これじゃダメなんでしょうか?

    根本的なことですが、.Net Frameworkの基本的な考え方として、ヒープ領域にメモリを確保する「Alloc」系のメソッドのIntPtr型である戻り値を、マネージドな(ラッピングしてある場合を含む)配列にキャストで放り込むことはしないと思います。(小生は、できない、と割り切って理解している。)


    • 編集済み 外池 2016年7月13日 8:16
    • 回答としてマーク 星 睦美 2016年9月15日 7:32
    2016年7月13日 8:01
  • VB.Net側で必要な配列を用意して、その配列の先頭のメモリポインタを以降の関数への引数に渡してやればいいかも。
    以下、メモリポインタの確保はGCHandle.AllocとUnsafeAddOfPinnedArrayElementで行ってます。

    Imports System.Runtime.InteropServices
    Module Module1
    
        Sub Main()
            'VB.Netで配列確保
            Dim ALL_POINTS As Integer = 10
            Dim corners() As OpenCvSharp.CvPoint2D32f = New OpenCvSharp.CvPoint2D32f(ALL_POINTS) {}
            Dim cornersTest(ALL_POINTS - 1) As OpenCvSharp.CvPoint2D32f
            ReDim corners(ALL_POINTS)
    
            '配列を固定してポインタを取得
            Dim gchandle As GCHandle = gchandle.Alloc(corners) '配列がGCで削除されないように
            Dim pCvAlloc As IntPtr = Marshal.UnsafeAddrOfPinnedArrayElement(corners, 0) '配列を固定して先頭のポインタを取得
    
            'メモリー上の値が配列に対応しているか確認
            corners(2).X = 1.2345 '配列側で値を変更してみる
            CopyFromMemory(pCvAlloc, cornersTest) 'メモリのポインタから配列に値をコピーして格納してみる
            If (cornersTest(2).X = corners(2).X) Then
                Console.WriteLine("メモリに書き込めてる")
            Else
                Console.WriteLine("書き込みがおかしい")
            End If
    
            cornersTest(3).Y = 9.876
            CopyToMemory(pCvAlloc, cornersTest, 3, 1) '配列からメモリーにコピーしてみる
            If (cornersTest(3).Y = corners(3).Y) Then
                Console.WriteLine("メモリに書き込めてる")
            Else
                Console.WriteLine("書き込みがおかしい")
            End If
    
            gchandle.Free() '使い終わったら破棄
    
    
            'OpenCVでメモリーを確保する場合
            Dim sizeCvPoint2D32f As Integer = Marshal.SizeOf(GetType(OpenCvSharp.CvPoint2D32f))
            pCvAlloc = OpenCvSharp.Cv.Alloc(sizeCvPoint2D32f * ALL_POINTS) 'OpenCVでメモリーを確保
            'cvFindChessboardCornersはポインタを渡しているのでポインタのままでいい
            OpenCvSharp.Cv.Free(pCvAlloc) 'CvAllowで確保したメモリーを開放
        End Sub
    
        Public Sub CopyFromMemory(Of T)(ByVal p0 As IntPtr, ByVal ar() As T, Optional ByVal start As Integer = 0, Optional ByVal length As Integer = Integer.MaxValue)
            Dim size As Integer = Marshal.SizeOf(GetType(T))
            length = Math.Min(ar.Length - start, length)
            Dim tail As Integer = start + length - 1
            p0 = p0 + size * start
            For i As Integer = start To tail
                ar(i) = Marshal.PtrToStructure(Of T)(p0)
                p0 = p0 + size
            Next
        End Sub
        Public Sub CopyToMemory(Of T)(ByVal p0 As IntPtr, ByVal ar() As T, Optional ByVal start As Integer = 0, Optional ByVal length As Integer = Integer.MaxValue)
            Dim size As Integer = Marshal.SizeOf(GetType(T))
            length = Math.Min(ar.Length - start, length)
            Dim tail As Integer = start + length - 1
            p0 = p0 + size * start
            For i As Integer = start To start + length - 1
                Marshal.StructureToPtr(ar(i), p0, False)
                p0 = p0 + size
            Next
        End Sub
    End Module

    VB.Netに変換したい元のコードを見るとcornersが使われている関数はOpenCVSharp2でもポインタ渡しになっているみたいなので、配列にせずにポインタのままでもいいような気が。

    #cvFindCornerSubPixはポインタの配列になっているのはなぜだろう…


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク 星 睦美 2016年9月15日 7:32
    2016年7月13日 10:11
  • PtrToStructure メソッドの Of 指定は、.NET Framework 4.5.1 以上の機能です。

    新規プロジェクトを作成する際に、プロジェクト作成画面上部で選択できる
    .NET Framework バージョンを御確認下さい。ここが 4.5 以下の場合、
    PtrToStructure(Of T) は利用できません。.PtrToStructure ならば利用できます。

    現在のプロジェクトの .NET Framework バージョンについては、
    ソリューション エクスプローラーの [My Project]を開いた上で、
    下記の項目から確認・変更を行うことができます。

    ★VB2012、2013、2015の場合★
     [アプリケーション]タブから指定できます。

    ☆VB2008、2010の場合☆
     [コンパイル]タブの「詳細コンパイル オプション」ボタンから指定できます。

    ☆VB2005以下の場合☆
     VS上からの事後変更は出来ません。.vbproj の手動修正が必要です。


    2016年7月14日 2:12

すべての返信

  • k-onoda さま よろしく。

    本題から外れますが、 Bing で検索すると こんなものが見付かります。 参考にはなりませんか?。

    http://www3010ui.sakura.ne.jp/vb/opencvopencvsharp_vb/

    http://hatsune.hatenablog.jp/entry/2013/10/04/073216

    2016年7月13日 7:34
  • コメントありがとうございます。
    OpenCV、OpenCVSharpの環境構築は既に終えています。

    dim corners as IntPtr = Cv.Alloc(Marshal.SizeOf(GetType(CvPoint2D32f)) * imageData.ALL_POINTS)

    だと同じ型なので値は入れられますが、

    OpenCVSharp.CvPoint2D32fへの型へいれかたが判らないでいます。
    ↓のソースをVBにしているところです。

    http://opencv.jp/sample/camera_calibration.htm



    • 編集済み k-onoda 2016年7月13日 7:46
    2016年7月13日 7:41
  • 外池と申します。

    http://schima.hatenablog.com/entry/2013/11/10/235351
    を見る限り、Marshal云々の難しいことを介することは不要なように見えるのですが・・・。
    普通に、CvPoint2D32fの配列としての、CvPoint2D32f[]型が定義、確保されてます。(C#風の表現)

    VBだったら、
    Dim
    corners As CvPoint2D32f= CvPoint2D32f(imageData.ALL_POINTS-1)

    これじゃダメなんでしょうか?

    根本的なことですが、.Net Frameworkの基本的な考え方として、ヒープ領域にメモリを確保する「Alloc」系のメソッドのIntPtr型である戻り値を、マネージドな(ラッピングしてある場合を含む)配列にキャストで放り込むことはしないと思います。(小生は、できない、と割り切って理解している。)


    • 編集済み 外池 2016年7月13日 8:16
    • 回答としてマーク 星 睦美 2016年9月15日 7:32
    2016年7月13日 8:01
  • コメントありがとうございます。
    式が理解できていなく、

    CvPoint2D32f *corners = (CvPoint2D32f *) cvAlloc (sizeof (CvPoint2D32f) * ALL_POINTS);

    C言語では、(CvPoint2D32f *)  はキャストしているのかなと考え

    cvAllocは、リファレンスを読むとメモリバッファの領域を確保する部分だと認識しました。


    上記式のC言語では、メモリ確保した領域をキャストして入れてるのではないかと考えまして、
    まねてVBで式を書きました。

    Dim corners As CvPoint2D32f= CvPoint2D32f(imageData.ALL_POINTS-1

    教えていただいた式を書いたのですが、
    ’CvPoint2D32f’は、型です。有効な式ではありませんとでました。

    まだ、理解しきれてなく、ご教授頂けると助かります。

    2016年7月13日 8:23
  • 配列確保の意味だとしたら、これでどうでしょう。

    '案1
    Dim corners1(imageData.ALL_POINTS - 1) As CvPoint2D32f
    
    '案2
    Dim corners2 = New CvPoint2D32f(imageData.ALL_POINTS - 1) {}

    2016年7月13日 8:31
  • VB.Net側で必要な配列を用意して、その配列の先頭のメモリポインタを以降の関数への引数に渡してやればいいかも。
    以下、メモリポインタの確保はGCHandle.AllocとUnsafeAddOfPinnedArrayElementで行ってます。

    Imports System.Runtime.InteropServices
    Module Module1
    
        Sub Main()
            'VB.Netで配列確保
            Dim ALL_POINTS As Integer = 10
            Dim corners() As OpenCvSharp.CvPoint2D32f = New OpenCvSharp.CvPoint2D32f(ALL_POINTS) {}
            Dim cornersTest(ALL_POINTS - 1) As OpenCvSharp.CvPoint2D32f
            ReDim corners(ALL_POINTS)
    
            '配列を固定してポインタを取得
            Dim gchandle As GCHandle = gchandle.Alloc(corners) '配列がGCで削除されないように
            Dim pCvAlloc As IntPtr = Marshal.UnsafeAddrOfPinnedArrayElement(corners, 0) '配列を固定して先頭のポインタを取得
    
            'メモリー上の値が配列に対応しているか確認
            corners(2).X = 1.2345 '配列側で値を変更してみる
            CopyFromMemory(pCvAlloc, cornersTest) 'メモリのポインタから配列に値をコピーして格納してみる
            If (cornersTest(2).X = corners(2).X) Then
                Console.WriteLine("メモリに書き込めてる")
            Else
                Console.WriteLine("書き込みがおかしい")
            End If
    
            cornersTest(3).Y = 9.876
            CopyToMemory(pCvAlloc, cornersTest, 3, 1) '配列からメモリーにコピーしてみる
            If (cornersTest(3).Y = corners(3).Y) Then
                Console.WriteLine("メモリに書き込めてる")
            Else
                Console.WriteLine("書き込みがおかしい")
            End If
    
            gchandle.Free() '使い終わったら破棄
    
    
            'OpenCVでメモリーを確保する場合
            Dim sizeCvPoint2D32f As Integer = Marshal.SizeOf(GetType(OpenCvSharp.CvPoint2D32f))
            pCvAlloc = OpenCvSharp.Cv.Alloc(sizeCvPoint2D32f * ALL_POINTS) 'OpenCVでメモリーを確保
            'cvFindChessboardCornersはポインタを渡しているのでポインタのままでいい
            OpenCvSharp.Cv.Free(pCvAlloc) 'CvAllowで確保したメモリーを開放
        End Sub
    
        Public Sub CopyFromMemory(Of T)(ByVal p0 As IntPtr, ByVal ar() As T, Optional ByVal start As Integer = 0, Optional ByVal length As Integer = Integer.MaxValue)
            Dim size As Integer = Marshal.SizeOf(GetType(T))
            length = Math.Min(ar.Length - start, length)
            Dim tail As Integer = start + length - 1
            p0 = p0 + size * start
            For i As Integer = start To tail
                ar(i) = Marshal.PtrToStructure(Of T)(p0)
                p0 = p0 + size
            Next
        End Sub
        Public Sub CopyToMemory(Of T)(ByVal p0 As IntPtr, ByVal ar() As T, Optional ByVal start As Integer = 0, Optional ByVal length As Integer = Integer.MaxValue)
            Dim size As Integer = Marshal.SizeOf(GetType(T))
            length = Math.Min(ar.Length - start, length)
            Dim tail As Integer = start + length - 1
            p0 = p0 + size * start
            For i As Integer = start To start + length - 1
                Marshal.StructureToPtr(ar(i), p0, False)
                p0 = p0 + size
            Next
        End Sub
    End Module

    VB.Netに変換したい元のコードを見るとcornersが使われている関数はOpenCVSharp2でもポインタ渡しになっているみたいなので、配列にせずにポインタのままでもいいような気が。

    #cvFindCornerSubPixはポインタの配列になっているのはなぜだろう…


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク 星 睦美 2016年9月15日 7:32
    2016年7月13日 10:11
  • 失礼、VBは長らく触ってなくて、文法がアヤフヤです。
    魔界の仮面弁士さんがフォローして下さっていて、ありがとうございます。

    小生からご紹介差し上げたサイトを眺めれば、OpenCVSharpのライブラリはかなり概観できます。ライブラリの中で使おうとしている各クラスや構造体の仕様は完全に理解すべきですし、VBの文法もしかり。

    C++のポインタの考え方は、ほとんど要らないはずです。
    2016年7月13日 12:27
  • ありがとうございます。すごく参考になりました。
    ソースをそのまま、VBに貼り付け、読んでいます。

    まだ読んで理解しているところですが、下記コードだけご教授いただけないでしょうか。

    Marshal.PtrToStructure(Of T)(p0)

    ↑なのですが、この型引数の数を受ける’PtrToStructure’がない為、
    オーバーロードの解決に失敗しました。

    と表示され、ライブラリが足りないのか、わからないでいます。
    ご教授頂けると幸いです。

    2016年7月14日 1:41
  • PtrToStructure メソッドの Of 指定は、.NET Framework 4.5.1 以上の機能です。

    新規プロジェクトを作成する際に、プロジェクト作成画面上部で選択できる
    .NET Framework バージョンを御確認下さい。ここが 4.5 以下の場合、
    PtrToStructure(Of T) は利用できません。.PtrToStructure ならば利用できます。

    現在のプロジェクトの .NET Framework バージョンについては、
    ソリューション エクスプローラーの [My Project]を開いた上で、
    下記の項目から確認・変更を行うことができます。

    ★VB2012、2013、2015の場合★
     [アプリケーション]タブから指定できます。

    ☆VB2008、2010の場合☆
     [コンパイル]タブの「詳細コンパイル オプション」ボタンから指定できます。

    ☆VB2005以下の場合☆
     VS上からの事後変更は出来ません。.vbproj の手動修正が必要です。


    2016年7月14日 2:12
  • ありがとうございます、
    PtrToStructure メソッドの Of 指定の問題は、解決しました。

    .NetFramworkは4.5を利用していました。
    4.52に選択しましたら、エラーを解決いたしました。

    ありがとうございます。


    • 編集済み k-onoda 2016年7月14日 2:21
    2016年7月14日 2:21
  • > ↓のソースをVBにしているところです。

    してみました。

    Option Strict On
    Imports OpenCvSharp
    Module Module1
        Const IMAGE_NUM As Integer = 25
        Const PAT_ROW As Integer = 7
        Const PAT_COL As Integer = 10
        Const PAT_SIZE As Integer = (PAT_ROW * PAT_COL)
        Const ALL_POINTS As Integer = (IMAGE_NUM * PAT_SIZE)
        Const CHESS_SIZE As Double = 24.0R
        Public Function Main() As Integer
    
            ' calib_img ディレクトリのある場所をカレントにしています。このサンプルでは
            ' calib_img の下に、640x480 で撮影されたチェスパターン(市松模様)の画像が
            ' 00.png~24.png という名で計25枚配置されている必要があります。
            System.IO.Directory.SetCurrentDirectory("C:\TEST\SAMPLE")
    
            Dim p_count(IMAGE_NUM - 1) As Integer
            Dim src_img(IMAGE_NUM - 1) As IplImage
            Dim pattern_size As New CvSize(PAT_COL, PAT_ROW)
            Dim objects(ALL_POINTS - 1) As CvPoint2D32f
    
            ' ここの宣言、ジャグ配列に変更しています
            Dim corners(IMAGE_NUM - 1)() As CvPoint2D32f
    
            Dim object_points As New CvMat(False)
            Dim image_points As New CvMat(False)
            Dim point_counts As New CvMat(False)
    
            Dim intrinsic As New CvMat(3, 3, MatrixType.F32C1)
            Dim rotation As New CvMat(1, 3, MatrixType.F32C1)
            Dim translation As New CvMat(1, 3, MatrixType.F32C1)
            Dim distortion As New CvMat(1, 4, MatrixType.F32C1)
    
            Try
                ' (1)キャリブレーション画像の読み込み
                For i As Integer = 0 To IMAGE_NUM - 1
                    Dim fileName As String = String.Format("calib_img\{0:00}.png", i)
                    fileName = "calib_img\00.png"
                    Try
                        src_img(i) = New IplImage(fileName, LoadMode.Color)
                    Catch ex As System.IO.IOException
                        Console.Error.WriteLine("cannot load image file : " & fileName)
                    End Try
                Next
    
                ' (2)3次元空間座標の設定
                For i As Integer = 0 To IMAGE_NUM - 1
                    For x As Integer = 0 To PAT_ROW - 1
                        For y As Integer = 0 To PAT_COL - 1
                            Dim p As Integer = i * PAT_SIZE + x * PAT_COL + y
                            objects(p) = New CvPoint2D32f(x * CHESS_SIZE, y * CHESS_SIZE)
                        Next
                    Next
                Next
                object_points.InitMatHeader(ALL_POINTS, 3, MatrixType.F32C1, objects)
    
                ' (3)チェスボード(キャリブレーションパターン)のコーナー検出
                Dim found_num As Integer = 0
                Cv.NamedWindow("Calibration", WindowMode.AutoSize)
                For i As Integer = 0 To IMAGE_NUM - 1
                    Console.Write("{0:00}...", i)
                    If src_img(i) Is Nothing Then
                        Console.Error.WriteLine("Skip")
                        Continue For
                    End If
    
                    Dim corner_count As Integer
                    Dim found As Boolean = src_img(i).FindChessboardCorners(pattern_size, corners(i), corner_count)
                    If found Then
                        Console.WriteLine("OK")
                        found_num += 1
                    Else
                        Console.Error.WriteLine("Fail")
                    End If
    
                    ' (4)コーナー位置をサブピクセル精度に修正,描画
                    Using src_gray As New IplImage(src_img(i).Size, BitDepth.U8, 1)
                        src_img(i).CvtColor(src_gray, ColorConversion.BgrToGray)
                        src_gray.FindCornerSubPix(corners(i), corner_count, Cv.Size(3, 3), Cv.Size(-1, -1), New CvTermCriteria(20, 0.03R))
                        src_img(i).DrawChessboardCorners(pattern_size, corners(i), found)
                    End Using
                    p_count(i) = corner_count
                    Cv.ShowImage("Calibration", src_img(i))
                    Cv.WaitKey(0)   'ユーザーのキー入力待ち
                Next
                Cv.DestroyWindow("Calibration")
    
                If found_num <> IMAGE_NUM Then
                    Return -1
                End If
    
                ' ジャグ配列を一次元配列に詰め替えています
                Dim corners_linear() As CvPoint2D32f = corners.SelectMany(Function(i) i).ToArray()
    
                image_points.InitMatHeader(ALL_POINTS, 1, MatrixType.F32C2, corners_linear)
                point_counts.InitMatHeader(IMAGE_NUM, 1, MatrixType.S32C1, p_count)
    
                ' (5)内部パラメータ,歪み係数の推定
                Cv.CalibrateCamera2(object_points, image_points, point_counts, Cv.Size(640, 480), intrinsic, distortion)
    
                ' (6)外部パラメータの推定
                Using sub_image_points As CvMat = image_points.GetRows(0, PAT_SIZE),
                    sub_object_points As CvMat = object_points.GetRows(0, PAT_SIZE)
    
                    Cv.FindExtrinsicCameraParams2(sub_object_points, sub_image_points, intrinsic, distortion, rotation, translation)
                End Using
    
                ' (7)XMLファイルへの書き出し
                Using fs As New CvFileStorage("camera.xml", Nothing, FileStorageMode.Write)
                    fs.Write("intrinsic", intrinsic)
                    fs.Write("rotation", rotation)
                    fs.Write("translation", translation)
                    fs.Write("distortion", distortion)
                End Using
    
                Return 0
            Finally
                For i As Integer = 0 To IMAGE_NUM - 1
                    If Not src_img(i) Is Nothing Then
                        src_img(i).Dispose()
                    End If
                Next
                intrinsic.Dispose()
                rotation.Dispose()
                translation.Dispose()
                distortion.Dispose()
    
                point_counts.Dispose()
                image_points.Dispose()
                object_points.Dispose()
            End Try
        End Function
    End Module

    2016年7月14日 5:30
  • ありがとうございます、
    すごい助かります。
    参考にさせていただきます。

    処理の流れを理解するため、コンパイルと実行をしました。
    実行すると以下コードでとまり、何が問題なのか理解できないでいます。

    Dim intrinsic As New CvMat(3, 3, MatrixType.F32C1)

    型 'System.TypeInitializationException' のハンドルされていない例外が OpenCvSharp.dll で発生しました

    と表示され、ご教授頂けると幸いです。

    2016年7月14日 8:44
  • ビルド構成を AnyCPU / x86 / x64 と切り替えてみましたが、今の所、その例外は再現できていません。

    こちらで使用しているパッケージは、OpenCvSharp-AnyCPU の version 2.4.10.20160316 で、開発環境は .NET Fremework 4.6.1 / Windows 7(x64) です。Visual Studio は 2013 が手元に無いので、2012 および 2015 で検証しています。

    『Dim intrinsic As New CvMat(3, 3, MatrixType.F32C1)』の代わりに、『Dim intrinsic As CvMat = Cv.CreateMat(3, 3, MatrixType.F32C1)』だとどうでしょうか。

    2016年7月14日 9:05