Как улучшить сегментацию изображения с помощью водораздела? - PullRequest
6 голосов
/ 14 апреля 2020

Я разрабатываю приложение для обнаружения области поражения, для этого я использую захват, чтобы определить область интереса и удалить фон с изображения. Однако на некоторых изображениях это не работает хорошо. Он заканчивает тем, что не определил границы области интереса хорошо. Водораздел может лучше идентифицировать края для этого типа работы, однако у меня возникают трудности при переходе от разборки к водоразделу. Перед обработкой захвата пользователь использует touchevent, чтобы отметить прямоугольник вокруг интересующего изображения (область раны), чтобы облегчить работу алгоритма. Как показано на рисунке ниже.

image

Однако, при использовании других изображений раны сегментация не является хорошей, показывая fl aws в обнаружении ROI.

Изображение с использованием grabcut в приложении

image

Изображение с использованием водораздела на рабочем столе

image

это код:

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
}

Выход:

image

Как обновить код для использования водораздела вместо grabcut?

1 Ответ

1 голос
/ 17 апреля 2020

Описание применения алгоритма водораздела в 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 содержит несколько хороших, быстрых примеров.

Надеюсь, это поможет!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...