2012年4月24日火曜日

エッジを検出する その3 - OpenCV for Android


続いてCannyフィルタです。

ガウシアンフィルタとSobelフィルタを組み合わせてエッジを検出します。
まず、ガウシアンフィルタでぼけた画像を作り、その画像にSobelフィルタをかけることでエッジを検出します。
実際には処理はそれだけではなく、色々とやっているようです。

Cannyフィルタを利用するには、Imgproc.Canny()メソッドを利用します。

Imgproc.Canny(Mat image, Mat edges, double threshold1,
double threshold2, int apertureSize, boolean L2gradient)

Mat src          処理したい元画像のMat
Mat edges        変換後Mat
double threshold1   第1閾値
double threshold2   第2閾値
int apertureSize    Sobelのアパーチャサイズ
boolean L2gradient  L2ノルム利用有無


threshold1、threshold2については、値が小さいほうがエッジ同士を接続するために用いられ、大きいほうが強いエッジの初期検出に用いられます。
順番を変えても結果は同じです。

apertureSizeについては、デフォルトが3で、3、5、7が選択出来ます。

L2gradientについては、画像勾配の強度を求めるために、精度の高いL2ノルムを利用するか、高速なL1ノルムを利用するかを設定します。
デフォルトではL1ノルム(false)です。


それでは、実際にやってみましょう。

元画像です。


Imgproc.Canny(mat, dstMat, 50, 200);


Imgproc.Canny(mat, dstMat, 100, 200);


Imgproc.Canny(mat, dstMat, 100, 300);


Imgproc.Canny(mat, dstMat, 50, 200, 5);


Imgproc.Canny(mat, dstMat, 50, 200, 7);


Imgproc.Canny(mat, dstMat, 100, 200, 3, false);


Imgproc.Canny(mat, dstMat, 100, 200, 3, true);


ちなみに平滑化⇒Sobelで同じような結果になるんでしょうか?
Imgproc.GaussianBlur(mat, mat, new Size(3, 3), 0, 0);
Imgproc.Sobel(mat, dstMat, mat.type(), 1, 1, 5);


そう単純では無いようですねw


エッジを検出する その2 - OpenCV for Android


続いてラプラシアンフィルタです。

ラプラシアンフィルタは2次微分を計算するフィルタで、Imgproc.Laplacian()メソッドを利用します。

Imgproc.Laplacian(Mat src, Mat dst, int ddepth,
int ksize, double scale, double delta, int borderType)

Mat src     処理したい元画像のMat
Mat dst     変換後Mat
int ddepth    変換後のビット深度
int ksize     カーネルサイズ
double scale  計算されたデリバティブの値の任意のスケールファクタ
double delta  オプションのデルタ値
int borderType ピクセル外挿手法


引数はその1でやったImgproc.Sobel()に出てきたものと同じです。

それでは、実際にやってみましょう。

おなじみの元画像です。


Imgproc.Laplacian(mat, dstMat, mat.type(), 1);



ksizeを変更してみます。
Imgproc.Laplacian(mat, dstMat, mat.type(), 7);



scaleを設定してみます。
Imgproc.Laplacian(mat, dstMat, mat.type(), 1, 50.0);



deltaを設定してみます。
Imgproc.Laplacian(mat, dstMat, mat.type(), 1, 1.0, 100.0);


x方向、y方向が無いので、Imgproc.Sobel()より単純ですね。


2012年4月15日日曜日

エッジを検出する その1 - OpenCV for Android


いよいよ画像解析っぽくなってきました。

エッジ検出とは、画素の明るさが急激に変化する場所を探し、画像の輪郭を算出する処理を指します。
明るさの変化は、微分演算を利用することで算出し、グラディエント(1次微分)とラプラシアン(2次微分)があります。

1次微分を計算するSobelフィルタを利用するには、Imgproc.Sobel()メソッドを利用します。

