Sort all the contours by top to bottom not enough năm 2024

cv2.CHAIN_APPROX_SIMPLE)
    # Sort all the contours by top to bottom.
    (vertical_contours, vertical_bounding_boxes) = sort_contours(vertical_contours, method="left-to-right")
    filtered_vertical_bounding_boxes = list(filter(lambda x:vertical_boxes_filter(x,height), vertical_bounding_boxes))
    # Morphological operation to detect horizontal lines from an image
    img_temp2 = cv2.erode(thresholded, hori_kernel, iterations=3)
    horizontal_lines_img = cv2.dilate(img_temp2, hori_kernel, iterations=3)
    _, horizontal_contours, _ = cv2.findContours(horizontal_lines_img.copy(), cv2.RETR_EXTERNAL,
                            cv2.CHAIN_APPROX_SIMPLE)
    horizontal_contours, horizontal_bounding_boxes = sort_contours(horizontal_contours, method="top-to-bottom")
    filtered_horizontal_bounding_boxes = list(filter(lambda x:horizontal_boxes_filter(x,width), horizontal_bounding_boxes))
    if DEBUG:
        color_image = cv2.cvtColor(gray_image.copy(), cv2.COLOR_GRAY2BGR)
        cv2.drawContours(color_image, vertical_contours, -1, (0, 255, 0), 2)
        cv2.drawContours(color_image, horizontal_contours, -1, (255, 0, 0), 2)
        # for filtered_horizontal_bounding_box in filtered_horizontal_bounding_boxes:
        #     x,y,w,h = filtered_horizontal_bounding_box
        #     cv2.rectangle(color_image,(x,y),(x+w,y+h),(0,255,255),2)
        #
        # for filtered_vertical_bounding_box in filtered_vertical_bounding_boxes:
        #     x,y,w,h = filtered_vertical_bounding_box
        #     cv2.rectangle(color_image,(x,y),(x+w,y+h),(0,255,255),2)

Answers to most computer vision problems lie in finding and analyzing the shapes present in the image, and getting the contour is one such approach for it. To a beginner, I would explain a contour as “simply as a curve joining all the points lying on the boundary of that shape”.

Let's say I have the following image of my hand, the contour of the hand would be the curve represented by the green line. The red dots show the points by connecting which we are making up the contour curve.

Contour points(Red). Connecting contour points, we create the contour curve(Green)

Following would be contours for the common shapes:

Contour(Green) for the common shapes

I still remember my advanced mathematics class where they taught contours. But it was hard to connect with the importance of this topic since the teacher never mentioned any applications of the contour in the actual world. And today is the day when I realize how important they are in computer vision.

Finding Contours in OpenCV

The only pre-condition that you need to find contours in an image is that the image should be binary. To know in detail about binary images, check my article.

So I converted my image to binary using Canny edge detection discussed in the previous article:

binary_image = edgeDetection(image) contours, hierarchy = cv2.findContours(binary_image, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)

Once you have the binary image, you just need to pass it to findContours() and that’s it you have the contours ready for you. Before we proceed let’s discuss the other arguments to this function.

2nd argument(Contour retrieval mode): A shape(or a contour) may be present inside another shape(or contour). So we may call the outer shape the parent and inner shape as a child. The retrieval mode argument decides if want the output of the contours list in a hierarchical(parent-child) manner or we simply want them as a list(having no hierarchy). Use cv2.RETR_LIST for no hierarchy/plain list. Check more variants here.

Note: there is no impact on contour detection. Just the response representation gets changed.

Contour Hierarchy

3rd argument(Contour approximation): A contour is simply a shape represented by a collection of points(red dots in the very first image). So this argument specifies how many points should be stored so that the contour shape is preserved and can be redrawn. cv2.CHAIN_APPROX_NONE signifies that we store all the points(no approximation). It takes more storage space but if there is a zig-zag line, we definitely need to store all the points to preserve the contour shape. But in the case of a straight line, 2 points would be enough as the points in between can be approximated based on the equation of a straight line, so we use cv2.CHAIN_APPROX_SIMPLE.

The output contours is a list of contour points each having an x and y coordinate.

Operations on Contours

  1. Contour Area: You can sort the contours based on their area.

contours = sorted(contours, key=lambda x: cv2.contourArea(x), reverse=True)

2. Contour perimeter: You can calculate the perimeter for each contour.

