如何使用分水岭算法改善图像分割?

12

我正在开发一个检测病变区域的应用程序,为此我使用grabcut来检测ROI并从图像中删除背景。然而在某些图像中它无法很好地工作。它最终无法很好地识别感兴趣区域的边界。对于这种类型的工作,watershed可以更好地识别边缘,但是我在从grabcut到watershed的过渡中遇到了困难。在处理grabcut之前,用户使用touchevent在感兴趣的图像周围标记一个矩形(伤口区域),以便算法更容易地进行工作。如下图所示。

然而,在使用其他伤口图像时,分割效果不佳,ROI检测存在缺陷。

应用程序中使用grabcut的图像

桌面上使用watershed的图像

这是代码:

private fun extractForegroundFromBackground(coordinates: Coordinates, currentPhotoPath: String): String {
    // TODO: Provide complex object that has both path and extension

    val width = bitmap?.getWidth()!!
    val height = bitmap?.getHeight()!!
    val rgba = Mat()
    val gray_mat = Mat()
    val threeChannel = Mat()
    Utils.bitmapToMat(bitmap, gray_mat)
    cvtColor(gray_mat, rgba, COLOR_RGBA2RGB)
    cvtColor(rgba, threeChannel, COLOR_RGB2GRAY)
    threshold(threeChannel, threeChannel, 100.0, 255.0, THRESH_OTSU)

    val rect = Rect(coordinates.first, coordinates.second)
    val fg = Mat(rect.size(), CvType.CV_8U)
    erode(threeChannel, fg, Mat(), Point(-1.0, -1.0), 10)
    val bg = Mat(rect.size(), CvType.CV_8U)
    dilate(threeChannel, bg, Mat(), Point(-1.0, -1.0), 5)
    threshold(bg, bg, 1.0, 128.0, THRESH_BINARY_INV)
    val markers = Mat(rgba.size(), CvType.CV_8U, Scalar(0.0))
    Core.add(fg, bg, markers)

    val marker_tempo = Mat()
    markers.convertTo(marker_tempo, CvType.CV_32S)

    watershed(rgba, marker_tempo)
    marker_tempo.convertTo(markers, CvType.CV_8U)

    val imgBmpExit = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
    Utils.matToBitmap(markers, imgBmpExit)

    image.setImageBitmap(imgBmpExit)


    // Run the grab cut algorithm with a rectangle (for subsequent iterations with touch-up strokes,
    // flag should be Imgproc.GC_INIT_WITH_MASK)
    //Imgproc.grabCut(srcImage, firstMask, rect, bg, fg, iterations, Imgproc.GC_INIT_WITH_RECT)

    // Create a matrix of 0s and 1s, indicating whether individual pixels are equal
    // or different between "firstMask" and "source" objects
    // Result is stored back to "firstMask"
    //Core.compare(mark, source, mark, Core.CMP_EQ)

    // Create a matrix to represent the foreground, filled with white color
    val foreground = Mat(srcImage.size(), CvType.CV_8UC3, Scalar(255.0, 255.0, 255.0))

    // Copy the foreground matrix to the first mask
    srcImage.copyTo(foreground, mark)

    // Create a red color
    val color = Scalar(255.0, 0.0, 0.0, 255.0)
    // Draw a rectangle using the coordinates of the bounding box that surrounds the foreground
    rectangle(srcImage, coordinates.first, coordinates.second, color)

    // Create a new matrix to represent the background, filled with black color
    val background = Mat(srcImage.size(), CvType.CV_8UC3, Scalar(0.0, 0.0, 0.0))

    val mask = Mat(foreground.size(), CvType.CV_8UC1, Scalar(255.0, 255.0, 255.0))
    // Convert the foreground's color space from BGR to gray scale
    cvtColor(foreground, mask, Imgproc.COLOR_BGR2GRAY)

    // Separate out regions of the mask by comparing the pixel intensity with respect to a threshold value
    threshold(mask, mask, 254.0, 255.0, Imgproc.THRESH_BINARY_INV)

    // Create a matrix to hold the final image
    val dst = Mat()
    // copy the background matrix onto the matrix that represents the final result
    background.copyTo(dst)

    val vals = Mat(1, 1, CvType.CV_8UC3, Scalar(0.0))
    // Replace all 0 values in the background matrix given the foreground mask
    background.setTo(vals, mask)

    // Add the sum of the background and foreground matrices by applying the mask
    Core.add(background, foreground, dst, mask)

    // Save the final image to storage
    Imgcodecs.imwrite(currentPhotoPath + "_tmp.png", dst)

    // Clean up used resources
    firstMask.release()
    source.release()
    //bg.release()
    //fg.release()
    vals.release()
    dst.release()

    return currentPhotoPath
}

