博主(昊虹图像算法)注:为了更好的理解这篇博文的内容,建议大家先阅读博主对OpenCV轮廓检测函数findContours()的详细介绍,链接 https://blog.csdn.net/wenhao_ir/article/details/125537919
本篇博文详细探究OpenCV的轮廓绘制函数drawContours()。
先来复习一下轮廓绘制函数drawContours()的原型和参数。
C++原型如下
void cv::drawContours( InputOutputArray image,
InputArrayOfArrays contours,
int contourIdx,
const Scalar & color,
int thickness = 1,
int lineType = LINE_8,
InputArray hierarchy = noArray(),
int maxLevel = INT_MAX,
Point offset = Point()
)
Python原型如下:
image = cv.drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]] )
OpenCV4.1.2官方文档对各参数意义的介绍如下:
使用迭代处理的典型代码如下:
int idx = 0;
for( ; idx >= 0; idx = hierarchy[idx][0] )
{
Scalar color( rand()&255, rand()&255, rand()&255 );
drawContours( dst, contours, idx, color, FILLED, 8, hierarchy );
}
可能有朋友看来上段这段话,还是不知道这段英文说得什么意思。举个例子来说吧,比如下面这幅图里的A轮廓和B轮廓,是相互连接的带孔轮廓,
如果进行填充绘制,我希望得到下面的填充绘制效果:
但实际上有可能函数会根据它内部的算法给我绘制成下面这样:
如果不想让这个函数给我自动填充绘制成这样,我们可以对每个轮廓单独进行填充绘制处理,具体来说可以对每个轮廓调用drawContours进行填充绘制,或者使用轮廓索引参数contourIdx进行迭代处理。
使用迭代处理的典型代码如下:
int idx = 0;
for( ; idx >= 0; idx = hierarchy[idx][0] )
{
Scalar color( rand()&255, rand()&255, rand()&255 );
drawContours( dst, contours, idx, color, FILLED, 8, hierarchy );
}
因为博主手上目前并没有样例来进行测试,所以上面的处理方式是博主举的例子而已,这个函数真实的处理效果可能与上面所举的例子并不一样,但大概意思就是例子中体现出的意思。
至于contourIdx为-1时的情况,一两句话说不清楚,所以就放在本文后面的内容中进行专题说明。
接下来用实例对上面的一些参数进行更深一步的认识:
以下面这幅手绘小白兔为例:
图片名字为“img_300_320.jpg”,百度网盘下载链接:https://pan.baidu.com/s/1IaJ8nrQzGuHt3RA8jbu0GQ?pwd=bjkm
从博文 https://blog.csdn.net/wenhao_ir/article/details/51798533我们知道使用函数findContours()能检测到25个轮廓,这25个轮廓的索引值分别0,1,2,…24。
我们随机抽取三个轮廓进行分别绘制,随机抽取值为4、7、16
代码如下:
# 博主微信/QQ 2487872782
# 有问题可以联系博主交流
# 有图像处理需求也请联系博主
# 图像处理技术交流QQ群 271891601
# !/usr/bin/env python
# -*- coding: utf-8 -*-
# OpenCV的版本为4.1
import numpy as np
import cv2 as cv
import sys
# image = cv.imread('F:/material/images/2022/2022-06/ring.bmp')
# image = cv.imread('F:/material/images/P0044-hand-02.jpg')
image = cv.imread('F:/material/images/2022/2022-06/img_300_320.jpg')
if image is None:
print('Error: Could not load image')
sys.exit()
# cv.imshow('Source Image', image)
# 原图像转化为灰度图
img_gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
# cv.imshow('img_gray', img_gray)
# 灰度图进行二值化处理,并不是函数findContours要求输入图像为二值图像,
# 而是函数findContours在进行轮廓提取前会把原图中的非0值全部当成1处理。
_, img_B = cv.threshold(img_gray, 71, 255, cv.THRESH_BINARY)
# cv.imshow('img_B', img_B)
# 轮廓检测
cnts, harch = cv.findContours(img_B, mode=cv.RETR_TREE, method=cv.CHAIN_APPROX_SIMPLE)
# 绘制轮廓
img_contours_4 = np.zeros((image.shape[0], image.shape[1]), dtype='uint8')
img_contours_4 = cv.drawContours(img_contours_4, cnts, 4, 255, 1, 1)
cv.imshow('img_contours_4', img_contours_4)
img_contours_7 = np.zeros((image.shape[0], image.shape[1]), dtype='uint8')
img_contours_7 = cv.drawContours(img_contours_7, cnts, 7, 255, 1, 1)
cv.imshow('img_contours_7', img_contours_7)
img_contours_16 = np.zeros((image.shape[0], image.shape[1]), dtype='uint8')
img_contours_16 = cv.drawContours(img_contours_16, cnts, 16, 255, 1, 1)
cv.imshow('img_contours_16', img_contours_16)
cv.waitKey(0)
cv.destroyAllWindows()
根据上面对参数maxLevel的介绍,再根据博文(https://blog.csdn.net/wenhao_ir/article/details/125570930)中绘制的图像轮廓的拓扑结构:
我们可以指定contourIdx的值为12,然后观察不同maxLevel情况下绘制的轮廓图:
为了便于大家进行观察分析,大家可以到博文 https://blog.csdn.net/wenhao_ir/article/details/125573892中下载查看每一个轮廓。
首先我们先看下索引值为12的轮廓是怎么样的:
当maxLevel=0时:
根据拓扑结构,此时应该绘制的是轮廓12。
img_contours = cv.drawContours(img_contours, cnts, 12, 255, 1, 1, harch, maxLevel=0)
当maxLevel=1时:
根据拓扑结构,此时应该绘制的是轮廓12、轮廓13至轮廓24。
img_contours = cv.drawContours(img_contours, cnts, 12, 255, 1, 1, harch, maxLevel=1)
运行结果如下:
轮廓13至轮廓24如下面两张截图所示:
当maxLevel=2时:
根据拓扑结构,此时应该绘制的是轮廓12、轮廓13至轮廓24、轮廓14。
img_contours = cv.drawContours(img_contours, cnts, 12, 255, 1, 1, harch, maxLevel=2)
运行结果如下:
轮廓14如下图:
当maxLevel=3时:
根据拓扑结构,此时应该绘制的轮廓还是和maxLevel=2时一样,即轮廓12、轮廓13至轮廓24、轮廓14。我们看下是不是这样:
img_contours = cv.drawContours(img_contours, cnts, 12, 255, 1, 1, harch, maxLevel=3)
可见,maxLevel=3和maxLevel=2的结果是一样的。
到这里,我们就把contourIdx的值不为-1时的情况有一个深入细致的了解了。
contourIdx的值为-1的情况就复杂了,博主对这个问题也是探索了许久才得出了结论,其间的曲折过程就不说了,直接上结论了吧。
由于是博主花时间和精力去研究探索并写出的内容,所以把这部分内部上传到了CSDN的付费下载区,价格为1.9元,链接:https://download.csdn.net/download/wenhao_ir/85896119
这部分内容的草稿链接:https://blog.csdn.net/wenhao_ir/article/details/125580624【注:这个草稿链接只有博主能访问,仅为博主查阅之方便,其他人均不能访问浏览】
看几个例子就知道效果了。
1、我们对下面这个轮廓(轮廓索引为0)进行以thickness=FILLED的绘制。
img_contours = cv.drawContours(img_contours, cnts, 0, 255, cv.FILLED, 1)
运行结果如下:
我们再对下面这个轮廓(轮廓索引为4)进行以thickness=FILLED的绘制。
img_contours = cv.drawContours(img_contours, cnts, 4, 255, cv.FILLED, 1)
光靠上面两个例子,还不足以完全明白FILLED是怎么填充的。看下面这个填充的例子:
上面这个轮廓用FILLED填充的结果如下:
通过上面这几个例子,大家就明白了当参数thickness的值为FILLED的轮廓绘制效果了吧。