寻找轮廓
processed_img, contours, hierarchy = cv2.findContours(img, retrieval_mode, contour_approximation_method)
"""
# findContours 是寻找白色像素的轮廓,即白色是前景,黑色是背景
# processed_img 获取轮廓过程中被修改过的原图像
# contours 获得的所有轮廓的数组,每个轮廓是该轮廓上所有像素点组成的数组
# hierarchy 各个轮廓之间的继承关系
# retrieval_mode: 常用:
# cv2.RETR_TREE 保留轮廓的层级结构
# cv2.RETR_EXTERNAL 只提取最外层的轮廓
# contour_approximation_method:
# cv2.CHAIN_APPROX_NONE 表示保存所有的边缘像素坐标
# cv2.CHAIN_APPROX_SIMPLE 直线的轮廓只保留直线的两个端点像素坐标
"""
算法论文:Topological Structural Analysis of Digitized Binary Images by Border Following
原理:
-
他主要介绍了两种算法,用来对数字二值图像进行拓扑分析。第一种算法是在确定二值图像边界的围绕关系,即确定外边界、孔边界以及他们的层次关系,由于这些边界和原图的区域具有一一对应关系(外边界对应像素值为1的连通区域,孔边界对应像素值为0的区域),因此我们就可以用边界来表示原图。第二种算法,是第一种算法的修改版本,本质一样,但是它只找最外面的边界。
-
也许你会问,这个算法怎么来确定外边界,孔边界以及他们的层级关系?他采用编码的思想,给不同的边界赋予不同的整数值,从而我们就可以确定它是什么边界以及层次关系。输入的二值图像即为0和1的图像,用f(i,j)表示图像的像素值。每次行扫描,遇到以下两种情况终止:
(1)f(i,j-1)=0,f(i,j)=1;//f(i,j)是外边界的起始点
(2)f(i,j)>=1,f(i,j+1)=0;//f(i,j)是孔边界的起始点
- 然后从起始点开始,标记边界上的像素。在这里分配一个唯一的标示符给新发现的边界,叫做NBD。初始时NBD=1,每次发现一个新边界加1。在这个过程中,遇到f(p,q)=1,f(p,q+1)=0时,将f(p,q)置为-NBD。什么意思呢?就是右边边界的终止点。
画轮廓
cv2.drawContours(img, contours, contour_index, color, thickness)
"""
# 在img上用color颜色thickness厚度画出第contour_index个contour
# contour_index为-1表示画出所有contour
"""
实例
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('laugh.jpg', cv2.IMREAD_GRAYSCALE)
img = cv2.bilateralFilter(img, 21, 75, 75)
laplacian = cv2.convertScaleAbs(cv2.Laplacian(img, cv2.CV_64F))
_, img = cv2.threshold(laplacian, 20, 255, cv2.THRESH_BINARY)
# 用于画轮廓的img
img_contour = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
_, contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 获取最长轮廓的长度
max_len = max(map(lambda x: len(x), contours))
# 获取最长的那个轮廓
longest_contour = list(filter(lambda x: len(x) == max_len, contours))
cv2.drawContours(img_contour, longest_contour, -1, (0, 255, 0), 2)
titles = ['Original Binary', 'contour']
imgs = [img, img_contour]
for i in range(2):
plt.subplot(1, 2, i + 1), plt.imshow(imgs[i], cmap='gray'), plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()

图像矩 Image Moments
图像矩保存了一个像素集(在这里就是contour)的一系列矩信息,用于计算该像素集的中心,面积等几何数据。
cv2.moments(contour)
以上面的实例为基础,我们看一下moments当中到底存了写什么内容
moments = cv2.moments(longest_contour[0])
下图列出了最长轮廓的所有矩信息,每个属性具体的定义请参考上面的链接。我们需要知道的是其他几何信息如中心坐标,轮廓面积都可以根据这些矩进行计算。矩的使用场景不多,因为计算面积等简单的稽核数据有直接的方法。

轮廓面积
area = cv2.contourArea(contour)
轮廓周长
perimeter = cv2.arcLength(contour, closed)
# closed: bool 是否将第一个和最后一个像素连接封闭
轮廓简化
大多数情况下,轮廓过于细节化,需要对轮廓进行简化,即减少轮廓像素的个数
approx = cv2.approxPolyDP(contour, epsilon, close)
# epsilon 简化的系数 epsilon越大,越简化
# close bool
轮廓凸包
获取轮廓的凸包
hull = cv2.convexHull(contour)
实例
继续上面的实例,对最长的轮廓进行简化
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('laugh.jpg', cv2.IMREAD_GRAYSCALE)
img = cv2.bilateralFilter(img, 21, 75, 75)
laplacian = cv2.convertScaleAbs(cv2.Laplacian(img, cv2.CV_64F))
_, img = cv2.threshold(laplacian, 20, 255, cv2.THRESH_BINARY)
_, contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
img_contour_1 = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
img_contour_2 = np.copy(img_contour_1)
img_contour_3 = np.copy(img_contour_1)
max_len = max(map(lambda x: len(x), contours))
longest_contour = list(filter(lambda x: len(x) == max_len, contours))
epsilon_1 = 0.01 * cv2.arcLength(longest_contour[0], True)
epsilon_2 = 0.02 * cv2.arcLength(longest_contour[0], True)
approx_1 = cv2.approxPolyDP(longest_contour[0], epsilon_1, True)
approx_2 = cv2.approxPolyDP(longest_contour[0], epsilon_2, True)
hull = cv2.convexHull(longest_contour[0])
cv2.drawContours(img_contour_1, list([approx_1]), -1, (0, 255, 0), 2)
cv2.drawContours(img_contour_2, list([approx_2]), -1, (0, 255, 0), 2)
cv2.drawContours(img_contour_3, list([hull]), -1, (0, 255, 0), 2)
titles = ['Original Binary', 'approx_epsilon_smaller', 'approx_epsilon_bigger', 'hull']
imgs = [img, img_contour_1, img_contour_2, img_contour_3]
for i in range(4):
plt.subplot(2, 2, i + 1), plt.imshow(imgs[i], cmap='gray'), plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()

网友评论