学习如何寻找并绘制轮廓

学习计算轮廓特征,如面积、周长、最小外接矩形

目录

目标

轮廓

寻找轮廓

绘制轮廓

轮廓特征

轮廓面积

轮廓周长

图像矩 

外接矩形

最小外接圆 

拟合椭圆

形状匹配

练习


小结


目标

1.了解轮廓概念

2.寻找并绘制轮廓

3.OpenCV函数:cv2.findContours()cv2.drawContours()

4.计算物体的周长、面积、质心、最小外接矩形等

5.OpenCV函数:cv2.contourArea()cv2.arcLength()cv2.approxPolyDP()

轮廓

轮廓是一系列相连的点组成的曲线,代表了物体的基本外形。

谈起轮廓不免想到边缘,它们确实很像。简单的说,轮廓是连续的,边缘并不全都连续(下图)。其实边缘主要是作为图像的特征使用,比如可以用边缘特征可以区分脸和手,而轮廓主要用来分析物体的形态,比如物体的周长和面积等,可以说边缘包括轮廓

opencv 识别不规则形状 opencv识别简单物体的形状_opencv

寻找轮廓的操作一般用于二值化图,所以通常会使用阈值分割或Canny边缘检测先得到二值图 

注:寻找轮廓是针对白色物体的,一定要保证物体是白色,而背景是黑色,不然很多人在寻找轮廓时会找到图片最外面的一个框

寻找轮廓

使用cv2.findContours()寻找轮廓:

import cv2

img = cv2.imread('handwriting.jpg')
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

