輪郭検出でマスクを自動生成する

今回は、マスク画像を自動的に作る方法を紹介します。画像の一部分しか解析に必要ない、除外したいという場合にはマスク画像を用いてこれを実現できます。マスクという言葉の”隠す”という意味の通り、マスク画像でゼロの値を持つ場所はゼロ、すなわち、黒になります。マスク画像でゼロ以外の値をもつpixel位置では、黒ではなく、元々の画像の値がコピーされます。ここでは、マスク画像の作り方について解説します。

手作業でなんとかなりそうなら、マスク画像を手作業で作成してもいいかもしれません。手作業の方が正確にマスクを作れるときもあります。非常に複雑なマスク画像や、大量のマスク画像、動画の全てのフレームに対する個別のマスク画像を用意する必要がある場合には、自動的にマスク画像を生成するのが現実的でしょう。自動的にマスク画像を生成するにはfindContours関数とdrawContours関数を組み合わせるのが、私の中では一般的です。輪郭検出の後に、個別の輪郭に対してマスクすべきかどうかを輪郭内面積や形状などから条件判断し、新たなMatを作って輪郭を白の塗りつぶしで描画します。個別の輪郭の描画についてはこちらで解説しています。このようにして作ったMatをマスクとして使います。

ここでは、輪郭検出からマスク画像を自動的に作成し、マスク処理を行うコードを紹介します。入力は下の図1です。まず、threshold関数により2値化を行います。ここでは大津の2値化により、自動で閾値を設定しています(図2)。

threshold(img, img, 0, 255, THRESH_BINARY | THRESH_OTSU);

さらに、輪郭検出をfindContours関数により行い、得られた輪郭情報を元に、contourArea関数で計算される輪郭内面積によって面積が5000以上15000以下の輪郭についてのみのマスク画像を輪郭をdrawContours関数で塗りつぶすことで作成しています(図5)。任意の輪郭のみをdrawContours関数で描画する方法についてはこちらを参考にしてください。

vector<vector<Point> > contours;
findContours(img, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
vector<vector<Point> > contours_subset;
for(int i=0; i<contours.size();i++){ 
    double area=contourArea(contours.at(i));
    printf("%f\n",area);
    if(area>5000&&area<15000){
        contours_subset.push_back(contours.at(i));
    }
}

ひとつ注意が必要なのは入力を2値化する前に、元の入力画像のコピーを取る必要があることです(図4)。コピーは単にイコール記号を用いるだけでは不完全で、clone()を用いる必要があります。

Mat img_copy = img.clone();

コピーを取っておかないと、マクス画像を生成するまではいいのですが、その後のマスク処理に必要な元画像がなくなってしまいます。また、findContours関数が入力に指定したMatを変更する点も今回は重要ではありませんが覚えておいた方が良い事項です(図3)。

makemaskin

mmbin

mmimg

▲図1〜3

makemaskin

mmmask

mmresult

▲図4〜6

5000以上15000以下の輪郭内面積をもつ輪郭についてのマスクが生成され、結果として、中くらいの大きさのオブジェクトのみが残ったマスク画像が得られました(図6)。

#include "opencv/cv.h"
#include "opencv/highgui.h"
#include <vector>
using namespace std;
using namespace cv;
int main(){
    Mat img = imread("makemaskin.png");
    Mat img_copy = img.clone();
    
    cvtColor(img, img, CV_BGR2GRAY);
    threshold(img, img, 0, 255, THRESH_BINARY | THRESH_OTSU);
    imshow("bin",img);
    
    vector<vector<Point> > contours;
    findContours(img, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
    
    vector<vector<Point> > contours_subset;
    
    for(int i=0; i<contours.size();i++){
        double area=contourArea(contours.at(i));
        printf("%f\n",area);
        if(area>5000&&area<15000){
            contours_subset.push_back(contours.at(i));
        }
    }
    
    Mat mask = Mat::zeros(img.rows, img.cols, CV_8UC1);
    drawContours(mask,contours_subset,-1,Scalar(255),-1);
    
    Mat result;
    img_copy.copyTo(result,mask);
    
    imshow("img",img);
    imshow("img_copy",img_copy);
    imshow("mask",mask);
    imshow("result",result);
    waitKey(0);
    return 0;
}