退出:

如何更新代码以使用Watershed算法替代GrabCut?


阅读这样长的帖子真是让人很烦恼。您可以通过使用Markdown标签调整图像大小来改善格式,例如: <img src="https://i.stack.imgur.com/nmzwj.png" width="210" height="150"> - karlphillip
1
@karlphillip 对不起,我不知道。我会纠正的。 - Carlos Diego
1个回答

2
如何在OpenCV中使用分水岭算法的说明可以在这里找到(链接),不过该示例是用Python编写的。此外,文档中也包含一些可能有用的示例。由于您已经有了二值图像,所以只需要应用欧几里得距离变换(EDT)和分水岭函数即可。因此,您需要将以下代码:Imgproc.grabCut(srcImage, firstMask, rect, bg, fg, iterations, Imgproc.GC_INIT_WITH_RECT)替换为:
Mat dist = new Mat();
Imgproc.distanceTransform(srcImage, dist, Imgproc.DIST_L2, Imgproc.DIST_MASK_3); // use L2 for Euclidean Distance 
Mat markers = Mat.zeros(dist.size(), CvType.CV_32S);
Imgproc.watershed(dist, markers); # apply watershed to resultant image from EDT
Mat mark = Mat.zeros(markers.size(), CvType.CV_8U);
markers.convertTo(mark, CvType.CV_8UC1);
Imgproc.threshold(mark, firstMask, 0, 255, Imgproc.THRESH_BINARY + Imgproc.THRESH_OTSU); # threshold results to get binary image

阈值处理步骤在这里被描述。另外,在应用Imgproc.watershed之前,您可以选择对EDT的结果应用一些形态学操作,例如膨胀、腐蚀:

Imgproc.dilate(dist, dist, Mat.ones(3, 3, CvType.CV_8U));

如果您对处理二进制图像时的形态学操作不熟悉,可以参考OpenCV文档中提供的一些好的例子。希望这能够帮助到您!

啊,抱歉 - distanceTransform 函数需要一个灰度图像,所以那一行代码应该从 Imgproc.distanceTransform(srcImage, dist, Imgproc.DIST_L2, Imgproc.DIST_MASK_3); 改为 Imgproc.distanceTransform(threeChannel, dist, Imgproc.DIST_L2, Imgproc.DIST_MASK_3);,因为在你的代码中 threeChannel 看起来是将 RGB 图像转换为灰度图像 (Imgproc.cvtColor(srcImage, threeChannel, Imgproc.COLOR_RGB2GRAY))。 - danielcahall
错误发生在这里:https://github.com/opencv/opencv/blob/master/modules/imgproc/src/segmentation.cpp#L161。看起来 dist 不是正确的类型 - 尝试在 Imgproc.distanceTransform(threeChannel, dist, Imgproc.DIST_L2, Imgproc.DIST_MASK_3); 之后添加 dist.convertTo(dist, CvType.CV_8UC3); - danielcahall
抱歉回复晚了 - 你应该能够从rgbarect实例化一个Mat,类似于这样的操作 Mat subregion = new Mat(rgba, rect)。可以查看这里的描述:https://stackoverflow.com/questions/35666255/get-a-sub-image-using-opencv-java,然后在那个矩阵上进行处理,而不是对`rgba`进行处理。 - danielcahall
丹尼尔,你还可以帮我,我没有得到我想要的结果。 - Tecnologia da Net
好的 - 你试过我上面评论的内容了吗?另外,我认为这可能需要一个新的问题。 - danielcahall
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接