55

Im trying to get the largest contour of a red book. I've got a little problem with the code because its getting the contours of the smallest objects (blobs) instead of the largest one and I can't seem to figure out why this is happening

The code I use:

camera = cv2.VideoCapture(0)
kernel = np.ones((2,2),np.uint8)

while True:
    #Loading Camera
    ret, frame = camera.read()

    blurred = cv2.pyrMeanShiftFiltering(frame, 3, 3)
    hsv = cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV)

    lower_range = np.array([150, 10, 10])
    upper_range = np.array([180, 255, 255])
    mask = cv2.inRange(hsv, lower_range, upper_range)

    dilation = cv2.dilate(mask,kernel,iterations = 1)

    closing = cv2.morphologyEx(dilation, cv2.MORPH_GRADIENT, kernel)
    closing = cv2.morphologyEx(dilation, cv2.MORPH_CLOSE, kernel)

    #Getting the edge of morphology
    edge = cv2.Canny(closing, 175, 175)
    _, contours,hierarchy = cv2.findContours(edge, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

    # Find the index of the largest contour
    areas = [cv2.contourArea(c) for c in contours]
    max_index = np.argmax(areas)
    cnt=contours[max_index]

    x,y,w,h = cv2.boundingRect(cnt)
    cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),2)


    cv2.imshow('threshold', frame)
    cv2.imshow('edge', edge)

    if cv2.waitKey(1) == 27:
        break


camera.release()
cv2.destroyAllWindows()

As you can see on this picture

Hopefully there is someone who can help

Zaptimist
  • 553
  • 1
  • 4
  • 5
  • I just tried to test it when the book had no blobs in it, Then I saw that it drew a contour around the whole book (sometimes). So my question now will be how can I draw a contour around the book even when it has some blobs in it? – Zaptimist Jun 16 '17 at 11:42
  • So does it needs to be a red book? Or it can be with other colors? – João Cartucho Jun 16 '17 at 13:34
  • Eventually I need to use this method to detect a circle on the ground and on other different objects. Thats why I use a red book as an example – Zaptimist Jun 16 '17 at 15:58

2 Answers2

71

You can start by defining a mask in the range of the red tones of the book you are looking for.

Then you can just find the contour with the biggest area and draw the rectangular shape of the book.

import numpy as np
import cv2

# load the image
image = cv2.imread("path_to_your_image.png", 1)

# red color boundaries [B, G, R]
lower = [1, 0, 20]
upper = [60, 40, 220]

# create NumPy arrays from the boundaries
lower = np.array(lower, dtype="uint8")
upper = np.array(upper, dtype="uint8")

# find the colors within the specified boundaries and apply
# the mask
mask = cv2.inRange(image, lower, upper)
output = cv2.bitwise_and(image, image, mask=mask)

ret,thresh = cv2.threshold(mask, 40, 255, 0)
if (cv2.__version__[0] > 3):
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
else:
    im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

if len(contours) != 0:
    # draw in blue the contours that were founded
    cv2.drawContours(output, contours, -1, 255, 3)

    # find the biggest countour (c) by the area
    c = max(contours, key = cv2.contourArea)
    x,y,w,h = cv2.boundingRect(c)

    # draw the biggest contour (c) in green
    cv2.rectangle(output,(x,y),(x+w,y+h),(0,255,0),2)

# show the images
cv2.imshow("Result", np.hstack([image, output]))

cv2.waitKey(0)

Using your image:

enter image description here

If you want the book to rotate you can use rect = cv2.minAreaRect(cnt) as you can find it here.

Edit:

You should also consider other colour spaces beside the RGB, as the HSV or HLS. Usually, people use the HSV since the H channel stays fairly consistent in shadow or excessive brightness. In other words, you should get better results if you use the HSV colourspace.

enter image description here

In specific, in OpenCV the Hue range is [0,179]. In the following figure (made by @Knight), you can find a 2D slice of that cylinder, in V = 255, where the horizontal axis is the H and the vertical axis the S. As you can see from that figure to capture the red you need both to include the lower (e.g., H=0 to H=10) and upper region (e.g., H=170 to H=179) of the Hue values.

