Hatena::ブログ(Diary)

Kazzzの日記 このページをアンテナに追加 RSSフィード

2013-12-21

[][]二値化(Binarization)処理

コメントで二値化のソースコードをみたいとのリクエストがあったので(本当はアプリケーション全体なのだろうが)一部を掲載しておく。

OpenCVUtil.java(抜粋)
import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;
import org.opencv.utils.Converters;

public final class OpenCVUtil {
    /**
     * 2値化(binarization)を実施します
     * @param src 元イメージのMatクラスをセット
     * @param thresholdValue 2値化の際の閾値をセットします
     * @return Mat 2値化を施されたイメージがMatクラスで戻ります
     */
    public static final Mat binarize(Mat src, double thresholdValue) {
        Mat grayed = new Mat(src.size(), CvType.CV_8UC1);
        Mat bin = new Mat(src.size(), CvType.CV_8UC1);

        Imgproc.cvtColor(src, grayed, Imgproc.COLOR_RGB2GRAY);//COLOR_BGR2GRAY);
        
        /* 濃淡画像から2値に変換(大津) */
        Imgproc.threshold(grayed, bin, thresholdValue, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
        
        grayed.release();
        return bin;    
    }
}

androidのBitmapはそのままでOpenCVでは使えないので、Macクラスに変換する。
下記のようなユーティリティを作っておくと便利だ。

    /**
     * BitMapからMatへの変換を行う
     * OpenCVではARGB_8888フォーマットしか対応していないので、この関数を使う
     * @param ctx コンテキストをセット
     * @param bitmap 対象のBitmapをセット
     * @return Mat 変換したMatが戻ります
     */
    public static Mat bitMapToMatEx(Context ctx, Bitmap bitmap) {
        Mat mat = new Mat(); 
        Utils.bitmapToMat(bitmap.copy(Config.ARGB_8888, true), mat);
        return mat;
    }

Activityではギャラリーカメラから取得した画像キャンバスからBitMapを取得して二値化を実施、画像に書き戻せばよい。

