粒をカウントする (粒子解析)

findContours関数で輪郭を検出する

すべての輪郭を検出し、種子の輪郭として識別することにします。OpenCVでは輪郭は点の集合として保持されます。輪郭を構成する点の重心を計算することで近似的な各種子の重心座標を求めます。

findContours関数は2値画像を入力に使い、すべての輪郭、すなわち、すべての白と黒の境界線を検出します。それゆえ、検出される輪郭は複数になります。さらに、いくつの輪郭が検出されるかはプログラムを実行してみるまでわからないため可変長配列である必要があります。したがって、標準ライブラリのvectorを使うことになります。さらにややこしいことに各輪郭を構成する点もいくつかわからないので、これにもvectorを使うことになります。したがって、vectorの入れ子構造になります。初めはわかりにくいと思いますが、こういうものとして覚えてもらっても実質上の問題はありません。輪郭を保持する入れ物は下のように宣言します。これをfindContour関数に渡すと、輪郭を検出してこの中に点の集合として輪郭の情報を入れて戻してくれます。

vector<vector<Point> > contours;
findContours(bin_img, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);

findContours関数のオプション

findContour関数の第三引数は輪郭の検出方法を指定します。上の例のCV_RETR_EXTERNALは一番外側の白の輪郭のみを取得することを意味します。他にも多くの種類がありますが、もう一つよく使うのはCV_RETR_LISTだと思います。白の輪郭、黒の輪郭、内側、外側関係なく、すべての輪郭が取得されます。第四引数には輪郭点の格納方法を指定します。CV_CHAIN_APPROX_NONEはすべての点を格納するため任意の隣接する2点は互いに8近傍内に存在します。CV_CHAIN_APPROX_SIMPLEというものもあり、これは水平・垂直・斜めの線分を圧縮し、それらの端点のみを残します。すなわち、直線で表せる分に関してデータを圧縮します。

重心計算

種子と思われる輪郭を推定したところで、次にその位置を知りたいのですべての種子の重心を求めます。今回は輪郭を構成するすべての点の重心を計算することにしましょう。重心は各点のx座標、y座標それぞれについての平均をとれば求まります。輪郭を構成する点の数はvectorの大きさ(要素数)であるのでsize()で求めることができます。これによって求めた、輪郭を構成する点の数をn個とすると、重心x座標はすべての点のx座標を足し合わせ、最後にnで割れば平均となるので計算できます。重心y座標も同様に求めます。なお、OpenCVでは近似ではなく、数学的に正しい重心を計算することも可能です。厳密な重心の求め方はこちら

下の例では、求めた座標を中心とする半径3の円を元の動画に描画しています。それに伴い、表示する画像も2値画像ではなく元の画像に円を描画したものに変更しています。表示される動画のスクリーンショットは下図のようになります。数個の種子がカウントから漏れていることがわかりますが、ほぼすべての種子を数えることができています。全自動でさらに精度を上げるにはより時間をかけたコーディングが必要になります。最後に標準ライブラリのcoutを使って種子の数を出力しています。下のように出力されるはずです。596個の種子を認識しカウントできたことになります。
596 seeds

seed71
#include "opencv/cv.h"
#include "opencv/highgui.h"
#include <iostream> //追加
#include <vector> //追加
using namespace cv;
using namespace std; //追加
int main(){
    Mat img = imread("seed.jpg", IMREAD_UNCHANGED);
    Mat gray_img;
    cvtColor(img, gray_img, CV_BGR2GRAY);
    Mat bin_img;
    adaptiveThreshold(gray_img, bin_img, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY, 99, 8);
    bin_img = ~bin_img; //色の反転
    Mat element = Mat::ones(5,5,CV_8UC1); //5×5の行列で要素はすべて1 erode処理に必要な行列

    erode(bin_img, bin_img, element, Point(-1,-1), 1); //収縮処理
    erode(bin_img, bin_img, element, Point(-1,-1), 1); //収縮処理
    erode(bin_img, bin_img, element, Point(-1,-1), 1); //収縮処理
    erode(bin_img, bin_img, element, Point(-1,-1), 1); //収縮処理

    //次の1行は上の4行と同じ意味を持ちます
    //erode(bin_img, bin_img, element, Point(-1,-1), 4); //最後の4が4回分ということを表す

    //追加ここから
    vector<vector<Point> > contours;
    findContours(bin_img, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
    for(int i=0; i<contours.size(); i++){
        int count=contours.at(i).size();
        double x=0; double y=0;
        for(int j=0;j<count;j++){
            x+=contours.at(i).at(j).x;
            y+=contours.at(i).at(j).y;
        }
        x/=count;
        y/=count;
        circle(img, Point(x,y),5, Scalar(0,0,255),2,4);
    }
    cout<<contours.size()<<" seeds"<<endl;
    //追加ここまで 
    imshow("IMAGE",img); //変更 
    waitKey(10000); return 0; 
}