Visual Basic とOpenCVを使用し、画像を合成するものをいくつか紹介します。マスクやROI を使用して、一部のエリアだけを処理対象とする例も紹介します。
加算
2 つの画像を加算するプログラムを紹介します。2 つの画像を開いて、それらを加算して結果を表示します。画像ファイルはドロップ、あるいはメニューから[開く]を選択します。2つの画像サイズは同じでなければなりません。
フォーム
画像処理の説明に先立ち、フォームを簡単に説明します。本プログラムのフォームは1 つだけです。
フォームには、StatusStrip、MenuStrip、Panel、およびPictureBox の4 つのコントロールを配置します。スクロールバー表示を行うので、PictureBox コントロールはPanel コントロールの上に配置します。
メニューを以降に示します。メニューに対応するメソッドは、メニュー項目をダブルクリックすると、自動的にメソッドが定義され、該当のソースファイル部分へカーソルが移動します。その部分に、適宜コードを記述します。
ソースリスト
Form1.vb
以降に、ソースリストを示します。
Imports System Imports System.IO Imports Sample.CCvLibrary Public Class Form1 Private ReadOnly ttl As String = "sample" Private mFname As List(Of String) Private ccvfunc As CCvFunc = Nothing Public Sub New() MyBase.New InitializeComponent() Text = ttl toolSSLbl.Text = "Status" Panel1.Dock = DockStyle.Fill 'スクロール対応 Panel1.AutoScroll = True PBox.Location = New Point(0, 0) AllowDrop = True AddHandler DragEnter, AddressOf Form1_DragEnter AddHandler DragDrop, AddressOf Form1_DragDrop mFname = New List(Of String)() ccvfunc = New CCvFunc() End Sub ' adjust window size Private Sub AdjustWinSize(img As Image) PBox.Size = img.Size 'スクロール対応 ClientSize = New Size( ' ウィンドウサイズ調整 img.Width, img.Height + MenuStrip1.Height _ + StatusStrip1.Height) End Sub ' open file Private Sub OpenFile(Optional fname As String = Nothing) Dim result = ccvfunc.OpenFileCv(fname) If result.Item2 Is Nothing Then Return End If mFname.Add(result.Item1) If mFname.Count > 2 Then mFname.RemoveAt(0) End If If mFname.Count = 2 Then toolSSLbl.Text = Path.GetFileName(mFname(0)) & " + " & Path.GetFileName(mFname(1)) 'ファイル名表示 Effect() Else toolSSLbl.Text = Path.GetFileName(mFname(0)) End If End Sub ' 「開く」メニュー項目 Private Sub FileMenuOpen_Click(sender As Object, e As EventArgs) _ Handles FileMenuOpen.Click Try OpenFile() Catch ex As Exception MessageBox.Show(ex.Message) End Try End Sub ' 処理 Private Sub Effect() Try Cursor = Cursors.WaitCursor Dim bmp As Bitmap = ccvfunc.DoCvFunction(mFname) If bmp Is Nothing Then MessageBox.Show("error", "size error") Return End If PBox.Image = bmp PBox.Size = PBox.Image.Size AdjustWinSize(PBox.Image) ' ウィンドウサイズ調整 Catch ex As Exception MessageBox.Show(ex.Message) Finally Cursor = Cursors.Default End Try End Sub ' 「名前を付けて保存」メニュー項目 Private Sub FileMenuSaveAs_Click(sender As Object, e As EventArgs) _ Handles FileMenuSaveAs.Click Try If PBox.Image IsNot Nothing Then ccvfunc.SaveAS() End If Catch ex As Exception MessageBox.Show(ex.Message) End Try End Sub 'ドラッグエンター Private Sub Form1_DragEnter(sender As Object, e As DragEventArgs) If e.Data.GetDataPresent(DataFormats.FileDrop) Then e.Effect = DragDropEffects.All Else e.Effect = DragDropEffects.None End If End Sub 'ドロップ Private Sub Form1_DragDrop(sender As Object, e As DragEventArgs) Try Dim fname = CType(e.Data.GetData(DataFormats.FileDrop, False), String()) For Each it In fname OpenFile(it) Next 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 Close() End Sub End Class
まず、ユーザーインターフェースの説明を行います。コンストラクターで、これまでと異なるのは、画像ファイル名を管理するmFname を生成することです。mFname はList(Of String) で、複数のファイル名を保持します。
OpenFile メソッドは、これまでと違い、複数の画像ファイル名を管理します。すでに画像ファイルを2 つ読み込み済みの場合、先に読み込んだファイルを破棄します。読み込んだ画像が2 つの場合は、Effectメソッドを呼び出し、[処理]メニュー項目を選択したときと同じ動作を行います。
[開く]メニュー項目が選択されたときの処理は、これまでのプログラムと同様です。
Effectメソッドが呼び出されたら、画像ファイル名が格納されているmFname を引数にして、CCvFunc クラスのDoCvFunction メソッドを呼び出します。このDoCvFunction メソッドでOpenCvSharp を使った処理が行われます。DoCvFunction メソッドは、処理後のBitmap オブジェクトを返します。それをPictureBox のImage へ設定したのち、フォームのサイズを画像サイズに合わせます。もし、DoCvFunction メソッドが返すBitmap オブジェクトがNothingの場合は、2つの画像が同じサイズでなかったと判断し、エラーメッセージをダイアログで表示します。
[名前を付けて保存]メニュー項目が選択されたら、FileMenuSaveAs_Click メソッドが呼び出されます。CCvFunc クラスのSaveAS メソッドを呼び出し、処理後の画像をファイルに保存します。
ファイルをドロップされたら、Form1_DragDrop メソッドに制御が移ります。これまでは複数のファイルがドロップされても最初のファイルしか処理しませんでした。このプログラムは、ドロップされた全ファイルを処理対象とします。fname に格納されているファイルを" For Each " 文で、すべて処理します。
CCvFunc.vb
本プログラム特有の処理を受け持つCCvFunc クラスのDoCvFunction メソッドを示します。
: Public Function DoCvFunction(ByVal mFname As List(Of String)) As Drawing.Bitmap Using src1 As Mat = Cv2.ImRead(mFname(0)) Using src2 As Mat = Cv2.ImRead(mFname(1)) If src1.Width <> src2.Width OrElse src1.Height <> src2.Height Then Return Nothing End If mDst = New Mat() Cv2.Add(src1, src2, mDst) End Using End Using Return OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mDst) End Function :
本プログラムは、Cv2.Add を使用し、2 つの画像を加算します。画像のサイズが異なる場合、Nothing を返します。
実行
以降に、2つの入力画像と処理結果を示します。
処理結果フォームのステータスバーに、対象となったファイル名が表示されます。
ブレンド
2 つの画像をブレンドする例と差分を取る例を紹介します。先のプログラムと異なるのはDoCvFunction メソッド中の1 行のみです。まず、Cv2.AddWeightedメソッドで、2 つの画像をブレンドする例を示します。
Cv2.Add(src1, src2, mDst)
を
Cv2.AddWeighted(src1, 0.5, src2, 0.5, .0, mDst)
へ変更します。
差分
次に、2 つの画像の差分を取る例を紹介します。先ほど同様、
Cv2.Add(src1, src2, mDst)
を
Cv2.Absdiff(src1, src2, mDst)
へ変更します。
マスクを使った2 つの画像加算
2 つの画像を加算しますが、その際にマスクを使用するプログラムを紹介します。これまでと異なるCCvFuncクラスのDoCvFunctionメソッドを示します。
Public Function DoCvFunction(ByVal mFname As List(Of String)) As Drawing.Bitmap Using src1 As Mat = Cv2.ImRead(mFname(0)) Using src2 As Mat = Cv2.ImRead(mFname(1)) If src1.Width <> src2.Width OrElse src1.Height <> src2.Height Then Return Nothing End If Using mask As Mat = New Mat(src1.Size(), MatType.CV_8UC1, Scalar.Black) Dim p0 As New Point(mask.Width / 4, mask.Height / 4) Dim p1 As New Point(mask.Width * 3 / 4, mask.Height * 3 / 4) Cv2.Rectangle(mask, p0, p1, Scalar.White, -1) mDst = src1.Clone() Cv2.Add(src1, src2, mDst, mask) End Using End Using End Using Return OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mDst) End Function
本プログラムは、最初のプログラムに近く、Cv2.Add メソッドを使用し2 つの画像を加算します。最初のプログラムと異なるのはマスクを使用することです。マスクは、中心部が0 以外で、外周部が0 の値を持つMat オブジェクトです。中心部の、四角形の範囲だけが処理対象となります。
実行
以降に、2つの入力画像と処理結果を示します。
加算だけではなく、差分や論理和でも同じようにマスクを使って、特定の範囲を処理対象とすることができます。以降に、プログラム内で生成したマスクを示します。
ここで使用するCv2.Add メソッドは、すでに紹介したメソッドですが、オーバーロードを使用します。
次に、マスクの形状を変更した例を示します。
Public Function DoCvFunction(ByVal mFname As List(Of String)) As Drawing.Bitmap Using src1 As Mat = Cv2.ImRead(mFname(0)) Using src2 As Mat = Cv2.ImRead(mFname(1)) If src1.Width <> src2.Width OrElse src1.Height <> src2.Height Then Return Nothing End If Using mask As Mat = New Mat(src1.Size(), MatType.CV_8UC1, Scalar.Black) Dim p0 As New Point(mask.Width / 8, mask.Height / 8) Dim p1 As New Point(mask.Width * 7 / 8, mask.Height * 7 / 8) Cv2.Rectangle(mask, p0, p1, Scalar.White, -1) Dim center As New Point(mask.Width / 2, mask.Height / 2) Cv2.Circle(mask, center, mask.Height / 8, Scalar.Black, -1) mDst = src1.Clone() Cv2.Add(src1, src2, mDst, mask) End Using End Using End Using Return OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mDst) End Function
以降に、実行結果とプログラム内で生成したマスクを示します。
少し複雑なマスクの例も示します。
Public Function DoCvFunction(ByVal mFname As List(Of String)) As Drawing.Bitmap : Using mask As Mat = New Mat(src1.Size(), MatType.CV_8UC1, Scalar.Black) Dim xUnit = mask.Width / 8 Dim yUnit = mask.Height / 8 Dim LLPoint2D As New List(Of List(Of Point)) From { New List(Of Point)(New Point() { New Point(4 * xUnit, 1 * yUnit), New Point(7 * xUnit, 6 * yUnit), New Point(1 * xUnit, 6 * yUnit)}), New List(Of Point)(New Point() { New Point(1 * xUnit, 2 * yUnit), New Point(7 * xUnit, 2 * yUnit), New Point(4 * xUnit, 7 * yUnit)})} Cv2.FillPoly(mask, LLPoint2D, Scalar.White) mDst = src1.Clone() Cv2.Add(src1, src2, mDst, mask) End Using : End Function
以降に、実行結果とプログラム内で生成したマスクを示します。
ROIを設定した2 つの画像加算
次に、2 つの画像を加算しますが、Mat オブジェクトにROI を設定し、ROI の範囲だけが処理される例を示します。
Public Function DoCvFunction(ByVal mFname As List(Of String)) As Drawing.Bitmap : Dim roi As New Rect(src1.Cols / 4, src1.Rows / 4, src1.Cols / 2, src1.Rows / 2) mDst = src1.Clone() Using src1Roi As New Mat(src1, roi) Using src2Roi As New Mat(src2, roi) Using dstRoi As New Mat(mDst, roi) Cv2.Add(src1Roi, src2Roi, dstRoi) End Using End Using End Using : End Function
本プログラムは、最初の例をマスクからROI へ変更したものです。Cv2.Add メソッドを使用し2 つの画像を加算しますが、先の例と異なり、マスクの代わりにROI を使用します。Rect オブジェクトroi にROI の座標を設定します。このROI を各画像に設定し、加算を行います。あるいは、roi の範囲のサブマトリックスを指定して加算を行うと表現しても良いでしょう。これによってROI の部分だけが処理対象となります。
以降に、2つの入力画像と処理結果を示します。
加算だけではなく、差分や論理和でも同じようにROIを使って、特定の範囲を処理対象とすることができます。
Matオブジェクトの要素アクセス
Matオブジェクトの各要素を直接操作したい場合があります。いくつか方法はありますが、ここではMatオブジェクトのGetGenericIndexerメソッドを利用し、Matオブジェクトの型を操作する例を示します。速度などを気にしないなら一般的なGet/Setメソッドを利用するのが良いでしょう。ここでは、Get/Setメソッドを利用する例は紹介しません。
CV_8UC1
グレイスケール画像を格納する際に頻繁に利用される、1チャンネル符号なし8ビットの例を示します。
Dim indexer = img.GetGenericIndexer(Of Byte)()
indexer(y, x) = 200
x,yはMatオブジェクト要素を指す座標値です。1チャンネルのため、スカラー値を指定します。
CV_8UC3
カラー画像を格納する際に頻繁に利用される、3チャンネル符号なし8ビットの例を示します。
Dim indexer = img.GetGenericIndexer(Of Vec3b)()
indexer(y, x) = New Vec3b(255, 0, 0)
この例では設定を行っています。indexerの指す要素に、Vec3bの値を設定します。
CV_32FC1
1チャンネル浮動小数点数32ビットの例を示します。
Dim indexer = img.GetGenericIndexer(Of Single)()
indexer(y, x) = 128.0F
CV_64FC3
3チャンネル浮動小数点数64ビットの例を示します。
Dim indexer = img.GetGenericIndexer(Of Vec3d)()
indexer(y, x) = New Vec3d(0.0D, 0.0D, 255D)