サイトアイコン CV & Technologies

根の長さを測定する

画像から根の長さを測定します。主根と側根で分けて長さを測定するといった応用もOpenCVの関数をうまく使うことで簡単に実装可能なことを示すチュートリアルです。

下のような根の写真を用意しました。この根の写真から根の長さを推定するプログラムを作成します。できるだけ背景が均一で認識したい物体とのコントラストの差が大きくなるようにするのが良いでしょう。


画像を表示する

最終的な目的は根の長さの測定ですが、まずは画像を表示することから始めましょう。これは画像へのパスは各々で画像の置いてある場所に合わせてください。

#include "opencv/cv.h"
#include "opencv/highgui.h"
using namespace cv;
int main(){
    Mat img = imread("root.jpg", IMREAD_UNCHANGED);
    imshow("IMAGE",img);
    waitKey(10000);
    return 0;
}

輪郭線検出に向けた下準備として2値化する

適当な閾値を設定して2値化し、さらに白黒を反転します。必要であればこのあと、膨張処理を施します。この処理によって細い根が途切れて繋がらずに検出されることを防ぎます。下の画像は2値化し、白黒反転させたものを表示したウィンドウのスクリーンショットです。

#include "opencv/cv.h"
#include "opencv/highgui.h"
using namespace cv;
int main(){
    Mat img = imread("root.jpg", IMREAD_UNCHANGED);
    Mat gray_img;
    cvtColor(img, gray_img, CV_BGR2GRAY);
    Mat bin_img;
    threshold(gray_img, bin_img, 0, 255, THRESH_BINARY|THRESH_OTSU);
    bin_img = ~bin_img;
    imshow("IMAGE",bin_img); //変更
    waitKey(10000);
    return 0;
}

findContours関数で輪郭を検出する

輪郭の周の長さを求め、根量の指標とします。ここで輪郭長を1/2にすれば根の長さの総和を近似できるということが、根の細長いことから考えられます。この長さを推定根長和とします。OpenCVでは輪郭は点の集合として保持されます。findContours関数は2値画像を入力に使い、すべての輪郭、すなわち、すべての白と黒の境界線を検出します。輪郭を構成する点の情報はvectorの入れ子構造になります。初めはわかりにくいと思いますが、こういうものとして覚えてもらっても実質上の問題はありません。輪郭を保持する入れ物は下のように宣言します。これをfindContour関数に渡すと、輪郭を検出してこの中に点の集合として輪郭の情報を入れて戻してくれます。findContour関数はダンゴムシの追跡プログラムでも扱っていますので、ぜひ参考にしてみてください。

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

findContours関数のオプション

findContour関数の第三引数は輪郭の検出方法を指定します。上の例のCV_RETR_LISTは白の輪郭、黒の輪郭、内側、外側関係なく、すべての輪郭を取得することを意味します。ダンゴムシの追跡のときの輪郭検出にはCV_RETR_EXTERNALを使いましたがそれと異なることに注意してください。CV_RETR_EXTERNALは一番外側の白の輪郭のみを取得することを意味します。

輪郭の長さの計算

findContour関数はすべての輪郭を取得します。今回はすべての根の長さの和を計算したいので各輪郭についてその周長を計算し和を計算します。輪郭の周はarcLength関数で計算することができます。第二引数には輪郭が閉じているか否かを指定しますが、今回は閉じているので1を指定します。開いている場合には0を指定します。下の例では、contour_lenという変数に輪郭の周の和を保持させています。contour_lenを2で割ることで推定根長和とし、標準ライブラリを使って出力しています。なお、輪郭はvectorを使った構造に保持されているため、任意番目の輪郭にat()を使ってアクセスできます。

#include "opencv/cv.h"
#include "opencv/highgui.h"
#include <iostream> //追加
using namespace cv;
using namespace std; //追加
int main(){
    Mat img = imread("root.jpg", IMREAD_UNCHANGED);
    Mat gray_img;
    cvtColor(img, gray_img, CV_BGR2GRAY);
    Mat bin_img;
    threshold(gray_img, bin_img, 0, 255, THRESH_BINARY|THRESH_OTSU);
    bin_img = ~bin_img;
    //追加ここから
    vector<vector<Point> > contours;
    findContours(bin_img, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
    double contour_len=0;
    for(int i = 0; i < contours.size(); ++i) {
        contour_len += arcLength(contours.at(i),1);
    }
    cout<<"estimated root length = "<<contour_len/2<<endl;
    //追加ここまで
    imshow("IMAGE",bin_img);
    waitKey(10000);
    return 0;
}

収縮処理

根長測定も最終回です。ダンゴムシの追跡で膨張処理を施しましたが、それとは反対の処理です。用意した根の写真では主根と側根の間に大きな太さの差があるため、白い部分を収縮させることで側根を消すことができます。下の画像はerodeによる収縮処理後の画像です。その細い根を消した2値画像、すなわち、主根のみの2値画像で同様に根長を求めれば主根の長さが求まります。収縮処理はerode関数により実装されています。下のコードでは側根も含めた合計の長さから主根の長さを引くことで側根の長さも求めています。一つ注意が必要なのはfindContour関数が入力画像を変更してしまうことです。今回は2回の輪郭検出を行う必要があるため、コピーを作成しています。コピーには.clone()を使います。 最終的に、以下のような出力が得られました。主根が510、側根が8184であることがわかります。なお、長さの単位はpixelです。

estimated root length (total) = 8694.72
estimated root length (main) = 510.685
estimated root length (lateral) = 8184.04

#include "opencv/cv.h"
#include "opencv/highgui.h"
#include <iostream>
using namespace cv;
using namespace std;
int main(){
    Mat img = imread("root.jpg", IMREAD_UNCHANGED);
    Mat gray_img;
    cvtColor(img, gray_img, CV_BGR2GRAY);
    Mat bin_img;
    threshold(gray_img, bin_img, 0, 255, THRESH_BINARY|THRESH_OTSU);
    bin_img = ~bin_img;
    Mat bin_img_copy=bin_img.clone(); //追加
    Mat element = Mat::ones(9,9,CV_8UC1); //追加
    vector<vector<Point> > contours;
    findContours(bin_img, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
    double contour_len=0;
    for(int i = 0; i < contours.size(); ++i) {
        contour_len += arcLength(contours.at(i),0);
    }
    cout<<"estimated root length (total) = "<<contour_len/2<<endl;
    //追加ここから
    erode(bin_img_copy, bin_img_copy, element, Point(-1,-1), 1);
    imshow("IMAGE_main_root",bin_img_copy);
    vector<vector<Point> > contours_main;
    findContours(bin_img_copy, contours_main, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
    double contour_len_main=0;
    for(int i = 0; i < contours_main.size(); ++i) {
        contour_len_main += arcLength(contours_main.at(i),0);
    }
    cout<<"estimated root length (main) = "<<contour_len_main/2<<endl;
    cout<<"estimated root length (lateral) = "<<(contour_len-contour_len_main)/2<<endl;
    //追加ここまで
    waitKey(10000);
    return 0;
}

モバイルバージョンを終了