    private void doBinarize(int threshold, Bitmap bm) {
        Mat src = OpenCVUtil.bitMapToMapEx(this, bm);
        Mat bined = OpenCVUtil.binarize(src, threshold);
        // Bitmapに変換する前にRGB形式に変換
        Imgproc.cvtColor(bined, bined, Imgproc.COLOR_GRAY2RGBA, 4);
        //  Bitmap dst に空のBitmapを作成
        this.sourceImages.put(source, Bitmap.createBitmap(bined.width(), bined.height(), Bitmap.Config.ARGB_8888));
        //  MatからBitmapに変換
        Utils.matToBitmap(bined, this.sourceImages.get(source));

        //Viewにセット
        this.canvas.setImageBitmap(this.sourceImages.get(source));
        src.release();
        bined.release();
    }


OpenCVはiOS、Androidのクロスプラットホームで暖めているネタがあり、落ち着いたら紹介したいのだが今は時間がない。
機会があればまた。

2013-02-18

[]色による探索 (cv::inRange)

Hueを最大180に設定している。元のイメージ黄色と黄緑色青色の矩形と円は全て認識することが出来ているが、それよりも先にHue0〜10程度で認識されていなければならない赤色の矩形が認識されていない。

赤が認識されないということで別な実装方法を試してみた。表題にネタバレしているがinRangeメソッドを使う方法だ。

cv::inRange

cv::inRange - Operations on Arrays ― OpenCV 2.4.4.0 documentation

inRangeは配列中の要素がをパラメタで与えられた下限と上限値の範囲に収まっているか検査して、収まっている要素を出力する。

つまりはこのメソッドを使うことで配列にHue(色相)の上限と下限でフィルタをかけることができる訳だ。

色による2値化(cv::inRange版)
+ (cv::Mat)binarizeHSB:(cv::Mat)src hueMin:(double)hueMin hueMax:(double)hueMax saturation:(double)saturation brightness:(double)brightness
{
    cv::Mat hsb;
    cv::Mat result;
    // カラー画像をHSV画像に変換
    cv::cvtColor(src, hsb, CV_RGB2HSV, 3);
    // inRangeによるフィルタ
    cv::inRange(hsb, cv::Scalar(hueMin, saturation, brightness, 0) , cv::Scalar(hueMax, 255, 255, 0), result);
    return result;
}

cv::thresholdを使う場合に比べてチャネル毎の論理演算不要なので、コードが非常にすっきりとしている。

実行結果

今回はアスペクト比を修正した。

f:id:Kazzz:20130215182016p:image
Hue最大を33程度に設定。今度はいきなり赤色の矩形が認識されている。

f:id:Kazzz:20130215182017p:image
Hue最大を38にすることで黄緑の矩形を認識する。黄色と色相で5しか違わないのだがはっきりと分離できる。

f:id:Kazzz:20130215182018p:image
Hue最大を110にすることで外縁を含めて、全ての矩形を認識する。

このようにcv:thresholdを使うことがベストでは無いケースもあるのだが、同様の処理を行うのに複数の方法を試すことができるのが、OpenCV汎用性の高さだろう。

2013-02-15

[]色による探索 (cv::threshold)

OpenCVのモバイルにおける典型的な目的の一つに、カメラを通してキャプチャした映像、画像に対して「顔認識」「人物認識(肌色認識)」「オブジェクト認識」を行うことがあるが、これらの探索のパラメタには前回紹介した特徴で認識する他に色を特徴のパラメタとして認識する方法がある。

手順

1. 認識する色の情報を決定する
2. 決定した色の範囲を決めて、RGB色空間からHSV色空間に変換する
3. 対象の画像を1.で決定した色情報から、Hue(色相)を使って2値化する
4. 2値化した画像から輪郭を取り出す

早速OpenCVで上記の機能を実装してみよう。

cv::thresholdを使った2値化 (OpencvTest::binarizeHSB)
+ (cv::Mat)binarizeHSB:(cv::Mat)src hueMin:(double)hueMin hueMax:(double)hueMax saturation:(double)saturation brightness:(double)brightness
{
    cv::Mat hsb, hsb1, hsb2;
    cv::Mat huemask, chromamask, brightnessmask;
    cv::Mat result;

    //HSV色空間に変換
    cv::cvtColor(src, hsb, CV_RGB2HSV, 3);

    //HSVにチャネル分割
    cv::extractChannel(hsb, hsb1, 0);

    //Channel[0](色相)で制限をかける
    cv::threshold(hsb1, hsb2, hueMax, 255, CV_THRESH_TOZERO_INV ); 
    cv::threshold(hsb2, huemask, hueMin, 255, CV_THRESH_BINARY );

    //Channel[1](彩度)で制限をかける
    cv::extractChannel(hsb, hsb1, 1);
    cv::threshold(hsb1, chromamask, saturation, 255, CV_THRESH_BINARY );
    cv::bitwise_and(huemask, chromamask, result);

    //Channel[2](輝度)で制限をかける
    cv::extractChannel(hsb, hsb1, 2);
    cv::threshold(hsb1, brightnessmask, brightness, 255, CV_THRESH_BINARY );
    cv::bitwise_and(result, brightnessmask, result);

    return result;
}

内容としてよく見るコードなので解説は割愛する。ポイントはCV_THRESH_TOZERO_INVを使って閾値よりも上を最初に除外してしまうこと。
HSVによる2値化は基本的にHueの範囲で決まるので、SaturationとBrightnessは範囲を設けずに単に閾値として使用している。

実行結果

f:id:Kazzz:20130215182020p:image
サンプル画像としてこのエントリの初回で用意したイメージを使い、iOSシミュレータ上で画面を作成、スライダーでHue(色相)の上限と加減の閾値を設定し、それを上の関数に渡すようにアプリケーションを組んだ。
Saturation(彩度)とBrightness(輝度)は今回は固定値 (70以上)に設定している。

f:id:Kazzz:20130215182014p:image
Hueを53°位に設定した結果(画像のアスペクト比が崩れてるのはUIImageViewのプロパティ設定ミス)。黄色と黄緑色の矩形が認識されているが、青色は除外されている。また、0°で認識されるはずの赤の矩形は認識されていない。

f:id:Kazzz:20130218171333p:image
Hueを最大115°に設定している。元のイメージの黄色と黄緑色、青色の矩形と円は全て認識することが出来ているが、やはりHue0°で認識されていなければならない赤色の矩形が認識されていない。※

特定の色相だけ使えないのでは意味が無い。 
調べて見るとthresholdで色空間を変換する際に階調情報が失われるらしく、掛からないようだ。

もう少し工夫しなくてはならないな。

※OpenCVのHueは本来の0°〜360°ではなく、0°〜180°で表されるが(1バイト長で済むから?)その両端が赤色である。

2013-02-13

[]領域の探索 その2

前回、OpenCVを用いて特定の条件に合致した四辺形を探す方法を幾つか書いた。

