《数字图像处理-OpenCV/Python》第14章:边缘检测与图像轮廓
本书京东 优惠购书链接 https://item.jd.com/14098452.html
本书CSDN 独家连载专栏 https://blog.csdn.net/youcans/category_12418787.html
第 14 章 边缘检测与图像轮廓
边缘是图像的基本特征。边缘检测根据灰度的突变检测边缘,检测到的边缘通常是零散的片段,并不是连续的整体,要从图像中提取目标物体,就要将边缘像素连接构成连续闭合的轮廓。边缘主要作为图像的特征使用,而轮廓主要用来分析物体的形态。
本章内容概要
- 理解边缘检测的原理,学习使用梯度算子进行边缘检测。
- 学习使用LoG算子、DoG算子和Canny算子进行边缘检测。
- 学习查找轮廓的方法,绘制轮廓图像。
- 介绍轮廓的属性、基本参数和形状特征。
14.6 轮廓的查找与绘制
轮廓是一系列相连的像素点组成的曲线,代表物体的基本外形。轮廓常用于形状分析和物体的检测和识别。
边缘检测根据灰度的突变检测边缘,但检测到的边缘通常是零散的片段,并未构成整体。从背景中分离目标,要将边缘像素连接构成轮廓,也就是说,轮廓是连续的,边缘不一定是连续的。边缘主要作为图像的特征使用,而轮廓主要用来分析物体的形态。
OpenCV中的函数cv.findContours用于从黑色背景的二值图像中寻找轮廓。
OpenCV中的函数cv.drawContours用于在图像上绘制轮廓线或填充轮廓。绘制图像轮廓并不是显示图像,而是在原始图像上添加轮廓线。
函数原型
cv.findContours(image, mode, method[, contours, hierarchy, offset]) → contours, hierarchy
cv.drawContours(image, contours, contourIdx, color[, thickness, lineType, hierarchy, maxLevel, offset]) → image
参数说明
- image:输入图像,是8位单通道二值图像。
- mode:轮廓查找模式。
- RETR_EXTERNAL:只查找最外层的轮廓。
- RETR_LIST:查找所有轮廓,不建立层次关系。
- RETR_CCOMP:查找所有轮廓,组织为两层,顶层是外部轮廓。
- RETR_TREE:查找所有轮廓,并重建嵌套轮廓的完整层次结构。
- method:轮廓的表示方法。
- CHAIN_APPROX_NONE:输出轮廓所有的像素点(x,y)。
- CHAIN_APPROX_SIMPLE:对于水平线/垂直线/对角线,只保留线段端点。
- CHAIN_APPROX_TC89_L1:应用Teh-Chin链近似算法L1。
- CHAIN_APPROX_TC89_KCOS:应用Teh-Chin 链近似算法KCOS。
- contours:查找到的所有轮廓,是列表格式,每个轮廓以点的坐标向量表示。
- hierarchy:轮廓的层次结构,是Numpy数组,形状为(1,L,4)。
- offset:偏移量,可选项。
注意问题
(1) 查找轮廓是针对黑色背景中的白色目标而言的,以得到白色目标的轮廓。如果背景为亮色和浅色,如白纸黑字的印刷书籍,要在查找轮廓前进行反色处理。
(2) 函数将输入图像按二值图像处理,所有非0像素都会被视为1,因此必须先通过阈值分割或边缘检测获得二值图像。推荐在平滑滤波后使用边缘检测方法,可以减少白色噪点,提高轮廓检测的效率和质量。
(3) contours是一个列表(List),不是Numpy数组,而轮廓列表中的元素是Numpy数组,列表长度L是查找到的轮廓总数。
(4) contours列表中的第i个元素contours[i]是形为(k,1,2)的Numpy数组,表示第i个轮廓,k是第i个轮廓中的像素点数量。contours[i]的每一行contours[i][k,1,:]有两个元素,分别表示第i个轮廓的第k个像素点的坐标(x,y)。
注意轮廓处理函数中像素点的坐标为(x,y),与OpenCV中像素点的坐标(y,x)的次序相反。
(5) hierarchy是形为(1,L,4)的Numpy数组。第i个轮廓的层次结构为:hierarchy[0,i,:]=[Next, Previous, FirstChild, Parent]。这4个元素hierarchy[0,i,0]~hierarchy[0,i,3]分别表示轮廓i的同层下一个轮廓Next、同层前一个轮廓Previous、第一个子轮廓FirstChild和父轮廓Parent的编号,-1表示不存在。
(6) 从实际图像中查找的轮廓往往数量很多、拓扑结构复杂,可以基于轮廓的层次结构进行筛选和识别。例如,使用 h i e r a r c h y [ 0 , i , 3 ] = = − 1 hierarchy[0,i,3]==-1 hierarchy[0,i,3]==−1可以筛选没有父轮廓的最外层轮廓,使用 h i e r a r c h y [ 0 , i , 2 ] = = − 1 hierarchy[0,i,2]==-1 hierarchy[0,i,2]==−1可以筛选没有子轮廓的最内层轮廓。
(7) 轮廓是由很多像素点组成的。使用CHAIN_APPROX_NONE时,contours[i]能保存轮廓所有的像素点,可以计算轮廓长度;而使用CHAIN_APPROX_SIMPLE时,contours[i]对水平线/垂直线/对角线只保留轮廓的线段端点,可以简化轮廓描述。
(8) 在OpenCV的不同版本中,函数cv.findContours的返回值不同,使用返回值格式不当会导致程序报错。例如,在OpenCV3中函数的返回值为[image,contours,hierarchy],而在OpenCV2、OpenCV4、OpenCV5 中函数的返回值为[contours,hierarchy]。
【例程1406】查找和绘制图像轮廓
本例程用于查找和绘制图像轮廓,并基于层次结构对轮廓进行筛选。
注意:在不同OpenCV版本中,函数cv.findContours的用法不同,详见程序注释。
python
# 【1406】查找和绘制图像轮廓
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
if __name__ == '__main__':
img = cv.imread("../images/Fig1402.png", flags=1)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) # 灰度图像
_, binary = cv.threshold(gray, 127, 255, cv.THRESH_OTSU + cv.THRESH_BINARY_INV)
# 寻找二值图中的轮廓
# binary, contours, hierarchy = cv.findContours(binary, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) # OpenCV3
contours, hierarchy = cv.findContours(binary, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) # OpenCV4~
# print("len(contours): ", len(contours)) # contours 是列表,只有长度没有形状
print("hierarchy.shape: ", hierarchy.shape) # 层次结构
# 绘制全部轮廓
contourTree = img.copy() # OpenCV 某些版本会修改原始图像
contourTree = cv.drawContours(contourTree, contours, -1, (0, 0, 255), 2) # OpenCV3
# 绘制最外层轮廓和最内层轮廓
imgContour = img.copy()
for i in range(len(contours)): # 绘制第 i 个轮廓
x, y, w, h = cv.boundingRect(contours[i]) # 外接矩形
text = "{}#({},{})".format(i, x, y)
contourTree = cv.putText(contourTree, text, (x, y), cv.FONT_HERSHEY_DUPLEX, 0.8, (0,0,0))
print("i={}\tcontours[{}]:{}\thierarchy[0,{}]={}"
.format(i, i, contours[i].shape, i, hierarchy[0][i]))
if hierarchy[0,i,2]==-1: # 最内层轮廓
imgContour = cv.drawContours(imgContour, contours, i, (0,0,255), thickness=-1) # 内部填充
if hierarchy[0,i,3]==-1: # 最外层轮廓
imgContour = cv.drawContours(imgContour, contours, i, (255,255,255), thickness=5)
plt.figure(figsize=(9, 3.2))
plt.subplot(131), plt.axis('off'), plt.title("1. Original")
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.subplot(132), plt.axis('off'), plt.title("2. Contours")
plt.imshow(cv.cvtColor(contourTree, cv.COLOR_BGR2RGB))
plt.subplot(133), plt.axis('off'), plt.title("3. Selected contour")
plt.imshow(cv.cvtColor(imgContour, cv.COLOR_BGR2RGB))
plt.tight_layout()
plt.show()
运行结果:
python
len(contours): 6
hierarchy.shape: (1, 6, 4)
i=0 contours[0]:(24, 1, 2) hierarchy[0,0]=[ 1 -1 -1 -1]
i=1 contours[1]:(24, 1, 2) hierarchy[0,1]=[ 2 0 -1 -1]
i=2 contours[2]:(4, 1, 2) hierarchy[0,2]=[-1 1 3 -1]
i=3 contours[3]:(8, 1, 2) hierarchy[0,3]=[-1 -1 4 2]
i=4 contours[4]:(11, 1, 2) hierarchy[0,4]=[ 5 -1 -1 3]
i=5 contours[5]:(11, 1, 2) hierarchy[0,5]=[-1 4 -1 3]
程序说明:
(1) 运行结果,图像轮廓如图14-6所示。图14-6(1)所示为浅色背景的原始图像,进行二值处理时反色为黑色背景和白色目标。
(2) 图14-6(2)所示为在原始图像上绘制查找到的全部轮廓,并标注轮廓编号。图14-6(3)所示为在原始图像上绘制指定的轮廓,外层轮廓以白色线条绘制,内层轮廓以红色填充。
(3) contours是所有轮廓的列表,长度为6,表示查找到6个轮廓。
(4) 查找轮廓时使用CHAIN_APPROX_SIMPLE选项,对水平线/垂直线/对角线只保留线段的端点。矩形轮廓最少可以用4个端点表示,如2#轮廓只有4个像素点,但看起来像矩形的轮廓也可能会有更多顶点,如3# 轮廓有8个像素点。
(5) hierarchy的形状为(1,6,4),每行表示一个轮廓的拓扑信息。结合运行结果逐行讨论如下。
hierarchy[0,0]=[1,-1,-1,-1],表示0#轮廓的同层下一个轮廓为1#,没有同层的前一个轮廓,没有子轮廓,没有父轮廓,因此是单层轮廓。
hierarchy[0,1]=[2,0,-1,-1],表示1#轮廓的同层下一个轮廓为2#,同层前一个轮廓为0#,没有子轮廓,没有父轮廓,因此是单层轮廓。
hierarchy[0,2]=[-1,1,3,-1],表示2# 轮廓没有同层下一个轮廓,同层前一个轮廓为1#,子轮廓为3#,没有父轮廓,因此是外层轮廓。
hierarchy[0,3] =[-1,-1,4,2],表示3#轮廓没有同层下一个轮廓,没有同层前一个轮廓,子轮廓为4#,父轮廓为2#。
hierarchy[0,4]=[5,-1,-1,3],表示4#轮廓的同层下一个轮廓为5#,没有同层前一个轮廓,没有子轮廓,父轮廓为3#,因此是内层轮廓。
hierarchy[0,5] =[-1,4,-1,3],表示5#轮廓没有同层下一个轮廓,同层前一个轮廓为4#,没有子轮廓,父轮廓为3#,因此是内层轮廓。
图14-6 图像轮廓
版权声明:
youcans@xupt 原创作品,转载必须标注原文链接:(https://blog.csdn.net/youcans/article/details/138395918)
Copyright 2024 youcans, XUPT
Crated:2024-05-01
《数字图像处理-OpenCV/Python》 独家连载专栏 : https://blog.csdn.net/youcans/category_12418787.html