3220 字
16 分钟
OpenCV:图像特征【描述轮廓】

1. 描述轮廓#

​ 边缘检测虽然能够检测出边缘,但边缘是不连续的,检测到的边缘并不是一个整体。图像轮廓是指将边缘连接起来形成的一个整体 ,可以用于后续的计算。

1.1 查找轮廓#

使用方法#

  • 通过函数cv2.findContours(),实现图像轮廓的查找
  • 函数原型
    • OpenCV 3.xdst, contours, hierarchy = cv2.findContours( src, mode, method )
    • OpenCV 4.xcontours, hierarchy = cv2.findContours( src, mode, method )
  • 参数说明
    • dst目标图像,与原始图像具有相同的大小和类型。
    • contours轮廓信息
      • type属性list类型每个元素都是图像的一个轮廓
      • 轮廓的个数len(contours)
      • 轮廓的点坐标contours[i]i为轮廓索引
      • 轮廓的点数len(contours[i])i为轮廓索引
    • hierarchy:关于图像轮廓层次拓扑信息
      • 图像内的轮廓可能位于不同的位置,如一个轮廓位于另一个轮廓的内部。这种情况下,将外部的轮廓称为父轮廓将内部的轮廓称为子轮廓
      • 每个轮廓contours[i]对应4个元素来说明当前轮廓的层次关系: [Next, Previous, First_Child, Parent]
        • Next后一个轮廓的索引编号。
        • Previous前一个轮廓的索引编号。
        • First_Child第1个子轮廓的索引编号。
        • Parent父轮廓的索引编号。
      • 索引-1表示不存在
    • src原始图像。8 位单通道图像,所有非零值被处理为 1,所有零值保持不变。建议直接使用二值图像
    • mode轮廓检索模式
      • cv2.RETR_EXTERNAL只检测外轮廓
      • cv2.RETR_LIST对检测到的轮廓不建立等级关系
      • cv2.RETR_CCOMP检索所有轮廓并将它们组织成两级层次结构。上面的一层为外边界,下面的一层为内孔的边界。如果内孔内还有一个连通物体,那么这个物体的边界仍然位于顶层。
      • cv2.RETR_TREE建立一个等级树结构的轮廓
    • method轮廓的近似方法
      • cv2.CHAIN_APPROX_NONE:存储所有的轮廓点,相邻两个点的像素位置差不超过1。
      • cv2.CHAIN_APPROX_SIMPLE:压缩水平方向、垂直方向、对角线方向的元素,只保留该方向的终点坐标。

示例1:轮廓信息解析#

