前言
本文主要记录OpenCV学习中的图像的轮廓部分相关基础知识:
*了解什么是轮廓
*学习找轮廓,绘制轮廓等
*找到轮廓的不同特征,如面积,周长,质心,边界框等
*学习提取一些常用的对象属性,如Solidity,Equivalent Diameter,Mask image,Mean Intensity等。
*凸性缺陷以及如何找到它们。
*寻找从点到多边形的最短距离
*匹配不同的形状
*了解了轮廓的层次结构,即Contours中的父子关系。
*你会看到这些函数:cv.findContours(),cv.drawContours()
一、什么是轮廓?
轮廓可以简单地解释为连接所有具有相同的颜色或强度的连续点(沿着边界)的曲线。轮廓是形状分析和物体检测和识别的很有用的工具。
- 为了更好的准确性,使用二进制图像,因此,在找到轮廓之前,应用阈值或canny边缘检测。
- 值得注意的是,从OpenCV 3.2开始,findContours()不再修改源图像,而是将修改后的图像作为三个返回参数中的第一个返回。
- 在OpenCV中,找到轮廓就像从黑色背景中找到白色物体。所以请记住,要找到的对象应该是白色,背景应该是黑色。
1.找到并绘制轮廓
代码如下(示例):
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)
# 三个参数,第一个是源图像,第二个是轮廓检索模式,第三个是轮廓的近似方法。它输出修改后的图像,显示出轮廓和层次结构。
im2, contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# 绘制轮廓
# 输入四个轮廓点
cnt = contours[4]
# 第三个参数为-1时绘制所有轮廓,其余参数是颜色,厚度等等
cv.drawContours(img, [cnt], 0, (0,255,0), 3)
# drawContours的第一个参数是源图像,第二个参数是应该作为Python列表传递的轮廓
2.轮廓特征
- 矩
图像矩可帮助你计算某些特征,如对象的质心,对象的区域等。具体定义可以查看图像矩的具体定义
函数cv.moments()给出了计算的所有矩值的字典。见下文:
import numpy as np
import cv2 as cv
img = cv.imread('star.jpg',0)
ret,thresh = cv.threshold(img,127,255,0)
# hierarchy是层次结构,在后面介绍
im2,contours,hierarchy = cv.findContours(thresh, 1, 2)
cnt = contours[0]
M = cv.moments(cnt)
print( M )
从这一刻起,你可以提取有用的数据,如面积,质心等。质心由关系给出。这可以按如下方式完成:
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
- 其他基本特征
# 轮廓区域
area = cv.contourArea(cnt)
# 轮廓周长
perimeter = cv.arcLength(cnt,True)
# 轮廓近似,epsilon是从轮廓到近似轮廓的最大距离,即准确度参数
epsilon = 0.1*cv.arcLength(cnt,True)
approx = cv.approxPolyDP(cnt,epsilon,True)
# 凸包
hull = cv.convexHull(points[, hull[, clockwise[, returnPoints]]
- points:是我们传入的轮廓。
- hull:是输出,通常我们忽略它。
- clocwise:方向标志。如果为True,则输出凸包顺时针方向。否则,它逆时针方向。
- returnPoints:默认为True。然后它返回凸包点的坐标。如果为False,则返回与凸包点对应的轮廓点的索引。
# 简单用法只需输入轮廓即可检查凸性
# 检查凸性
hull = cv.convexHull(cnt)
# 边界矩形(直角边)
x,y,w,h = cv.boundingRect(cnt)
cv.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
# 旋转矩形
rect = cv.minAreaRect(cnt)
box = cv.boxPoints(rect)
box = np.int0(box)
cv.drawContours(img,[box],0,(0,0,255),2)
# 最小外接圈,找到一个恰好完全覆盖物体的圆圈
(x,y),radius = cv.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
cv.circle(img,center,radius,(0,255,0),2
# 椭圆拟合
ellipse = cv.fitEllipse(cnt)
cv.ellipse(img,ellipse,(0,255,0),2)
# 拟合一条线
rows,cols = img.shape[:2]
[vx,vy,x,y] = cv.fitLine(cnt, cv.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
cv.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)
3.轮廓属性
# Solidity(密实比)轮廓区域与其凸包区域的比率。
area = cv.contourArea(cnt)
hull = cv.convexHull(cnt)
hull_area = cv.contourArea(hull)
solidity = float(area)/hull_area
# Mask & Pixel Points(掩模和像素点)
mask = np.zeros(imgray.shape,np.uint8)
cv.drawContours(mask,[cnt],0,255,-1)
# Numpy函数以**(行,列)格式给出坐标
pixelpoints=np.transpose(np.nonzero(mask))
# OpenCV以(行,列)**格式给出坐标
pixelpoints = cv.findNonZero(mask)
# 极点,对象的最顶部,最底部,最右侧和最左侧
leftmost = tuple(cnt[cnt[:,:,0].argmin()][0])
rightmost = tuple(cnt[cnt[:,:,0].argmax()][0])
topmost = tuple(cnt[cnt[:,:,1].argmin()][0])
bottommost = tuple(cnt[cnt[:,:,1].argmax()][0])
4.其他功能
- 凸缺陷
物体与该凸包的任何偏差都可以被认为是凸缺陷。
OpenCV附带了一个现成的函数来查找它,cv.convexityDefects()。基本函数调用如下所示:
# 必须在找到凸包时传递returnPoints = False,以便找到凸缺陷。
hull = cv.convexHull(cnt,returnPoints = False)
defects = cv.convexityDefects(cnt,hull)
- 点多边形测试
此功能可查找图像中的点与轮廓之间的最短距离。当点在轮廓外时返回负值,当点在内部时返回正值,如果点在轮廓上则返回零。
# 第三个参数是measureDist。如果为True,则查找距离。如果为False,则查找该点是在内部还是外部或在轮廓上(它分别返回+1,-1,0)。
dist = cv.pointPolygonTest(cnt,(50,50),True)
- 匹配形状
OpenCV附带了一个函数cv.matchShapes(),它使我们能够比较两个形状或两个轮廓,并返回一个显示相似性的度量。结果越小,匹配就越好。
ret = cv.matchShapes(cnt1,cnt2,1,0.0)
5.层次结构
当我们使用cv.findContours()函数在图像中找到轮廓时,我们已经传递了一个参数Contour Retrieval Mode,通常传递cv.RETR_LIST或cv.RETR_TREE可达到较好的效果。
什么是层次结构?
在某些情况下,某些形状在其他形状内,在这种情况下,我们将外部一个称为父项,将内部
项称为子项,这种关系的表示称为层次结构。
OpenCV中的层次结构表示
OpenCV中表示为四个值的数组:[Next,Previous,First_Child,Parent]
- “Next表示同一层级的下一个轮廓。”
- “Previous表示同一层级的先前轮廓。”
- “First_Child表示其第一个子轮廓。”
- “Parent表示其父轮廓的索引。”
注意:如果没有子项或父项,则该字段将被视为-1
轮廓检索模式
-
RETR_LIST
检索所有轮廓,但不创建任何父子关系 -
RETR_EXTERNAL
使用此标志,则仅返回极端外部标志。所有child轮廓都被忽略 -
RETR_CCOMP
此标志检索所有轮廓并将它们排列为***2级***层次结构。即对象的外部轮廓(即其边界)放置在层次结构-1中。对象内部的孔的轮廓(如果有的话)放在层次结构-2中。
如果其中有任何对象,则其轮廓仅再次放置在层次结构-1中。它在层次结构-2中的孔等等。
-
RETR_TREE
可以说是所谓的Mr.Perfect(bushi)-_-。它检索所有轮廓并创建完整的族层次结构列表。它甚至告诉,谁是爷爷,父,子,孙子,甚至超越… 😃。