OpenCV(python) 找二值图像的轮廓。轮廓是一系列相连的点组成的曲线,代表了物体的基本外形。轮廓是连续的,边缘并不全都图边缘包括轮廓。在 OpenCV 中其实边缘主要是作为图像的特征使用,比如可以用边缘特征可以区分脸和手,而轮廓主要用来分析物体的形态,比如物体的周长和面轮廓。

加载图像

1
img_url = "https://github.com/xujinzh/archive/blob/master/images/opencv/9.png?raw=true"
1
2
3
4
5
import cv2
import matplotlib.pyplot as plt
import numpy as np
import requests
from PIL import Image
1
2
3
4
5
6
7
response = requests.get(img_url, stream=True)
img = np.array(Image.open(response.raw).convert("RGB"))

img_init = img.copy()
print(img.shape)
plt.imshow(img)
plt.show()
(225, 224, 3)

png

转化为二值图像

1
2
3
4
5
6
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 使用 cv2.THRESH_BINARY_INV 把 9 用作前景,白色表示
# 使用 cv2.THRESH_OTSU 进行滤波
ret, thresh = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
plt.imshow(thresh, cmap="gray")
plt.show()

png

1
2
# 只取 0, 255
set(thresh.reshape(1, -1).tolist()[0])
{0, 255}

find Contours

1
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
1
len(contours)
2
1
len(hierarchy), type(hierarchy)
(1, numpy.ndarray)
1
hierarchy
array([[[-1, -1,  1, -1],
        [-1, -1, -1,  0]]], dtype=int32)
1
2
3
for cnt in contours:
print(type(cnt))
# print(cnt)
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>

画 contours

1
2
3
4
5
# 把所有轮廓画在一起
img_contours = cv2.drawContours(img, contours, -1, (0, 255, 0), 2)

plt.imshow(img_contours)
plt.show()

png

1
2
3
4
5
6
# 分别画每个轮廓
for cnt in contours:
img = img_init.copy()
cv2.drawContours(img, [cnt], 0, (255, 0, 255), 2)
plt.imshow(img)
plt.show()

png

png

计算轮廓面积

1
2
for cnt in contours:
print(cv2.contourArea(cnt))
10624.0
3585.5

计算周长

1
2
for cnt in contours:
print(cv2.arcLength(cnt, True))
530.6173120737076
239.17871356010437

计算矩

1
2
3
4
5
6
for cnt in contours:
M = cv2.moments(cnt)
print(M)
# 质心
cx, cy = M["m10"] / M["m00"], M["m01"] / M["m00"]
print(cx, cy)
{'m00': 10624.0, 'm10': 1179807.0, 'm01': 988628.0, 'm20': 138264122.0, 'm11': 110420621.75, 'm02': 108261792.33333333, 'm30': 16915839222.900002, 'm21': 12973061316.9, 'm12': 12258148255.633333, 'm03': 13524838384.2, 'mu20': 7245244.246893823, 'mu11': 632384.2880270928, 'mu02': 16263926.89809236, 'mu30': -47729935.87674332, 'mu21': -33712518.19269228, 'mu12': 117861541.09828925, 'mu03': 423503079.8199959, 'nu20': 0.06419140872094325, 'nu11': 0.005602797768874818, 'nu02': 0.1440951254846342, 'nu30': -0.00410270882762122, 'nu21': -0.0028978175530693143, 'nu12': 0.01013099171869212, 'nu03': 0.036402936483911824}
111.05111069277109 93.05609939759036
{'m00': 3585.5, 'm10': 387701.3333333333, 'm01': 283841.5, 'm20': 43129861.08333333, 'm11': 30132658.208333332, 'm02': 23606262.25, 'm30': 4925989623.3, 'm21': 3294219072.0333333, 'm12': 2462194527.1, 'm03': 2049563404.8500001, 'mu20': 1207584.171202682, 'mu11': -559219.6344593987, 'mu02': 1136314.6493166909, 'mu30': 1190115.5424232483, 'mu21': 836261.0243297517, 'mu12': -1818823.7834247053, 'mu03': 894210.7344172001, 'nu20': 0.0939329492050244, 'nu11': -0.04349936904672335, 'nu02': 0.0883891895741609, 'nu30': 0.0015460189391694732, 'nu21': 0.0010863444225512572, 'nu12': -0.0023627420329802268, 'nu03': 0.0011616239614875813}
108.13033979454283 79.1637149630456

轮廓外接矩形

1
2
3
4
5
6
7
8
9
for cnt in contours:
x, y, w, h = cv2.boundingRect(cnt)
img = img_init.copy()
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
rect = cv2.minAreaRect(cnt)
box = np.intp(cv2.boxPoints(rect))
cv2.drawContours(img, [box], 0, (255, 0, 0), 2)
plt.imshow(img)
plt.show()

png

png

轮廓外接圆

1
2
3
4
5
6
7
for cnt in contours:
img = img_init.copy()
(x, y), radius = cv2.minEnclosingCircle(cnt)
(x, y, radius) = np.intp((x, y, radius))
cv2.circle(img, (x, y), radius, (0, 0, 255), 2)
plt.imshow(img)
plt.show()

png

png

轮廓拟合椭圆

1
2
3
4
5
6
for cnt in contours:
img = img_init.copy()
ellipse = cv2.fitEllipse(cnt)
cv2.ellipse(img, ellipse, (255, 255, 0), 2)
plt.imshow(img)
plt.show()

png

png

轮廓匹配度

1
cv2.matchShapes(contours[0], contours[1], 1, 0.0)
0.2869094336215315

参考文献

  1. cv2.findContours() - 轮廓提取算法 – OpenCV