2

This is the picture of my scene taken from an Intel Realsense d435 rgb camera. enter image description here

I want to be able to identify the metal object in my image and draw a closed contour around it. This way I can reference all the pixels within the contour for future use.

Current Methodology

  1. Currently, I'm cropping the scene image pretending I have some object recognition software running allowing me to create a bounding box around my object. So I crop that section and apply it over a blank image.

Cropped Image with threshold applied

  1. I followed OpenCV documentation and used morphology transforms and the watershed algorithm to segment the image. I end up extracting the sure foreground image and running a canny edge detection and contour detection. However, the lines they return are fairly poor.

2.5. Currently, I'm just using the sure foreground image and taking all the pixels that are black and saving those as my object, however, I have these huge white spots inside my sure foreground image that aren't being picked up.

Sure Foreground image with white spots Contours of my edges after filtering (bad)

How can I improve my image segmentation to get a better contour of my image so I can grab all the pixels (most) that are enclosed by my object?

I can add my code if it helps, but it is quite large.

EDIT: I tried the GrabCut Algorithm from a SentDex tutorial however while it can remove some of the backgrounds I the watershed algorithm fails to find an accurate foreground representation afterward.

enter image description hereenter image description here

The image on the left is after GrabCut has been applied and then on the right the GrabCut Algorithm is passed to the watershed algorithm to find the sure foreground.

NoviceCoder
  • 424
  • 1
  • 4
  • 20
  • 1
    Try to use grabCut. It will be fine for your case – Nuzhny Feb 14 '19 at 03:08
  • GrabCut was a good suggestion, but the application of my program won't involve any user interface. The bounding box is placed statically as a place holder for later object recognition. Applying GrabCut gives makes it harder for the rest of my code to identify edges. – NoviceCoder Feb 14 '19 at 04:21

2 Answers2

2

I was able to get a better contour of my object by switching from color recognition and analysis over the RGB image to edge detection on the depth information from my camera.

Here are the general steps I took to find a better edge map.

  1. Save my depth information in an NxMx1 matrice. Where N,M values is the shape of my image's resolution. For a 480,640 image, I had a matrix that was (480,640,1), where each pixel (i,j) stored corresponding depth value for that pixel coordinate.

  2. Used a 2D Gaussian kernel to smooth and fill in any missing data in my depth matrix, using astropy's convolve method.

  3. Find the gradient of my depth matrix and the corresponding magnitude of each pixel in the gradient.

  4. Filter out data based on uniform depth. Where uniform depth would imply a flat object so I found the Gaussian distribution for my magnitudes (from depth gradient) and those that fill within X standard deviations were set to zero. This reduced some additional noise in the image.

  5. Then I normalized the values of my magnitude matrix from 0 to 1 so my matrix could be considered a channel 1 image matrix.

So since my depth matrix was of the form (480,640,1) and when I found my corresponding gradient matrix which was also (480,640,1) then I scaled the values (:,:,1) to go from 0 to 1. This way I could represent it as grayscale or binary image later on.

def gradient_demo(self, Depth_Mat):
    """
    Gradient display entire image
    """
    shape = (Depth_Mat.shape)
    bounds = ( (0,shape[0]), (0, shape[1]) )

    smooth_depth = self.convolve_smooth_Depth(Depth_Mat, bounds)
    gradient, magnitudes = self.depth_gradient(smooth_depth, bounds)
    magnitudes_prime = magnitudes.flatten()

    #hist, bin = np.histogram(magnitudes_prime, 50)  # histogram of entire image
    mean = np.mean(magnitudes_prime)
    variance = np.var(magnitudes_prime)
    sigma = np.sqrt(variance)

    # magnitudes_filtered = magnitudes[(magnitudes > mean - 2 * sigma) & (magnitudes < mean + 2 * sigma)]
    magnitudes[(magnitudes > mean - 1.5 * sigma) & (magnitudes < mean + 1.5 * sigma)] = 0

    magnitudes = 255*magnitudes/(np.max(magnitudes))
    magnitudes[magnitudes != 0] = 1

    plt.title('magnitude of gradients')
    plt.imshow(magnitudes, vmin=np.nanmin(magnitudes), vmax=np.amax(magnitudes), cmap = 'gray')
    plt.show()

    return  magnitudes.astype(np.uint8)
def convolve_smooth_Depth(self, raw_depth_mtx, bounds):
    """
    Iterate over subimage and fill in any np.nan values with averages depth values
    :param image: 
    :param bounds: ((ylow,yhigh), (xlow, xhigh)) -> (y,x)
    :return: Smooted depth values for a given square
    """
    ylow, yhigh = bounds[0][0], bounds[0][1]
    xlow, xhigh = bounds[1][0], bounds[1][1]

    kernel = Gaussian2DKernel(1)    #generate kernel 9x9 with stdev of 1
    # astropy's convolution replaces the NaN pixels with a kernel-weighted interpolation from their neighbors
    astropy_conv = convolve(raw_depth_mtx[ylow:yhigh, xlow:xhigh], kernel, boundary='extend')
    # extended boundary assumes original data is extended using a constant extrapolation beyond the boundary
    smoothedSQ = (np.around(astropy_conv, decimals= 3))

    return smoothedSQ

def depth_gradient(self, smooth_depth, bounds):
    """

    :param smooth_depth: 
    :param shape: Tuple with y_range and x_range of the image. 
            shape = ((0,480), (0,640)) (y,x) -> (480,640)
            y_range = shape[0]
            x_range = shape[1]
    :return: 
    """
    #shape defines the image array shape. Rows and Cols for an array

    ylow, yhigh = bounds[0][0], bounds[0][1]
    xlow, xhigh = bounds[1][0], bounds[1][1]
    gradient = np.gradient(smooth_depth)
    x,y = range(xlow, xhigh), range(ylow, yhigh)
    xi, yi = np.meshgrid(x, y)
    magnitudes = np.sqrt(gradient[0] ** 2 + gradient[1] ** 2)

    return gradient, magnitudes

Using this method/code I was able to get the following image. Just FYI I changed the scene a little bit.

enter image description here

I asked another related question here: How to identify contours associated with my objects and find their geometric centroid

That shows how to find contours, centroids of my object in the image.

NoviceCoder
  • 424
  • 1
  • 4
  • 20
0

How about dilate then erode before find contours:

element = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(21, 21))

dilate = cv2.dilate(gray,element,1)
cv2.imshow("dilate",dilate)
erode = cv2.erode(dilate,element,1)

#use erode as a mask to extract the object from the original image
erode = cv2.bitwise_not(erode)

erode = cv2.cvtColor(erode, cv2.COLOR_GRAY2BGR)

res = cv2.add(original,erode)

I just show how to apply mask because I don't have the image and object recognition software that you use.

enter image description here enter image description here enter image description here

Ha Bom
  • 2,787
  • 3
  • 15
  • 29
  • I'm trying to get the contour that surrounds the object within my preset bounding box not the contour of the bounding box. I'm interested in separating my foreground object from the background. – NoviceCoder Feb 14 '19 at 22:54
  • I've edited the answer. You can simply use the erode image as a mask to separate foreground – Ha Bom Feb 15 '19 at 01:38
  • I think you misunderstand I'm trying to identify the metal object in the image with the blue handle. My current results are that of the watershed algorithm, which implements eroding and dilation. – NoviceCoder Feb 16 '19 at 00:46