imgproc.Imgproc.Sobel(Mat src, Mat dst, int ddepth,
int dx, int dy, int ksize, double scale, double delta, int borderType))

Mat src     処理したい元画像のMat
Mat dst     変換後Mat
int ddepth    変換後のビット深度
int dx       dx
int dy       dy
int ksize     カーネルサイズ
double scale  計算されたデリバティブの値の任意のスケールファクタ
double delta  オプションのデルタ値
int borderType ピクセル外挿手法

dx、dyについては、ksizeが1の場合を除き、必ずksize未満である必要があります。
ksizeが1の場合は、3 x 1もしくは1 x 3のカーネルが利用されますので、dx、dyの最大値は2です。

ksizeについては、ksizeが1の場合を除き、導関数を計算するためにksize × ksizeのカーネルが利用されます。
ksizeは奇数かつ31以下である必要があります。

ksizeが特殊な値「Imgproc.CV_SCHARR」の場合、3×3のSobelフィルタより精度が高い、3×3のScharrフィルタに対応します。
Scharrフィルタを利用する場合、dxかdyのどちらかが1、どちらかが0である必要があります。


それでは、実際にやってみましょう。

グレースケールに変換してから処理します。

まずは元画像。


Imgproc.Sobel(mat, dstMat, mat.type(), 1, 0, 1);


dxを変更してみます。
Imgproc.Sobel(mat, dstMat, mat.type(), 2, 0, 1);


ドキュメントには、このメソッドはほとんどの場合(dx = 1, dy = 0, ksize = 3)もしくは(dx = 0, dy = 1, ksize = 3) の引数で呼び出されるとあるので、それぞれ試してみます。
Imgproc.Sobel(mat, dstMat, mat.type(), 1, 0, 3);


Imgproc.Sobel(mat, dstMat, mat.type(), 0, 1, 3);


ksizeを変更してみます。
Imgproc.Sobel(mat, dstMat, mat.type(), 1, 1, 7);


Imgproc.Sobel(mat, dstMat, mat.type(), 1, 1, 21);


Scharrフィルタを利用してみます。
Imgproc.Sobel(mat, dstMat, mat.type(), 1, 0,
  Imgproc.CV_SCHARR);


Imgproc.Sobel(mat, dstMat, mat.type(), 0, 1,
  Imgproc.CV_SCHARR);


scaleを変更してみます。
Imgproc.Sobel(mat, dstMat, mat.type(), 1, 1, 3, 50.0);


deltaを変更してみます。
Imgproc.Sobel(mat, dstMat, mat.type(), 1, 1, 3, 1.0,
  100.0);


ところで微分って何?おいしいの?

2012年3月24日土曜日

画像を平滑化する その2 - OpenCV for Android


前回に続いて平滑化です。

ボックスフィルタを用いた平滑化を行うにはImgproc.boxFilter()メソッドを利用します。


Imgproc.boxFilter(Mat src, Mat dst, int ddepth,
Size ksize, Point anchor, boolean normalize, int borderType)

Mat src         処理したい元画像のMat
Mat dst         変換後Mat
int ddepth        変換後のビット深度
Size ksize       カーネルサイズ
Point anchor      アンカー点
boolean normalize  正規化の有無
int borderType     ピクセル外挿手法


ddepthについては、変換後の画像のビット深度を指定します。
通常はsrc.type()を指定すれば良いようです。

ksizeについては、この値が大きくなるとぼかしが強くなります。

anchorについては、どちらかに寄せると画像が少しズレます。
new Size(-1, -1)で中心を指定すれば良いようです。

normalizeについては、trueを指定すると正規化されます。
正規化されていないボックスフィルタは、特徴検出などに利用されるようです。

また、Imgproc.blur()というメソッドもありますが、boxFilter()メソッドの第6引数にtrueを指定したものと同じ結果になります。



それでは、実際にやってみましょう。

元画像です。


