0
votes

I want to find the contours of a binary image of segmented rocks. There are some problems with the findContours function from opencv.

  1. The contour size is around 1000 while the contours from the binary image could be around 30-50.

  2. When I draw ALL the contours, they seem to be a decent representation of the black boundaries from the binary image. But When I draw only one contour of some random index, it shows a small contour.

Images are given below :

  • Binary Image

enter image description here

  • Contours of all the index

enter image description here

  • Contour of a random contour index. The small green contour

enter image description here

I would like to have just the exact number of contours as in the binary image.

Code :

std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(input_image, contours,hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE);
  for( int i = 0; i < (int)contours.size(); i++)
  {

      cv::drawContours(input_rgb_image, contours, 512 , cv::Scalar(0,255,0), 1, 8, hierarchy,1);


  }


2

2 Answers

1
votes

There are two problems with your code. You will get better results if you invert and blur the image. These are my results after applying those two operations before finding the contours: contours

The OpenCV findContours() function finds dark contours on the light background. If you want to find the white spaces, which are the rocks, you need to invert the binary image first. You can invert a binary image like this invertedImage = 255 - binaryImage. Blurring also helps because it connects pixels that should be connected but aren't because of the low resolution. Blurring is done with the code blurredImage = cv2.blur(img, (2,2)). This is the inverted blurred image:

invert and blur

This is the code that I used:

import cv2
import random
# Read image
gray = 255-cv2.imread('/home/stephen/Desktop/image.png', 0)
gray = cv2.blur(gray, (2,2))
# Find contours in image
contours, _ = cv2.findContours(gray, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print(len(contours))
img = cv2.imread('/home/stephen/Desktop/image.png')
for cnt in contours:
    color = random.randint(0,255),random.randint(0,255),random.randint(0,255)
    img = cv2.drawContours(img, [cnt], 0, color, cv2.FILLED)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
1
votes

I would try a couple of things:

  • bilateral filter instead of blur. It smoothes things in a way similar to blur but also tries to preserve boundaries, which is good for segmentation. Downsides - it's computationally expensive but you may find "your" params that play well for free
  • blur + meanshift segmentation before the watershed. Blur will act just like expected and meanshift will average and join contours with similar colors and as such make the number of contours smaller. Depending on params, meanshift is also expensive. Just play with it.

More advanced thing is contours analysis afterward. You could unite some of the neighbors based on:

  • the similarity of the histogram on some of hsv channels;
  • contours properties, such as roundness. If roundness of two united neighbors is better than the roundness of any of them then they can be united. Something like this.

Roundness calculating:

float calcRoundness(std::vector<cv::Point> &contour, double area)
{
        float p = cv::arcLength(contour, true);
        if (p == 0)
                return 0;
        float k = (4 * M_PI * area) / pow(p, 2);

        /* 1 is circle, 0.75 - squared area, etc. */
        return k;
}