Segmentation de bassin versant OpenCV basée sur la transformation de distance pour la segmentation d'image

Flux de traitement de segmentation d'image

  1. Échelle de gris de l'image originale, binarisation, opération ouverte pour éliminer le bruit

  2. Transformation de distance, normalisation distanceTransform normalize

  3. Binarisez à nouveau, obtenez une certaine perspective, c'est-à-dire la graine

  4. Le marqueur est généré en fonction de la graine, qui peut être générée des deux manières suivantes:

  5. Transformation des bassins versants watershed

  6. Image de sortie

Transformation de distance

Le principe est expliqué en détail

Calculez la distance entre chaque pixel de l'image d'origine et le pixel 0 le plus proche.

Calculez la distance approximative ou exacte de chaque pixel d'image binaire au pixel zéro le plus proche. Pour zéro pixel d'image, la distance sera égale à zéro.

Généralement, le premier plan de l'image binaire est blanc (255) et l'arrière-plan est noir (0). La transformation de distance consiste à calculer la distance entre le premier plan et l'arrière-plan. Par conséquent, plus les pixels de la cible de premier plan sont éloignés de l'arrière-plan, plus la distance est grande et plus l'image transformée est lumineuse de l'arrière-plan.

Fonction OpenCV

void cv::distanceTransform( InputArray		src,
                          	OutputArray		dst,
                            OutputArray		labels,
                            int				distanceType,
                            int				maskSize,
                            int				labelType = DIST_LABEL_CCOMP 
                            )		
// Python:
dst, labels = cv.distanceTransformWithLabels(src, distanceType, maskSize[, dst[, labels[, labelType]]])

void cv::distanceTransform(	InputArray		src,
                            OutputArray		dst,
                            int				distanceType,
                            int				maskSize,
                            int				dstType = CV_32F 
                            )
// Python
dst = cv.distanceTransform(src, distanceType, maskSize[, dst[, dstType]]) 

Explication de la fonction

Lors de la maskSize == DIST_MASK_PRECISEsomme distanceType == DIST_L2, la fonction exécute l'algorithme 1 . L'algorithme est parallélisé avec la bibliothèque TBB.

Dans d'autres cas, l'algorithme 2 est utilisé . Cela signifie que pour un pixel, la fonction peut trouver le chemin le plus court vers le pixel zéro le plus proche. Le chemin se compose des déplacements de base suivants: horizontal, vertical, diagonal ou mouvement de chevalier (disponible lorsque le masque est 5 × 5). La distance totale est calculée comme la somme de ces distances de base. Puisque la fonction de distance doit être symétrique, tous les déplacements horizontaux et verticaux doivent avoir le même coût (noté a), tous les décalages diagonaux doivent avoir le même coût (noté b), et tous les déplacements de chevalier doivent avoir le même coût (exprimé par c). Pour DIST_Cet DIST_L1type, la distance peut être calculée avec précision, et pour DIST_L2(distance euclidienne), seule une distance calculée d'erreur relative (un masque 5 × 5 peut fournir des résultats plus précis). Pour a, bet c, OpenCV utilise les valeurs suggérées dans l'article original:

  • DIST_L1 : a = 1, b = 2
  • DIST_L2 :
    • 3 x 3: a = 0,955, b = 1,3693
    • 5 x 5: a = 1, b = 1,4, c = 2,1969
  • DIST_C : a = 1, b = 1

Généralement, pour une estimation rapide et approximative de la distance DIST_L2, un masque 3 × 3 est utilisé. Afin d'obtenir une estimation de distance plus précise DIST_L2, un masque 5 × 5 ou un algorithme précis peut être utilisé. Veuillez noter que l'algorithme exact et l'algorithme approximatif sont linéaires en nombre de pixels.

Une variante de cette fonction ne calcule pas seulement chaque pixel (x, y) (x, y)( x ,y ) est la distance minimale et identifie égalementle composant connecté le plus proche composé dezéro pixel (labelType == DIST_LABEL_CCOMP) ou du zéro pixel le plus proche (labelType == DIST_LABEL_PIXEL). L'index de communication / pixel est stocké danslabels(x,y)lefichier. LorsquelabelType == DIST_LABEL_CCOMPl'heure, la fonction trouve automatiquement les composants connectés à zéro pixel dans l'image d'entrée, et les marque avec une étiquette différente. LorsquelabelType == DIST_LABEL_CCOMPle temps, la fonction de numérisation d'image d'entrée, et utiliser des étiquettes différentes pour marquer tous les pixels zéro.