正規化有です。
Imgproc.boxFilter(mat, dstMat, mat.type(),
  new Size(10, 10), new Point(-1, -1), true);


ksizeを変えてみます。
Imgproc.boxFilter(mat, dstMat, mat.type(),
  new Size(50, 5), new Point(-1, -1), true);


正規化無しです。
Imgproc.boxFilter(mat, dstMat, mat.type(),
  new Size(2, 1), new Point(-1, -1), false);


ksizeを変えてみます。
あまり大きな数字にすると画像が真っ白になります。
Imgproc.boxFilter(mat, dstMat, mat.type(),
  new Size(2, 2), new Point(-1, -1), false);


ちなみにddepthにおかしなtypeを指定すると、処理は可能ですが当然こうなります。
Imgproc.boxFilter(mat, dstMat, CvType.CV_16S,
  new Size(10, 10), new Point(-1, -1), true);


余談でしたね。

画像を平滑化する その1 - OpenCV for Android


おなじみの平滑化です。

はい。すいません。読めすらしません。
「へいかつか」と読みますが、いわゆるぼかし処理です。

いくつか種類があるようで、利用するフィルタ毎にメソッドが用意されています。

ガウシアンフィルタを用いた平滑化を行うにはImgproc.GaussianBlur()メソッドを利用します。

Imgproc.GaussianBlur(Mat src, Mat dst, Size ksize,
double sigma1, double sigma2, int borderType)

Mat src      処理したい元画像のMat
Mat dst      変換後Mat
Size ksize    ガウシアンカーネルサイズ
double sigma1  ガウシアンカーネルのx方向の標準偏差
double sigma2  ガウシアンカーネルのy方向の標準偏差
int borderType  ピクセル外挿手法


ksizeについては、ksize.widthとksize.heightがそれぞれ正の奇数である必要があります。
サイズを大きくすると、それだけぼかしが強くなります。
ゼロを指定した場合は、sigma1、sigma2から計算されます。

sigma1、sigma2については、それぞれx方向(横方向)、y方向(縦方向)の標準偏差です。
ksizeの対応する方向軸がゼロの場合のみ計算に利用されます。
片方がゼロであれば、もう片方と等しくなるよう設定され、両方がゼロであればksizeから計算されます。

borderTypeについては、画像を回転する その2 - OpenCV for Androidの第6引数と同じで、画像の外側をどう扱うかを指定します。
例によって省略可能です。



メディアンフィルタを用いた平滑化を行うにはImgproc.medianBlur()メソッドを利用します。

Imgproc.medianBlur(Mat src, Mat dst, int ksize)

Mat src     処理したい元画像のMat
Mat dst     変換後Mat
int ksize    アパーチャサイズ


ksizeについては、1より大きな奇数を指定します。
ネットで調べるとksizeによって、処理出来る画像の画像のビット深度が違うと書いてありますが、特にエラーは出ませんでした。



バイラテラルフィルタを用いた平滑化を行うにはImgproc.bilateralFilter()メソッドを利用します。
基本的に処理が重たい割りに、大きな変化はありません。

Imgproc.bilateralFilter(Mat src, Mat dst, int d,
double sigmaColor, double sigmaSpace, int borderType)

Mat src          処理したい元画像のMat
Mat dst          変換後Mat
int d            各ピクセル近傍領域の直径
double sigmaColor   色空間におけるフィルタシグマ
double sigmaSpace  座標空間におけるフィルタシグマ
int borderType      ピクセル外挿手法

srcについては、tyepがCV_8UC1かCV_8UC3である必要があります。
CvType.typeToString(mat.type())で確認し、必要に応じて変換して処理します。

dについては、この値が大きくなるとぼかしが強くなります。
ゼロ以下の場合は、sigmaSpaceから計算されます。

sigmaColorについては、この値が大きくなると、近くにある色的により遠くのピクセルが混ぜ合わせられます。

