本文内容是对Opencv官方文档的学习笔记
初识轮廓
轮廓可以简单地认为是将连续的点(连着边界)连在一起的曲线,具有相同的颜色或者灰度。轮廓在形状分析和物体的监测和识别中很有用。
为了更加准确,要使用二值化图像。在寻找轮廓之前,要进行阈值化处理或者Canny边界检测。
查找轮廓的函数会修改原始图像,如果你在找到轮廓之后还想使用原始图像的话,你需要将原始图像存储到其他变量,或者作如下处理:
img=cv2.imread('test1.jpg')#读取原始图像
draw_img=img.copy()#复制原始图像,用draw_img来画轮廓
在OpenCV中,查找轮廓就像在黑色背景中找白色物体,要找的物体应该是白色的,而背景应该是黑色的。
需要用到的函数
在二值图像中查找轮廓需要用到函数:cv2.findContours(img,mode,method)
使用时一般会这样表达:
binary,contours,hierarchy=cv2.findContours(img,mode,method)
让我们看一下等号前面的binary,contours,hierarchy代表的意思:
binary是在img中查找出的轮廓的图像。
contours为list结构的轮廓点,轮廓信息。
hierarchy为一个层级,现在还用不上。
函数内部参数:
img:原始图像,需要在此图像中查找轮廓
mode:轮廓检索模式,分为以下四种:
RETR_EXTERNAL:只检索最外面的轮廓
RETR_LIST:检索所有的轮廓,并将他们保存到一条链表当中
RETR_CCOMP:检索所有的轮廓,并将他们组织为两层,顶层是各部分的外部边界,第二层是空洞的边界
RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次(一般常用这个)
method:轮廓逼近方法
CHAIN_APPROX_NONE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(定点的序列)
CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,函数值保留他们的终点部分。
我们用下图中的矩形来演示这个技术。在轮廓列表中的每一个坐标上画一个蓝色圆圈。第一个图显示使用cv2.CHAIN_APPROX_NONE的效果,一共 734 个点。第二个图是使用cv2.CHAIN_APPROX_SIMPLE 的结果,只有 4 个点。
下图为两者之间的区别:
差别
绘制轮廓用到的函数:
cv2.drawContours(draw_img,contours,i,color,width)
让我们看一下各项参数的含义:
draw_img:为用来绘制轮廓的图像。
contours:为轮廓信息。
i:为需要绘制第几层轮廓,默认为-1,是指绘制所有轮廓。
color:绘制轮廓用到哪种颜色的线条,例如(0,0,255)为红色线条,因为opencv是BGR格式。
width:为线条的粗细程度。
代码实现
让我们看一下如何来查找轮廓并进行绘制:
import cv2
import numpy as np
img=cv2.imread('test1.jpeg')#读取原始图像
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#将原始图像转化为灰度图
ret,thresh = cv2.threshold(imgray,127,255,0)#图像阈值处理
image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)#查找轮廓
draw_img=img.copy()#复制原始图像
img1 = cv2.drawContours(draw_img, contours, -1, (0,255,0), 3)#绘制轮廓
cv2.imshow('original',img)
cv2.imshow('img1',img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
代码实现
轮廓特征
矩
图像的矩可以帮助我们计算图像的质心,面积等。
利用到的函数为cv2.moments(),会得到一个矩以一个字典的形式返回。如下:
M=cv2.moments(cnt)
print(M)
M
可以计算出对象的重心:
重心公式
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
面积和周长
cnt=contours[0]
cv2.contourArea(cnt)#轮廓面积
cv2.arcLength(cnt,True)#轮廓周长 ,True为闭合的
轮廓近似
轮廓近似是指将轮廓形状近似到另外一种由更少点组成的轮廓形状,新轮廓的点的数目由我们设定的准确度来决定。
为了帮助理解,假设我们要在一幅图像中查找一个矩形,但是由于图像的种种原因,我们不能得到一个完美的矩形,而是一个“坏形状”(如下图所示)。现在你就可以使用函数cv2.approxPolyDP(cnt,epsilon)来近似这个形状了。这个函数的第二个参数叫epsilon,它是从原始轮廓到近似轮廓的最大距离。它是一个准确度参数。选择一个好的 epsilon 对于得到满意结果非常重要。
代码实现:
import cv2
import numpy as np
img=cv2.imread('lunkuo.png')
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,127,255,0)
image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
draw_img=img.copy()
cnt=contours[0]
epsilon=0.1*cv2.arcLength(cnt,True)
approx=cv2.approxPolyDP(cnt,epsilon,True)
draw_img=cv2.drawContours(draw_img,[approx],-1,(0,0,255),2)
add_img=np.hstack((img,draw_img))
cv2.imshow('add_img',add_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2020-02-19 20-40-49.png
凸包
凸包与轮廓近似相似,但不同,虽然有些情况下它们给出的结果是一样的。函数 cv2.convexHull()可以用来检测一个曲线是否具有凸性缺陷,并能纠正缺陷。一般来说,凸性曲线总是凸出来的,至少是平的。如果有地方凹进去了就被叫做凸性缺陷。例如下图中的手。红色曲线显示了手的凸包,凸性缺陷被双箭头标出来了.
2020-02-19 20-43-38屏幕截图.png
用到的函数为:
hull = cv2.convexHull(points[, hull[, clockwise[, returnPoints]]
参数:
points: 我们要传入的轮廓
hull: 输出,通常不需要
clockwise: 方向标志。如果设置为 True,输出的凸包是顺时针方向的。否则为逆时针方向。
returnPoints: 默认值为 True。它会返回凸包上点的坐标。如果设置为 False,就会返回与凸包点对应的轮廓上的点。
几何图形拟合轮廓
直边界矩形
一个直矩形(就是没有旋转的矩形)。边界矩形的面积不是最小的。可以使用函数cv2.boundingRect() 查找得到。(x,y)为矩形左上角的坐标,(w,h)是矩形的宽和高。
x,y,w,h = cv2.boundingRect(cnt)
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
旋转的边界矩形
面积是最小的。用到的函数为cv2.minAreaRect()。返回的是一个 Box2D 结构,其中包含矩形左上角角点的坐标(x,y),矩形的宽和高(w,h),以及旋转角度。但是要绘制这个矩形需要矩形的 4 个角点,可以通过函数 cv2.boxPoints() 获得。
rect = cv.minAreaRect(cnt)
box = cv.boxPoints(rect)
box = np.int0(box)
cv.drawContours(img,[box],0,(0,0,255),2)
最小外接圆
函数 cv2.minEnclosingCircle()可以帮我们找到一个对象的外切圆。它是所有能够包括对象的圆中面积最小的一个。
(x,y),radius =cv2.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
img = cv2.circle(img,center,radius,(0,255,0),2)
椭圆拟合
使用的函数为 cv2.ellipse(),返回值其实就是旋转边界矩形的内切圆。
ellipse = cv2.fitEllipse(cnt)
img = cv2.ellipse(im,ellipse,(0,255,0),2)
直线拟合
我们可以根据一组点拟合出一条直线,同样我们也可以为图像中的白色点拟合出一条直线。
rows,cols = img.shape[:2]
[vx,vy,x,y] = cv2.fitLine(cnt, cv2.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
img = cv2.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)