Dans ce mode, la complexité est toujours linéaire. En d'autres termes, cette fonction fournit une méthode très rapide pour calculer le diagramme de Voronoi d'une image binaire. Actuellement, la deuxième variante ne peut utiliser que l'algorithme de transformation de distance approximative, c'est-à-dire qu'elle n'est pas encore prise en charge maskSize = DIST_MASK_PRECISE.

Explication des paramètres

paramètre Explication
src Image originale 8 bits, monocanal
dst L'image de sortie de la distance calculée. Image monocanal 8 bits ou 32 bits de même taille que src
Étiquettes Sortie d'un tableau bidimensionnel d'étiquettes (diagramme de Voronoi discret). Le type est CV_32SC1 et la taille est la même que src.
distanceType Type de distance
maskSize La taille du masque de transformation de distance. Non pris en charge DIST_MASK_PRECISE. Dans le cas du type DIST_L1ou DIST_Cdistance, ce paramètre est forcé à 3, car 3 × 3 a le même résultat qu'un masque de 5 × 5 ou plus.
labelType Le type de tableau de balises à construire, DIST_LABEL_CCOMPetDIST_LABEL_PIXEL

Exemple C ++

int main()
{
    
    
    string outDir = "./";
    Mat img = imread("手.png", 0);
    // 二值化
    Mat imgBinary;
    threshold(img, imgBinary, 0, 255, THRESH_BINARY_INV|THRESH_OTSU);
    imshow("bin", imgBinary);
    // 距离变换
    Mat imgDist, imgDistBin;
    distanceTransform(imgBinary, imgDist, DIST_L2, 3);
    normalize(imgDist, imgDist, 0, 1, NORM_MINMAX);
    imshow("dist", imgDist);
  	// 再次阈值处理, 可以获取距离背景最远的部分(手掌心)
    threshold(imgDist, imgDistBin, 0.7, 1, THRESH_BINARY);
  	imshow("distBin", imgDistBin);
    waitKey();
    return 0;
}

Insérez la description de l'image ici

Algorithme de bassin versant

Le principe est expliqué en détail

Le concept de bassin versant repose sur la visualisation d'une image en trois dimensions: deux coordonnées spatiales en fonction du niveau de gris, comme le montre la figure suivante:

Insérez la description de l'image ici

Dans cette interprétation topographique, trois types de points sont considérés:

  1. Point minimum local: le point qui appartient à la valeur minimum d'une zone,
  2. Bassin versant ou bassin versant: traiter un point comme une goutte d'eau, si ces points sont placés à n'importe quelle position, la goutte d'eau tombera à un certain point minimum
  3. Ligne de division ou ligne de division de l'eau: l'eau à ce point s'écoulera vers plus d'un de ces points minimum avec une probabilité égale

L'objectif principal est de trouver le bassin versant. L'idée de base: Supposons qu'un trou soit fait dans la valeur minimale de chaque zone, et que l'eau puisse monter à travers le trou à une vitesse uniforme, submergeant le terrain de bas en haut. Lorsque les eaux montantes dans différents bassins versants se rassemblent, construisez un barrage pour arrêter ce rassemblement. L'eau atteindra le point où seuls les sommets des barrages peuvent être vus sur la ligne de flottaison, et les limites de ces barrages correspondent aux lignes de division du bassin versant. Il s'agit de la frontière extraite par l'algorithme du bassin versant.

Construction de barrages

La construction des barrages est basée sur des graphes binaires, le moyen le plus simple de construire des barrages est d'utiliser l' expansion morphologique .

Utilisez le diagramme ci-dessous pour illustrer comment utiliser l'expansion morphologique pour construire un barrage. La figure a montre les deux bassins versants qui ont été submergés à l'étape n-1, et la figure b montre le résultat de l'étape d'inondation n. L'eau a débordé d'un bassin à l'autre, il faut donc construire des barrages pour éviter que cela ne se produise.

Insérez la description de l'image ici