sigmaSpaceについては、この値が大きくなると、より遠くのピクセル同士が影響しあいます。
dがゼロ以下の場合のみ計算に利用されます。

borderTypeについては、同じく省略可能です。



それでは長くなりましたが、実際にやってみましょう。


元画像です。


ガウシアンフィルタです。
Imgproc.GaussianBlur(mat, dstMat, new Size(11, 11), 0, 0);


ksizeを変更してみます。
Imgproc.GaussianBlur(mat, dstMat, new Size(51, 3), 0, 0);


メディアンフィルタです。
Imgproc.medianBlur(mat, dstMat, 7);


ksizeを変更してみます。
Imgproc.medianBlur(mat, dstMat, 21);


バイラテラルフィルタです。
Imgproc.cvtColor(mat, mat, Imgproc.COLOR_RGBA2BGR);
Imgproc.bilateralFilter(mat, dstMat, 10, 50, 0);
Imgproc.cvtColor(dstMat, dstMat, Imgproc.COLOR_BGR2RGBA);


dを変更してみます。
かなり重たいです。
Imgproc.cvtColor(mat, mat, Imgproc.COLOR_RGBA2BGR);
Imgproc.bilateralFilter(mat, dstMat, 50, 50, 0);
Imgproc.cvtColor(dstMat, dstMat, Imgproc.COLOR_BGR2RGBA);




しかし、なぜGaussianBlur()だけ大文字から始まるんでしょう??
謎です。


2012年3月20日火曜日

画像の膨張・収縮処理を行う その3 - OpenCV for Android


膨張、収縮処理についてもう一点。

kernelことdilate()、erode()、morphologyEx()メソッドに渡す構造要素についてです。

はじめは以下のように、コンストラクタでサイズを指定してMatクラスのインスタンスを生成したのですが、どうやらこれでは間違っているようです。
Imgproc.dilate(mat, mat, new Mat(5, 10, CvType.CV_8UC1));




実行するたびに若干違う結果になったり、数ピクセル画像がズレたり、正しく画像を処理できません。

デフォルトコンストラクタでMatを生成した場合は特に問題は起きないので、サイズがゼロだと正しく3×3の構造要素を作成してくれるようです。

では構造要素を指定したい場合どうするかと言うと、Imgproc.getStructuringElement()メソッドを利用します。


Imgproc.getStructuringElement(int shape, Size ksize, Point anchor)

int shape   構造要素の形状
Size ksize  構造要素のサイズ
Point anchor 要素内のアンカー位置


shapeについては、Imgprocに用意された以下の定数から選択します。

MORPH_RECT   矩形構造要素
MORPH_ELLIPSE  楕円構造要素
MORPH_CROSS  十字形構造要素


anchorについては、十字形構造要素の場合にのみ意味を成します。


それでは実際にやってみましょう。