enter image description here

João Cartucho
  • 3,688
  • 32
  • 39
  • 3
    This works exactly how it needs to detect an object with color. I switched your code back to the camera. The only thing I need to focus on is the reflection/shadow range (of the color) so it constanly picking up the object. Thank you so much for your time. Sadly the upvote is not visible on this post. – Zaptimist Jun 16 '17 at 16:11
  • @Zaptimist your welcome! If you need something comment here and I will help if I know – João Cartucho Jun 16 '17 at 17:02
  • I've got a slight problem in some cases, Im getting this error when the object is too big: c = max(contours, key = cv2.contourArea) ValueError: max() arg is an empty sequence Doest this mean it could not find a contour for the color red? – Zaptimist Jun 19 '17 at 11:41
  • It seems when it tries to draw a very small rectangle the program crashes and getting the "max() arg is an empty sequence" error message, In the image you can see the green dot [link to image](https://gyazo.com/17de789ba2976eb7c1846604b3d5003f) @João Cartucho – Zaptimist Jun 19 '17 at 12:22
  • @Zaptimist I edited the code. It was failing when there were no contours as you said, so we can add a if case to check it – João Cartucho Jun 19 '17 at 12:51
  • Thats the check where I was looking for. The check is so simple and still I couldn't find it haha, Thanks again. Hopefully I can do my work without asking more questions ;) – Zaptimist Jun 19 '17 at 13:47
  • What exactly is `c` here? I tried to print and it printed a matrix in a matrix. – Schütze Jan 02 '19 at 12:55
  • 2
    @Schütze it's a contour – João Cartucho Jan 02 '19 at 15:12
  • @JoãoCartucho A bit late to the party but: When I draw `c` with `cv2.drawContours()` I only get an unevenly dotted line as the contour while I get nice smooth lines when I draw `contours`. Is this an expected result? And is there a way to fix it? – GittingGud Apr 09 '20 at 10:44
  • @GittingGud in the code I am drawing `contours` with `cv2.drawContours()` and not `c`. – João Cartucho Apr 10 '20 at 05:42
  • @JoãoCartucho Yes and that works perfectly for me as well, but I only want to draw the biggest contour. Therefore I tried to draw `c` with `cv2.drawContours()` but it's only unevenly dotted. I just wanted to know if there's a fix for that but as it is not really a part of the original question its not important. – GittingGud Apr 11 '20 at 11:04
  • A contour is a list of points that represent a curve in an image. – João Cartucho Apr 12 '20 at 07:18
  • @JoãoCartucho how did you determine the red boundaries? – Curious G. Oct 13 '20 at 18:58
  • for the HSV or RGB colourspace? But for both, if you can simply do a `imshow` and have a look at the values – João Cartucho Oct 13 '20 at 20:58
  • I think you intended `int(cv2.__version__.split(".")[0]) > 3` for your first if statement. – Shmack Dec 25 '22 at 19:50
2

Use this to convert Grayscale masks to Rectangles

def mask_to_rect(image):
    '''
    
        Give rectangle cordinates according to the mask image
        
        
        Params: image : (numpy.array) Gray Scale Image
        
        Returns: Cordinates : (list) List of cordinates [x, y, w h]
    
    '''
    
    # Getting the Thresholds and ret
    ret,thresh = cv2.threshold(image, 0, 1, 0)
    
    # Checking the version of open cv I tried for (version 4)
    #    Getting contours on the bases of thresh
    if (int(cv2.__version__[0]) > 3):
        contours, hierarchy = cv2.findContours(thresh.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    else:
        im2, contours, hierarchy = cv2.findContours(thresh.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    
    # Getting the biggest contour
    if len(contours) != 0:
        # draw in blue the contours that were founded
        cv2.drawContours(output, contours, -1, 255, 3)

        # find the biggest countour (c) by the area
        c = max(contours, key = cv2.contourArea)
        x,y,w,h = cv2.boundingRect(c)
        
    return [x, y, w, h]

Result

enter image description here

Sohaib Anwaar
  • 1,517
  • 1
  • 12
  • 29