クラスを導入し、ユーザーインターフェースとOpenCvSharpを分離したものを紹介します。
UIとOpenCVを分離
ユーザーインターフェースとOpenCvSharp に関するコードを分離する例を紹介します。これまでのプログラムはForm に対応したソースファイルがOpenCvSharp を参照しており、ユーザーインターフェースとOpenCV が混在していました。本プログラムは、OpenCvSharpをクラス内に封じ込め、ユーザーインターフェースとOpenCV を完全に分離します。これによって、プログラムを読みやすくするとともにメンテナンス性の向上も目指します。
Form は従来のVisual Basic で開発したプログラムと同様とし、クラスはOpenCV の共通機能を実現したメソッドなどで構成されたベースクラスと特定のアプリケーションに依存した派生クラスから成り立ちます。OpenCV に対応したクラスの構造を図に示します。
これまでは、ユーザーインターフェースとOpenCvSharpが同居していたため、System 名前空間と OpenCvSharp 名前空間の両方に存在する同一名のクラスなどは、どちらのものであるか明示する必要がありました。本プログラム以降は、ユーザーインターフェースとOpenCVを利用するファイルを分離したため、System 名前空間や OpenCvSharp 名前空間を明示的に示す必要はありません。例えばForm1などのSizeはSystem.Drawing.Sizeであり、CCvクラスのSizeはOpenCvSharp.Sizeです。これ以降は名前空間を明示的に示す必要はありませんが、理解を助けるために明示しても構いません。
共通に使われる機能を、スーパークラス(基底クラス)のCCv クラスに実装し、各プログラム特有の機能をCCv クラスのサブクラス(派生クラス)であるCCvFunc クラスに実装します。
Form1.vb
まず、読み込んだ画像を表示するフォームに対するコードが記述されているForm1.vb を示します。これまでのプログラムと異なる部分のみを示します。
Imports System Imports System.IO Imports Filters.CCvLibrary Public Class Form1 Private ReadOnly ttl As String = "sample" Private ReadOnly mForm2 As Form2 = Nothing Private ccvfunc As CCvFunc = Nothing Public Sub New() : mForm2 = New Form2 ccvfunc = New CCvFunc() End Sub : ' open file Private Sub OpenFile(ByVal Optional fname As String = Nothing) Dim result = ccvfunc.OpenFileCv(fname) If result.Item2 Is Nothing Then Return End If PBox.Image = result.Item2 PBox.Size = PBox.Image.Size AdjustWinSize(PBox.Image) ' ウィンドウサイズ調整 toolSSLbl.Text = Path.GetFileName(result.Item1) 'ファイル名表示 If mForm2.Validate() Then mForm2.Hide() 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 ToolMenuEffect_Click(sender As Object, e As EventArgs) _ Handles ToolMenuEffect.Click Try If PBox.Image Is Nothing Then Return ' 読み込んでいるか Cursor = Cursors.WaitCursor mForm2.DoCvShow(ccvfunc) Catch ex As Exception MessageBox.Show(ex.Message) Finally Cursor = Cursors.[Default] End Try End Sub :
ユーザーインターフェースとOpenCvSharp に関するコードを分離したため、「Imports OpenCvSharp 」がなくなり、開発したクラスを利用するための「Imports Filters.CCvLibrary 」を追加します。
コンストラクター内でフォームのタイトルやステータスバーを設定するのは、これまでと同様です。コンストラクターの最後で、CCv クラスのサブクラスCCvFunc をインスタンス化します。
OpenFile メソッドは、ファイルの読み込みと表示などをまとめたものです。これまではCIo クラスを利用していましたが、本プログラムはコンストラクターでインスタンス化したCCvFunc クラスのOpenFileCv メソッドを使用します。
[開く]メニュー項目が選択されたときに呼び出されるFileMenuOpen_Click メソッドも単純化され、OpenFile メソッドを呼び出します。
[処理]メニュー項目が選択されたときに呼び出されるToolMenuEffect_Click メソッドは、CCvFunc クラスのインスタンスを引数にForm2 のDoCvShow メソッドを呼び出します。
Form2.vb
処理結果を表示するフォームに対するコードが記述されているForm2.vb を示します。これまでのプログラムと異なる部分を主に示します。
Imports System Imports Filters.CCvLibrary Public Class Form2 Private ReadOnly ttl As String = "処理結果" Private mCcvfunc As CCvFunc = Nothing : Private Sub FileMenuSaveAs_Click(sender As Object, e As EventArgs) _ Handles FileMenuSaveAs.Click Try mCcvfunc.SaveAS() Catch ex As Exception MessageBox.Show(ex.Message) End Try End Sub : Public Sub DoCvShow(ByVal ccvfunc As CCvFunc) mCcvfunc = ccvfunc Dim bmp As Bitmap = mCcvfunc.DoCvFunction() If bmp IsNot Nothing Then PBox.Image = bmp AdjustWinSize(PBox.Image) Show() End If End Sub :
Form1 同様、ユーザーインターフェースとOpenCvSharp に関するコードを分離したため、「Imports OpenCvSharp 」の代わりに「 Imports Filters.CCvLibrary 」を追加します。Form1 から渡されるCCvFunc クラスのインスタンスを保持するため、Private フィールドmCcvfunc を宣言します。
[名前を付けて保存]メニュー項目が選択されたときに制御が渡るのがFileMenuSaveAs_Click メソッドです。単純にmCcvfunc のSaveAS メソッドを呼び出すだけです。実際にOpenCvSharp を使用して保存する処理はクラスにカプセル化されます。
DoCvShow メソッドは、Form1 で[処理]メニュー項目が選択されたときに呼び出されます。渡されたCCvFunc クラスのインスタンスをmCcvfunc へ保存したのち、DoCvFunction メソッドを呼び出し、受け取ったBitmap オブジェクトを表示します。画像処理自体はDoCvFunctionメソッドで行われます。
CCvFunc クラスのSaveAS メソッドやDoCvFunction メソッドは、これまでのプログラムのフォームに対応したソースコードで実行していたものを、クラス内へ移動しただけです。このようにクラスを利用すると、同じコードを何回も記述する必要はなくなります。
CCvFunc クラス
以降にCCvFunc クラスのソースコードを示します。
Imports OpenCvSharp Namespace CCvLibrary Public Class CCvFunc Inherits CCv 'コンストラクタ Public Sub New() MyBase.New() End Sub ' OpenCVを使用して処理 Public Function DoCvFunction() As Bitmap mDst = New Mat() Return OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mDst) End Function End Class End Namespace
単純に、既に紹介した「ブラー処理」のプログラムをクラス化しただけです。DoCvFunction メソッドの内容を書き換えるだけで、いろいろな画像処理へ対応できます。
CCv クラス
本クラスに基本的な機能を実装します。後で使用するメソッドやプロパティも実装しています。簡単ですので、各メソッドの説明は省きます。
Imports OpenCvSharp Namespace CCvLibrary Public Class CCv Private _mSrc As Mat Private _mDst As Mat Protected Property mSrc() As Mat Get Return _mSrc End Get Set _mSrc = Value End Set End Property Protected Property mDst() As Mat Get Return _mDst End Get Set _mDst = Value End Set End Property '---------------------------------------------------------------- 'コンストラクタ Public Sub New() End Sub '---------------------------------------------------------------- ' 読み込みファイル名を取得、 ' ダイアログを使用して読み込みファイルを選択させる Public Function GetReadFile(Optional filter As _ String = "画像ファイル(*.jpg,*.bmp,*.png)|*.jpg;*.bmp;*.png|" _ & "すべてのファイル(*.*)|*.*") As String Dim fname As String = Nothing Using openDlg As OpenFileDialog = New OpenFileDialog() openDlg.CheckFileExists = True openDlg.Filter = filter openDlg.FilterIndex = 1 If openDlg.ShowDialog() = DialogResult.OK _ Then fname = openDlg.FileName End Using Return fname End Function '---------------------------------------------------------------- ' 書き込みファイル名を取得、 ' ダイアログを使用して読み込みファイルを選択させる Public Function GetWriteFile(Optional filter As _ String = "画像ファイル(*.jpg,*.bmp,*.png)|*.jpg;*.bmp;*.png|" _ & "すべてのファイル(*.*)|*.*") As String Dim fname As String = Nothing Using svDlg As SaveFileDialog = New SaveFileDialog() svDlg.Filter = filter svDlg.FilterIndex = 1 If svDlg.ShowDialog() = DialogResult.OK _ Then fname = svDlg.FileName End Using Return fname End Function '---------------------------------------------------------------- ' ファイルを開く、ファイル名が指定されていない場合は、 ' ダイアログを使用して読み込みファイルを選択させる Public Function OpenFileCv(fname As String) As (String, Bitmap) Dim bmp As Bitmap = Nothing Dim newfname = fname If Equals(fname, Nothing) Then newfname = GetReadFile() End If If Not Equals(newfname, Nothing) Then Dim img As Mat = Cv2.ImRead(newfname) If Not img.Empty() Then mSrc = img bmp = Extensions.BitmapConverter.ToBitmap(mSrc) End If End If Return (newfname, bmp) End Function '---------------------------------------------------------------- ' 名前を付けてファイルを保存 Public Sub SaveAS() Dim fname As String = GetWriteFile() If Not Equals(fname, Nothing) Then Cv2.ImWrite(fname, mDst) ' OpenCV End If End Sub '---------------------------------------------------------------- ' create cos k mat Public Function CreateCosMat(rows As Integer, cols As Integer) As Mat Dim mat As New Mat(rows, cols, MatType.CV_8UC3, New Scalar(0)) '画像アクセス用 Dim indexer_dst As MatIndexer(Of Vec3b) = mat.GetGenericIndexer(Of Vec3b)() Dim center As New Point(cols / 2, rows / 2) Dim radius = Math.Sqrt(Math.Pow(center.X, 2) + Math.Pow(center.Y, 2)) 'ピクセルアクセス For y = 0 To mat.Height - 1 For x = 0 To mat.Width - 1 ' distance from center Dim distance = Math.Sqrt( Math.Pow(center.X - x, 2) + Math.Pow(center.Y - y, 2)) ' radius=π, current radian Dim radian = distance / radius * Math.PI ' cosθ, normalize -1.0~1.0 to 0~1.0 Dim cd = (Math.Cos(radian) + 1.0) / 2.0 ' normalize (Y) 0~1.0 to 0.0~255.0 cd *= 255.0 indexer_dst(y, x) = New Vec3b(cd, cd, cd) 'mat.Set(y, x, New Vec3b(cd, cd, cd)) 'インデックサーを用いない Next Next Return mat End Function '---------------------------------------------------------------- ' mulMask Public Function MulMat(mat As Mat, table As Mat) As Mat Dim dst As New Mat() Using mat32f As New Mat(), dst32f As New Mat() Dim table32f As New Mat() mat.ConvertTo(mat32f, MatType.CV_32FC3) table.ConvertTo(table32f, MatType.CV_32FC3) table32f /= 255.0F Cv2.Multiply(mat32f, table32f, dst32f) dst32f.ConvertTo(dst, MatType.CV_8UC3) End Using Return dst End Function '--------------------------------------------------------- ' Size change by Gausian ' ' dst: Mat ' ListRect: areas ' toSmall: 0:to Big, 1:to small ' Protected Function doChgObjsGausian(dst As Mat, ListRect As List(Of Rectangle), toSmall As Integer) As Drawing.Bitmap For Each rr In ListRect If rr.Width = 0 OrElse rr.Height = 0 Then Continue For 'skip if area is 0 Dim rect As New Rect(rr.X, rr.Y, rr.Width, rr.Height) Dim obj As New Mat(dst, rect) ' set roi Dim mapX As New Mat(obj.Size(), MatType.CV_32FC1) ' map x cord. mat Dim mapY As New Mat(obj.Size(), MatType.CV_32FC1) ' map y cord. mat Dim cx As Single = obj.Cols / 2.0F ' center cord. Dim cy As Single = obj.Rows / 2.0F Dim indexerX = mapX.GetGenericIndexer(Of Single)() Dim indexerY = mapY.GetGenericIndexer(Of Single)() For y As Integer = 0 To obj.Rows - 1 ' calc src cord. For x As Integer = 0 To obj.Cols - 1 Dim dx = x - cx ' x cord. form center Dim dy = y - cy ' y cord. form center Dim r = Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2)) ' distabce ' ガウス関数、 u: 0, a: 1, sigma: obj.Cols / 8 Dim gauss = gaussf(r, 1.0F, 0.0F, obj.Cols / 8.0F) If toSmall = 0 Then ' 変換座標の計算 indexerX(y, x) = cx + (dx / (gauss + 1.0F)) indexerY(y, x) = cy + (dy / (gauss + 1.0F)) Else indexerX(y, x) = cx + (dx * (gauss + 1.0F)) indexerY(y, x) = cy + (dy * (gauss + 1.0F)) End If Next Next Cv2.Remap(obj, obj, mapX, mapY, InterpolationFlags.Cubic, BorderTypes.Replicate) Next Return Extensions.BitmapConverter.ToBitmap(dst) End Function '--------------------------------------------------------- ' gaussf Private Function gaussf(x As Single, a As Single, mu As Single, sigma As Single) As Single Return a * CSng(Math.Exp(-Math.Pow(x - mu, 2) / (2 * Math.Pow(sigma, 2)))) End Function '---------------------------------------------------------------- ' Size change by Cos Table ' ' src: source Mat ' dst: destination Mat ' ListRect: areas ' scale: scale ' Protected Function DoChgObjs(src As Mat, dst As Mat, ListRect As List(Of Rectangle), scale As Single) As Drawing.Bitmap Dim srcobjs As New List(Of Mat)(), dstobjs As New List(Of Mat)() For Each r In ListRect If r.Width = 0 OrElse r.Height = 0 Then Continue For 'skip if area is 0 If scale > 1.0F Then ' to Big '入力切り出し Dim srcrect As New Rect(r.X, r.Y, r.Width, r.Height) Dim srcroi As New Mat(src, srcrect) srcobjs.Add(srcroi) '出力切り出し、少し大きくする Dim deltaW As Integer = CInt(r.Width * (scale - 1.0F)) / 2 Dim deltaH As Integer = CInt(r.Height * (scale - 1.0F)) / 2 Dim dstrect As New Rect(r.X - deltaW, r.Y - deltaH, r.Width + (deltaW * 2), r.Height + deltaH * 2) Dim cliprect As Rect = ClipIt(dst.Size(), dstrect) Dim dstroi As New Mat(dst, cliprect) dstobjs.Add(dstroi) ' to Small Else '入力切り出し、少し大きくする Dim deltaW As Integer = CInt(r.Width * (1.0F - scale)) / 2 Dim deltaH As Integer = CInt(r.Height * (1.0F - scale)) / 2 Dim srcrect As New Rect(r.X - deltaW, r.Y - deltaH, r.Width + (deltaW * 2), r.Height + (deltaH * 2)) Dim cliprect As Rect = ClipIt(dst.Size(), srcrect) Dim srcroi As New Mat(src, cliprect) srcobjs.Add(srcroi) '出力切り出し Dim dstrect As New Rect(r.X, r.Y, r.Width, r.Height) Dim dstroi As New Mat(dst, dstrect) dstobjs.Add(dstroi) End If Next ' 大きさを合わせる For i = 0 To srcobjs.Count - 1 Cv2.Resize(srcobjs(i), srcobjs(i), New Size(dstobjs(i).Cols, dstobjs(i).Rows)) Next ' マージ、重みづけ加算 For i = 0 To srcobjs.Count - 1 Dim weightMat As Mat = CreateCosMat(srcobjs(i).Rows, srcobjs(i).Cols) Dim iWeightMat As Mat = Scalar.All(255) - weightMat Dim srcWeight As Mat = MulMat(srcobjs(i), weightMat) Dim dstWeight As Mat = MulMat(dstobjs(i), iWeightMat) Cv2.Add(dstWeight, srcWeight, dstobjs(i)) Next Return Extensions.BitmapConverter.ToBitmap(dst) End Function '--------------------------------------------------------- ' clip Rect Private Function ClipIt(size As Size, rect As Rect) As Rect Dim clip As Rect = rect clip.Width = If(rect.X < 0, rect.Width + rect.X, clip.Width) clip.X = If(rect.X < 0, 0, clip.X) clip.Height = If(rect.Y < 0, rect.Height + rect.Y, clip.Height) clip.Y = If(rect.Y < 0, 0, clip.Y) clip.Width = If(clip.X + rect.Width >= size.Width, size.Width - clip.X, clip.Width) clip.Height = If(clip.Y + rect.Height >= size.Height, size.Height - clip.Y, clip.Height) Return clip End Function End Class End Namespace