Image Processing in OpenCV - Introduction to Contour + Contour Features

Image Processing in OpenCV - Introduction to Contour + Contour Features

1. Contours in OpenCV

1.1 Profile overview

Always the same. Before learning about contours in OpenCV, let's understand what a contour is. A contour can be simply interpreted as a curve that connects all consecutive points (along a boundary) with the same color or intensity. Contours are used for shapes Useful tools for analysis and object and detection and recognition

  • For better accuracy, we use binary images, so before finding contours we apply thresholding or Canny edge detection
  • In OpenCV, finding the contour is to find the white object from the black background, so the object we are looking for should be white and the background should be black (after applying threshold or Canny edge detection)

To find the image of a binary image, we need to use a function: cv.findContours() , this function includes three parameters, the first is the original image, the second is the contour retrieval mode, and the third is the contour approximation method, this function outputs contours and hierarchies, contours refer to a Python list of all images in the image, each individual contour is a Numpy array of (x,y) coordinates object of the boundary points

import numpy as np
import cv2 as cv
im = cv.imread('test.jpg')
imgray = cv.cvtColor(im, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE,
cv.CHAIN_APPROX_SIMPLE)

The value of the second parameter contour retrieval mode can be:

  • RETR_EXTERNAL: retrieve only the outermost contours

  • RETR_LIST: Retrieve all contours and save them in a linked list

  • RETR_CCOMP: Retrieves all contours and organizes them into two layers: the top layer is the outer boundaries of the parts, the second layer is the boundaries of the cavities

  • RETR_TREE: common, retrieves all contours, and reconstructs the entire hierarchy of nested contours. save all outlines

The value of the third parameter contour approximation method can be:

  • CHAIN_APPROX_NONE: Output contours as Freeman chain codes, all other methods output polygons (sequences of vertices). Draw all contours normally

  • CHAIN_APPROX_SIMPLE: Compress the horizontal, vertical, and oblique parts, that is, the function only retains their end point coordinates. Compression results in more streamlined results, for example, a rectangular outline only needs 4 points to save the outline information

The data reference comes from the article: [opencv] (6) Image contour processing

1.2 Contour drawing