  • 面積
  • フェレ径比率
  • 構造

また、これらの方法は画像の質に極めて依存するということで、カメラで撮影した映像などでは必ずしも有効な探索方法とはいえないことも書いた。

今回は多くの人が使っている、ポピュラーな探索方法を紹介しておこう。

色による探索

OpenCVのモバイルにおける典型的な目的の一つに、カメラを通してキャプチャした映像、画像に対して「顔認識」「人物認識(肌色認識)」「オブジェクト認識」を行うことがあるが、これらの探索のパラメタには前回紹介した特徴で認識する他に色を特徴のパラメタとして認識する方法がある。

OpenCVを使う場合、具体的には以下の手順と方法で物体(四辺形、円形、オブジェクト)を探索、認識することになるだろう。

1. 認識する色の情報を決定する
2. 決定した色の範囲を決めて、RGB色空間からHSV色空間に変換する
3. 対象の画像を1.で決定した色情報から、Hue(色相)を使って2値化する
4. 2値化した画像から輪郭を取り出す

色情報として一般的なRGBではなく、HSV(B)を使うことは重要だ。 色相(Hue)は0°〜360°(OpenCVの場合0°〜180°)の範囲で色の範囲を一つのパラメタで表すことができるためだ。他のパラメタ彩度(Saturation)と輝度(Brightness)はある程度緩くしてもHueさえ合致していれば認識できるということにもなる。

色は人間が識別しやすいパラメタのひとつだが、ComputerVisionにとっても認識しやすいパラメタということだ。

次回は具体的に色を使った探索のためのコードを書いてみようと思う。

2013-02-04

[][][]cvBlobsLib (その2)

cvBlobsLib - OpenCV Wiki
cvBlobsLibが使えそうだということは解ったのだが、問題は主要なモバイルプラットホームで使えるかどうかだ。

iPhone(iOS)

aaronsung/cvBlobsLib-iOS · GitHub
Objective-C(++)へのポートとも言える実装。本家と同等に使えそうだ。

android

.... 残念ながら無いようだ。
同様の用途に使うライブラリィは見つかったのだが、
cvblob-for-android - cvBlob for Android - Google Project Hosting
こちらはソースコードを見るとandroid.graphicsパッケージを利用した単独のクラスで構成されており、OpenCVとの関連性は無い。

ということで、現時点でcvBlobsLibをandroidで使うには

1. 本家のソースコードをNDKでビルドしてJNI経由でandroidから呼び出す
2. 本家のソースコードをandroid版のOpenCVを使って移植する

このどちらかということになる。
自分でやるならば後者だが、時間が無いなぁ。

2013-02-01

[]cvBlobsLib

f:id:Kazzz:20120819123035j:image:w100
cvBlobsLib - OpenCV Wiki
cvBlobsLibは"blob detection"、つまりイメージからオブジェクト(blob)を抽出、ラベリングするためのライブラリィ。
ベースがOpenCVなので、これを使って先日のエントリに書いた「領域探索」が出来るんじゃないかと期待している。

早速試してみよう。

2013-01-29

[]領域の探索

OpenCVを使って、iPhoneのカメラから撮影した画像中にから条件に合致した四辺形を探したい。

OpenCVを使ったイディオムとしては処理は以下のようになるだろう。

1. 画像の前処理 (median, gausian等、フィルタによるノイズ除去等)
2. 画像の2値化 (threshold,adaptiveThreshold等)
3. 輪郭の抽出 (findContour+ approxPolyDPによる輪郭抽出)
4. 四辺形の抽出 (contourの辺数が4で一定のcontourAreaを持つものを選択)

問題は複数抽出されるであろう四辺形のうち、どうやって目的のものを探すかだ。

例えばこのように矩形や台形、円などの図形が描かれたポスターがあって、それをカメラで撮影するとしよう。
f:id:Kazzz:20130129205057p:image:w480

抽出したいのは右下の赤い線で描かれた四辺形である。(判りやすくするために色を付けているが、実際には2値化してしまうため、色は意味をなさない) このイメージでは長方形となっているが、実際には方形とは限らず平行四辺形や台形、不等四辺形の場合もある。

さて、特定の四辺形をどうやって抽出すれば良いのだろう。

ぱっと思いつくのはOpenCVで予め用意されたAPIを使って、対象の四辺形を特徴付けることだろう。

面積

contourAreaによって、四辺形の内側の面積を求めることができる。
この面積が合致する四辺形のみを抽出する。

フェレ径比率

boundingRectによって輪郭の集合を含む矩形情報(フェレ径)を取得できる。
このフェレ径の縦横比を特有の情報として抽出する
(画像は歪んでいる可能性があるので、輪郭情報の単なる縦横比では駄目)

構造

findContorsで収集された輪郭の集合は引数にもよるが、輪郭の階層構造を形作る。
この図で言うと赤い四辺形は一番外側の輪郭の一つ下の階層に位置しているので、他の輪郭との関係で特定できる。


これらの方法の良い所は単純で簡単な所だ。輪郭さえ取得できれば面積もフェレ径も構造もAPI一発で取得できることだ。

逆にシンプルな方法ならではの弱点もある。
上の画像はカメラで撮影されるため、撮影された際の様々な条件(明るさ、背景の色、フォーカス、画素数、etc.)によってイメージの品質に影響を受ける。従って上記の特徴を得るための情報に曖昧さが発生することだ。

例えばカメラとの距離や位置によって画像の大きさや輪郭にひずみが出来れば面積に狂いが出るし、認識できない輪郭が出れば構造も変わってしまうのである。

うーん。難しいなぁ。