Visual Basic でOpenCV⑥ - 2画面表示

Visual BasicOpenCVを使用しフィルタ処理を行います。ここで紹介するプログラムは、処理結果を別のフォームへ表示します。

2画面表示

これまでのプログラムは、読み込んだ画像と処理結果の画像を同一フォームへ表示します。このため、処理前と処理後の画像を観察するのは簡単ではありません。そこで、原画像と処理後の画像を比較しやすいように、原画像表示用のフォームのほかに処理後の画像を表示するフォームを用意します。さらに、処理後の画像を表示するフォームで、処理結果を保存できる機能を追加します。本プログラムの画像処理はVisual BasicOpenCVと変わりありません。

最初に原画像を表示するフォームの外観を示します。コントロールの配置もメニューも、これまでのプログラムと同様です。

次に、処理結果を表示するフォームを示します。プロジェクトに処理後の画像を表示するフォームを追加し、各種コントロールを配置します。以降にフォームの外観を示します。

処理結果を表示するフォームのメニューは[名前を付けて保存]と[閉じる]の2 つのメニュー項目を設定します。

読み込んだ画像を表示するフォームに対するコードが記述されているForm1.vb を示します。Visual BasicOpenCVのプログラムと大きな違いはありません。

:
Public Class Form1
    :
    Private ReadOnly mForm2 As Form2 = Nothing

    Public Sub New()
        MyBase.New
        :
        mForm2 = New Form2
    End Sub
    :

    ' open file
    Private Sub OpenFile(fname As String)
        Dim bmp As Bitmap = Nothing
        Dim newfname = fname

        If Equals(newfname, Nothing) Then Return

        mSrc = Cv2.ImRead(newfname)
        bmp = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mSrc)

        PBox.Image = bmp
        AdjustWinSize(PBox.Image)                  ' ウィンドウサイズ調整

        toolSSLbl.Text = Path.GetFileName(fname)   'ファイル名表示

        If mForm2.Validate() Then
            mForm2.Hide()
        End If
    End Sub

    ' 「開く」メニュー項目
    Private Sub FileMenuOpen_Click(sender As Object, e As EventArgs) _
                                                Handles FileMenuOpen.Click
        Try
            Dim cio As New CIo()
            Dim fname As String = cio.GetReadFile()
            OpenFile(fname)
        Catch ex As Exception
        :
    End Sub

    ' 「処理」メニュー項目
    Private Sub ToolMenuEffect_Click(sender As Object, e As EventArgs) _
                                                Handles ToolMenuEffect.Click
        :
            Using dst = New Mat()
                Cv2.Blur(mSrc, dst, New Size(11, 11))                   ' Blur
                mForm2.ShowResult(dst)
            End Using
        Catch ex As Exception
        :
    End Sub
    :
End Class

Form1のコンストラクターでフォームのタイトルやステータスバーを設定するのは、これまでと同様です。コンストラクターの最後で、結果表示用のフォームForm2 オブジェクトmForm2 を生成します。この時点では生成のみで表示は行いません。
OpenFile メソッドは、ファイルの読み込みと表示などをまとめたものです。本メソッドは、[開く]メニュー項目が選択されたときに呼び出されるFileMenuOpen_Click メソッド経由で呼ばれます。Cv2.ImRead でファイルを読み込み、Bitmap オブジェクトへ変換する部分などは、これまでと同様です。結果表示のフォームが表示されている場合は、以前の結果が表示されているため、Hide メソッドを呼び出し結果表示のフォームは隠します。
[開く]メニュー項目が選択されたときに呼び出されるFileMenuOpen_Click メソッドは、CIo クラス(Visual BasicOpenCVで解説済)を利用し対象ファイル名を取り出します。これを引数にOpenFile メソッドを呼び出します。
[処理]メニュー項目が選択されたときに呼び出されるToolMenuEffect_Click メソッドは、画像処理を行い、処理したMat オブジェクトを引数にForm2 のShowResult メソッドを呼び出します。

次に、結果を表示するForm2 に対応するソースコードを示します。Form2 は、画像処理機能は持たず、単に結果表示と保存機能だけを持つ単純なフォームです。

Imports System

Imports OpenCvSharp
Imports Filters.CIoLibrary

