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;
}
処理手順
- cvtColor関数を使って、処理しやすいグレースケール画像に変換する。
- ガウシアンフィルタを適用して、画像のエッジをぼかす
- threathold関数を適用して画像を2値化する
- findContours関数を適用して、画像の輪郭抽出を行うHu不変量モーメントを求める
だいたいこんな感じ
書く処理手順の適用結果はこんなん。
1, グレースケール
2, ガウシアンフィルタを適用して画像全体をぼかす。
3, threathold関数を使って画像を2値化する
4, 輪郭を抽出する
ここまでで輪郭抽出ができた。
ちなみにHu不変量モーメントの値は、
一番外側の輪郭でHu = [0.17484, 0.00290, 0.00000, 0.00000, 0.00000, 0.00000, 0.00000
となっていた。(小数第5位まで)
(ちなみに、一つ内側の輪郭も同じ値。このことからHu不変量モーメントの値の検出までは正しく行えている様子。)
次はここからさらに掘り下げて、カードの内容を識別できるようにしたい。
しかしカードの種別が多いからどうやるか試行錯誤していかないと。