読者です 読者をやめる 読者になる 読者になる

catalinaの備忘録

ソフトウェアやハードウェアの備忘録。後で逆引きできるように。

C++でOpenCv

Opencvで輪郭抽出、実験としてJavaでやっていたけど、今回はC++でやってみることにした。やっぱり慣れてる言語っていいね。

2014/06/04追記:コード部分の体裁を修正

 

    char* filename = argc == 2 ? argv[1] : (char*)"ajani.jpg";
    if( (src = imread(filename,1)).data == 0 )
        return -1;

    Mat lmDst;            // 最終出力画像の格納用
    Mat lGray;
    Mat    lOutput;
    cvtColor(src, lGray, CV_RGB2GRAY);

    // ガウシアンフィルタを作成する
    // arg1 画像の型
    // arg2 アパーチャサイズ
    // arg3 ガウシアンΣ(sigmaX)
    // arg4 ガウシアンΣ(sigmaY)
    // arg5 利用される境界の種類(borderInterpolate関数の説明を参照)
    Ptr<FilterEngine> gFilter = createGaussianFilter(CV_8UC3, Size(5, 5), 1.2, 1.2, BORDER_CONSTANT);

    // ガウシアンフィルタを適用する
    Mat    lGaussian;
    GaussianBlur(lGray, lGaussian, Size(5, 5), 1.5, 1.5 );
    imwrite("gaussian.jpg", lGaussian);

    // 画像の二値化を行う
    // 近傍N点の平均値から、arg7を減算した結果をもって二値化する
    // arg1, 入力画像
    // arg2, 二値化された画像
    // arg3, 条件TRUE時に塗りつぶされる色
    // arg4, ガウシアン
    // arg5, 正論理, 負論理
    // arg6, 閾値
    // arg7, 演算結果に加算する値
    Mat lBinaryImage;
//    threshold(lGray, lBinaryImage, threshold, maxValue, cv::THRESH_BINARY);
    adaptiveThreshold(lGaussian, lBinaryImage, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY, 7, 0);
    imwrite("binary.jpg", lBinaryImage);

    // cannyフィルタを適用し、画像のエッジ抽出を行う
    // arg1 入力画像
    // arg2 出力画像(エッジ)
    // ヒステリシス閾値 1
    // ヒステリシス敷地 2
    // Sobel() オペレータのアパーチャサイズ(カーネルサイズ)
//    Canny(lBinaryImage, lmDst, 50.0f, 20.0f, 3);
//  imwrite("canny.jpg", lmDst);

    // 特徴量検出器を使用して、物体の輪郭抽出を行う
    // arg1, 入力画像
    // arg2, 出力される輪郭情報
    // arg3, 輪郭抽出モード
    //        CV_RETR_EXTERNAL 最も外側の輪郭のみを抽出します.
    //        CV_RETR_LIST すべての輪郭を抽出し,それらをリストに保存します.
    //        CV_RETR_CCOMP すべての輪郭を抽出し,をれらを2階層構造として保存します:上のレベルには,連結成分の外側の境界線が,下のレベルには,連結成分の内側に存在する穴の境界線が属します.
    //        CV_RETR_TREE すべての輪郭を抽出し,入れ子構造になった輪郭を完全に表現する階層構造を構成します.
    // arg4, 近似手法s
    //        CV_CHAIN_CODE 出力はフリーマンチェーンコード. 他の手法では,ポリゴン(頂点のシーケンス)を出力します.
    //        CV_CHAIN_APPROX_NONE チェーンコードの全ての点を,通常の点群に変換します.
    //        CV_CHAIN_APPROX_SIMPLE 水平・垂直・斜めの線分を圧縮し,それらの端点のみを残します.
    //        CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS Teh-Chin チェーン近似アルゴリズムの1つを適用します.
    //        CV_LINK_RUNS 値が1のセグメントを水平方向に接続する,全く異なる輪郭抽出アルゴリズム.この手法を用いる場合は,抽出モードが CV_RETR_LIST でなければいけません.
    vector< vector<cv::Point> > contours;
//    findContours(lBinaryImage, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
    findContours(lBinaryImage, contours, CV_RETR_TREE, CV_CHAIN_APPROX_NONE);

#if 0
    // すべての輪郭をダンプするときはこっち
    for(int i = 0; i < contours.size(); i++){
#else
    // 一番外側の輪郭をダンプするときはこっち
    {
        int i = 0;
#endif
        // 検出した輪郭のHu不変量モーメントを求める
        Moments    moments = cv::moments(contours[i]);

        const int huMomentNum = 7;            // Hu不変量モーメントは全部で7個
        double    lhuMoments[huMomentNum];    // Hu不変量モーメントを取り出す
        HuMoments(moments, lhuMoments);

        // Hu不変量モーメントをdumpする
        printf("Hu = ");
        for(int j = 0; j < huMomentNum; j++){
            printf("%3.5f, ", j, lhuMoments[j] );
        }
        printf("\n");
    }

    // 抽出した物体の輪郭を描画する。
    // 描画した結果は、画像ファイルに出力する
    Mat contourImage(lBinaryImage.size(), CV_8U, cv::Scalar(255));;
    const int drawAllContours = -1;
    cv::drawContours(contourImage, contours, drawAllContours, Scalar(0));
    imwrite("contours.jpg", contourImage);

    // Matの内容のダンプ確認
//    dump_mat_info(dst);

    imwrite("Gray.jpg", lGray);

    return 0;
}

 

処理手順

  1. cvtColor関数を使って、処理しやすいグレースケール画像に変換する。
  2. ガウシアンフィルタを適用して、画像のエッジをぼかす
  3. threathold関数を適用して画像を2値化する
  4. findContours関数を適用して、画像の輪郭抽出を行うHu不変量モーメントを求める

だいたいこんな感じ

書く処理手順の適用結果はこんなん。

1, グレースケール

f:id:Catalina1344:20140603094708j:plain

2, ガウシアンフィルタを適用して画像全体をぼかす。

f:id:Catalina1344:20140603094709j:plain3, threathold関数を使って画像を2値化する

 

f:id:Catalina1344:20140603094729j:plain

 

4, 輪郭を抽出する

f:id:Catalina1344:20140603094746j:plain

ここまでで輪郭抽出ができた。

ちなみにHu不変量モーメントの値は、

一番外側の輪郭でHu = [0.17484, 0.00290, 0.00000, 0.00000, 0.00000, 0.00000, 0.00000

となっていた。(小数第5位まで)

(ちなみに、一つ内側の輪郭も同じ値。このことからHu不変量モーメントの値の検出までは正しく行えている様子。)

 

次はここからさらに掘り下げて、カードの内容を識別できるようにしたい。

しかしカードの種別が多いからどうやるか試行錯誤していかないと。