C++において、OpenCV画像分割と分水嶺アルゴリズムを実現します。


スプリットリッジアルゴリズムは、画像領域分割法であり、分割の過程で、近接画素との類似性を重要な参照根拠とし、空間位置に近く、階調値に近い画素点を相互に接続して、閉鎖的な輪郭を構成し、閉塞性はスプリントリッジアルゴリズムの重要な特徴である。
APIの紹介

void watershed( InputArray image, InputOutputArray markers );
パラメータの説明:
  • イメージ:8 bit 3チャネルカラー画像行列シーケンスでなければなりません。
  • markers:分水嶺関数watershedを実行する前に、第二のパラメータmarkersを処理しなければならない。それは異なる領域の輪郭を含むべきで、各輪郭は自分の唯一の番号があり、輪郭の位置付けはOpencvにおいてfindConturs方法によって実現でき、これは分水嶺を実行する前の要求である。アルゴリズムは、マーカーが導入した輪郭をシード(いわゆる注水点)として、画像上の他の画素点をスプリンクラーアルゴリズム規則に基づいて判断し、各画素点の領域帰属を画像上の全ての画素点を処理するまで画定する。領域と領域の境界における値を「-1」として区分します。
  • 距離変換と分水嶺を用いて互いに接触する物体をどのように分割するかの例を示した。
    下の硬貨の画像を考えてみます。これらの硬貨は互いに接触します。閾値化しても、互いに触れることができます。
    CSDN图标
    私たちは硬貨を見つけた概算値から始めます。そのためには、大津の二値化を利用することができます。
    
    #include<iostream>
    #include<opencv2\opencv.hpp>
    
    using namespace std;
    using namespace cv;
    
    int main() {
    	Mat gray, thresh;
    	Mat img = imread("coins.jpg");
    	cvtColor(img, gray, COLOR_BGR2GRAY);
    	threshold(gray, thresh, 0, 255, THRESH_BINARY_INV+CV_THRESH_OTSU);
    
    	imshow("Otst    ", thresh);
    	waitKey(0);
    	return 0;
    }
    
    閾値後の画像は以下の通りです。
    CSDN图标
    画像の中の小さな白色ノイズを除去する必要があります。そのために、私たちは形を使って操作することができます。物体の小さな穴を取り除くために,形態的な閉塞操作を用いることができる。したがって、今私たちが確認できるのは、物体の中心に近い領域が前景であり、物体から遠い領域が背景であることです。私たちが不確定な領域だけがコインの境界領域です。
    ですから、私たちは硬貨の区域であることを確認します。侵食除去境界画素。だから、いくら残っても、私たちは硬貨であることを確認できます。物体が接触しないなら、それでいいです。しかし、それらは互いに接触しているので、もう一つの良い選択は距離変換を見つけて適切な閾値を適用することである。次に私たちは硬貨ではないと確信する区域を探さなければなりません。そのために結果を拡張した。膨張は物体境界を背景に増加させる。このようにして,境界領域のために結果のどの背景領域も真の背景であることを確認できた。
    CSDN图标
    残りの地域は私たちが知らないのです。コインも背景も。分水嶺アルゴリズムはそれを見つけることができるはずです。これらのエリアは通常硬貨の境界をめぐっています。つまり、前景と背景が出会うところです。私たちは境界と呼びます。sureで_をかくfg面積からsure_を減算するbg面積が得られます。
    
    Mat opening; Mat sure_bg;
    Mat sure_fg; Mat unknow;
    Mat dist_transform;
    double maxValue;
    // noise removal
    Mat kernel = Mat::ones(3, 3, CV_8U);
    morphologyEx(thresh, opening, MORPH_OPEN, kernel);
    
    // sure background area
    dilate(opening, sure_bg, kernel, Point(-1, -1), 3);
    
    // Finding sure foreground area
    distanceTransform(opening, dist_transform, DIST_L2, 5);
    minMaxLoc(dist_transform, 0, &maxValue, 0, 0);
    threshold(dist_transform, sure_fg, 0.7*maxValue, 255, 0);
    
    // Finding unknown region
    sure_fg.convertTo(sure_fg, CV_8U);
    subtract(sure_bg, sure_fg, unknow);
    
    結果が見える。しきい値画像では、いくつかの領域のコインを得て、これらの硬貨は独立していると判断しました。場合によっては、前景分割だけに興味があるかもしれませんが、互いに接触する対象の分割には興味がありません。この場合、距離変換を使う必要はありません。浸食すれば十分です。浸食は前景領域を抽出する他の方法にすぎない。これだけである。)
    CSDN图标  CSDN图标
    今はどのような硬貨の領域が背景などなのか確認できます。したがって、元の画像と同じサイズの配列であるが、int 32データタイプを使用してマーカーを作成し、その中にマーカー領域を表示します。私たちが決定した領域は(前景でも背景でも)任意の正の整数としてマークされていますが、異なる整数は、私たちが不確定な領域は0として保存されます。そのために、connectedComponents()を使用しました。0で画像の背景をマークし、1から始まる整数で他のオブジェクトをマークします。
    しかし、私たちはbackgroundを0と表記すれば、watershedは未知の領域だと考えています。だから私たちは違う整数でマークします。逆に未知の領域をマークします。unknownで定義されています。0です。
    
    // Marker labelling
    Mat markers;
    connectedComponents(sure_fg, markers);
    
    // Add one to all labels so that sure background is not 0, but 1
    markers = markers + 1;
    
    // Now, mark the region of unknown with zero
    markers.setTo(0, unknow);
    
    私たちのタグ画像が用意されました。最後のステップになったら、分水嶺を適用します。マーク画像を変更します。境界領域は-1としてマークされます。
    
    Mat marker;
    Mat mask;
    watershed(img, markers);
    compare(markers, -1, mask, CMP_EQ);
    img.setTo(Scalar(0, 0, 255), mask);
    
    以下の結果を参照してください。いくつかの硬貨については、それらが接触する領域は正しく分割されていますが、他の硬貨については分割されていません。
    CSDN图标
    ここでC++の中でOpenCV画像分割と分水嶺アルゴリズムを実現する記事について紹介します。OpenCV画像分割と分水嶺アルゴリズムの内容については、以前の記事を検索したり、下記の関連記事を見たりしてください。これからもよろしくお願いします。