精华内容
下载资源
问答
  • matlab 分水岭分割算法

    2017-07-28 09:57:10
    matlab分水岭分割算法实例,内涵有实测数据,亲测可用!
  • 风水岭算法常见的有三种方法:(1)基于浸泡理论的分水岭分割方法;(2)基于连通图方法;(3)基于距离变换的方法。OpenCV 中是基于距离变换的分割方法,就相当于我们的小山头(认为造成的)。基本的步骤:例子1 粘连对象...

    1. 分水岭分割方法

    它是依赖于形态学的,图像的灰度等级不一样,如果图像的灰度等级一样的情况下怎么人为的把它造成不一样?可以通过距离变换实现,这样它们的灰度值就有了阶梯状的变换。风水岭算法常见的有三种方法:(1)基于浸泡理论的分水岭分割方法;(2)基于连通图方法;(3)基于距离变换的方法。OpenCV 中是基于距离变换的分割方法,就相当于我们的小山头(认为造成的)。

    基本的步骤:

    7bf9c79221dd1f2d5b82ed9235710240.png

    例子1 粘连对象分离和计数。

    例子代码:

    #include#includeusing namespace std;using namespace cv;void test(){    Mat srcImg;    srcImg = imread("pill_002.png");    if (srcImg.empty())    {        cout <>contours;    //找到 marker 的轮廓    findContours(distMaskImg, contours, RETR_EXTERNAL,                 CHAIN_APPROX_SIMPLE, Point(0, 0));    //create marker 填充 marker    Mat  markersImg = Mat::zeros(srcImg.size(), CV_32SC1);    for (int i = 0; i (i),                     Scalar::all(static_cast(i)+1), -1);     }    circle(markersImg, Point(5, 5), 3, Scalar(255), -1);    //形态学操作 - 彩色图像,目的是去掉干扰,让结果更好。    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));    morphologyEx(srcImg, srcImg, MORPH_ERODE, kernel);    //完成分水岭变换    watershed(srcImg, markersImg);    Mat mark = Mat::zeros(markersImg.size(), CV_8UC1);    markersImg.convertTo(mark, CV_8UC1);    bitwise_not(mark, mark, Mat());    namedWindow("watershed", CV_WINDOW_AUTOSIZE);    imshow("watershed", mark);    //下面的步骤可以不做,最好做出来让结果显示更美观。    //生成随机颜色    vectorcolors;    for (int i = 0; i (i, j);            if (index > 0 && index <= contours.size())            {                dstImg.at(i, j) = colors[index - 1];            }            else            {                dstImg.at(i, j) = Vec3b(0, 0, 0);            }        }    }    cout <
    53b595c6823d946099d883cbd6d0cabe.png

    总结:有时候会导致碎片化,过度分割,因为二值化中如果有很多小的黑点或碎片,在分割的时候导致很多 mask ,即小山头太多了,这个时候我们要考虑怎么去合并它,可以通过联通区域的直方图,或者像素值均值相似程度等。

    例子2:图像分割

    #include#includeusing namespace std;using namespace cv;//执行分水岭算法函数Mat watershedCluster(Mat &srcImg, int &numSegments);//结果显示函数void DisplaySegments(Mat &markersImg, int numSegments);void test(){    Mat srcImg;    srcImg = imread("toux.jpg");    if (srcImg.empty())    {        cout <>contours;    vectorhireachy;    findContours(distImg, contours, hireachy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);    if (contours.empty())    {        return Mat();    }    Mat markersImg(distImg.size(), CV_32S);    markersImg = Scalar::all(0);    for (int i = 0; i colors;    for (int i = 0; i (i, j);            if (index > 0 && index <= numSegments)            {                dstImg.at(i, j) = colors[index - 1];            }            else            {                dstImg.at(i, j) = Vec3b(255, 255, 255);            }        }    }    cout <

    效果图:

    acd16b313fb1b997258f93b9c7063b81.png

    2. GrabCut 算法分割图像

    GrabCut 算法的原理前面有介绍过,这里就不在介绍了,具体可以看下文章末尾往期推荐中阅读。下面例子实现图像中对象的抠图。

    基本步骤:

    3929cc252aa94166ccbab16ef4ab36fe.png

    例子代码:

    #include#includeusing namespace std;using namespace cv;int numRun = 0; //算法迭代次数bool init = false;Rect rect;Mat srcImg, MaskImg, bgModel, fgModel;//鼠标回调函数void onMouse(int event, int x, int y, int flags, void* param);void showImg();  //显示画的图片void setRoiMask();  //选择 ROI 的函数void runGrabCut();  //执行算法函数static void ShowHelpText();  //提示用户操作函数void test(){    srcImg = imread("toux.jpg");    if (srcImg.empty())    {        cout < 1 && rect.height > 1)        {            showImg();        }        break;    default:        break;    }}void showImg(){    Mat result, binMask;    binMask.create(MaskImg.size(), CV_8UC1);    binMask = MaskImg & 1;    if (init)    {        srcImg.copyTo(result,binMask);    }    else    {        srcImg.copyTo(result);    }    rectangle(result, rect, Scalar(0, 0, 255), 2, 8);    namedWindow("Original image", CV_WINDOW_AUTOSIZE);    imshow("Original image", result);}void setRoiMask(){    //GC_BGD = 0   明确属于背景的像素    //GC_FGD = 1   明确属于前景的像素    //GC_PR_BGD = 2  可能属于背景的像素    //GC_PR_FGD = 3  可能属于前景的像素    MaskImg.setTo(GC_BGD);      //为了避免选择越界    rect.x = max(0, rect.x);    rect.y = max(0, rect.y);    rect.width = min(rect.width, srcImg.cols - rect.x);    rect.height = min(rect.height, srcImg.rows - rect.y);    //把我们选取的那一块设为前景    MaskImg(rect).setTo(Scalar(GC_PR_FGD));}void runGrabCut(){    if (rect.width 

    效果图:

    d9535a7ebfc47ccd6fdbba003b16eeda.png
    665bbb1079d04b75956b2c6ef4705dd5
    展开全文
  • matlab实现图像分水岭分割算法并且有改进的算法实现,两者相互比较效果,并且文档最后直接复制了全部的实现代码
  • 基于Matlab的标记分水岭分割算法

    千次阅读 2017-11-01 13:56:53
    基于Matlab的标记分水岭分割算法    (2011-05-21 13:57:31) 转载▼ 标签:  matlab   标记分水岭   分割   代码     lyqmath ...Separating touch

    基于Matlab的标记分水岭分割算法 

     (2011-05-21 13:57:31)
    标签: 

    matlab

     

    标记分水岭

     

    分割

     

    代码

     
     

    lyqmath

    http://blog.sina.com.cn/lyqmath

    1 综述

    Separating touching objects in an image is one of the more difficult image processing operations. The watershed transform is often applied to this problem. The watershed transform finds "catchment basins"(集水盆) and "watershed ridge lines"(山脊线) in an image by treating it as a surface where light pixels are high and dark pixels are low.

    如果图像中的目标物体是连接在一起的,则分割起来会更困难,分水岭分割算法经常用于处理这类问题,通常会取得比较好的效果。分水岭分割算法把图像看成一幅“地形图”,其中亮度比较强的区域像素值较大,而比较暗的区域像素值较小,通过寻找“汇水盆地”和“分水岭界限”,对图像进行分割。

    Segmentation using the watershed transform works better if you can identify, or "mark," foreground objects and background locations. Marker-controlled watershed segmentation follows this basic procedure:

    直接应用分水岭分割算法的效果往往并不好,如果在图像中对前景对象和背景对象进行标注区别,再应用分水岭算法会取得较好的分割效果。基于标记控制的分水岭分割方法有以下基本步骤:

    1. Compute a segmentation function. This is an image whose dark regions are the objects you are trying to segment.

    1.计算分割函数。图像中较暗的区域是要分割的对象。

    2. Compute foreground markers. These are connected blobs of pixels within each of the objects.

    2.计算前景标志。这些是每个对象内部连接的斑点像素。

    3. Compute background markers. These are pixels that are not part of any object.

    3.计算背景标志。这些是不属于任何对象的像素。

    4. Modify the segmentation function so that it only has minima at the foreground and background marker locations.

    4.修改分割函数,使其仅在前景和后景标记位置有极小值。

    5. Compute the watershed transform of the modified segmentation function.

    5.对修改后的分割函数做分水岭变换计算。

    Use by Matlab Image Processing Toolbox

    使用MATLAB图像处理工具箱

    注:期间用到了很多图像处理工具箱的函数,例如fspecial、imfilter、watershed、label2rgb、imopen、imclose、imreconstruct、imcomplement、imregionalmax、bwareaopen、graythresh和imimposemin函数等。

     

    2 步骤

     

    Step 1: Read in the Color Image and Convert it to Grayscale

    第一步:读入彩色图像,将其转化成灰度图像

    clc; clear all; close all;

    rgb = imread('pears.png');

    if ndims(rgb) == 3

        I = rgb2gray(rgb);

    else

        I = rgb;

    end

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 2, 1); imshow(rgb); title('原图');

    subplot(1, 2, 2); imshow(I); title('灰度图');

     

    基于Matlab的标记分水岭分割算法 

    Step 2: Use the Gradient Magnitude as the Segmentation Function

    第2步:将梯度幅值作为分割函数

    Use the Sobel edge masks, imfilter, and some simple arithmetic to compute the gradient magnitude. The gradient is high at the borders of the objects and low (mostly) inside the objects.

    使用Sobel边缘算子对图像进行水平和垂直方向的滤波,然后求取模值,sobel算子滤波后的图像在边界处会显示比较大的值,在没有边界处的值会很小。

    hy = fspecial('sobel');

    hx = hy';

    Iy = imfilter(double(I), hy, 'replicate');

    Ix = imfilter(double(I), hx, 'replicate');

    gradmag = sqrt(Ix.^2 + Iy.^2);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 2, 1); imshow(I,[]), title('灰度图像')

    subplot(1, 2, 2); imshow(gradmag,[]), title('梯度幅值图像')

    基于Matlab的标记分水岭分割算法 

    Can you segment the image by using the watershed transform directly on the gradient magnitude?

    可否直接对梯度幅值图像使用分水岭算法?

    L = watershed(gradmag);

    Lrgb = label2rgb(L);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 2, 1); imshow(gradmag,[]), title('梯度幅值图像')

    subplot(1, 2, 2); imshow(Lrgb); title('梯度幅值做分水岭变换')

    基于Matlab的标记分水岭分割算法 

    No. Without additional preprocessing such as the marker computations below, using the watershed transform directly often results in "oversegmentation."

    直接使用梯度模值图像进行分水岭算法得到的结果往往会存在过度分割的现象。因此通常需要分别对前景对象和背景对象进行标记,以获得更好的分割效果。

    Step 3: Mark the Foreground Objects

    第3步:标记前景对象

    A variety of procedures could be applied here to find the foreground markers, which must be connected blobs of pixels inside each of the foreground objects. In this example you'll use morphological techniques called "opening-by-reconstruction" and "closing-by-reconstruction" to "clean" up the image. These operations will create flat maxima inside each object that can be located using imregionalmax.

    有多种方法可以应用在这里来获得前景标记,这些标记必须是前景对象内部的连接斑点像素。这个例子中,将使用形态学技术“基于开的重建”和“基于闭的重建”来清理图像。这些操作将会在每个对象内部创建单位极大值,使得可以使用imregionalmax来定位。

    开运算和闭运算:先腐蚀后膨胀称为开;先膨胀后腐蚀称为闭。开和闭这两种运算可以除去比结构元素小的特定图像细节,同时保证不产生全局几何失真。开运算可以把比结构元素小的突刺滤掉,切断细长搭接而起到分离作用;闭运算可以把比结构元素小的缺口或孔填充上,搭接短的间隔而起到连接作用。

    Opening is an erosion followed by a dilation, while opening-by-reconstruction is an erosion followed by a morphological reconstruction. Let's compare the two. First, compute the opening using imopen.

    开操作是腐蚀后膨胀,基于开的重建(基于重建的开操作)是腐蚀后进行形态学重建。下面比较这两种方式。首先,用imopen做开操作。

    se = strel('disk', 20);

    Io = imopen(I, se);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 2, 1); imshow(I, []); title('灰度图像');

    subplot(1, 2, 2); imshow(Io), title('图像开操作')

    基于Matlab的标记分水岭分割算法 

    Next compute the opening-by-reconstruction using imerode and imreconstruct.

    接下来,通过腐蚀后重建来做基于开的重建计算。

    Ie = imerode(I, se);

    Iobr = imreconstruct(Ie, I);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 2, 1); imshow(I, []); title('灰度图像');

    subplot(1, 2, 2); imshow(Iobr, []), title('基于开的重建图像')

    基于Matlab的标记分水岭分割算法 

    Following the opening with a closing can remove the dark spots and stem marks. Compare a regular morphological closing with a closing-by-reconstruction. First try imclose:

    开操作后,接着进行闭操作,可以移除较暗的斑点和枝干标记。对比常规的形态学闭操作和基于闭的重建操作。首先,使用imclose:

    Ioc = imclose(Io, se);

    Ic = imclose(I, se);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(2, 2, 1); imshow(I, []); title('灰度图像');

    subplot(2, 2, 2); imshow(Io, []); title('开操作图像');

    subplot(2, 2, 3); imshow(Ic, []); title('闭操作图像');

    subplot(2, 2, 4); imshow(Ioc, []), title('开闭操作');

    基于Matlab的标记分水岭分割算法 


    Now use imdilate followed by imreconstruct. Notice you must complement the image inputs and output of imreconstruct. IM2 = imcomplement(IM) computes the complement(补集) of the image IM. IM can be a binary, intensity, or RGB image. IM2 has the same class and size as IM.

    现在使用imdilate,然后使用imreconstruct。注意必须对输入图像求补,对imreconstruct输出图像求补。IM2 = imcomplement(IM)计算图像IM的补集。IM可以是二值图像,或者RGB图像。IM2与IM有着相同的数据类型和大小。

    Iobrd = imdilate(Iobr, se);

    Iobrcbr = imreconstruct(imcomplement(Iobrd), imcomplement(Iobr));

    Iobrcbr = imcomplement(Iobrcbr);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(2, 2, 1); imshow(I, []); title('灰度图像');

    subplot(2, 2, 2); imshow(Ioc, []); title('开闭操作');

    subplot(2, 2, 3); imshow(Iobr, []); title('基于开的重建图像');

    subplot(2, 2, 4); imshow(Iobrcbr, []), title('基于闭的重建图像');

     

    基于Matlab的标记分水岭分割算法 

    As you can see by comparing Iobrcbr with Ioc, reconstruction-based opening and closing are more effective than standard opening and closing at removing small blemishes without affecting the overall shapes of the objects. Calculate the regional maxima of Iobrcbr to obtain good foreground markers.

    通过比较Iobrcbr和loc可以看到,在移除小污点同时不影响对象全局形状的应用下,基于重建的开闭操作要比标准的开闭重建更加有效。计算Iobrcbr的局部极大来得到更好的前景标记。

    fgm = imregionalmax(Iobrcbr);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 3, 1); imshow(I, []); title('灰度图像');

    subplot(1, 3, 2); imshow(Iobrcbr, []); title('基于重建的开闭操作');

    subplot(1, 3, 3); imshow(fgm, []); title('局部极大图像');

    基于Matlab的标记分水岭分割算法 

    To help interpret the result, superimpose(叠加) the foreground marker image on the original image.

    为了帮助理解这个结果,叠加前景标记到原图上。

    It1 = rgb(:, :, 1);

    It2 = rgb(:, :, 2);

    It3 = rgb(:, :, 3);

    It1(fgm) = 255; It2(fgm) = 0; It3(fgm) = 0;

    I2 = cat(3, It1, It2, It3);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(2, 2, 1); imshow(rgb, []); title('原图像');

    subplot(2, 2, 2); imshow(Iobrcbr, []); title('基于重建的开闭操作');

    subplot(2, 2, 3); imshow(fgm, []); title('局部极大图像');

    subplot(2, 2, 4); imshow(I2); title('局部极大叠加到原图像');

    基于Matlab的标记分水岭分割算法 

    Notice that some of the mostly-occluded and shadowed objects are not marked, which means that these objects will not be segmented properly in the end result. Also, the foreground markers in some objects go right up to the objects' edge. That means you should clean the edges of the marker blobs and then shrink them a bit. You can do this by a closing followed by an erosion.

    注意到大多闭塞处和阴影对象没有被标记,这就意味着这些对象在结果中将不会得到合理的分割。而且,一些对象的前景标记会一直到对象的边缘。这就意味着应该清理标记斑点的边缘,然后收缩它们。可以通过闭操作和腐蚀操作来完成。

    se2 = strel(ones(5,5));

    fgm2 = imclose(fgm, se2);

    fgm3 = imerode(fgm2, se2);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(2, 2, 1); imshow(Iobrcbr, []); title('基于重建的开闭操作');

    subplot(2, 2, 2); imshow(fgm, []); title('局部极大图像');

    subplot(2, 2, 3); imshow(fgm2, []); title('闭操作');

    subplot(2, 2, 4); imshow(fgm3, []); title('腐蚀操作');

    基于Matlab的标记分水岭分割算法 

    This procedure tends to leave some stray isolated pixels that must be removed. You can do this using bwareaopen, which removes all blobs that have fewer than a certain number of pixels. BW2 = bwareaopen(BW,P) removes from a binary image all connected components (objects) that have fewer than P pixels, producing another binary image, BW2.

    这个过程将会留下一些偏离的孤立像素,应该移除它们。可以使用bwareaopen,用来移除少于特定像素个数的斑点。BW2 = bwareaopen(BW,P)从二值图像中移除所以少于P像素值的连通块,得到另外的二值图像BW2。

    fgm4 = bwareaopen(fgm3, 20);

    It1 = rgb(:, :, 1);

    It2 = rgb(:, :, 2);

    It3 = rgb(:, :, 3);

    It1(fgm4) = 255; It2(fgm4) = 0; It3(fgm4) = 0;

    I3 = cat(3, It1, It2, It3);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(2, 2, 1); imshow(I2, []); title('局部极大叠加到原图像');

    subplot(2, 2, 2); imshow(fgm3, []); title('闭腐蚀操作');

    subplot(2, 2, 3); imshow(fgm4, []); title('去除小斑点操作');

    subplot(2, 2, 4); imshow(I3, []); title('修改局部极大叠加到原图像');

    基于Matlab的标记分水岭分割算法 

    Step 4: Compute Background Markers

    Now you need to mark the background. In the cleaned-up image, Iobrcbr, the dark pixels belong to the background, so you could start with a thresholding operation.

    第4步:计算背景标记

    现在,需要标记背景。在清理后的图像Iobrcbr中,暗像素属于背景,所以可以从阈值操作开始。

    bw = im2bw(Iobrcbr, graythresh(Iobrcbr));

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 2, 1); imshow(Iobrcbr, []); title('基于重建的开闭操作');

    subplot(1, 2, 2); imshow(bw, []); title('阈值分割');



    基于Matlab的标记分水岭分割算法 


    The background pixels are in black, but ideally we don't want the background markers to be too close to the edges of the objects we are trying to segment. We'll "thin" the background by computing the "skeleton by influence zones", or SKIZ, of the foreground of bw. This can be done by computing the watershed transform of the distance transform of bw, and then looking for the watershed ridge lines (DL == 0) of the result. D = bwdist(BW) computes the Euclidean distance transform of the binary image BW. For each pixel in BW, the distance transform assigns a number that is the distance between that pixel and the nearest nonzero pixel of BW. bwdist uses the Euclidean distance metric by default. BW can have any dimension. D is the same size as BW.

    背景像素在黑色区域,但是理想情形下,不必要求背景标记太接近于要分割的对象边缘。通过计算“骨架影响范围”来“细化”背景,或者SKIZ,bw的前景。这个可以通过计算bw的距离变换的分水岭变换来实现,然后寻找结果的分水岭脊线(DL==0)。D = bwdist(BW)计算二值图像BW的欧几里得矩阵。对BW的每一个像素,距离变换指定像素和最近的BW非零像素的距离。bwdist默认使用欧几里得距离公式。BW可以由任意维数,D与BW有同样的大小。

    D = bwdist(bw);

    DL = watershed(D);

    bgm = DL == 0;

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(2, 2, 1); imshow(Iobrcbr, []); title('基于重建的开闭操作');

    subplot(2, 2, 2); imshow(bw, []); title('阈值分割');

    subplot(2, 2, 3); imshow(label2rgb(DL), []); title('分水岭变换示意图');

    subplot(2, 2, 4); imshow(bgm, []); title('分水岭变换脊线图');

    基于Matlab的标记分水岭分割算法 

    Step 5: Compute the Watershed Transform of the Segmentation Function.

    The function imimposemin can be used to modify an image so that it has regional minima only in certain desired locations. Here you can use imimposemin to modify the gradient magnitude image so that its only regional minima occur at foreground and background marker pixels.

    第5步:计算分割函数的分水岭变换

    函数imimposemin可以用来修改图像,使其只是在特定的要求位置有局部极小。这里可以使用imimposemin来修改梯度幅值图像,使其只在前景和后景标记像素有局部极小。

    gradmag2 = imimposemin(gradmag, bgm | fgm4);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(2, 2, 1); imshow(bgm, []); title('分水岭变换脊线图');

    subplot(2, 2, 2); imshow(fgm4, []); title('前景标记');

    subplot(2, 2, 3); imshow(gradmag, []); title('梯度幅值图像');

    subplot(2, 2, 4); imshow(gradmag2, []); title('修改梯度幅值图像');

    基于Matlab的标记分水岭分割算法 

    Finally we are ready to compute the watershed-based segmentation.

    最后,可以做基于分水岭的图像分割计算。

    Step 6: Visualize the Result

    One visualization technique is to superimpose the foreground markers, background markers, and segmented object boundaries on the original image. You can use dilation as needed to make certain aspects, such as the object boundaries, more visible. Object boundaries are located where L == 0.

    第6步:查看结果

    一个可视化技术是叠加前景标记、背景标记、分割对象边界到初始图像。可以使用膨胀来实现某些要求,比如对象边界,更加清晰可见。对象边界定位于L==0的位置。

    It1 = rgb(:, :, 1);

    It2 = rgb(:, :, 2);

    It3 = rgb(:, :, 3);

    fgm5 = imdilate(L == 0, ones(3, 3)) | bgm | fgm4;

    It1(fgm5) = 255; It2(fgm5) = 0; It3(fgm5) = 0;

    I4 = cat(3, It1, It2, It3);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 2, 1); imshow(rgb, []); title('原图像');

    subplot(1, 2, 2); imshow(I4, []); title('标记和对象边缘叠加到原图像');

    基于Matlab的标记分水岭分割算法 

    This visualization illustrates how the locations of the foreground and background markers affect the result. In a couple of locations, partially occluded darker objects were merged with their brighter neighbor objects because the occluded objects did not have foreground markers.

    可视化说明了前景和后景标记如何影响结果。在几个位置,部分的较暗对象与它们相邻的较亮的邻接对象相融合,这是因为受遮挡的对象没有前景标记。

    Another useful visualization technique is to display the label matrix as a color image. Label matrices, such as those produced by watershed and bwlabel, can be converted to truecolor images for visualization purposes by using label2rgb.

    另外一个有用的可视化技术是将标记矩阵作为彩色图像进行显示。标记矩阵,比如通过watershed和bwlabel得到的,可以使用label2rgb转换到真彩图像来显示。

    Lrgb = label2rgb(L, 'jet', 'w', 'shuffle');

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 2, 1); imshow(rgb, []); title('原图像');

    subplot(1, 2, 2); imshow(Lrgb); title('彩色分水岭标记矩阵');

     

    基于Matlab的标记分水岭分割算法 

    You can use transparency to superimpose this pseudo-color label matrix on top of the original intensity image.

    可以使用透明度来叠加这个伪彩色标记矩阵在原亮度图像上进行显示。

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 2, 1); imshow(rgb, []); title('原图像');

    subplot(1, 2, 2); imshow(rgb, []); hold on;

    himage = imshow(Lrgb);

    set(himage, 'AlphaData', 0.3);

    title('标记矩阵叠加到原图像');

     

    基于Matlab的标记分水岭分割算法 

    参考:http://blueben-chong.blog.sohu.com/141881444.html

    总结

    代码:

    clc; clear all; close all;

    rgb = imread('pears.png');

    if ndims(rgb) == 3

        I = rgb2gray(rgb);

    else

        I = rgb;

    end

     

    hy = fspecial('sobel');

    hx = hy';

    Iy = imfilter(double(I), hy, 'replicate');

    Ix = imfilter(double(I), hx, 'replicate');

    gradmag = sqrt(Ix.^2 + Iy.^2);

     

    L = watershed(gradmag);

    Lrgb = label2rgb(L);

     

    se = strel('disk', 20);

    Io = imopen(I, se);

     

    Ie = imerode(I, se);

    Iobr = imreconstruct(Ie, I);

     

    Ioc = imclose(Io, se);

     

    Iobrd = imdilate(Iobr, se);

    Iobrcbr = imreconstruct(imcomplement(Iobrd), imcomplement(Iobr));

    Iobrcbr = imcomplement(Iobrcbr);

     

    fgm = imregionalmax(Iobrcbr);

     

    It1 = rgb(:, :, 1);

    It2 = rgb(:, :, 2);

    It3 = rgb(:, :, 3);

    It1(fgm) = 255; It2(fgm) = 0; It3(fgm) = 0;

    I2 = cat(3, It1, It2, It3);

     

    se2 = strel(ones(5,5));

    fgm2 = imclose(fgm, se2);

    fgm3 = imerode(fgm2, se2);

     

    fgm4 = bwareaopen(fgm3, 20);

    It1 = rgb(:, :, 1);

    It2 = rgb(:, :, 2);

    It3 = rgb(:, :, 3);

    It1(fgm4) = 255; It2(fgm4) = 0; It3(fgm4) = 0;

    I3 = cat(3, It1, It2, It3);

     

    bw = im2bw(Iobrcbr, graythresh(Iobrcbr));

     

    D = bwdist(bw);

    DL = watershed(D);

    bgm = DL == 0;

     

    gradmag2 = imimposemin(gradmag, bgm | fgm4);

    L = watershed(gradmag2);

     

    It1 = rgb(:, :, 1);

    It2 = rgb(:, :, 2);

    It3 = rgb(:, :, 3);

    fgm5 = imdilate(L == 0, ones(3, 3)) | bgm | fgm4;

    It1(fgm5) = 255; It2(fgm5) = 0; It3(fgm5) = 0;

    I4 = cat(3, It1, It2, It3);

     

    Lrgb = label2rgb(L, 'jet', 'w', 'shuffle');

    展开全文
  • 分水岭分割算法

    2018-04-19 10:54:20
    MATLAB对图片进行分水岭分割算法
  • 分水岭分割算法把图像看成一幅“地形图”,其中亮度比较强的区域像素值较大,而比较暗的区域像素值较小,通过寻找“汇水盆地”和“分水岭界限”,对图像进行分割。 Segmentation using the watershed transform ...

    1 综述

    Separating touching objects in an image is one of the more difficult image processing operations. The watershed transform is often applied to this problem. The watershed transform finds "catchment basins"(集水盆) and "watershed ridge lines"(山脊线) in an image by treating it as a surface where light pixels are high and dark pixels are low.

    如果图像中的目标物体是连接在一起的,则分割起来会更困难,分水岭分割算法经常用于处理这类问题,通常会取得比较好的效果。分水岭分割算法把图像看成一幅“地形图”,其中亮度比较强的区域像素值较大,而比较暗的区域像素值较小,通过寻找“汇水盆地”和“分水岭界限”,对图像进行分割。

    Segmentation using the watershed transform works better if you can identify, or "mark," foreground objects and background locations. Marker-controlled watershed segmentation follows this basic procedure:

    直接应用分水岭分割算法的效果往往并不好,如果在图像中对前景对象和背景对象进行标注区别,再应用分水岭算法会取得较好的分割效果。基于标记控制的分水岭分割方法有以下基本步骤:

    1. Compute a segmentation function. This is an image whose dark regions are the objects you are trying to segment.

    1.计算分割函数。图像中较暗的区域是要分割的对象。

    2. Compute foreground markers. These are connected blobs of pixels within each of the objects.

    2.计算前景标志。这些是每个对象内部连接的斑点像素。

    3. Compute background markers. These are pixels that are not part of any object.

    3.计算背景标志。这些是不属于任何对象的像素。

    4. Modify the segmentation function so that it only has minima at the foreground and background marker locations.

    4.修改分割函数,使其仅在前景和后景标记位置有极小值。

    5. Compute the watershed transform of the modified segmentation function.

    5.对修改后的分割函数做分水岭变换计算。

    Use by Matlab Image Processing Toolbox

    使用MATLAB图像处理工具箱

    注:期间用到了很多图像处理工具箱的函数,例如fspecial、imfilter、watershed、label2rgb、imopen、imclose、imreconstruct、imcomplement、imregionalmax、bwareaopen、graythresh和imimposemin函数等。

    2 步骤

    Step 1: Read in the Color Image and Convert it to Grayscale

    第一步:读入彩色图像,将其转化成灰度图像

    clc; clear all; close all;

    rgb = imread('pears.png');

    if ndims(rgb) == 3

    I = rgb2gray(rgb);

    else

    I = rgb;

    end

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 2, 1); imshow(rgb); title('原图');

    subplot(1, 2, 2); imshow(I); title('灰度图');

    Step 2: Use the Gradient Magnitude as the Segmentation Function

    第2步:将梯度幅值作为分割函数

    Use the Sobel edge masks, imfilter, and some simple arithmetic to compute the gradient magnitude. The gradient is high at the borders of the objects and low (mostly) inside the objects.

    使用Sobel边缘算子对图像进行水平和垂直方向的滤波,然后求取模值,sobel算子滤波后的图像在边界处会显示比较大的值,在没有边界处的值会很小。

    hy = fspecial('sobel');

    hx = hy';

    Iy = imfilter(double(I), hy, 'replicate');

    Ix = imfilter(double(I), hx, 'replicate');

    gradmag = sqrt(Ix.^2 + Iy.^2);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 2, 1); imshow(I,[]), title('灰度图像')

    subplot(1, 2, 2); imshow(gradmag,[]), title('梯度幅值图像')

    Can you segment the image by using the watershed transform directly on the gradient magnitude?

    可否直接对梯度幅值图像使用分水岭算法?

    L = watershed(gradmag);

    Lrgb = label2rgb(L);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 2, 1); imshow(gradmag,[]), title('梯度幅值图像')

    subplot(1, 2, 2); imshow(Lrgb); title('梯度幅值做分水岭变换')

    No. Without additional preprocessing such as the marker computations below, using the watershed transform directly often results in "oversegmentation."

    直接使用梯度模值图像进行分水岭算法得到的结果往往会存在过度分割的现象。因此通常需要分别对前景对象和背景对象进行标记,以获得更好的分割效果。

    Step 3: Mark the Foreground Objects

    第3步:标记前景对象

    A variety of procedures could be applied here to find the foreground markers, which must be connected blobs of pixels inside each of the foreground objects. In this example you'll use morphological techniques called "opening-by-reconstruction" and "closing-by-reconstruction" to "clean" up the image. These operations will create flat maxima inside each object that can be located using imregionalmax.

    有多种方法可以应用在这里来获得前景标记,这些标记必须是前景对象内部的连接斑点像素。这个例子中,将使用形态学技术“基于开的重建”和“基于闭的重建”来清理图像。这些操作将会在每个对象内部创建单位极大值,使得可以使用imregionalmax来定位。

    开运算和闭运算:先腐蚀后膨胀称为开;先膨胀后腐蚀称为闭。开和闭这两种运算可以除去比结构元素小的特定图像细节,同时保证不产生全局几何失真。开运算可以把比结构元素小的突刺滤掉,切断细长搭接而起到分离作用;闭运算可以把比结构元素小的缺口或孔填充上,搭接短的间隔而起到连接作用。

    Opening is an erosion followed by a dilation, while opening-by-reconstruction is an erosion followed by a morphological reconstruction. Let's compare the two. First, compute the opening using imopen.

    开操作是腐蚀后膨胀,基于开的重建(基于重建的开操作)是腐蚀后进行形态学重建。下面比较这两种方式。首先,用imopen做开操作。

    se = strel('disk', 20);

    Io = imopen(I, se);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 2, 1); imshow(I, []); title('灰度图像');

    subplot(1, 2, 2); imshow(Io), title('图像开操作')

    Next compute the opening-by-reconstruction using imerode and imreconstruct.

    接下来,通过腐蚀后重建来做基于开的重建计算。

    Ie = imerode(I, se);

    Iobr = imreconstruct(Ie, I);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 2, 1); imshow(I, []); title('灰度图像');

    subplot(1, 2, 2); imshow(Iobr, []), title('基于开的重建图像')

    Following the opening with a closing can remove the dark spots and stem marks. Compare a regular morphological closing with a closing-by-reconstruction. First try imclose:

    开操作后,接着进行闭操作,可以移除较暗的斑点和枝干标记。对比常规的形态学闭操作和基于闭的重建操作。首先,使用imclose:

    Ioc = imclose(Io, se);

    Ic = imclose(I, se);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(2, 2, 1); imshow(I, []); title('灰度图像');

    subplot(2, 2, 2); imshow(Io, []); title('开操作图像');

    subplot(2, 2, 3); imshow(Ic, []); title('闭操作图像');

    subplot(2, 2, 4); imshow(Ioc, []), title('开闭操作');

    Now use imdilate followed by imreconstruct. Notice you must complement the image inputs and output of imreconstruct. IM2 = imcomplement(IM) computes the complement(补集) of the image IM. IM can be a binary, intensity, or RGB image. IM2 has the same class and size as IM.

    现在使用imdilate,然后使用imreconstruct。注意必须对输入图像求补,对imreconstruct输出图像求补。IM2 = imcomplement(IM)计算图像IM的补集。IM可以是二值图像,或者RGB图像。IM2与IM有着相同的数据类型和大小。

    Iobrd = imdilate(Iobr, se);

    Iobrcbr = imreconstruct(imcomplement(Iobrd), imcomplement(Iobr));

    Iobrcbr = imcomplement(Iobrcbr);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(2, 2, 1); imshow(I, []); title('灰度图像');

    subplot(2, 2, 2); imshow(Ioc, []); title('开闭操作');

    subplot(2, 2, 3); imshow(Iobr, []); title('基于开的重建图像');

    subplot(2, 2, 4); imshow(Iobrcbr, []), title('基于闭的重建图像');

    As you can see by comparing Iobrcbr with Ioc, reconstruction-based opening and closing are more effective than standard opening and closing at removing small blemishes without affecting the overall shapes of the objects. Calculate the regional maxima of Iobrcbr to obtain good foreground markers.

    通过比较Iobrcbr和loc可以看到,在移除小污点同时不影响对象全局形状的应用下,基于重建的开闭操作要比标准的开闭重建更加有效。计算Iobrcbr的局部极大来得到更好的前景标记。

    fgm = imregionalmax(Iobrcbr);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 3, 1); imshow(I, []); title('灰度图像');

    subplot(1, 3, 2); imshow(Iobrcbr, []); title('基于重建的开闭操作');

    subplot(1, 3, 3); imshow(fgm, []); title('局部极大图像');

    To help interpret the result, superimpose(叠加) the foreground marker image on the original image.

    为了帮助理解这个结果,叠加前景标记到原图上。

    It1 = rgb(:, :, 1);

    It2 = rgb(:, :, 2);

    It3 = rgb(:, :, 3);

    It1(fgm) = 255; It2(fgm) = 0; It3(fgm) = 0;

    I2 = cat(3, It1, It2, It3);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(2, 2, 1); imshow(rgb, []); title('原图像');

    subplot(2, 2, 2); imshow(Iobrcbr, []); title('基于重建的开闭操作');

    subplot(2, 2, 3); imshow(fgm, []); title('局部极大图像');

    subplot(2, 2, 4); imshow(I2); title('局部极大叠加到原图像');

    Notice that some of the mostly-occluded and shadowed objects are not marked, which means that these objects will not be segmented properly in the end result. Also, the foreground markers in some objects go right up to the objects' edge. That means you should clean the edges of the marker blobs and then shrink them a bit. You can do this by a closing followed by an erosion.

    注意到大多闭塞处和阴影对象没有被标记,这就意味着这些对象在结果中将不会得到合理的分割。而且,一些对象的前景标记会一直到对象的边缘。这就意味着应该清理标记斑点的边缘,然后收缩它们。可以通过闭操作和腐蚀操作来完成。

    se2 = strel(ones(5,5));

    fgm2 = imclose(fgm, se2);

    fgm3 = imerode(fgm2, se2);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(2, 2, 1); imshow(Iobrcbr, []); title('基于重建的开闭操作');

    subplot(2, 2, 2); imshow(fgm, []); title('局部极大图像');

    subplot(2, 2, 3); imshow(fgm2, []); title('闭操作');

    subplot(2, 2, 4); imshow(fgm3, []); title('腐蚀操作');

    This procedure tends to leave some stray isolated pixels that must be removed. You can do this using bwareaopen, which removes all blobs that have fewer than a certain number of pixels. BW2 = bwareaopen(BW,P) removes from a binary image all connected components (objects) that have fewer than P pixels, producing another binary image, BW2.

    这个过程将会留下一些偏离的孤立像素,应该移除它们。可以使用bwareaopen,用来移除少于特定像素个数的斑点。BW2 = bwareaopen(BW,P)从二值图像中移除所以少于P像素值的连通块,得到另外的二值图像BW2。

    fgm4 = bwareaopen(fgm3, 20);

    It1 = rgb(:, :, 1);

    It2 = rgb(:, :, 2);

    It3 = rgb(:, :, 3);

    It1(fgm4) = 255; It2(fgm4) = 0; It3(fgm4) = 0;

    I3 = cat(3, It1, It2, It3);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(2, 2, 1); imshow(I2, []); title('局部极大叠加到原图像');

    subplot(2, 2, 2); imshow(fgm3, []); title('闭腐蚀操作');

    subplot(2, 2, 3); imshow(fgm4, []); title('去除小斑点操作');

    subplot(2, 2, 4); imshow(I3, []); title('修改局部极大叠加到原图像');

    Step 4: Compute Background Markers

    Now you need to mark the background. In the cleaned-up image, Iobrcbr, the dark pixels belong to the background, so you could start with a thresholding operation.

    第4步:计算背景标记

    现在,需要标记背景。在清理后的图像Iobrcbr中,暗像素属于背景,所以可以从阈值操作开始。

    bw = im2bw(Iobrcbr, graythresh(Iobrcbr));

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 2, 1); imshow(Iobrcbr, []); title('基于重建的开闭操作');

    subplot(1, 2, 2); imshow(bw, []); title('阈值分割');

    The background pixels are in black, but ideally we don't want the background markers to be too close to the edges of the objects we are trying to segment. We'll "thin" the background by computing the "skeleton by influence zones", or SKIZ, of the foreground of bw. This can be done by computing the watershed transform of the distance transform of bw, and then looking for the watershed ridge lines (DL == 0) of the result. D = bwdist(BW) computes the Euclidean distance transform of the binary image BW. For each pixel in BW, the distance transform assigns a number that is the distance between that pixel and the nearest nonzero pixel of BW. bwdist uses the Euclidean distance metric by default. BW can have any dimension. D is the same size as BW.

    背景像素在黑色区域,但是理想情形下,不必要求背景标记太接近于要分割的对象边缘。通过计算“骨架影响范围”来“细化”背景,或者SKIZ,bw的前景。这个可以通过计算bw的距离变换的分水岭变换来实现,然后寻找结果的分水岭脊线(DL==0)。D = bwdist(BW)计算二值图像BW的欧几里得矩阵。对BW的每一个像素,距离变换指定像素和最近的BW非零像素的距离。bwdist默认使用欧几里得距离公式。BW可以由任意维数,D与BW有同样的大小。

    D = bwdist(bw);

    DL = watershed(D);

    bgm = DL == 0;

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(2, 2, 1); imshow(Iobrcbr, []); title('基于重建的开闭操作');

    subplot(2, 2, 2); imshow(bw, []); title('阈值分割');

    subplot(2, 2, 3); imshow(label2rgb(DL), []); title('分水岭变换示意图');

    subplot(2, 2, 4); imshow(bgm, []); title('分水岭变换脊线图');

    Step 5: Compute the Watershed Transform of the Segmentation Function.

    The function imimposemin can be used to modify an image so that it has regional minima only in certain desired locations. Here you can use imimposemin to modify the gradient magnitude image so that its only regional minima occur at foreground and background marker pixels.

    第5步:计算分割函数的分水岭变换

    函数imimposemin可以用来修改图像,使其只是在特定的要求位置有局部极小。这里可以使用imimposemin来修改梯度幅值图像,使其只在前景和后景标记像素有局部极小。

    gradmag2 = imimposemin(gradmag, bgm | fgm4);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(2, 2, 1); imshow(bgm, []); title('分水岭变换脊线图');

    subplot(2, 2, 2); imshow(fgm4, []); title('前景标记');

    subplot(2, 2, 3); imshow(gradmag, []); title('梯度幅值图像');

    subplot(2, 2, 4); imshow(gradmag2, []); title('修改梯度幅值图像');

    Finally we are ready to compute the watershed-based segmentation.

    最后,可以做基于分水岭的图像分割计算。

    Step 6: Visualize the Result

    One visualization technique is to superimpose the foreground markers, background markers, and segmented object boundaries on the original image. You can use dilation as needed to make certain aspects, such as the object boundaries, more visible. Object boundaries are located where L == 0.

    第6步:查看结果

    一个可视化技术是叠加前景标记、背景标记、分割对象边界到初始图像。可以使用膨胀来实现某些要求,比如对象边界,更加清晰可见。对象边界定位于L==0的位置。

    It1 = rgb(:, :, 1);

    It2 = rgb(:, :, 2);

    It3 = rgb(:, :, 3);

    fgm5 = imdilate(L == 0, ones(3, 3)) | bgm | fgm4;

    It1(fgm5) = 255; It2(fgm5) = 0; It3(fgm5) = 0;

    I4 = cat(3, It1, It2, It3);

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 2, 1); imshow(rgb, []); title('原图像');

    subplot(1, 2, 2); imshow(I4, []); title('标记和对象边缘叠加到原图像');

    This visualization illustrates how the locations of the foreground and background markers affect the result. In a couple of locations, partially occluded darker objects were merged with their brighter neighbor objects because the occluded objects did not have foreground markers.

    可视化说明了前景和后景标记如何影响结果。在几个位置,部分的较暗对象与它们相邻的较亮的邻接对象相融合,这是因为受遮挡的对象没有前景标记。

    Another useful visualization technique is to display the label matrix as a color image. Label matrices, such as those produced by watershed and bwlabel, can be converted to truecolor images for visualization purposes by using label2rgb.

    另外一个有用的可视化技术是将标记矩阵作为彩色图像进行显示。标记矩阵,比如通过watershed和bwlabel得到的,可以使用label2rgb转换到真彩图像来显示。

    Lrgb = label2rgb(L, 'jet', 'w', 'shuffle');

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 2, 1); imshow(rgb, []); title('原图像');

    subplot(1, 2, 2); imshow(Lrgb); title('彩色分水岭标记矩阵');

    You can use transparency to superimpose this pseudo-color label matrix on top of the original intensity image.

    可以使用透明度来叠加这个伪彩色标记矩阵在原亮度图像上进行显示。

    figure('units', 'normalized', 'position', [0 0 1 1]);

    subplot(1, 2, 1); imshow(rgb, []); title('原图像');

    subplot(1, 2, 2); imshow(rgb, []); hold on;

    himage = imshow(Lrgb);

    set(himage, 'AlphaData', 0.3);

    title('标记矩阵叠加到原图像');

    总结

    代码:

    clc; clear all; close all;

    rgb = imread('pears.png');

    if ndims(rgb) == 3

    I = rgb2gray(rgb);

    else

    I = rgb;

    end

    hy = fspecial('sobel');

    hx = hy';

    Iy = imfilter(double(I), hy, 'replicate');

    Ix = imfilter(double(I), hx, 'replicate');

    gradmag = sqrt(Ix.^2 + Iy.^2);

    L = watershed(gradmag);

    Lrgb = label2rgb(L);

    se = strel('disk', 20);

    Io = imopen(I, se);

    Ie = imerode(I, se);

    Iobr = imreconstruct(Ie, I);

    Ioc = imclose(Io, se);

    Iobrd = imdilate(Iobr, se);

    Iobrcbr = imreconstruct(imcomplement(Iobrd), imcomplement(Iobr));

    Iobrcbr = imcomplement(Iobrcbr);

    fgm = imregionalmax(Iobrcbr);

    It1 = rgb(:, :, 1);

    It2 = rgb(:, :, 2);

    It3 = rgb(:, :, 3);

    It1(fgm) = 255; It2(fgm) = 0; It3(fgm) = 0;

    I2 = cat(3, It1, It2, It3);

    se2 = strel(ones(5,5));

    fgm2 = imclose(fgm, se2);

    fgm3 = imerode(fgm2, se2);

    fgm4 = bwareaopen(fgm3, 20);

    It1 = rgb(:, :, 1);

    It2 = rgb(:, :, 2);

    It3 = rgb(:, :, 3);

    It1(fgm4) = 255; It2(fgm4) = 0; It3(fgm4) = 0;

    I3 = cat(3, It1, It2, It3);

    bw = im2bw(Iobrcbr, graythresh(Iobrcbr));

    D = bwdist(bw);

    DL = watershed(D);

    bgm = DL == 0;

    gradmag2 = imimposemin(gradmag, bgm | fgm4);

    L = watershed(gradmag2);

    It1 = rgb(:, :, 1);

    It2 = rgb(:, :, 2);

    It3 = rgb(:, :, 3);

    fgm5 = imdilate(L == 0, ones(3, 3)) | bgm | fgm4;

    It1(fgm5) = 255; It2(fgm5) = 0; It3(fgm5) = 0;

    I4 = cat(3, It1, It2, It3);

    Lrgb = label2rgb(L, 'jet', 'w', 'shuffle');

    展开全文
  • 应用标记符控制分水岭分割 有效解决了分水岭算法在图像分割中的过分割问题
  • 分水岭(Watershed)是基于地理形态的分析的图像分割算法,模仿地理结构(比如山川、沟壑,盆地)来实现对不同物体的分类。分水岭算法中会用到一个重要的概念——测地线距离。测地线距离(Geodesic Distance)测地线...

    分水岭(Watershed)是基于地理形态的分析的图像分割算法,模仿地理结构(比如山川、沟壑,盆地)来实现对不同物体的分类。

    分水岭算法中会用到一个重要的概念——测地线距离。

    测地线距离(Geodesic Distance)

    测地线距离就是地球表面两点之间的最短路径(可执行路径)的距离,在图论中,Geodesic Distance 就是图中两节点的最短路径的距离,这与平时在几何空间通常用到的 Euclidean Distance(欧氏距离),即两点之间的最短距离有所区别。

    在下图中,两个黑点的 Euclidean Distance 是用虚线所表示的线段的长度

    ,而 Geodesic Distance 作为实际路径的最短距离,其距离应为沿途实线段距离之和的最小值,即

    5243e49c3eac21e64974226f8ba8e943.png
    图1

    在三维曲面空间中两点间的测地距离就是两点间沿着三维曲面的表面走的最短路径。

    分水岭算法

    图像的灰度空间很像地球表面的整个地理结构,每个像素的灰度值代表高度。其中的灰度值较大的像素连成的线可以看做山脊,也就是分水岭。其中的水就是用于二值化的gray threshold level,二值化阈值可以理解为水平面,比水平面低的区域会被淹没,刚开始用水填充每个孤立的山谷(局部最小值)。

    当水平面上升到一定高度时,水就会溢出当前山谷,可以通过在分水岭上修大坝,从而避免两个山谷的水汇集,这样图像就被分成2个像素集,一个是被水淹没的山谷像素集,一个是分水岭线像素集。最终这些大坝形成的线就对整个图像进行了分区,实现对图像的分割。

    c18182669d74a1f8708bb100c8d9b59d.png
    图2

    在该算法中,空间上相邻并且灰度值相近的像素被划分为一个区域。

    分水岭算法的整个过程:

    1. 把梯度图像中的所有像素按照灰度值进行分类,并设定一个测地距离阈值。
    2. 找到灰度值最小的像素点(默认标记为灰度值最低点),让threshold从最小值开始增长,这些点为起始点。
    3. 水平面在增长的过程中,会碰到周围的邻域像素,测量这些像素到起始点(灰度值最低点)的测地距离,如果小于设定阈值,则将这些像素淹没,否则在这些像素上设置大坝,这样就对这些邻域像素进行了分类。

    fb5dd94e868d27b5c1c265ee04df96dd.png
    图3

    4. 随着水平面越来越高,会设置更多更高的大坝,直到灰度值的最大值,所有区域都在分水岭线上相遇,这些大坝就对整个图像像素的进行了分区。

    整个过程可以查看下面这个动图:

    02a829b00c8ab462d6cda59564d72419.png
    图4

    用上面的算法对图像进行分水岭运算,由于噪声点或其它因素的干扰,可能会得到密密麻麻的小区域,即图像被分得太细(over-segmented,过度分割),这因为图像中有非常多的局部极小值点,每个点都会自成一个小区域。

    其中的解决方法:

    1. 对图像进行高斯平滑操作,抹除很多小的最小值,这些小分区就会合并。
    2. 不从最小值开始增长,可以将相对较高的灰度值像素作为起始点(需要用户手动标记),从标记处开始进行淹没,则很多小区域都会被合并为一个区域,这被称为基于图像标记(mark)的分水岭算法

    下面三个图分别是原图,分水岭过分割的图以及基于标记的分水岭算法得到的图:

    a9a4ea1a3a9961193e658064c62638ca.png
    图5

    其中标记的每个点就相当于分水岭中的注水点,从这些点开始注水使得水平面上升,但是如上图所示,图像中需要分割的区域太多了,手动标记太麻烦,我们可是使用距离转换的方法进行标记,OpenCV中就是使用的这种方法。

    OpenCV中分水岭算法

    在OpenCV中,我们需要给不同区域贴上不同的标签。用大于1的整数表示我们确定为前景或对象的区域,用1表示我们确定为背景或非对象的区域,最后用0表示我们无法确定的区域。然后应用分水岭算法,我们的标记图像将被更新,更新后的标记图像的边界像素值为-1。

    下面对相互接触的硬币应用距离变换和分水岭分割。

    9369e657ce25de12bc739af722ca6af5.png
    图6

    先使用 Otsu's 二值化对图像进行二值化。

    import cv2
    import numpy as np
    
    img = cv2.imread('coins.png')
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

    e17a2841bba7b49373d6857de2fe337a.png
    图7

    先使用开运算去除图像中的细小白色噪点,然后通过腐蚀运算移除边界像素,得到的图像中的白色区域肯定是真实前景,即靠近硬币中心的区域(下面左边的图);膨胀运算使得一部分背景成为了物体到的边界,得到的图像中的黑色区域肯定是真实背景,即远离硬币的区域(下面中间的图)。

    剩下的区域(硬币的边界附近)还不能确定是前景还是背景。可通过膨胀图减去腐蚀图得到,下图中的白色部分为不确定区域(下面右边的图)。

    # noise removal
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
    opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
    
    sure_bg = cv2.dilate(opening, kernel, iterations=2)  # sure background area
    sure_fg = cv2.erode(opening, kernel, iterations=2)  # sure foreground area
    unknown = cv2.subtract(sure_bg, sure_fg)  # unknown area

    99023cfbc8d6639919d9cf029ce4f6cb.png
    图8

    剩下的区域不确定是硬币还是背景,这些区域通常在前景和背景接触的区域(或者两个不同硬币接触的区域),我们称之为边界。通过分水岭算法应该能找到确定的边界。

    由于硬币之间彼此接触,我们使用另一个确定前景的方法,就是带阈值的距离变换

    下面左边的图为得到的距离转换图像,其中每个像素的值为其到最近的背景像素(灰度值为0)的距离,可以看到硬币的中心像素值最大(中心离背景像素最远)。对其进行二值处理就得到了分离的前景图(下面中间的图),白色区域肯定是硬币区域,而且还相互分离,下面右边的图为之前的膨胀图减去中间这个表示前景的图。

    # Perform the distance transform algorithm
    dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
    # Normalize the distance image for range = {0.0, 1.0}
    cv2.normalize(dist_transform, dist_transform, 0, 1.0, cv2.NORM_MINMAX)
    
    # Finding sure foreground area
    ret, sure_fg = cv2.threshold(dist_transform, 0.5*dist_transform.max(), 255, 0)
    
    # Finding unknown region
    sure_fg = np.uint8(sure_fg)
    unknown = cv2.subtract(sure_bg,sure_fg)

    827faf8c91e1becada5d9c0579a9a6be.png
    图9

    现在我们可以确定哪些是硬币区域,哪些是背景区域。然后创建标记(marker,它是一个与原始图像大小相同的矩阵,int32数据类型),表示其中的每个区域。分水岭算法将标记的0的区域视为不确定区域,将标记为1的区域视为背景区域,将标记大于1的正整数表示我们想得到的前景。

    我们可以使用 cv2.connectedComponents() 来实现这个功能,它是用0标记图像的背景,用大于0的整数标记其他对象。所以我们需要对其进行加一,用1来标记图像的背景。

    cv2.connectedComponents() 将传入图像中的白色区域视为组件(前景)。

    # Marker labelling
    ret, markers = cv2.connectedComponents(sure_fg)
    # 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[unknown==255] = 0

    注意:得到的markers矩阵的元素类型为 int32,要使用 imshow() 进行显示,需要将其转换为 uint8 类型( markers=np.uint8(markers) )。

    我们对得到的markers进行显示:

    markers_copy = markers.copy()
    markers_copy[markers==0] = 150  # 灰色表示背景
    markers_copy[markers==1] = 0    # 黑色表示背景
    markers_copy[markers>1] = 255   # 白色表示前景
    
    markers_copy = np.uint8(markers_copy)

    4c0ed83f6ec1bfe3344cb2c306275c39.png
    图10

    标记图像已经完成了,最后应用分水岭算法。然后标记图像将被修改,边界区域将被标记为-1。

    # 使用分水岭算法执行基于标记的图像分割,将图像中的对象与背景分离
    markers = cv2.watershed(img, markers)
    img[markers==-1] = [0,0,255]  # 将边界标记为红色

    经过分水岭算法得到的新的标记图像和分割后的图像如下图所示:

    5d34333d9826d1cb28d22b4eae1d43d4.png
    图11

    任何两个相邻连接的组件不一定被分水岭边界(-1的像素)分开;例如在传递给 watershed 函数的初始标记图像中的物体相互接触。

    总结

    我们通过一个例子介绍了分水岭算法的整个过程,主要分为以下几步:

    1. 对图进行灰度化和二值化得到二值图像
    2. 通过膨胀得到确定的背景区域,通过距离转换得到确定的前景区域,剩余部分为不确定区域
    3. 对确定的前景图像进行连接组件处理,得到标记图像
    4. 根据标记图像对原图像应用分水岭算法,更新标记图像

    参考:

    OpenCV Watershed Algorithm

    IMAGE SEGMENTATION AND MATHEMATICAL MORPHOLOGY

    Classic Watershed

    基于标记的分水岭分割算法/OpenCV中距离变换

    剪刀手/分水岭分割法

    The Watershed Transform

    对扑克牌应用分水岭的例子

    展开全文
  • matlab分水岭分割

    2014-06-04 19:46:56
    该程序使用matlab完成的分水岭算法,很具有代表性,程序很好,很实用
  • 分水岭(Watershed)是基于地理形态的分析的图像分割算法,模仿地理结构(比如山川、沟壑,盆地)来实现对不同物体的分类。分水岭算法中会用到一个重要的概念——测地线距离。测地线距离(Geodesic Distance)测地线...
  • 风水岭算法常见的有三种方法:(1)基于浸泡理论的分水岭分割方法;(2)基于连通图方法;(3)基于距离变换的方法。OpenCV 中是基于距离变换的分割方法,就相当于我们的小山头(认为造成的)。 基本的步骤: 例子1 ...
  • Matlab的标记分水岭分割算法

    千次阅读 2018-03-29 18:41:58
    如果图像中的目标物体是连接在一起的,则分割起来会更困难,分水岭分割算法经常用于处理这类问题,通常会取得比较好的效果。分水岭分割算法把图像看成一幅“地形图”,其中亮度比较强的区域像素值较大,而比 较暗的...
  • matlab之watershed分水岭分割算法

    千次阅读 2017-02-22 14:34:04
    如果图像中的目标物体是连接在一起的,则分割起来会更困难,分水岭分割算法经常用于处理这类问题,通常会取得比较好的效果。分水岭分割算法把图像看成一幅“地形图”,其中亮度比较强的区域像素值较大,而比 较暗的 ...
  • 基于matlab分水岭算法分割黏连细胞程序代码。。。。。
  • 此处为用分水岭算法实现MATLAB的图像分割,在实际运用中有较强的实用价值
  • 形态学分水岭算法多数的图像分割算法均是基于灰度值的不连续和相似的性质。在前者中,算法以灰度突变为基础分割一幅图像,如图像边缘分割。假设图像不同区域的边界彼此完全不同,且与背景不同,从而允许基于灰度的...
  • matlab分水岭算法.docx

    2021-03-19 21:14:17
    matlab分水岭算法的图像分割
  • matlab实现分水岭算法处理图像分割

    千次阅读 2018-11-08 09:03:15
    matlab实现分水岭算法处理图像分割
  • 如果图像中的目标物体是连接在一起的,则分割起来会更困难,分水岭分割算法经常用于处理这类问题,通常会取得比较好的效果。分水岭分割算法把图像看成一幅“地形图”,其中亮度比较强的区域像素值较大,而比 较暗的 ...
  • matlab 分水岭算法

    2015-12-30 10:52:48
    matlab 多个分水岭算法实现图像分割,注释详细
  • matlab基于分水岭算法处理图像分割的源程序,其中有不同的方法,是我收集过来效果比较好的,有利于大家的学习交流
  • 直接应用分水岭分割算法的效果往往并不好,如果在图像中对前景对象和背景对象进行标注区别,再应用分水岭算法会取得较好的分割效果。基于标记控制的分水岭分割方法有以下基本步骤: 1. Compute a segmentation ...
  • 图像分割算法分水岭算法、斑点计数算法、霍夫圆/椭圆算法、轮廓检测算法等。在本文中,我使用了轮廓检测和分水岭算法。涉及的步骤:读取图像转换为HSV阈值模糊它删除多余/不需要的区域在原始图像上绘制轮廓使用...

空空如也

空空如也

1 2 3 4 5 ... 7
收藏数 129
精华内容 51
关键字:

matlab分水岭分割算法

matlab 订阅