C#/VB.NETで画像処理⑧<画像の輪郭抽出>
こんにちは、SKです。
C#/VB.NETで画像処理シリーズの第8弾。
カメラ画像の輪郭抽出の処理を実装します。
輪郭抽出とは、二値化画像の白領域と黒領域の境界線(輪郭)を検出する処理のことを言います。
↓参考
オブジェクト輪郭検出 | OpenCV / findContours を使用して画像中のオブジェクトの輪郭を検出する方法
OpenCVSharpとは?
(前回)画像を二値化する
動画手順
①輪郭抽出ボタンを配置する
フォーム上にボタンを配置します。
このボタンを押すと、輪郭抽出フラグを立て、カメラ画像を二値化・輪郭抽出し、輪郭に赤い線を描画してPictureBoxに表示するようにします。
表示テキストは"Contours"に。
輪郭抽出フラグのbool変数isContoursを追加します。
②画像を二値化する
まずは前回記事同様、画像を二値化します。
ただ今回、PictureBoxには元のRGB画像に輪郭線のみ描画したカラー画像を表示するため、二値化の出力画像は別変数grayに格納します。
まずはこう。
局所的に使用するMat型変数は、.NETの最強のリソース開放システムである"Using"を使って定義するようにしましょう!
Usingについて
C# Tips −usingを使え、使えったら使え(^^)−
③輪郭を抽出する
輪郭抽出には、OpenCVのFindContours関数を使用しますが、輪郭座標のみ抜き出す場合は、その派生関数であるFindContoursAsArrayを使うほうがシンプルに記述できます。これはOpenCVには無く、OpenCVSharp特有の便利関数ですね。
FindContoursAsArray関数
Cv2.FindContoursAsArray Method
・第1引数:入力画像(二値化)
・第2引数:輪郭抽出モード
・第3引数:輪郭近似モード
第2/3引数のモードについては、下記リンクが参考になります。
https://www.pynote.info/entry/opencv-findcontours
ここでは、RetrievalModes.Tree、ContourApproximationModes.ApproxSimpleを使います。
また、戻り値はPoint型の二次元配列になっているため、二重Forループで座標を抜き出し、Cv2.Line関数で輪郭に赤い線を描画しています。
private void Process(Mat img) { if (isContours) { using (Mat gray = new Mat(img.Size(), MatType.CV_8UC1)) { Cv2.CvtColor(img, gray, ColorConversionCodes.BGR2GRAY); Cv2.Threshold(gray, gray, 0, 255, ThresholdTypes.Otsu); Point[][] contours = Cv2.FindContoursAsArray(gray, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple); foreach (Point[] points in contours) { for (int i = 0; i < points.Length; i++) { Cv2.Line(img, points[i], points[(i + 1) % points.Length], Scalar.Red, 2); } } } } }
このあたりは記述方法にやや癖があるかもしれませんが、慣れて使いこなしていきましょう!
実行します。
起動時
Contours押下時
最後に、コード全文を載せておきます。
C#
using System; using System.Threading.Tasks; using System.Windows.Forms; using OpenCvSharp; using OpenCvSharp.Extensions; namespace WindowsFormsApp1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { Task.Run(() => { using (VideoCapture v = new VideoCapture(0)) using (Mat img = new Mat()) { while (true) { v.Read(img); Process(img); pictureBox1.Image = BitmapConverter.ToBitmap(img); } } }); } private bool isContours = false; private void Process(Mat img) { if (isContours) { using (Mat gray = new Mat(img.Size(), MatType.CV_8UC1)) { Cv2.CvtColor(img, gray, ColorConversionCodes.BGR2GRAY); Cv2.Threshold(gray, gray, 0, 255, ThresholdTypes.Otsu); Point[][] contours = Cv2.FindContoursAsArray(gray, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple); foreach (Point[] points in contours) { for (int i = 0; i < points.Length; i++) { Cv2.Line(img, points[i], points[(i + 1) % points.Length], Scalar.Red, 2); } } } } } private void button1_Click(object sender, EventArgs e) { isContours = !isContours; } } }
Imports OpenCvSharp Imports OpenCvSharp.Extensions Public Class Form1 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load Task.Run(Sub() Using v As New VideoCapture(0) Using img As New Mat Do v.Read(img) Process(img) PictureBox1.Image = img.ToBitmap Loop End Using End Using End Sub) End Sub Private isContours As Boolean = False Private Sub Process(ByVal img As Mat) If isContours Then Using gray As New Mat(img.Size, MatType.CV_8UC1) Cv2.CvtColor(img, gray, ColorConversionCodes.BGR2GRAY) Cv2.Threshold(gray, gray, 0, 255, ThresholdTypes.Otsu) Dim contours As Point()() = Cv2.FindContoursAsArray(gray, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple) For Each points As Point() In contours For i = 0 To points.Length - 1 Cv2.Line(img, points(i), points((i + 1) Mod points.Length), Scalar.Red, 2) Next Next End Using End If End Sub Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click isContours = Not isContours End Sub End Class