import cv2
image = cv2.imread("sample_1.png", 0)
val, image = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)
cv2.imwrite("sample_1_binary.png", image)
contours, hierarchy = cv2.findContours( image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
print (len(contours)) # 3
print (len(contours[0])) # 256
print (len(contours[1])) # 342
print (len(contours[2])) # 198
原图二值化的图

示例2:拓扑信息解析#

import cv2
image = cv2.imread("sample_2.png", 0)
val, image = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)
cv2.imwrite("sample_2_binary.png", image)
contours, hierarchy = cv2.findContours( image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
print("cv2.RETR_EXTERNAL: ")
print(hierarchy)
contours, hierarchy = cv2.findContours( image, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
print("cv2.RETR_LIST: ")
print(hierarchy)
contours, hierarchy = cv2.findContours( image, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
print("cv2.RETR_CCOMP: ")
print(hierarchy)
contours, hierarchy = cv2.findContours( image, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
print("cv2.RETR_TREE: ")
print(hierarchy)
原图二值化的图
类型图示说明
cv2.RETR_EXTERNAL[[[1 -1 -1 -1], [-1 0 -1 -1]]]
cv2.RETR_LIST[[[1 -1 -1 -1], [2 0 -1 -1], [-1 1 -1 -1]]]
cv2.RETR_CCOMP[[[1 -1 -1 -1], [-1 0 2 -1], [-1 -1 -1 1]]]
cv2.RETR_TREE[[[1 -1 -1 -1], [-1 0 2 -1], [-1 -1 -1 1]]]

1.2 绘制轮廓#

使用方法#

  • 通过函数cv2.drawContours()绘制图像轮廓
  • 函数原型image = cv2.drawContours( image, contours, contourIdx, color, thickness, lineType, hierarchy, maxLevel, offset )
  • 参数说明
    • image待绘制轮廓的图像。函数会在原始图像上直接绘制轮廓,即函数执行完成以后,image不再是原始图像,而是包含了轮廓的图像
    • contours需要绘制的轮廓
      • 对应函数cv2.findContours()输出的轮廓信息
    • contourIdx需要绘制的边缘索引,提示函数要绘制某一条轮廓还是 全部轮廓。
      • 参数是一个整数或者:绘制对应索引号的轮廓
      • 参数是负数:绘制全部轮廓
    • color绘制的颜色,用BGR格式表示。
    • thickness绘制轮廓时所用画笔的粗细
    • lineType绘制轮廓时所用的线型
    • hierarchy:关于图像轮廓层次拓扑信息
      • 对应函数cv2.findContours()输出的轮廓拓扑信息
    • maxLevel控制所绘制的轮廓层次的深度
    • offset偏移参数,使轮廓偏移到不同的位置。

示例#

import cv2
import numpy as np
image = cv2.imread("sample_1.png", 0)
val, image = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours( image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
amount = len(contours)
for i in range(amount):
zero = np.zeros(image.shape, np.uint8)
dst = cv2.drawContours(image=zero, contours=contours, contourIdx=i, color=[255,255,255], thickness=1)
cv2.imwrite("sample_1_draw_" + str(i) + ".png", dst)
原图二值化的图轮廓0轮廓1轮廓2

2. 拟合轮廓#

​ 在计算轮廓时,可能并不需要实际的轮廓,而仅需要一个接近于轮廓的近似多边形

2.1 矩形包围框#

使用方法#

  • 通过函数cv2.boundingRect()绘制轮廓的矩形边界
  • 函数原型
    • ret = cv2.boundingRect( array )
    • x, y, w, h = cv2.boundingRect( array )
  • 参数说明
    • ret:元组形式,矩形边界的左上角顶点的坐标值及矩形边界的宽度高度
    • x:矩形边界左上角顶点的x坐标
    • y:矩形边界左上角顶点的y坐标
    • w:矩形边界的x方向的长度
    • h:矩形边界的y方向的长度
    • array灰度图像轮廓

示例#

import cv2
image = cv2.imread('sample_3.png')
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(image_gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
x,y,w,h = cv2.boundingRect(contours[0])
cv2.rectangle(image, (x,y), (x+w,y+h), (0,255,255), 1)
cv2.imwrite("sample_3_rect.png", image)
原图矩形包围框

2.2 最小包围矩形框#

使用方法#

  • 通过函数cv2.minAreaRect()绘制轮廓的最小包围矩形框

  • 函数原型box = cv2.minAreaRect( points )

  • 参数说明

    • box:矩形的特征信息,结构为:(最小外接矩形的中心坐标(x,y), (宽度,高度), 旋转角度)。
      • 不符合函数cv2.drawContours()的参数结构要求需要通过cv2.boxPoints()进行转换,才能进行轮廓绘制。
    • points轮廓
  • 函数原型points = cv2.boxPoints( box )

  • 参数说明

    • box:矩形的特征信息,结构为:(最小外接矩形的中心坐标(x,y), (宽度,高度), 旋转角度)。
    • points轮廓

示例#

import cv2
import numpy as np
image = cv2.imread('sample_3.png')
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(image_gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
rect = cv2.minAreaRect(contours[0])
pts = cv2.boxPoints(rect) # 转换坐标信息
pts = np.intp(pts) # 转换坐标信息为整数
image = cv2.drawContours(image, [pts], 0, (0,255,255), 1)
cv2.imwrite("sample_3_minAreaRect.png", image)
原图最小包围矩形框

2.3 最小包围圆#

使用方法#

  • 通过函数cv2.minEnclosingCircle()绘制轮廓的面积最小的包围圆形
  • 函数原型center, radius = cv2.minEnclosingCircle( points )
  • 参数说明
    • center:最小包围圆形的中心
    • radius:最小包围圆形的半径
    • points轮廓

示例#

import cv2
image = cv2.imread('sample_3.png')
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(image_gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
(x, y), radius = cv2.minEnclosingCircle(contours[0])
center = (int(x), int(y)) # 转换信息为整数
radius = int(radius) # 转换信息为整数
cv2.circle(image, center, radius, (0,255,255), 1)
cv2.imwrite("sample_3_minEnclosingCircle.png", image)
原图最小包围圆

2.4 最小包围三角形#

使用方法#

  • 通过函数cv2.minEnclosingTriangle()绘制轮廓的面积最小的包围三角形
  • 函数原型area, triangle = cv2.minEnclosingTriangle( points )
  • 参数说明
    • area:最小包围三角形的面积
    • triangle:最小包围三角形的三个顶点集
    • points轮廓

示例#

import cv2
image = cv2.imread('sample_3.png')
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(image_gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
area, triangle = cv2.minEnclosingTriangle(contours[0])
for n in range(len(triangle)):
cv2.line(image, tuple(triangle[n][0].astype(int)), tuple(triangle[(n+1)%3][0].astype(int)), (0,255,255), 1)
cv2.imwrite("sample_3_minEnclosingTriangle.png", image)
原图最小包围三角形

2.5 最优拟合椭圆#

使用方法#

  • 通过函数cv2.fitEllipse()构造最优拟合椭圆
  • 函数原型ret = cv2.fitEllipse( points )
  • 参数说明
    • ret RotatedRect类型的值,包含外接矩形的质心旋转角度等参数信息,与椭圆的中心点轴长度旋转角度等信息吻合。
    • points轮廓

示例#

import cv2
image = cv2.imread('sample_3.png')
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(image_gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
ellipse = cv2.fitEllipse(contours[0])
cv2.ellipse(image, ellipse, (0,255,0), 2)
cv2.imwrite("sample_3_fitEllipse.png", image)
原图最优拟合椭圆

2.6 最优拟合直线#

使用方法#

  • 通过函数cv2.fitLine()构造最优拟合直线
  • 函数原型line = cv2.fitLine( points, distType, param, reps, aeps )
  • 参数说明
    • line最优拟合直线参数。对于2D场景,返回4个值:(vx, vy, x, y)
      • 斜率vyvx\frac{v_y}{v_x}
      • 定点(x0,y0)(x_0, y_0)
      • 可使用直线点斜式方程yy0=k(xx0)y-y_0=k·(x-x_0),求解其他位置的解
    • points轮廓
    • distType距离类型
      • cv2.DIST_USER:用户自定义距离
      • cv2.DIST_L1: L-1范数,街区距离,dist=x1x2+y1y2dist=|x_1-x_2|+|y_1-y_2|
      • cv2.DIST_L2:L-2范数,欧式距离,dist=(x1x2)2+(y1y2)2dist=\sqrt{(x_1-x_2)^2+(|y_1-y_2)^2}
      • cv2.DIST_C: L-∞范数,棋盘距离,dist=max(x1x2,y1y2)dist=max(|x_1-x_2|,|y_1-y_2|)
    • param距离参数,与距离类型有关。当被设置为0时,会自动选择最优值。
    • reps:用于表示拟合直线所需要的径向精度,通常被设定为0.01
    • aeps:用于表示拟合直线所需要的角度精度,通常该值被设定为0.01

示例#

import cv2
image = cv2.imread('sample_3.png')
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
row, col = image_gray.shape
ret, binary = cv2.threshold(image_gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
[[vx],[vy],[x],[y]] = cv2.fitLine(contours[0], cv2.DIST_L2, 0, 0.01, 0.01)
left_y = int(vy / vx * (0 - x) + y)
right_y = int(vy / vx * (col - x) + y)
cv2.line(image, (0, left_y), (col - 1, right_y), (0,255,0), 2)
cv2.imwrite("sample_3_fitLine.png", image)
原图最优拟合直线

2.7 逼近多边形#

使用方法#

  • 通过函数cv2.approxPolyDP()构造指定精度的逼近多边形曲线
    • 算法Douglas-Peucker算法
  • 函数原型approxCurve = cv2.approxPolyDP( curve, epsilon, closed )
  • 参数说明
    • approxCurve 逼近多边形点集
    • curve轮廓
    • epsilon精度原始轮廓的边界点逼近多边形边界之间的最大距离
    • closed 轮廓封闭信息,用来表示轮廓是闭合的还是开口的
      • True:闭合
      • False:开口

示例#

import cv2
image = cv2.imread('sample_3.png')
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(image_gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
proportion = [0.10, 0.08, 0.06, 0.04, 0.02, 0.01]
for n in range(len(proportion)):
epsilon = proportion[n] * cv2.arcLength(contours[0], True)
approx = cv2.approxPolyDP(contours[0], epsilon, True)
img = cv2.drawContours(image.copy(), [approx], 0, (0,0,255), 2)
cv2.imwrite("sample_3_approxPolyDP_" + str(n+1) + ".png", img)
逼近多边形逼近多边形逼近多边形

2.8 凸包#

逼近多边形针对轮廓的高度近似,但有时候希望使用一个多边形的凸包来简化它。

凸包指的是完全包含原有轮廓,并且仅由轮廓上的点所构成的多边形

凸包每一处都是凸的,即在凸包内连接任意两点的直线都在凸包的内部。在凸包内,任意连续三个点的内角小于180°

使用方法#

  • 通过函数cv2.convexHull()获取轮廓的凸包
  • 函数原型hull = cv2.convexHull( points, clockwise, returnPoints )
  • 参数说明
    • hull 凸包角点集
    • points轮廓
    • clockwise:布尔型数据。
      • True:凸包角点将按顺时针方向排列。
      • False:凸包角点将按逆时针方向排列。
    • returnPoints:布尔型数据。
      • True:返回凸包角点坐标
      • False:返回凸包角点索引

示例#

import cv2
image = cv2.imread('sample_4.png')
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(image_gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
hull = cv2.convexHull(contours[0])
cv2.polylines(image, [hull], True, (0, 255, 0), 2)
cv2.imwrite("sample_4_hull.png", image)
原图凸包

2.9 凸缺陷#

​ 在轮廓边缘与凸包之间的部分,被称为凸缺陷

使用方法#

  • 通过函数cv2.convexityDefects()获取轮廓的凸缺陷
  • 函数原型defects = cv2.convexityDefects( contour, contourhull )
  • 参数说明
    • defects凸缺陷角点集
      • 它是一个数组,每一行包含的值是[起点终点轮廓上距离凸包最远的点最远点到凸包的近似距离]
    • contour轮廓
    • contourhull凸包角点集
      • 调用cv2.convexHull()时,参数returnPoints的值必须是False

示例#

import cv2
image = cv2.imread('sample_4.png')
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(image_gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
hull = cv2.convexHull(contours[0], returnPoints=False)
defects = cv2.convexityDefects(contours[0], hull)
for i in range(defects.shape[0]):
s, e, f, d = defects[i,0]
start = tuple(contours[0][s][0])
end = tuple(contours[0][e][0])
far = tuple(contours[0][f][0])
cv2.line(image, start, end, [0,255,0], 2)
cv2.circle(image, far, 5, [255,0,0], 1)
cv2.imwrite("sample_4_hullDefects.png", image)
原图凸缺陷

2.10 测试轮廓是否是凸形的#

使用方法#

  • 通过函数cv2.isContourConvex()判断轮廓是否是凸形的
  • 函数原型ret = cv2.isContourConvex( contour )
  • 参数说明
    • ret:布尔型数据。
      • True:轮廓凸形的。
      • False:轮廓不是凸形的。
    • contour轮廓

示例#

import cv2
image = cv2.imread('sample_4.png')
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(image_gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# 逼近多边形
epsilon = 0.02 * cv2.arcLength(contours[0], True)
approx = cv2.approxPolyDP(contours[0], epsilon, True)
# 凸包
hull = cv2.convexHull(contours[0])
# 检测轮廓是否是凸形
print("逼近多边形为凸形?", cv2.isContourConvex(approx))
print("凸包为凸形?", cv2.isContourConvex(hull))
逼近多边形为凸形? False
凸包为凸形? True

2.11 计算点到轮廓的距离#

使用方法#

  • 通过函数cv2.pointPolygonTest()计算点到多边形轮廓的最短距离
  • 函数原型ret = cv2.pointPolygonTest( contour, point, measureDist )
  • 参数说明
    • ret:与参数measureDist的值有关。
    • contour轮廓
    • point待判定的点
    • measureDist:布尔型数据。
      • True:计算点到轮廓的距离负数表示点在轮廓外部0表示在轮廓上正数表示点在轮廓内部
      • False:不计算距离,只返回 -101 中的一个值,分别表示在轮廓外在轮廓上在轮廓内

示例#

import cv2
image = cv2.imread('sample_4.png')
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(image_gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
hull = cv2.convexHull(contours[0])
ptA = (150, 125)
ptB = (155, 63)
ptC = (200, 60)
distA = cv2.pointPolygonTest(hull, ptA, True)
distB = cv2.pointPolygonTest(hull, ptB, True)
distC = cv2.pointPolygonTest(hull, ptC, True)
print("点A到轮廓的距离", distA)
print("点B到轮廓的距离", distB)
print("点C到轮廓的距离", distC)
cv2.polylines(image, [hull], True, (0, 255, 255), 2)
cv2.circle(image, ptA, 1, (0, 0, 255), -1)
cv2.circle(image, ptB, 1, (0, 255, 0), -1)
cv2.circle(image, ptC, 1, (255, 0, 0), -1)
cv2.imwrite("sample_4_dist.png", image)
原图点和凸包
封面
示例歌曲
示例艺术家
封面
示例歌曲
示例艺术家
0:00 / 0:00