Utilisez 3 x 3 éléments structurels pour développer les composants connectés du graphique a. Le premier cycle d'expansion (la zone gris clair de la figure c) élargit les limites de chaque composant connecté d'origine et élargit uniformément les limites de chaque zone. Dans le deuxième cycle d'expansion (la zone noire sur la figure c), le chemin connecté à un pixel représenté par la ligne croisée se dilatera en même temps que les composants connectés gauche et droit se dilateront. Ce chemin constitue le barrage de segmentation souhaité. Construire un barrage consiste à définir la valeur en pixels de tous les points sur le chemin de construction du barrage à une valeur supérieure à la valeur de gris maximale de l'image, généralement définie sur la valeur de gris maximale autorisée dans l'image plus 1. De cette façon, lorsque le niveau de l'eau monte, les eaux de crue peuvent être empêchées de déborder du barrage achevé.

Le barrage construit grâce à ce processus est la frontière de division que nous espérons obtenir, ce qui peut éliminer le problème de la fracture de la ligne de division.

Utilisation de la marque

L'application directe de l'algorithme de bassin versant ci-dessus entraîne généralement une sur-segmentation due au bruit et à d'autres irrégularités locales dans le gradient. La gravité de la sur-segmentation est suffisante pour rendre les résultats obtenus par l'algorithme inutiles, comme le montre la figure ci-dessous. . Dans ce cas, cela signifie qu'il existe un grand nombre de régions divisées. Le nombre de régions autorisées peut être limité en ajoutant une étape de prétraitement, et l'étape de prétraitement se réfère à l'application de connaissances supplémentaires au processus de segmentation.

Insérez la description de l'image ici

Une méthode utilisée pour contrôler la sur-segmentation est basée sur le concept d'étiquetage. Les marqueurs font référence aux composants connectés appartenant à une image. La marque associée à l'objet d'intérêt est appelée la marque interne et la marque associée à l'arrière-plan est appelée la marque externe. Le processus typique de sélection d'un tag se compose de deux étapes principales: 1. Prétraitement, 2. Définir un ensemble de critères que le tag doit respecter. Une partie de la raison de la sur-segmentation de l'image ci-dessus est qu'il existe un grand nombre de minima potentiels. En raison de leur taille, de nombreux minima sont des détails non pertinents. Un moyen efficace de minimiser l'impact des petits détails spatiaux consiste à filtrer l'image avec un filtre de lissage.

La marque interne est définie comme:

  1. Zone entourée de points "d'altitude" plus élevés
  2. Les points qui forment un composant connecté dans la zone
  3. Les composants connectés ont la même valeur de gris pour tous les points

Une fois l'image lissée, les marqueurs internes s'affichent dans des zones rouges en forme de points dans le centre gauche de la figure ci-dessous. Ensuite, l'algorithme de bassin versant peut être appliqué à l'image lissée sous la limitation de la zone minimale autorisée pour ces marqueurs internes. La figure de gauche montre les lignes de partage des eaux obtenues. Ces lignes de partage des eaux sont définies comme des marques externes et les points le long de la ligne de partage des eaux passent par le point le plus élevé entre les marques adjacentes.

Le marqueur externe divise efficacement l'image en différentes régions, et chaque région contient un marqueur interne et un arrière-plan. De cette manière, le problème se réduit à diviser chaque zone en deux parties: un seul objet et son arrière-plan. Selon ce problème simplifié, différentes techniques de segmentation peuvent être appliquées; une autre méthode consiste simplement à appliquer l'algorithme de bassin versant à chaque zone. En d'autres termes, après avoir obtenu le gradient de l'image lissée, l'algorithme est alors restreint pour n'opérer que sur un seul bassin versant qui contient cette marque dans la région spéciale. Les résultats obtenus en utilisant cette méthode sont indiqués à droite dans la figure ci-dessous, et l'amélioration est évidente.

Insérez la description de l'image ici

La sélection des marqueurs peut être classée sur la base d'un processus simple de valeur grise et de connectivité. La clé est d'utiliser les connaissances préalables liées au problème de segmentation apporté par l'utilisation de marqueurs.

Fonction OpenCV

void cv::watershed(	InputArray			image,
                    InputOutputArray	markers 
                    )		
// Python:
markers = cv.watershed(image, markers)

Utilisez l'algorithme de bassin versant pour effectuer une segmentation d'image basée sur des étiquettes.

Cette fonction implémente une variante de bassin versant, un algorithme de segmentation basé sur les marques Non paramétrique 3

