07/19のツイートまとめ

Revetronique

Oculus rift + touchが5万!?安い! 買える!! https://t.co/ECGZKYVv3I
07-19 00:56

UnityでOpenCV その4

さて、なんやかんや2か月以上も過ぎていましたがorz
いよいよ前回の実装に入ります。
OpenCVUnity_findcontour1.png

抽出処理については、基本的にこちらの記事を参考にしました。
OpenCVで輪郭抽出から隣接領域の切り出し(その1)輪郭抽出まで

では、抽出処理の流れに沿って、それぞれの処理を見てみましょう。
なお、ここから先は画像処理に関係する処理を主に抜粋して載せています。
追記に当方の作成した全ソースコードを載せているため、先にそちらをご覧いただいてもかまいません。

【前処理】
まずは、元画像、および画像処理とマスク用のMat型インスタンスをそれぞれ用意しておきます。
画像処理用のMatには、元画像のグレースケールを格納します。

//元画像を、リソースフォルダからTexture2Dとして読み込む
Texture2D texture_src = Resources.Load(texturePath) as Texture2D;

//元画像のMatを用意
Mat imgMat= new Mat(src.height, src.width, CvType.CV_8UC4);
//Texture2DをMatに変換(OpenCVUnityのUtilクラス内メソッド)
Utils.texture2DToMat(src, imgMat);

//画像処理用Mat画像
Mat mat_proc = new Mat(imgMat.rows(), imgMat.cols(), CvType.CV_8UC1);
//元画像のグレースケールを入れておく
Imgproc.cvtColor(imgMat, mat_proc, Imgproc.COLOR_RGBA2GRAY);
//マスク用Mat画像
Mat maskMat = new Mat(imgMat.rows(), imgMat.cols(), CvType.CV_8UC1);


また、二値化処理をかけて輪郭抽出に適した画像(モノクロ画像)にしておきます。
二値化処理はいくつか方法があり、まず手動で調整するとこのようになります。

//mat_procは画像処理の対象(Mat)、thresは二値化の閾値(0~255)
//Imgproc.THRESH_BINARY_INV: 閾値より大きい値は0に,それ以外はmaxValに
Imgproc.threshold(mat_proc, mat_proc, thres, 255, Imgproc.THRESH_BINARY_INV);


ただ、閾値は画像の状態によって大きく左右されるため、いちいち手動で値を設定するのは面倒です。
そこで、画像に適した閾値を自動で計算してくれるアルゴリズムを用いた方法もあるため、下で紹介していきます。
まずは大津の手法による二値化処理です。

//大津の手法による、単純2値化処理
Imgproc.threshold(mat_proc, mat_proc, 0, 255, Imgproc.THRESH_BINARY_INV | Imgproc.THRESH_OTSU);


ただし、先ほどの手法でも画素内の明るさが大きく異なる部分があると、処理の結果が芳しくないことがあります。
そこで、それぞれの場所に応じた閾値を設定できる、適応的閾値処理を使うことも可能です(引数の詳しい説明などは省きます)。

//GaussianCによる適応的閾値処理
//adaptiveMethod: Imgproc.ADAPTIVE_THRESH_MEAN_C と分けて使う
Imgproc.adaptiveThreshold(mat_proc, mat_proc, 255, Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, Imgproc.THRESH_BINARY_INV, 201, 11);


【輪郭抽出】
ここまで、輪郭抽出に必要な二値化画像が手に入った段階です。
いよいよ輪郭の抽出に入っていきます。

まずは全体の流れから見ていきましょう。

//procMode: 輪郭抽出の手法を決める列挙型ExtractMode
//ThresArea: 輪郭を抽出したい領域の最小面積

//輪郭
List<MatOfPoint> contours = new List<MatOfPoint>();

