添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接



OpenCV 中轮廓提取函数 findContours() 实现的是这篇论文的算法:

Satoshi Suzuki and others. Topological structural analysis of digitized binary images by border following. Computer Vision, Graphics, and Image Processing, 30(1):32–46, 1985.

论文解决的是对于二值图像的轮廓提取。



轮廓

学习如何寻找并绘制轮廓。

目标

  • 了解轮廓概念
  • 寻找并绘制轮廓
  • OpenCV 函数:cv2.findContours(), cv2.drawContours()

教程

啥叫轮廓

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

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


寻找轮廓的操作一般用于二值化图,所以通常会使用阈值分割或 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)
# 寻找二值化图中的轮廓
contours, hierarchy = cv2.findContours(
    thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print(len(contours))  # 结果应该为 2

  • 参数2:轮廓的查找方式,一般使用cv2.RETR_TREE,表示提取所有的轮廓并建立轮廓间的层级。更多请参考: RetrievalModes
  • 参数3:轮廓的近似方法。比如对于一条直线,我们可以存储该直线的所有像素点,也可以只存储起点和终点。使用cv2.CHAIN_APPROX_SIMPLE就表示用尽可能少的像素点表示轮廓。更多请参考: ContourApproximationModes
  • 简便起见,这两个参数也可以直接用真值3和2表示。

函数有 2 个返回值,hierarchy 是轮廓间的层级关系(番外篇:轮廓层级),暂时不用理会。我们主要看 contours,它就是找到的轮廓了,以数组形式存储,记录了每条轮廓的所有像素点的坐标 (x,y)。

绘制轮廓

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

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

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

经验之谈:很多人画图时明明用了彩色,但没有效果,请检查你是在哪个图上画,画在灰度图和二值图上显然是没有彩色的(⊙o⊙)。

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

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

小结

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



轮廓特征

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

目标

  • 计算物体的周长、面积、质心、最小外接矩形等
  • OpenCV 函数:cv2.contourArea(), cv2.arcLength(), cv2.approxPolyDP() 等

教程

在计算轮廓特征之前,我们先用上一节的代码把轮廓找到:

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)
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

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

如果统计二值图中像素点个数,应尽量避免循环,可以使用cv2.countNonZero(),更加高效。

轮廓周长

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

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

图像矩

矩可以理解为图像的各类几何特征,详情请参考:[Image Moments]

M = cv2.moments(cnt)

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

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

外接矩形

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

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)

拟合椭圆

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

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

形状匹配

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

img = cv2.imread('shapes.jpg', 0)
_, thresh = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
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 是匹配方法,详情可参考:ShapeMatchModes
  • 参数 4 是 OpenCV 的预留参数,暂时没有实现,可以不用理会。

形状匹配是通过图像的Hu矩来实现的(cv2.HuMoments()),大家如果感兴趣,可以参考:Hu-Moments

练习

  • 前面我们是对图片中的数字3进行轮廓特征计算的,大家换成数字1看看。
  • (选做)用形状匹配比较两个字母或数字(这相当于很简单的一个OCR噢)。

小结

常用的轮廓特征:

  • cv2.contourArea() 算面积,cv2.arcLength() 算周长,cv2.boundingRect() 算外接矩。
  • cv2.minAreaRect() 算最小外接矩,cv2.minEnclosingCircle() 算最小外接圆。
  • cv2.matchShapes() 进行形状匹配。

接口文档

引用














reference


https://zhuanlan.zhihu.com/p/107257870