Avant de passer l'image à la fonction, vous devez utiliser un index positif (> 0) pour délimiter approximativement la zone souhaitée dans le marqueur d'image. Par conséquent, chaque zone est représentée comme un ou plusieurs composants connectés, avec des valeurs de pixel de 1, 2, 3, etc., respectivement. Vous pouvez être utilisé findContourset drawContoursrécupérer ces marqueurs à partir du masque binaire. Le marqueur est la "graine" de la future zone d'image. Tous les pixels du marqueur dont la relation avec la zone de contour est inconnue et doivent être définis par l'algorithme doivent être mis à 0. Dans la sortie de fonction, chaque pixel du marqueur est défini sur la valeur du composant "germe" ou -1 à la limite entre les régions.

paramètre:

  1. image : Entrée d'image à trois canaux 8 bits
  2. markers: Image monocanal 32 bits entrée / sortie marquée. Sa taille doit être la même que celle de l'image.

Exemple du processus global de segmentation

Algorithme de segmentation des bassins versants basé sur la transformation de distance

int main()
{
    
    
    Mat src = imread("./coins_001.jpg");
    if (src.empty()) {
    
    
        cout << "could not load image..." << endl;
        return -1;
    }
    namedWindow("input image", WINDOW_AUTOSIZE);
    imshow("input image", src);
	// 均值漂移,边缘保留,平滑色彩细节
    Mat gray, binary, shifted;
    pyrMeanShiftFiltering(src, shifted, 21, 51);
    imshow("shifted", shifted);
	// 二值化
    cvtColor(shifted, gray, COLOR_BGR2GRAY);
    threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
    imshow("binary", binary);

    // 距离变换
    Mat dist;
    distanceTransform(binary, dist, DIST_L2, 3, CV_32F);
    normalize(dist, dist, 0, 1, NORM_MINMAX);
    imshow("distance result", dist);

    // 二值化,获取种子
    threshold(dist, dist, 0.4, 1, THRESH_BINARY);
    imshow("distance binary", dist);

    // 通过寻找轮廓,绘制轮廓,获取标记
    Mat dist_m;
    dist.convertTo(dist_m, CV_8U);
    vector<vector<Point>> contours;
    findContours(dist_m, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));

    Mat markers = Mat::zeros(src.size(), CV_32SC1);
    for (int t = 0; t < contours.size(); t++) {
    
    
        drawContours(markers, contours, t, Scalar::all(t + 1), -1);
    }
    circle(markers, Point(5, 5), 3, Scalar(255), -1);
    
    // 形态学操作 - 彩色图像,目的是去掉干扰,让效果更好
    Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
    morphologyEx(src, src, MORPH_ERODE, k);

    // 完成分水岭变换
    watershed(src, markers);
    Mat mark = Mat::zeros(markers.size(), CV_8UC1);
    markers.convertTo(mark, CV_8UC1);
    imshow("watershed result", mark);

    // 生成随机颜色
    vector<Vec3b> colors;
    for (size_t i = 0; i < contours.size(); i++) {
    
    
        int r = theRNG().uniform(0, 255);
        int g = theRNG().uniform(0, 255);
        int b = theRNG().uniform(0, 255);
        colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
    }

    // 颜色填充与最终显示
    Mat dst = Mat::zeros(markers.size(), CV_8UC3);
    int index = 0;
    for (int row = 0; row < markers.rows; row++) {
    
    
        for (int col = 0; col < markers.cols; col++) {
    
    
        	index = markers.at<int>(i,j);
          	// index == -1 是分水线(边缘)
            if(index == -1){
    
    
                dst.at<Vec3b>(row, col) = Vec3b(255, 255, 255);
            // 背景
            } else if (index <= 0 || index > contours.size()) {
    
    
                dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
            } else {
    
    
                dst.at<Vec3b>(row, col) = colors[index - 1];
            }
        }
    }

    imshow("Final Result", dst);
    cout << "number of objects : " << contours.size() << endl;;

    waitKey(0);
    return 0;

}

Insérez la description de l'image ici


  1. Pedro Felzenszwalb et Daniel Huttenlocher. Transformations de distance des fonctions échantillonnées . Rapport technique, Université Cornell, 2004. ↩︎

  2. Gunilla Borgefors. Transformations de distance dans les images numériques. Vision par ordinateur, graphiques et traitement d'image , 34 (3): 344–371, 1986. ↩︎

  3. Fernand Meyer. Segmentation d'image couleur. In Image Processing and its Applications, 1992., International Conference on , pages 303–306. IET, 1992. ↩︎

Je suppose que tu aimes

Origine blog.csdn.net/m0_38007695/article/details/114807973
conseillé
Classement