//処理状態によってマスク処理に使うMat画像を切り替える
//2値化画像を直接使う
if (procMode == (int)ExtractMode.BINARY)
{
//縮小処理(erode)
Imgproc.erode(maskMat, maskMat, new Mat(), new Point(-1, 1), 1);
//コピー
mat_proc.copyTo(maskMat);
}
//2値化画像の輪郭を切り取って使う
else if (procMode == (int)ExtractMode.CONTOUR)
{
//輪郭抽出の処理
Imgproc.findContours(mat_proc, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
//Imgproc.findContours(mat_proc, contours, new Mat(), Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE); //輪郭内にある輪郭もツリー構造で抽出

//領域(面積)の割合が一定以上の輪郭を探す: getCertainAreaContour
List<MatOfPoint> areas = getCertainAreaContour(contours, ThresArea);
contours.Clear();
foreach (MatOfPoint point in areas)
{
contours.Add(point);
}

//マスク領域の生成
//Imgproc.drawContours の線の太さを負の値にすると、内部の領域も塗りつぶす
Imgproc.drawContours(maskMat, contours, -1, new Scalar(255), -1);
}
//2値化画像の輪郭を直線近似して領域を求める
else if (procMode == (int)ExtractMode.POLYLINE)
{
//輪郭抽出の処理
Imgproc.findContours(mat_proc, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

//領域(面積)の割合が一定以上の輪郭を直線近似
List<MatOfPoint> areas = getCertainAreaContour(contours, ThresArea);
contours.Clear();
foreach (MatOfPoint point in areas)
{
MatOfPoint approxf = getLineApproxContour(point, 0.001);
contours.Add(approxf);
}

//マスク領域の生成
Imgproc.fillPoly(maskMat, contours, new Scalar(255));
}
//2値化画像の輪郭を凸包して領域を求める
else if (procMode == (int)ExtractMode.HULL)
{
//輪郭抽出の処理
Imgproc.findContours(mat_proc, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

//領域(面積)の割合が一定以上の輪郭を直線近似
List<MatOfPoint> areas = getCertainAreaContour(contours, ThresArea);
contours.Clear();
foreach (MatOfPoint point in areas)
{
MatOfPoint mopOut = getHullContour(point);
contours.Add(mopOut);
}

//マスク領域の生成
Imgproc.fillPoly(maskMat, contours, new Scalar(255));
}


実は、上記のスクリプトでは一部、自作のメソッドを用いていたのですが、
各メソッドの実装内容は以下の通りです。

/// <summary>
/// 抽出した輪郭リストから一定以上の面積を持つ輪郭を返す
/// </summary>
/// <param name="contours">輪郭のリスト</param>
/// <param name="thresArea">面積の閾値</param>
/// <returns>面積が閾値以上の輪郭すべて</returns>
List<MatOfPoint> getCertainAreaContour(List<MatOfPoint> contours, double thresArea = 10)
{
List<MatOfPoint> result = new List<MatOfPoint>();

foreach (var each in contours)
{
//面積を求める
double area = Imgproc.contourArea(each);
if (area > thresArea) result.Add(each);
}

return result;
}

/// <summary>
/// 輪郭(MatOfPoint)から直線近似を施した輪郭を返す
/// </summary>
/// <param name="mopIn">変換元の輪郭(MatOfPoint)</param>
/// <param name="approxRate">カーブの細かさ</param>
/// <returns>変換後の輪郭</returns>
MatOfPoint getLineApproxContour(MatOfPoint mopIn, double approxRate = 0.001)
{
MatOfPoint approxf1 = new MatOfPoint();
MatOfPoint2f curveContour = new MatOfPoint2f(mopIn.toArray());
MatOfPoint2f approxContour = new MatOfPoint2f();
Imgproc.approxPolyDP(curveContour, approxContour, approxRate * Imgproc.arcLength(curveContour, true), true);

//直線近似した輪郭を変換
approxContour.convertTo(approxf1, CvType.CV_32S);

return approxf1;
}

/// <summary>
/// 輪郭(MatOfPoint)から凸包近似を施した輪郭を返す
/// </summary>
/// <param name="mopIn">変換元の輪郭(MatOfPoint)</param>
/// <returns>変換後の輪郭</returns>
MatOfPoint getHullContour(MatOfPoint mopIn)
{
MatOfPoint mopOut = new MatOfPoint();

//輪郭を凸包
MatOfInt hull = new MatOfInt();
Imgproc.convexHull(mopIn, hull);

//凸包した輪郭を変換
int size = (int)hull.size().height;
mopOut.create(size, 1, CvType.CV_32SC2);
for (int i = 0; i < size; i++)
{
int index = (int)hull.get(i, 0)[0];
double[] point = new double[] { mopIn.get(index, 0)[0], mopIn.get(index, 0)[1] };
mopOut.put(i, 0, point);
}

return mopOut;
}


【仕上げ】
ここまで、画像の特定の領域に対する輪郭抽出ができました。
ですが、わずかに残るノイズを処理するため、以下の処理を加えて不要な部分を除去します。

//ノイズ除去
//縮小処理(erode)
Imgproc.erode(maskMat, maskMat, new Mat(), new Point(-1, 1), 1);
//膨張処理(dilate)
Imgproc.dilate(maskMat, maskMat, new Mat(), new Point(-1, 1), 1);
//オープニング
Imgproc.morphologyEx(maskMat, maskMat, Imgproc.MORPH_OPEN, new Mat());


【マスク処理】
あとは抽出できた輪郭を使い、画像の一部分だけを取り出す処理を行ってみましょう。

//マスク処理
Mat imgResult = new Mat(src.height, src.width, CvType.CV_8UC4, new Scalar(255, 255, 255));
imgMat.copyTo(imgResult, maskMat); //元画像のマットにマスクを当てはめたうえで結果画像(Mat)にコピー


こうして、輪郭抽出の大まかな流れは終わりです。
以上の処理をもとに輪郭抽出した結果はこちらです。
OpenCVUnity_findcontour2.png

Unityで画像処理をする機会は少ないかもしれませんが、当方の記事が少しでも参考になれば幸いです。
ではまた。

続きを読む

07/08のツイートまとめ

Revetronique

東大制作展(SUKIMANIAC)では、吊革でゲームができるコミュニケーション作品や、グネグネと動いたり固くなったりする3Dプリンタで作成された作品や、言葉が所々欠けていても脳が補填して聞こえるという作品もありました! https://t.co/dXn9XY5iJT
07-08 16:19

東大制作展SUKIMANIAC(スキマニアック)を見学!アプリで2着連動可能なLED Tシャツは黒Tが良かった!HMDはいらないんじゃないかという、大きな半円の中に顔を埋めて動画を見る作品や、アクリル板で作られた透明な地図上で玉を… https://t.co/GRe4FNRIk6
07-08 16:12

07/03のツイートまとめ

Revetronique

4DマシンSWAY CHAIRにも1時間並んでギリギリ。GearVR用コントローラーを長押ししてのシューティングゲーム。UIなのか、使われすぎてなのか、中の展示と違ってトリガーが押せているのか不明…でも5万点超えたからできてたのか… https://t.co/h3tTeuldWK
07-03 20:45

原宿のBANK GALLERYにて、Gear VRを体験中。一番乗りたかったVR PULSAR、これを体験するために来て何度も並んだけれどこれだけ乗れず。挙げ句、メンテナンス中…残念な最終日の #galaxystudio 一回転し… https://t.co/DbmdoSDO2K
07-03 19:18

RT @GalaxyMobileJP: #GalaxyStudio Tokyo🎢5/26(金)スタート❗#GalaxyS8 や日本初上陸のVRアトラクションが勢揃い!GalaxyのSNSフォロー&RTで特典も。詳しくは会場で✨詳細👉 https://t.co/CL3CXC
07-03 16:26

07/01のツイートまとめ

Revetronique

初めての番組協力The music day@幕張メッセに来ております!めっちゃ楽しみ‼ #musicday https://t.co/jBNXNMNxF5
07-01 17:17

プロフィール

Reveちゃん

Author:Reveちゃん
コンビでやってます。
夢担当と技術担当がいます。

大学院卒業 → ロボットベンチャー(漆黒)就職 → 1年で退職 → ベトナムで仕事中(今ここ) → メディアアーティスト(未来☆)

リンクフリーです。

最新記事
最新コメント
月別アーカイブ
カテゴリ
アクセス数
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QR