After finding the contour through the cv.findContours() function, we usually draw the contour through the cv.drawContours() function. As long as there are boundary points, it can also be used for you to draw any shape. The first parameter of this function is the image resource , the second parameter is the contour that should be passed as a Python list, the third parameter is the index of the contour (useful when drawing a single contour, if you need to draw all contours, pass in the parameter -1), and the rest of the parameters are Common parameters such as color thickness

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread('E:/image/test09.png')
# 在寻找轮廓时要传入单通道图像
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, threshold = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
contours, hierarchy = cv.findContours(threshold, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# 绘制所有轮廓
img1 = cv.drawContours(img, contours, -1, (0, 255, 0), 3)
# 绘制单个轮廓
img2 = cv.drawContours(img, contours, 3, (0, 255, 0), 3)
# 常用的绘制轮廓的方法
cnt = contours[3]
img3 = cv.drawContours(img, [cnt], 0, (0, 255, 0), 3)
plt.subplot(1, 3, 1), plt.imshow(img, cmap='gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(1, 3, 2), plt.imshow(img, cmap='gray')
plt.title('ALL_IMG'), plt.xticks([]), plt.yticks([])
plt.subplot(1, 3, 3), plt.imshow(img, cmap='gray')
plt.title('Usually'), plt.xticks([]), plt.yticks([])
plt.show()

insert image description here

1.3 Contour Approximation Method

The cv.findContours() function we mentioned earlier has three parameters to pass, and the third parameter is the contour approximation method, which is the contour approximation method we are going to talk about here. A contour is the boundary of a shape with the same strength, it stores the (x,y) coordinates of the shape boundary, but it does not store all the coordinates

And whether it stores all the coordinates depends on the use of the contour approximation method. If we pass in the cv.CHAIN_APPROX_NONE parameter, the contour will store all the coordinates, but in actual situations, is it necessary for us to store all the coordinates? ?

For example, if we find a rectangular contour, storing all the coordinates will waste a lot of space, we only need to store the coordinates of four vertices, this operation is the parameter cv.CHAIN_APPROX_SIMPLE, this contour approximation method can meet our needs

2. Contour features

2.1 Characteristic moments

Before learning the feature moment, we must first understand its concept. First, the feature moment represents the global features of a contour and an image. The moment information contains different types of geometric features of the corresponding object. The feature moment is divided into three types: spatial moment, Central moments and normalized central moments

Central moment: For high-order images, the characteristic moment will change with the change of position. In order to solve this problem, the central moment came into being. It obtains the invariance of translation by subtracting the mean value, so it can compare two images in different positions. Whether the object is consistent, that is, the central moment has the characteristics of translation invariance

Normalized central moment: In addition to translation, some images will also encounter zooming, that is, their characteristics can also be judged after zooming. The normalized central moment is obtained by dividing by the total size of the object to obtain zoom invariance

Going back to our OpenCV feature moments, it can help us calculate some features, such as centroid, area, etc., an important function cv.moments() provides a dictionary of all calculated moment values

import numpy as np
import cv2 as cv
img = cv.imread('star.jpg',0)
ret,thresh = cv.threshold(img,127,255,0)
contours,hierarchy = cv.findContours(thresh, 1, 2)
cnt = contours[0] 
M = cv.moments(cnt)
print( M )

2.2 Contour area + perimeter

The contour area is given by the function cv.contourArea () or from the moment M['m00']

The perimeter is also known as the arc length and can be found using the cv.arcLength () function. The second parameter specifies whether the shape is a closed contour ( True ) or a curve

area = cv.contourArea(cnt)
perimeter = cv.arcLength(cnt,True)

2.3 Contour Approximation

Contour approximation is to approximate the contour shape to other shapes with fewer vertices through the Douglas-Puk algorithm according to the intensive reading we specified.

When it comes to the Douglas-Puke algorithm, we must look at its essence: when digitizing an image, the curve is sampled, that is, a limited number of points are taken on the curve, and the connection becomes a polyline, and to a certain extent, the original some shape

import cv2
import numpy as np
from matplotlib import pyplot as plt

src = cv2.imread('E:/image/test10.png')
gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(~thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnt = contours[0]
img1 = src.copy()
epslion1 = 0.05 * cv2.arcLength(cnt, True)
approx1 = cv2.approxPolyDP(cnt, epslion1, True)
img1 = cv2.drawContours(img1, [approx1], 0, (0, 0, 255), 2)
img2 = src.copy()
epslion2 = 0.01 * cv2.arcLength(cnt, True)
approx2 = cv2.approxPolyDP(cnt, epslion2, True)
img2 = cv2.drawContours(img2, [approx2], 0, (0, 255, 0), 2)
plt.subplot(1, 2, 1), plt.imshow(img1, cmap='gray')
plt.title('JinSi1'), plt.xticks([]), plt.yticks([])
plt.subplot(1, 2, 2), plt.imshow(img2, cmap='gray')
plt.title('JInSi2'), plt.xticks([]), plt.yticks([])
plt.show()

insert image description here

As a review of the previous knowledge, we analyze the whole process of drawing two approximate contours of the image with different precisions. First, we load some third-party libraries we need, so that we can use their methods in the code, and then in the fifth line of code We read an image through the cv,imread() function, and then use the cv.cvtColor() function to convert it into a grayscale image. Why should we convert it into a grayscale image? Because we're going to use image thresholding next! In the global threshold function cv.threshold() function, we have four parameters to pass in. The first parameter is our source image, which is generally a grayscale image, which is why we need to change img to gray, and the second The first parameter is the threshold we set, which is used to classify the pixels in the image, and the pixels greater than the threshold will be set to the maximum value, the third parameter is the maximum value we set, and the fourth parameter is a representation different types of signs

The code reaches the 8th line. From here on, it will involve what we have learned in this part. The first thing to bear the brunt is to find the contour. We use the cv.findContours() function. The three parameters of this function are image and contour retrieval mode. and the contour approximation method

At line 11, a strange thing appeared, which is one of the core codes of contour approximation. Epslion is a parameter closely related to the approximation accuracy, which represents the maximum distance from the contour to the approximate contour. How to calculate the maximum distance? Then we need to use a function: cv.arcLength(), is it familiar, it is the function we mentioned above to find the perimeter of the contour, we need to choose the correct epsilon to get the correct output

If we change the 0.05 of the 11 lines of code to 0.1, we will find that we cannot get the output we want. This is because the parameter epsilon is too rough. The smaller the parameter, the more approximate the contour we get

2.4 Convex Hull

The contour convex hull and the contour approximation look similar, but in fact they have nothing to do with each other. Regarding the contour convex hull, we also have an important function cv.convexHull() function, which is used to check whether the curve has convex and concave defects and correct it. No correction, it depends on the parameters of this function

Generally speaking, a convex curve is generally a convex or flat curve. If it is convex (concave) inside, we call it a convexity defect. We will elaborate on the convexity defect later.

Let's talk about the syntax of the function cv.convexHull(). It looks like this:

hull = cv.convexHull(points[, hull[, clockwise[, returnPoints]]

point is the contour we passed (why use point? Did you forget that the essence of the contour is a curve connected by all consecutive points with the same color or intensity), hull (bulge) is the output, we It is generally ignored, clockwise is a direction mark, if it is passed in True, it is clockwise, otherwise it is counterclockwise, returnPoint is True by default, it returns the coordinates of the convex hull, if it is False, it returns the point corresponding to the convex hull The index of the contour point of

Then go to the example! (Keep practicing to grow!)

import cv2
import numpy as np
from matplotlib import pyplot as plt

src = cv2.imread('E:/image/test11.png')
gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 寻找轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
    hull = cv2.convexHull(cnt)
    length = len(hull)
    # 如果凸包点集中的点个数大于5
    if length > 5:
        # 绘制图像凸包的轮廓
        for i in range(length):
            cv2.line(src, tuple(hull[i][0]), tuple(hull[(i + 1) % length][0]), (0, 0, 255), 2)
cv2.imshow('line', src)
cv2.waitKey()

insert image description here

Article reference source: OpenCV entry to find the convex hull of the image (convex hull)

2.5 Bounding rectangle (right-angled rectangle + rotated rectangle)

2.5.1 Right-angled rectangle

The right-angled rectangle does not consider the rotation of the object, so the area of ​​the right-angled bounding rectangle is not the smallest. Finding this rectangle involves a function: cv.boundingRect()

# (x,y)为左上角坐标,(w,h)为宽和高
x,y,w,h = cv.boundingRect(cnt)
cv.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)

2.5.2 Rotate rectangle

The rotated bounding rectangle is drawn with the minimum area, so it takes into account the rotation of the object. The function involved in finding the rotated bounding rectangle is cv.minAreaRect(), which returns a Box2D structure that contains coordinates, width, height, and rotation angle , but we want to get this rectangle is quite troublesome, we need the coordinates of the four vertices

rect = cv.minAreaRect(cnt)
box = cv.boxPoints(rect)
box = np.int0(box)
cv.drawContours(img,[box],0,(0,0,255),2)

2.8 Minimum closed circle + fitting ellipse

2.8.1 Minimum closed circle

Finding the minimum enclosing circle of an object requires the use of the function: cv.minEnclosingCircle(), which is a circle that completely covers the object with the smallest area

(x,y),radius = cv.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
cv.circle(img,center,radius,(0,255,0),2)

2.8.2 Fitting an ellipse

Fitting an object's ellipse will use the function: cv.fitEllipse()

ellipse = cv.fitEllipse(cnt)
cv.ellipse(img,ellipse,(0,255,0),2)

Let's take a look at the application code:

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('E:/image/test13.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 设置阈值
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnt = contours[0]
x, y, w, h = cv2.boundingRect(cnt)
img = cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
img = cv2.drawContours(img, [box], 0, (0, 0, 255), 2)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyWindow()

insert image description here


(Note: For the content of the article, refer to the official Chinese document of OpenCV4.1)
If the article is helpful to you, remember to support it with one click and three links

Guess you like

Origin blog.csdn.net/qq_50587771/article/details/123819620