Real-time ellipsoid detection using OpenCV feature tools

Viewed 436

I am trying to create a program to detect ellipsoid shapes from a real-time camera feed. The goal is to identify the rims of dishware (for example, a bowl or cup). After many OpenCV tutorials and documentation pages, I've tried FAST corner detection, Canny edge (with dialated contours of a minimum specified area), Sobel edge, and Hough circles. None of these seem to work even with tweaking paramters.

For reference, here is an imgur album of a single frame of a sample scene from each method https://imgur.com/a/A7d7UxI

Poly approximation for countours relies on the number of corners, but any complex shape with more than 4 corners seems to be flagged as "circlular or ellipsoid". Likewise, pure circle detection creates repeated findings where each tangentially fit circle of an ellipsoid is marked. Corner detection is a bit of a failure because there are obviously no corners in curved shapes.

Honestly, I'm stumped. I have limited experience with OpenCV and do not know of if there is a way to detect and label ellipsoids (continious or broken) for the purposes of finding their edge positions on screen. Any direction or help would be appreciated (I can do some digging on my own), my biggest lead is trying to identify the strongest visible contours from the Sobel detection, but have no way to verify their curvature or identify these lines.

While not explicitly relevant, here is the WIP code I use to display each of these from a camera feed.

import numpy as np
import cv2

def empty():
    pass

def getContours(img,imgContour):
    contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

    for cnt in contours:
        area = cv2.contourArea(cnt)
        if area > 1000:
            cv2.drawContours(imgContour, cnt, -1, (255, 0, 255), 7)
            peri = cv2.arcLength(cnt, False)
            approx = cv2.approxPolyDP(cnt, 0.02 * peri, False)
            if len(approx) > 5:
                x, y, w, h = cv2.boundingRect(approx)
                cv2.rectangle(imgContour, (x, y), (x + w, y + h), (0, 255, 0), 5)

# Capturing from the video input
# Use VideoCapture(0) for default webcam
# I use a feed from my phone via USB, which is webcam 2 that uses VideoCapture(1)
cap = cv2.VideoCapture(1)

"""Trackbar"""
cv2.namedWindow("Parameters")
cv2.createTrackbar("Threshold1_Canny", "Parameters", 150, 255, empty)
cv2.createTrackbar("Threshold2_Canny", "Parameters", 255, 255, empty)

while True:
    # reading the frame
    ret, frame = cap.read()
    # flipping the frame
    frame = cv2.flip(frame, 1)
    if ret:

        """Image Preprocessing"""
        img = cv2.GaussianBlur(frame, (21, 21), cv2.BORDER_DEFAULT)
        imgContour = img.copy()
        img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

        """FAST Corner Detection"""
        # Initiate FAST object with default values
        fast = cv2.FastFeatureDetector_create()

        # find and draw the keypoints
        kp = fast.detect(img,None)
        img2 = cv2.drawKeypoints(img, kp, None, color=(255,0,0))

        # Disable nonmaxSuppression
        fast.setNonmaxSuppression(0)
        kp = fast.detect(img,None)
        imgFAST = cv2.drawKeypoints(img, kp, None, color=(255,0,0))

        """Canny Edge Detection"""
        threshold1 = cv2.getTrackbarPos("Threshold1_Canny", "Parameters")
        threshold2 = cv2.getTrackbarPos("Threshold2_Canny", "Parameters")
        edges = cv2.Canny(img, threshold1, threshold2)
        kernal = np.ones((5, 5))
        imgDil = cv2.dilate(edges, kernal, iterations=1)
        getContours(imgDil,imgContour)

        """Hough Circles"""
        #cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
        #circles = cv2.HoughCircles(img,cv2.HOUGH_GRADIENT,1,100,
        #                           param1=50,param2=30,minRadius=60,maxRadius=400)
        #circles = np.uint16(np.around(circles))


        """Sobel Edge Detection"""
        imgSobel = cv2.Sobel(img, cv2.CV_16S, 0, 1, ksize=-1)
        imgSobel = np.absolute(imgSobel)
        imgSobel = np.uint8(imgSobel)

        """Show All Processing"""
        cv2.imshow('Raw', frame)
        cv2.imshow('FAST', imgFAST)
        cv2.imshow('Canny', imgDil)
        cv2.imshow('Sobel', imgSobel)
        #cv2.imshow('Hough', cimg)

        """Key Controls"""
        # getting the input from the keyboard
        k = cv2.waitKey(1) & 0xFF

        # press 'q' to quit
        if k == ord('q'):
            cap.release()
            break

cap.release()
cv2.destroyAllWindows()
1 Answers

OpenCV provides three different functions for fitting a set of points to an ellipse:

These functions operate on a list of points (i.e. a contour), so you could use any of these functions with contours. The method I would propose then is to:

  • binarize the image
  • detect contours in the binary image
  • keep only the external contours (i.e. the bowl has many ellipses, but really you just want the outer most ones)
  • for each remaining contour:
    • fit an ellipse
    • compare the detected contour with the contour of the ellipse (e.g. using matchShapes()).
    • threshold that comparison to say whether the contour is an ellipse or not

This process only relies on one major parameter (comparison threshold) which could be decided on labeled data, so it isn't too bad as an algorithm. However, the preprocessing step here (Canny or other binarization) could potentially have a lot of variables and could change quite a lot given lighting and so on.

In general, classical algorithms like these that rely on image processing work best in constrained environments (known lighting, fixed camera position, etc) since it is likely that you can actually find preprocessing parameters that will work well (and it's generally not hard in those industrial settings to provide a couple sliders to change thresholds on the fly). For an unconstrained environment however, like waving a phone around in some arbitrary lighting pointing at whatever colored objects, you would likely be better served by ML, training an object or mask detector with the specific classes you are interested in.

Related