矩形構造要素です。
Imgproc.dilate(mat, mat,
  Imgproc.getStructuringElement(Imgproc.MORPH_RECT,
  new Size(2, 10));


楕円構造要素です。若干丸みがありますね。
Imgproc.dilate(mat, mat,
  Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE,
  new Size(2, 10));


十字形構造要素です。まずはアンカー位置デフォルトから。
Imgproc.dilate(mat, mat,
  Imgproc.getStructuringElement(Imgproc.MORPH_CROSS,
  new Size(5, 10), new Point(-1, -1)));


アンカー位置を端によせてみます。
Imgproc.dilate(mat, mat,
  Imgproc.getStructuringElement(Imgproc.MORPH_CROSS,
  new Size(5, 10), new Point(4, 9)));


逆サイドへ。
Imgproc.dilate(mat, mat,
  Imgproc.getStructuringElement(Imgproc.MORPH_CROSS,
  new Size(5, 10), new Point(0, 0)));


色々な形状の構造要素を試してみくなりましたが、それはまた別の機会に。

画像の膨張・収縮処理を行う その2 - OpenCV for Android


前回実験した、膨張(Dilation)と収縮(Erosion)はそれ単体で利用されることは少なく、それぞれを組み合わせることで様々な画像処理を行います。
このあたりをまとめて「モルフォロジー演算」と呼び、ノイズ除去や平滑化などに利用されます。

OpenCVでは膨張、収縮を併せると7種類の演算が実装されていて、Imgproc.morphologyEx()メソッドを利用します。

Imgproc.morphologyEx(Mat src, Mat dst, int op, Mat kernel,
Point anchor, int iterations)

Mat src     処理したい元画像のMat
Mat dst     変換後Mat
int op      演算の種類
Mat kernel   使用する構造要素を表すMat
Point anchor  要素内のアンカー位置
int iterations  実行回数


op以外の引数は、Imgproc.dilate()や、Imgproc.erode()と同じです。

opについては、Imgprocに用意された以下の定数から選択します。

MORPH_ERODE   結果はerode()と同様
MORPH_DILATE    結果はdilate()と同様
MORPH_OPEN    オープニング
MORPH_CLOSE    クロージング
MORPH_GRADIENT  モルフォロジー勾配(エッジ検出)
MORPH_TOPHAT   トップハット
MORPH_BLACKHAT  ブラックハット

オープニングは収縮した結果に対して同じ回数だけ膨張する処理を指します。
クロージングは膨張した結果に対して同じ回数だけ収縮する処理を指します。
モルフォロジー勾配は膨張した結果から収縮した結果を差し引く処理を指します。
トップハットは元画像からオープニングした画像を差し引く処理を指します。
ブラックハットはクロージングした画像から元画像を差し引く処理を指します。

なお、余談ですが「Imgproc.MORPH_」から始まる定数は、上記以外にも「MORPH_RECT」「MORPH_CROSS」「MORPH_ELLIPSE」の3つあります。
これらは、Imgproc.getStructuringElement()メソッドで利用し、モルフォロジー演算の引数(kernel)となる、指定されたサイズと形状の構造要素を返します。


それでは実際にやってみましょう。

元画像は月にしてみます。
「Imgproc.THRESH_OTSU」を利用すると単なる円になってしまうので、閾値を指定して二値化します。
Imgproc.threshold(mat, mat, 220.0, 255.0,
  Imgproc.THRESH_BINARY_INV);



まずは収縮です。
Imgproc.morphologyEx(mat, mat, Imgproc.MORPH_ERODE,
  new Mat(), new Point(-1, -1), 3);




続いて膨張です。
Imgproc.morphologyEx(mat, mat, Imgproc.MORPH_DILATE,
  new Mat(), new Point(-1, -1), 3);



オープニング。(収縮 ⇒ 膨張)
Imgproc.morphologyEx(mat, mat, Imgproc.MORPH_OPEN,
  new Mat(), new Point(-1, -1), 3);



クロージング。(膨張 ⇒ 収縮)
Imgproc.morphologyEx(mat, mat, Imgproc.MORPH_CLOSE,
  new Mat(3, 3, CvType.CV_8UC1), new Point(-1, -1), 3);



モルフォロジー勾配。(膨張 マイナス 収縮)
Imgproc.morphologyEx(mat, mat, Imgproc.MORPH_GRADIENT,
  new Mat(), new Point(-1, -1), 3);



トップハット。(元画像 マイナス オープニング)
Imgproc.morphologyEx(mat, mat, Imgproc.MORPH_TOPHAT,
  new Mat(), new Point(-1, -1), 3);



ブラックハット。(クロージング マイナス 元画像)
Imgproc.morphologyEx(mat, mat, Imgproc.MORPH_BLACKHAT,
  new Mat(), new Point(-1, -1), 3);


ということで、モルフォロジー演算にはゴミ、ノイズの除去や、単純二値化では検出出来ないものを検出、抽出出来るようにしたりなど、様々な応用が考えられます。