本教程将学习如下内容:
- 了解轮廓是什么
- 学习寻找轮廓、绘制轮廓等
- 介绍两个关键函数:
cv2.findContours()
、cv2.drawContours()
理论基础
轮廓可以简单地解释为连接所有连续点(沿边界)的曲线,具有相同的颜色或强度。 轮廓是形状分析和物体检测与识别的有用工具。
- 为了获得更好的准确性,请使用二值图像。因此在找到轮廓之前,应用阈值或精明的边缘检测。
- findContours函数修改源图像。如果想在找到轮廓后得到源图像,就已经将其存储到其他变量中了。
- 在OpenCV中,查找轮廓就像从黑色背景中查找白色对象。所以记住,要找到的对象应该是白色的,背景应该是黑色的。
接下来看一下如何找到二值图像的轮廓:
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import cv2
# im = cv2.imread('test.jpg')
im = cv2.imread('/data/cvdata/messi5.jpg')
imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,127,255,0)
# image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
# image, contours = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.findContours()
出现错误:
ValueError: not enough values to unpack (expected 3, got 2)
出现错误原因:如果用的是cv 4.0版本,findContours返回的是两个参数,旧版的返回的则是三个参数。
解决方法:移除第一个参数赋值:
将 image, cnts, hierarchy = cv2.findContours(diff.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
改为 cnts, hierarchy = cv2.findContours(diff.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
。
cv2.findContours()
函数中有三个参数,第一个是源图像,第二个是轮廓检索模式,第三个是轮廓近似方法。
并输出图像、轮廓和层次结构。轮廓是图像中所有轮廓的Python列表。
每个单独的轮廓都是对象边界点的(x,y)坐标的Numpy数组。
注意: 稍后我们将详细讨论第二和第三个论点以及层次结构。在此之前,代码示例中给出的值对所有图像都适用。
如何绘制轮廓
要绘制轮廓,请使用cv2.drawContours
函数。
它也可以用来绘制任何形状,只要有它的边界点。
第一个参数是源图像,第二个参数是应该作为Python列表传递的轮廓,
第三个参数是轮廓索引(在绘制单个轮廓时很有用。要绘制所有轮廓,请传递-1),其余参数是颜色、厚度等。
要绘制图像中的所有轮廓,请执行以下操作:
img = cv2.drawContours(im, contours, -1, (0,255,0), 3)
plt.imshow(img)
<matplotlib.image.AxesImage at 0x7fd47468eb70>
要绘制单个轮廓,假设第四个轮廓:
img = cv2.drawContours(im, contours, 3, (0,255,0), 3)
plt.imshow(img)
<matplotlib.image.AxesImage at 0x7fd47468d3a0>
但大多数时候,以下方法会很有用:
cnt = contours[4]
img = cv2.drawContours(im, [cnt], 0, (0,255,0), 3)
plt.imshow(img)
<matplotlib.image.AxesImage at 0x7fd47456b6b0>
注意: 最后两种方法是相同的,但当实际应用时会发现最后一种方法更有用。
轮廓近似法
这是cv2.findContours
函数中的第三个参数。它实际上意味着什么?
轮廓是具有相同强度的形状的边界,存储形状边界的(x,y)坐标。
但是它存储了所有的坐标吗?这是由该轮廓近似方法指定的。
如果传递cv2.CHAIN_APPROX_NONE
,则会存储所有边界点。
但实际上我们需要所有的分数吗?
例如找到了一条直线的轮廓。需要这条线上的所有点来代表那条线吗?
只需要这条线的两个端点。
这就是cv2.CHAIN_APPROX_SIMPLE
所做的。它删除所有冗余点并压缩轮廓,从而节省内存。
下面的矩形图像演示了这种技术。只需在轮廓阵列中的所有坐标上画一个圆(用蓝色绘制)。
第一张图片显示了用cv2.CHAIN_APPROX_NONE
得到的点(734个点),第二张图片显示的是用cv2.CHAIN_APPORX_SIMPLE
得到的一个点(只有4个点)。
节省了多少内存。