Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rectangle detection inaccuracy using approxPolyDP() in openCV

Tags:

python

opencv

As part of a program which contains a series of images to be processed, I first need to detect a green-coloured rectangle. I'm trying to write a program that doesn't use colour masking, since the lighting and glare on the images will make it difficult to find the appropriate HSV ranges.

(p.s. I already have two questions based on this program, but this one is unrelated to those. It's not a follow up, I want to address a separate issue.)

I used the standard rectangle detection technique, making use of findContours() and approxPolyDp() methods. I added some constraints that got rid of unnecessary rectangles (like aspectRatio>2.5, since my desired rectangle is clearly the "widest" and area>1500, to discard random small rectangles) .

import numpy as np
import cv2 as cv

img = cv.imread("t19.jpeg")

width=0 
height=0

start_x=0 
start_y=0
end_x=0 
end_y=0


output = img.copy()
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)


#threshold
th = cv.adaptiveThreshold(gray,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,cv.THRESH_BINARY,9,2)

cv.imshow("th",th)



#rectangle detection

contours, _ = cv.findContours(th, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)

for contour in contours:

    approx = cv.approxPolyDP(contour, 0.01* cv.arcLength(contour, True), True)
    
    cv.drawContours(img, [approx], 0, (0, 0, 0), 5)
    
    x = approx.ravel()[0]
    y = approx.ravel()[1]

    x1 ,y1, w, h = cv.boundingRect(approx)
    a=w*h    
    if len(approx) == 4 and x>15  :
            
        aspectRatio = float(w)/h
        if  aspectRatio >= 2.5 and a>1500:          
          print(x1,y1,w,h)
          width=w
          height=h   
          start_x=x1
          start_y=y1
          end_x=start_x+width
          end_y=start_y+height      
          cv.rectangle(output, (start_x,start_y), (end_x,end_y), (0,0,255),3)
          cv.putText(output, "rectangle "+str(x1)+" , " +str(y1-5), (x1, y1-5), cv.FONT_HERSHEY_COMPLEX, 0.5, (0, 0, 0))
          
cv.imshow("op",output)

print("start",start_x,start_y)
print("end", end_x,end_y)
print("width",width)
print("height",height)

It is working flawlessly for all the images, except one:

enter image description here

I used adaptive thresholding to create the threshold, which was used by the findContours() method. I tried displaying the threshold and the output , and it looks like this:

enter image description here

The thresholds for the other images also looked similar...so I can't pinpoint what exactly has gone wrong in the rectangle detection procedure.

Some tweaks I have tried:

  1. Changing the last two parameters in the adaptive parameters method. I tried 11,1 , 9,1, and for both of them, the rectangle in the threshold looked more prominent : but in this case the output detected no rectangles at all.
  2. I have already disregarded otsu thresholding, as it is not working for about 4 of my test images.

What exactly can I tweak in the rectangle detection procedure for it to detect this rectangle?

I also request , if possible, only slight modifications to this method and not some entirely new method. As I have mentioned, this method is working perfectly for all of my other test images, and if the new suggested method works for this image but fails for the others, then I'll find myself back here asking why it failed.

Edit: The method that abss suggested worked for this image, however failed for:

  • image 4
  • image 1, far off

Other test images:

  • image 1, normal
  • image 2
  • image 3
  • image 9, part 1
  • image 9, part 2
like image 872
satan 29 Avatar asked Jun 14 '21 14:06

satan 29


People also ask

How do I use cv2 approxPolyDP?

Working of approxPolyDP() function in OpenCVThe image of a polygon whose shape of a contour must be approximated is read using the imread() function. Then the input image is converted into a grayscale image. Then thresholding function is applied on the grayscale image to convert it into a binary image.

How do you know if a rectangle is OpenCV?

Use the findContours() and contourArea() Function of OpenCV to Detect Rectangles in Images in Python. We can detect a rectangle present in an image using the findContours() function of OpenCV, and we can use the contourArea() function to sort different rectangles according to their area.

What does approxPolyDP return?

approxPolyDP() allows the approximation of polygons, so if your image contains polygons, they will be quite accurately detected, combining the usage of cv2. findContours and cv2.

What function is used to detect polygons OpenCV?

Contours – convex contours and the Douglas-Peucker algorithm The first facility OpenCV offers to calculate the approximate bounding polygon of a shape is cv2. approxPolyDP. This function takes three parameters: A contour.


2 Answers

You can easily do it by adding this line of code after your threshold

kernel = cv.getStructuringElement(cv.MORPH_RECT,(3,3))
th = cv.morphologyEx(th,cv.MORPH_OPEN,kernel)

This will remove noise within the image. you can see this link for more understanding about morphologyEx https://docs.opencv.org/master/d9/d61/tutorial_py_morphological_ops.html

The results I got is shown below enter image description here

like image 106
Abdelsalam Hamdi Avatar answered Oct 22 '22 00:10

Abdelsalam Hamdi


I have made a few modifications to your code so that it works with all of your test images. There are a few false positives that you may have to filter based on HSV color range for green (since your target is always a shade of green). Alternately you can take into account the fact that the one of the child hierarchy of your ROI contour is going to be > 0.4 or so times than the outer contour. Here are the modifications:

  1. Used DoG for thresholding useful contours
  2. Changed arcLength multiplier to 0.5 instead of 0.1 as square corners are not smooth
  3. cv2.RETR_CCOMP to get 2 level hierarchy
  4. Moved ApproxPolyDP inside to make it more efficient
  5. Contour filter area changed to 600 to filter ROI for all test images
  6. Removed a little bit of unnecessary code

Check with all the other test images that you may have and modify the parameters accordingly.

img = cv2.imread("/path/to/your_image")

width=0 
height=0

start_x=0 
start_y=0
end_x=0 
end_y=0

output = img.copy()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

gw, gs, gw1, gs1, gw2, gs2 = (3,1.0,7,3.0, 3, 2.0)

img_blur = cv2.GaussianBlur(gray, (gw, gw), gs)
g1 = cv2.GaussianBlur(img_blur, (gw1, gw1), gs1)
g2 = cv2.GaussianBlur(img_blur, (gw2, gw2), gs2)
ret, thg = cv2.threshold(g2-g1, 127, 255, cv2.THRESH_BINARY)

contours, hier = cv2.findContours(thg, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)

img_cpy = img.copy()

width=0 
height=0

start_x=0 
start_y=0
end_x=0 
end_y=0

for i in range(len(contours)):
    
    if hier[0][i][2] == -1:
        continue
        
    x ,y, w, h = cv2.boundingRect(contours[i])
    a=w*h    
    aspectRatio = float(w)/h
    if  aspectRatio >= 2.5 and a>600:          
        approx = cv2.approxPolyDP(contours[i], 0.05* cv2.arcLength(contours[i], True), True)
        if len(approx) == 4 and x>15  :
            width=w
            height=h   
            start_x=x
            start_y=y
            end_x=start_x+width
            end_y=start_y+height      
            cv2.rectangle(img_cpy, (start_x,start_y), (end_x,end_y), (0,0,255),3)
            cv2.putText(img_cpy, "rectangle "+str(x)+" , " +str(y-5), (x, y-5), cv2.FONT_HERSHEY_COMPLEX, 0.5, (0, 0, 0))
          
plt.imshow(img_cpy)

print("start",start_x,start_y)
print("end", end_x,end_y)

enter image description here

like image 24
Knight Forked Avatar answered Oct 22 '22 02:10

Knight Forked