本文内容是对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)