OpenCV+Python轮廓

2018-07-13  本文已影响315人  音符纸飞机

为了最好的效果,先对图像进行二值化以及边缘检测进行了解。

寻找轮廓

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)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)是孔边界的起始点

画轮廓

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

下图列出了最长轮廓的所有矩信息,每个属性具体的定义请参考上面的链接。我们需要知道的是其他几何信息如中心坐标,轮廓面积都可以根据这些矩进行计算。矩的使用场景不多,因为计算面积等简单的稽核数据有直接的方法。


Moments

轮廓面积

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

轮廓简化及凸包效果图
上一篇 下一篇

猜你喜欢

热点阅读