# 寻找二值化图中的轮廓
image, contours, hierarchy = cv2.findContours(
    thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print(len(contours))  # 结果应该为2

参数2:轮廓的查找方式,一般使用cv2.RETR_TREE,表示提取所有的轮廓并建立轮廓间的层级。更多请参考:OpenCV: Structural Analysis and Shape Descriptors

参数3:轮廓的近似方法。比如对于一条直线,我们可以存储该直线的所有像素点,也可以只存储起点和终点。使用cv2.CHAIN_APPROX_SIMPLE就表示用尽可能少的像素点表示轮廓。更多请参考:OpenCV: Structural Analysis and Shape Descriptors

简便起见,这两个参数也可以直接用真值3和2表示 

函数有3个返回值,image还是原来的二值化图片,hierarchy是轮廓间的层级关系,暂时不用理会。主要看contours,它就是找到的轮廓了,以数组形式存储,记录了每条轮廓的所有像素点的坐标(x,y)

opencv 识别不规则形状 opencv识别简单物体的形状_拟合_02

 

绘制轮廓

轮廓找出来后,为了方便观看,可以像前面图中那样用红色画出来:cv2.drawContours()

cv2.drawContours(img, contours, -1, (0, 0, 255), 2)

其中参数2就是得到的contours,参数3表示要绘制哪一条轮廓,-1表示绘制所有轮廓,参数4是颜色(B/G/R通道,所以(0,0,255)表示红色),参数5是线宽,之前在绘制图形中介绍过 

一般情况下,会首先获得要操作的轮廓,再进行轮廓绘制及分析:

cnt = contours[1]
cv2.drawContours(img, [cnt], 0, (0, 0, 255), 2)

 

轮廓特征

在计算轮廓特征之前,先把轮廓找到:

opencv 识别不规则形状 opencv识别简单物体的形状_opencv 识别不规则形状_03

import cv2
import numpy as np

img = cv2.imread('handwriting.jpg', 0)
_, thresh = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
image, contours, hierarchy = cv2.findContours(thresh, 3, 2)

# 以数字3的轮廓为例
cnt = contours[0]

为了便于绘制,创建出两幅彩色图,并把轮廓画在第一幅图上:

img_color1 = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
img_color2 = np.copy(img_color1)
cv2.drawContours(img_color1, [cnt], 0, (0, 0, 255), 2)

轮廓面积

area = cv2.contourArea(cnt)  # 4386.5

注意轮廓特征计算的结果并不等同于像素点的个数,而是根据几何方法算出来的,所以有小数 

轮廓周长

perimeter = cv2.arcLength(cnt, True)  # 585.7

参数2表示轮廓是否封闭,显然我们的轮廓是封闭的,所以是True

图像矩 

矩可以理解为图像的各类几何特征,

M = cv2.moments(cnt)

M中包含了很多轮廓的特征信息,比如M['m00']表示轮廓面积,与前面cv2.contourArea()计算结果是一样的。质心也可以用它来算: 

cx, cy = M['m10'] / M['m00'], M['m01'] / M['m00']  # (205, 281)

外接矩形

形状的外接矩形有两种,如下图,绿色的叫外接矩形,表示不考虑旋转并且能包含整个轮廓的矩形。蓝色的叫最小外接矩,考虑了旋转:

opencv 识别不规则形状 opencv识别简单物体的形状_拟合_04

x, y, w, h = cv2.boundingRect(cnt)  # 外接矩形
cv2.rectangle(img_color1, (x, y), (x + w, y + h), (0, 255, 0), 2)
rect = cv2.minAreaRect(cnt)  # 最小外接矩形
box = np.int0(cv2.boxPoints(rect))  # 矩形的四个角点取整
cv2.drawContours(img_color1, [box], 0, (255, 0, 0), 2)

其中np.int0(x)是把x取整的操作,比如377.93就会变成377,也可以用x.astype(np.int)

最小外接圆 

外接圆跟外接矩形一样,找到一个能包围物体的最小圆:

(x, y), radius = cv2.minEnclosingCircle(cnt)
(x, y, radius) = np.int0((x, y, radius))  # 圆心和半径取整
cv2.circle(img_color2, (x, y), radius, (0, 0, 255), 2)

 

opencv 识别不规则形状 opencv识别简单物体的形状_OpenCV_05

拟合椭圆

我们可以用得到的轮廓拟合出一个椭圆:

ellipse = cv2.fitEllipse(cnt)
cv2.ellipse(img_color2, ellipse, (255, 255, 0), 2)

opencv 识别不规则形状 opencv识别简单物体的形状_opencv 识别不规则形状_06

形状匹配

cv2.matchShapes()可以检测两个形状之间的相似度,返回值越小,越相似。先读入下面这张图片:

opencv 识别不规则形状 opencv识别简单物体的形状_取整_07

img = cv2.imread('shapes.jpg', 0)
_, thresh = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
image, contours, hierarchy = cv2.findContours(thresh, 3, 2)
img_color = cv2.cvtColor(thresh, cv2.COLOR_GRAY2BGR)  # 用于绘制的彩色图

图中有3条轮廓,我们用A/B/C表示: 

cnt_a, cnt_b, cnt_c = contours[0], contours[1], contours[2]
print(cv2.matchShapes(cnt_b, cnt_b, 1, 0.0))  # 0.0
print(cv2.matchShapes(cnt_b, cnt_c, 1, 0.0))  # 2.17e-05
print(cv2.matchShapes(cnt_b, cnt_a, 1, 0.0))  # 0.418

可以看到BC相似程度比AB高很多,并且图形的旋转或缩放并没有影响。

其中,参数3是匹配方法,详情可参考:OpenCV: Structural Analysis and Shape Descriptors 

参数4是OpenCV的预留参数,暂时没有实现,可以不用理会

形状匹配是通过图像的Hu矩来实现的cv2.HuMoments()

练习

前面是对图片中的数字3进行轮廓特征计算的,换成数字1

opencv 识别不规则形状 opencv识别简单物体的形状_拟合_08

opencv 识别不规则形状 opencv识别简单物体的形状_拟合_09

opencv 识别不规则形状 opencv识别简单物体的形状_opencv 识别不规则形状_10

import cv2
import numpy as np

# 载入手写数字图片
img = cv2.imread('handwriting.jpg', 0)
_, thresh = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
image, contours, hierarchy = cv2.findContours(thresh, 3, 2)

# 创建出两幅彩色图用于绘制
img_color1 = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
img_color2 = np.copy(img_color1)

# 计算数字1的轮廓特征
cnt = contours[1]
cv2.drawContours(img_color1, [cnt], 0, (0, 0, 255), 2)

# 1.轮廓面积
area = cv2.contourArea(cnt)  # 6289.5
print(area)


# 2.轮廓周长
perimeter = cv2.arcLength(cnt, True)  # 527.4041
print(perimeter)


# 3.图像矩
M = cv2.moments(cnt)
print(M)
print(M['m00'])  # 同前面的面积:6289.5
cx, cy = M['m10'] / M['m00'], M['m01'] / M['m00']  # 质心
print(cx, cy)

# 4.图像外接矩形和最小外接矩形
x, y, w, h = cv2.boundingRect(cnt)  # 外接矩形
cv2.rectangle(img_color1, (x, y), (x + w, y + h), (0, 255, 0), 2)

rect = cv2.minAreaRect(cnt)  # 最小外接矩形
box = np.int0(cv2.boxPoints(rect))  # 矩形的四个角点并取整
cv2.drawContours(img_color1, [box], 0, (255, 0, 0), 2)

cv2.imshow('contours', img_color1)
cv2.waitKey(0)


# 5.最小外接圆
(x, y), radius = cv2.minEnclosingCircle(cnt)
(x, y, radius) = map(int, (x, y, radius))  # 这也是取整的一种方式
cv2.circle(img_color2, (x, y), radius, (0, 0, 255), 2)


# 6.拟合椭圆
ellipse = cv2.fitEllipse(cnt)
cv2.ellipse(img_color2, ellipse, (0, 255, 0), 2)

cv2.imshow('contours2', img_color2)
cv2.waitKey(0)

小结

1.轮廓特征非常有用,使用cv2.findContours()寻找轮廓,cv2.drawContours()绘制轮廓 

2.常用的轮廓特征:

cv2.contourArea()算面积,cv2.arcLength()算周长,cv2.boundingRect()算外接矩

cv2.minAreaRect()算最小外接矩,cv2.minEnclosingCircle()算最小外接圆

cv2.matchShapes()进行形状匹配