Public Class Form2
    Private ReadOnly ttl As String = "処理結果"
    Private mDst As Mat

    Public Sub New()
        InitializeComponent()

        Text = ttl
        Panel1.Dock = DockStyle.Fill    'スクロール対応
        Panel1.AutoScroll = True
        PBox.Location = New Drawing.Point(0, 0)
    End Sub

    ' resize
    Private Sub AdjustWinSize(ByVal img As Image)
        PBox.Size = img.Size            'スクロール対応

        ClientSize = New Drawing.Size(  ' ウィンドウサイズ調整
                    img.Width, img.Height + MenuStrip1.Height)
    End Sub

    '名前を付けて保存
    Private Sub FileMenuSaveAs_Click(sender As Object, e As EventArgs) _
                                                Handles FileMenuSaveAs.Click
        Try
            Dim cio As New CIo()
            Dim fname As String = cio.GetWriteFile()
            If Not Equals(fname, Nothing) Then
                Cv2.ImWrite(fname, mDst)
            End If
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try
    End Sub

    ' 「閉じる」メニュー項目
    Private Sub FileMenuClose_Click(sender As Object, e As EventArgs) _
                                                Handles FileMenuClose.Click
        Me.Hide()        '隠す
    End Sub

    ' 結果表示
    Public Sub ShowResult(ByVal dst As Mat)
        mDst = dst.Clone()
        PBox.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mDst)
        AdjustWinSize(PBox.Image)
        Show()
    End Sub

    ' ×ボタン
    Private Sub Form2_FormClosing(sender As Object, e As FormClosingEventArgs) _
                                                        Handles MyBase.FormClosing
        Me.Hide()        '隠す
        e.Cancel = True
    End Sub
End Class

このフォームもCIo クラスを使用するため、Imports Filters.CIoLibrary を追加します。Imports に指定するFiltersはアセンブリ名(通常プロジェクト名と同じ)で、それに続き自身で指定したNamespaceです。
コンストラクターでフォームのタイトルやステータスバーを設定します。Panel コントロールのDock プロパティを設定し、パネルがクライアント全面を覆うようにします。次に、Panel コントロールのAutoScroll プロパティをTrue に設定します。また、PictureBox コントロールの位置を左上隅に合わせます。先ほどのForm1同様、PictureBox コントロールのサイズがPanel コントロールより大きくなると自動でスクロールバーが現れます。
AdjustWinSize メソッドは、新規に追加したメソッドです。引数のImage オブジェクトimgをフォームに表示できるようにクライアントサイズを変更します。
[名前を付けて保存] メニュー項目が選択されたときに、制御が渡ってくるのがFileMenuSaveAs_Click メソッドです。例外を捕捉するためにTry & Catch で囲みます。CIo クラスを使って、書き込み対象ファイル名を取得します。このファイル名を使ってCv2.ImWrite メソッドでファイルを書き込みます。
[閉じる]メニュー項目を選択するとFileMenuClose_Click メソッドへ制御が渡ってきます。Hide メソッドを呼び出し、フォームを隠します。このプログラムは、結果表示を閉じてもフォーム自体が閉じられるわけではなく隠すだけです。
ShowResult メソッドは、Form1 から呼び出されるPublic メソッドです。引数で渡されたMat オブジェクトをBitmap オブジェクトへ変換し表示します。
ウィンドウのクローズボタン(×)が押されるなど何らかの理由でフォームが閉じられようとすると、Form2_FormClosing メソッドへ制御が渡ります。Hide メソッドを呼び出し、フォームを隠したあと、引数のFormClosingEventArgs のCancel プロパティにTrue を設定します。このようにすると、クローズボタンを押してもフォームは閉じられず隠されるだけです。このようにしないと、[閉じる]メニュー項目を選択したときとクローズボタンを押したときの処理に相違が出てしまいます。この記述がないとクローズボタンを押したときに、Form2 が破棄されます。

実行

以降に、動作の様子を示します。

コラム:Nortonの不具合
Visual Studio などを使って開発したプログラムなどを実行すると、Nortonがウィルスと誤認識し実行ファイルを削除するケースがあります。この現象は何年も前から続いており、Norton Internet Securityの時代から、現在使用中のNorton 360 Premiumでも同様です。ここで紹介したプログラムも、結果を書き込もうとするとNortonが危ない行為と判断するのか、書き込みを失敗する場合があります。
このようなことから、数回におよびNortonのヘルプセンターへ問い合わせたのですが解決法は提示されません。案内されるのは、そのドライブ/パーティション/プログラムなどをNortonの監視対象から外しなさいと案内されます。ドライブ全部を監視対象から外せば手間はありません。しかし、それでは安くもないウィルス対応ソフトウェアを導入した意味がありません。プログラムを単体ごとに外すのは手間を考えると現実的ではありません。ということで長年、Nortonのこの挙動には悩まされています。
今のところ改善される見込みはなさそうですが、このコラムを読むころには改善されている可能性がないともいませんので、そのようなときは本コラムを無視してください。