for contour in contours:

perimeter = cv2.arcLength(contour, True)
3. Vertices of a contour: Since we know that the contour is just the representation of x and y coordinates of its points. So getting the length of the contour, gives us the vertices of the contour.

len(contour) == 4

Rectangle

4. Contour Approximation: This is a little bit tricky, so needs more explanation. This is very similar to 3rd argument that we discussed above, just it gives us more manual control.

Let’s say OpenCV detected a contour as shown in the below image(1). What do you think would be the closest regular shape that can be obtained, definitely a line(assuming that the peak is not important to us). Also since the peak is not important to us, why waste our storage to store coordinates of the peak. Instead if somehow we can approximate this contour to a line, we only need 2 coordinates to store our contour. This is what the cv2.approxPolyDP() does for us. It tries to approximate the irregular contour to some other shape so that the contour can be simplified.

How contour approximation is controlled

How do we control this approximation? The cv2.approxPolyDP() takes an epsilon parameter whose value tells OpenCV, how much deviation we can allow from the original shape in order to receive our simplified contour. Image(3) shows an epsilon of 10% which means we are somewhat willing to get the simplified contour(a straight line as in 5). Image(4) shows an epsilon of 20% which means we are more willing to get the simplified contour(a straight line as in 5). See the peak moves down towards a straight line as we increase the epsilon value. More epsilon means more willingness to get simplified contour.

for contour in contours:

perimeter = cv2.arcLength(contour, True)  
approximatedShape = cv2.approxPolyDP(contour, 0.02 * perimeter, True)
See how we passed the second parameter(epsilon). The last parameter just says if want a closed contour.

5. Minimum Enclosing Circle: Using minEnclosingCircle() you can find a circle that completely covers the contour of the object with a minimum area.

(centerXCoordinate, centerYCoordinate), radius = cv2.minEnclosingCircle(contour)

6. Moments: The moments let you extract important contour’s physical properties like the center of mass of the object, area of the object, etc.

for contour in contours:

moment = cv2.moments(contour)
You can use the moment to calculate further important attribute of contour like centroid:

centroidXCoordinate = int(moment['m10'] / moment['m00']) centroidYCoordinate = int(moment['m01'] / moment['m00'])

You can check more such operations permitted on a contour here.

Convex Hull

If explained in layman terms, a convex hull of an object is the minimum boundary that can completely enclose or wrap the object(or contour of that object).

Reference is taken from here

There are many algorithms that can be used to find the convex hull for a given contour but I would not be discussing them in detail in this article. Once you find out the contours, it is pretty straightforward to find the convex hull

for contour in contours:

convexHull = cv2.convexHull(contour)  
cv2.drawContours(image, [convexHull], -1, (255, 0, 0), 2)

Display the final convex hull image

cv2.imshow('ConvexHull', image) cv2.waitKey(0)

Convexity Defects

Any deviation of the contour from its convex hull is known as the convexity defect.

The vertical lines in the last image are what we call the convexity defects

Note: There can be multiple convexity defect points where the hull deviates from the contour but in the last image I have only shown major convexity defects so as not to confuse.

OpenCV provides a simple way to find the convexity defects from contour and hull:

convexityDefects = cv2.convexityDefects(contour, convexhull)

convexityDefects contains a list of all the convexity defects found. Each element in this list has the following attributes:

How do you sort contours?

We first compute the bounding boxes of each contour, which is simply the starting (x, y)-coordinates of the bounding box followed by the width and height. The boundingBoxes enable us to sort the actual contours. We return the (now sorted) list of bounding boxes and contours.

What is the contour approximation method?

Contour ApproximationIt approximates a contour shape to another shape with less number of vertices depending upon the precision we specify. It is an implementation of Douglas-Peucker algorithm. Check the wikipedia page for algorithm and demonstration.

How do you resize contour?

How to resize contours.

Step 1: Translate the contour to the origin..

Step 2: Scale each point of the contour..

Step 3: Translate back the contour to it's original place..

What is the difference between Chain_approx_simple and Chain_approx_none?

We can use CHAIN_APPROX_SIMPLE to do this function I mentioned. This way all unnecessary points will be removed and the contour will be compressed. CHAIN_APPROX_NONE gives us all the contours. It is underperforming compared to CHAIN_APPROX_SIMPLE in terms of memory savings.