色相特徴のテンプレート探索による青色ボールの検出
輪郭線によるテンプレート探索は背景の輪郭線が濃いとボールを検出できなかったので、別の方法として色相の特徴を使った検出を試してみました。検出対象は、前回いくつか検出できなかった青色のボールとしました。
検出方法
まず前回と同じくテキトウにテンプレートマッチ用のフィルターを定義しました。
// テキトウな円形検出フィルター #define CIRCLE_FILTER_WIDTH 16 #define CIRCLE_FILTER_HEIGHT 16 const int CIRCLE_FILTER[CIRCLE_FILTER_HEIGHT][CIRCLE_FILTER_WIDTH] = { { -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3 }, { -3, -3, -3, -3, +1, +1, +1, +1, +1, +1, +1, +1, -3, -3, -3, -3 }, { -3, -3, -3, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, -3, -3, -3 }, { -3, -3, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, -3, -3 }, { -3, -3, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, -3, -3 }, { -3, -3, +1, +1, +1, +1, +2, +2, +2, +2, +1, +1, +1, +1, -3, -3 }, { -3, -3, +1, +1, +1, +2, +2, +2, +2, +2, +2, +1, +1, +1, -3, -3 }, { -3, -3, +1, +1, +1, +2, +2, +2, +2, +2, +2, +1, +1, +1, -3, -3 }, { -3, -3, +1, +1, +1, +2, +2, +2, +2, +2, +2, +1, +1, +1, -3, -3 }, { -3, -3, +1, +1, +1, +2, +2, +2, +2, +2, +2, +1, +1, +1, -3, -3 }, { -3, -3, +1, +1, +1, +1, +2, +2, +2, +2, +1, +1, +1, +1, -3, -3 }, { -3, -3, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, -3, -3 }, { -3, -3, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, -3, -3 }, { -3, -3, -3, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, -3, -3, -3 }, { -3, -3, -3, -3, +1, +1, +1, +1, +1, +1, +1, +1, -3, -3, -3, -3 }, { -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3 } }; // 円形かの閾値 #define CIRCLE_FILTER_THRESHOLD 110
今回は円の中身があるため、四画っぽい物体を誤検知しないように、少し厳密にしました。
次に、『青色』の範囲を定義しました。
// 青色の範囲 #define HSV_BLUE_LOWER cvScalar(95, 0, 20) #define HSV_BLUE_UPPER cvScalar(130, 235, 255)
H(色相),S(彩度),V(明度)です。OpenCVではHの範囲が0〜180°で、個人的感覚により95〜130くらいが青としました。この範囲の色を抽出してそれに対してテンプレート探索を行います。
探索方法は前回と同じく、フィルターを探索ウィンドウとして青色抽出した画像内を探索し、各エリアでのフィルターの出力値から青色ボールがあるかを判定します。フィルターの出力値は、フィルターに対応する各ピクセルの色(黒:1 or 白:0)との積の総和としました。これが閾値を超える場合は、そこに青いボールがあると判定します。
一番下に試したソースコードを載せています。
結果
画像は前回同じものを使用しました。
http://www.flickr.com/photos/person/56233211/
輪郭線による方法では、中央に3つある青いボールが検出できませんでした。
探索中はプログラムから上のような感じで見えています。青い領域が白くなり、それ以外の領域が黒くなっています。
うまくいきました。背景に補色が多いのもうまくいく理由だと思います。
他、前回ダメだった芝生の画像でも青いボールが検出できていました。
まとめ
あらかじめ色と形が限定されていて、対象物体の色が背景と異なる場合は、輪郭線よりも色の特徴で探したほうがよさそう。
ソースコード
OpenCVが必要です。
#include <stdio.h> #include <stdlib.h> #include "highgui.h" #include "cv.h" #include "cxcore.h" #define _DEBUG_IMG 0 // テキトウな円形検出フィルター #define CIRCLE_FILTER_WIDTH 16 #define CIRCLE_FILTER_HEIGHT 16 const int CIRCLE_FILTER[CIRCLE_FILTER_HEIGHT][CIRCLE_FILTER_WIDTH] = { { -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3 }, { -3, -3, -3, -3, +1, +1, +1, +1, +1, +1, +1, +1, -3, -3, -3, -3 }, { -3, -3, -3, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, -3, -3, -3 }, { -3, -3, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, -3, -3 }, { -3, -3, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, -3, -3 }, { -3, -3, +1, +1, +1, +1, +2, +2, +2, +2, +1, +1, +1, +1, -3, -3 }, { -3, -3, +1, +1, +1, +2, +2, +2, +2, +2, +2, +1, +1, +1, -3, -3 }, { -3, -3, +1, +1, +1, +2, +2, +2, +2, +2, +2, +1, +1, +1, -3, -3 }, { -3, -3, +1, +1, +1, +2, +2, +2, +2, +2, +2, +1, +1, +1, -3, -3 }, { -3, -3, +1, +1, +1, +2, +2, +2, +2, +2, +2, +1, +1, +1, -3, -3 }, { -3, -3, +1, +1, +1, +1, +2, +2, +2, +2, +1, +1, +1, +1, -3, -3 }, { -3, -3, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, -3, -3 }, { -3, -3, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, -3, -3 }, { -3, -3, -3, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, -3, -3, -3 }, { -3, -3, -3, -3, +1, +1, +1, +1, +1, +1, +1, +1, -3, -3, -3, -3 }, { -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3 } }; // 円形かの閾値 #define CIRCLE_FILTER_THRESHOLD 110 // 青色の範囲 #define HSV_BLUE_LOWER cvScalar(95, 0, 20) #define HSV_BLUE_UPPER cvScalar(130, 235, 255) // 円形? int isCircle(int val) { return CIRCLE_FILTER_THRESHOLD < val ? 1:0; } // 青色ボール検出 int blueBallDetect(IplImage *base_image) { IplImage *hsv_image = cvCreateImage(cvGetSize(base_image), IPL_DEPTH_8U, 3); IplImage *blue_base_image = cvCreateImage(cvGetSize(base_image), IPL_DEPTH_8U, 1); IplImage *blue_image = cvCreateImage(cvGetSize(base_image), IPL_DEPTH_8U, 1); IplImage *temp_image; int count = 0; double scale = 1.3; double current_scale = 1; // 青色領域抽出 CvScalar blue_lower = HSV_BLUE_LOWER; CvScalar blue_upper = HSV_BLUE_UPPER; cvCvtColor(base_image, hsv_image, CV_BGR2HSV); cvInRangeS(hsv_image, blue_lower, blue_upper, blue_base_image); cvCopy(blue_base_image, blue_image); // 探索 while (CIRCLE_FILTER_WIDTH < blue_image->width && CIRCLE_FILTER_HEIGHT < blue_image->height) { for (int y = 0; y + CIRCLE_FILTER_HEIGHT < blue_image->height; y += 2) { for (int x = 0; x + CIRCLE_FILTER_WIDTH < blue_image->width; x += 2) { // フィルター出力値計算 int filter_value = 0; for (int fy = 0; fy < CIRCLE_FILTER_HEIGHT; ++fy ) { for (int fx = 0; fx < CIRCLE_FILTER_WIDTH; ++fx ) { CvScalar color = cvGet2D( blue_image, y + fy, x + fx ); filter_value += CIRCLE_FILTER[fy][fx] * (color.val[0] == 0 ? 0:1); } } if (isCircle(filter_value)) { // ヒット int rx = cvRound(x * current_scale); int ry = cvRound(y * current_scale); int rw = cvRound(CIRCLE_FILTER_WIDTH * current_scale); int rh = cvRound(CIRCLE_FILTER_HEIGHT * current_scale); #ifdef _DEBUG printf("filter value: %d\n", filter_value); #endif #if _DEBUG_IMG cvRectangle( blue_image, cvPoint(x, y), cvPoint(x + CIRCLE_FILTER_WIDTH, y + CIRCLE_FILTER_HEIGHT), cvScalar(0xcc), 2 ); #endif // マーク cvRectangle( base_image, cvPoint(rx, ry), cvPoint(rx + rw, ry + rh), CV_RGB(0xff, 0x00, 0x00), 2 ); ++count; } } } #if _DEBUG_IMG cvShowImage("BASE", blue_image); cvWaitKey(); #endif temp_image = blue_image; blue_image = cvCreateImage( cvSize( cvRound(blue_image->width / scale), cvRound(blue_image->height / scale) ), IPL_DEPTH_8U, 1 ); cvResize(blue_base_image, blue_image); cvReleaseImage(&temp_image); current_scale *= scale; } cvReleaseImage(&blue_image); cvReleaseImage(&blue_base_image); return count; } int main(int argc, const char *argv[]) { IplImage *base_image; if (argc == 1) { fprintf(stderr, "usage: balldetect file\n"); return EXIT_FAILURE; } base_image = cvLoadImage(argv[1], 3); if (! base_image) { fprintf(stderr, "load error: %s\n", argv[1]); return EXIT_FAILURE; } cvNamedWindow("BASE"); blueBallDetect(base_image); cvShowImage("BASE", base_image); cvWaitKey(); cvReleaseImage(&base_image); return EXIT_SUCCESS; }