opencv函数findcontours_OpenCV 中的轮廓应用

目录:

  1. 轮廓常用函数
  2. 第一个应用
  3. 第二个应用

轮廓就是连接所有连续点(沿着边界)的曲线,具有相同的颜色或灰度值。轮廓是形状分析、物体检测和识别的有用工具。为了提高提取轮廓的精确度,需要先通过阈值处理或canny边缘检测将图像转换为二值图像。

在 OpenCV 中,寻找轮廓就像从黑色背景中寻找白色物体,所以要找到的物体应该是白色的,背景应该是黑色的。

只罗列和轮廓相关的几个函数没啥意思,通过两个例子可以对其用法有更深入的理解。

一、轮廓常用函数

1、查找轮廓

在二值图像中获取轮廓:

import cv2
im = cv2.imread('test.jpg')
imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255, 0)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

cv2.findContours() 函数中有三个参数:

  • thresh:源图像
  • cv2.RETR_TREE:表示轮廓检索模式
  • cv2.CHAIN_APPROX_SIMPLE:表示轮廓近似方法

返回值为获取到的轮廓 contourshierarchycontours为包含图像中所有轮廓的python列表(三维数组),每个轮廓是包含边界所有坐标点(x, y)的Numpy数组。hierarchy 是一个三维数组,它储存了所有等高线(轮廓)的层级结构,详情可以查看 1 和 2

轮廓是具有相同灰度值的形状的边界,即一个轮廓可以看做是一个等高线。它存储形状边界的(x, y)坐标。我们可以使用第三个参数来指定是否存储形状边界的所有坐标点。

第二个参数决定 hierarchy 采取什么样的格式输出。第三个参数可以指定两个值,如果是 cv2.CHAIN_APPROX_NONE,则存储形状边界的所有坐标点。但有时我们不需要所有的点,比如一个矩形的轮廓,我们只需要矩形的四个端点就可以了。这时我们就可以传入 cv2.CHAIN_APPROX_SIMPLE,它会移除所有冗余的点并压缩轮廓,从而节省内存。

比如下面这个例子,我们标记出矩形所有轮廓点,第一张图片是使用 cv2.CHAIN_APPROX_NONE 得到的结果,一共有734个点;第二张图片是使用 cv2.CHAIN_APPROX_SIMPLE 得到的结果,只有4个点。

opencv函数findcontours_OpenCV 中的轮廓应用_第1张图片

2、绘制轮廓

可以使用 cv2.drawContours() 函数来绘制轮廓,只要有轮廓的边界点,就可以用来绘制任何形状的轮廓。

下面是绘制轮廓的三个例子:

# To draw all the contours in an image:
cv2.drawContours(img, contours, -1, (0,255,0), 3)
# To draw an individual contour, say 4th contour:
cv2.drawContours(img, contours, 3, (0,255,0), 3)
# But most of the time, below method will be useful:
cnt = contours[4]
cv2.drawContours(img, [cnt], 0, (0,255,0), 3)

cv2.drawContours() 函数中有三个参数,第一个参数是源图像;第二个参数是应该包含轮廓的Python列表;第三个参数是列表索引,用来选择要绘制的轮廓,为-1时表示绘制所有轮廓;第四个参数是轮廓颜色、第五个参数是轮廓线的宽度,为-1时表示填充。

注意:指定轮廓颜色的值要和图像通道数一致。

3、轮廓外接矩形

轮廓外接矩形分为正矩形和最小矩形。使用 cv2.boundingRect(cnt) 来获取轮廓的外接正矩形,它不考虑物体的旋转,所以该矩形的面积一般不会最小;使用 cv.minAreaRect(cnt) 可以获取轮廓的外接最小矩形。

两者区别如下图所示,绿线表示外接正矩形,红线表示外接最小矩形:

opencv函数findcontours_OpenCV 中的轮廓应用_第2张图片

cv2.boundingRect(cnt) 的返回值包含四个值,矩形框左上角的坐标(x, y)、宽度w和高度h。

x,y,w,h = cv2.boundingRect(cnt)

cv.minAreaRect(cnt) 的返回值中还包含旋转信息,返回值信息为包括中心点坐标(x,y),宽高(w, h)和旋转角度。

然而我们绘制矩形需要矩形的四个顶点坐标,可以通过 cv.boxPoints() 来获取,如下代码所示:

rect = cv2.minAreaRect(cnt)
print(rect)  # center(x, y), (width, height), angle of rotation
box = cv2.boxPoints(rect)  # box.shape=(4, 2)
box = np.int0(box)
cv2.drawContours(img,[box],0,(0,0,255),2)

angle的范围为 (-90,-0],如上图中的角

(与矩形框最低点相连的右边的线),一个矩形逆时针旋转,
的值变化为:-0 -> -30 -> -60 -> -0,然后不断循环。

4、轮廓面积

我们可以通过 cv2.contourArea(cnt) 来获取轮廓的面积,这里的面积表示该形状内包含的像素点数量。

5、轮廓周长

通过 cv2.arcLength(cnt,True) 来绘制轮廓周长或者曲线长度,第二个参数指定形状是为闭合轮廓(True)还是普通曲线。这里的周长/长度表示该形状边界上的像素点数量。

6、轮廓近似

我们可以将一个轮廓/曲线近似为另一个顶点数量较少的轮廓/曲线,使得它们之间的距离小于或等于指定的精度,通过 cv2.approxPolyDP(cnt, epsilon, True) 来实现。第二个参数用于轮廓近似的精度,表示原始轮廓与其近似轮廓的最大距离,值越小,近似轮廓越拟合原轮廓。第三个参数指定近似轮廓是否是闭合的。

比如下面这张图,其中物体的原始轮廓(红线所示)如第一张图所,第二张图中绿线就是epsilon为原始轮廓周长的10%时的近似轮廓,第三张图中绿线就是epsilon为原始轮廓周长的1%时的近似轮廓。

opencv函数findcontours_OpenCV 中的轮廓应用_第3张图片

二、第一个应用

原图为:

opencv函数findcontours_OpenCV 中的轮廓应用_第4张图片

现在我们只想获取图中圆形的内圆,不包含黑色边缘部分,就使用我们上面介绍过的函数实现。

先读取图像并将其转换为灰度图:

import cv2
import numpy as np

img = cv2.imread("shapes.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

使用阈值处理将其转换为二值图:

ret, threshed = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)

然后就可以查找二值图中的轮廓:

contours, _ = cv2.findContours(threshed, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

二值图和查找到的轮廓如下所示:

opencv函数findcontours_OpenCV 中的轮廓应用_第5张图片

我们得到了很多轮廓,但是只想要最中间的那个内圆轮廓,所以我们需要对这些轮廓进行筛选:

# 按轮廓面积将轮廓进行升序排列
cnts = sorted(contours, key=cv2.contourArea)
H, W = img.shape[:2]
for cnt in cnts:
    # 获取轮廓外接矩形的坐标和长宽
    x,y,w,h = cv2.boundingRect(cnt)
    # 得到第一个满足条件的轮廓,就退出循环
    if cv2.contourArea(cnt) > 100 and (0.8 < w/h < 1.2) and (W/4 < x + w//2 < W*3/4) and (H/4 < y + h//2 < H*3/4):
        circle = cnt
        break

仔细观察原图,可以发现我们想获取的那个圆形轮廓面积足够大,长宽比接近1,所以满足条件:

cv2.contourArea(cnt) > 100 and (0.8 < w/h < 1.2)

而且该圆形轮廓的中心点在图像的正中间部分,所以有:

(W/4 < x + w//2 < W*3/4) and (H/4 < y + h//2 < H*3/4)

当通过满足上述条件时,我们就可以获取到我们想要的内圆轮廓。

创建轮廓掩码并与原图进行逐位与运算:

mask = np.zeros(img.shape[:2], np.uint8)
cv2.drawContours(mask, [circle], -1, 255, -1)
dst = cv2.bitwise_and(img, img, mask=mask)

得到最终结果:

opencv函数findcontours_OpenCV 中的轮廓应用_第6张图片

三、第二个应用

在这个例子中我们使用轮廓相关知识把这个人物给抠出来,类似于抠图。

opencv函数findcontours_OpenCV 中的轮廓应用_第7张图片

还是先读取图像,并转换为灰度图,还要对其进行高斯模糊,事先过滤掉不同轮廓之间的细线连接。

第一个应用我们通过阈值处理得到二值图,这里使用 Canny 边缘检测,得到二值图,然后对其进行膨胀和腐蚀操作,方便稍后提取轮廓。关于形态学操作的内容可以参考这篇文章。

import cv2
import numpy as np

img = cv2.imread('Levi.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

blur = cv2.GaussianBlur(gray, (3, 3), 0)

edges = cv2.Canny(blur, 10, 200)  # Edge detection
edges = cv2.dilate(edges, None)  # 默认(3x3)
edges = cv2.erode(edges, None)

边缘图,膨胀图和腐蚀图如下所示:

opencv函数findcontours_OpenCV 中的轮廓应用_第8张图片

接下来我们就可以获取处理后的图像中的轮廓。

先使用 cv2.findContours 获取所有轮廓,再根据轮廓面积进行降序排列,并获取到面积最大的轮廓,即图中人物的轮廓。

接下来通过 cv2.arcLength 得到轮廓周长,并将周长的0.1%作为近似轮廓的精度。然后再使用 cv2.approxPolyDP 得到近似轮廓。

# Find contours in edges(binary image)
contours, _ = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse=True)
max_contour = contours[0]

epsilon = 0.001*cv2.arcLength(max_contour, True)
approx = cv2.approxPolyDP(max_contour, epsilon, True)

然后根据近似轮廓建立一个轮廓的掩码mask,在其上绘制出最大轮廓对应的填充多边形,用于下一步抠图。

对掩码进行高斯模糊用于平滑边缘,可以消除锯齿。

mask = np.zeros(img.shape[:2], np.uint8)
cv2.drawContours(mask, [approx], -1, 255, -1)

mask = cv2.GaussianBlur(mask, (5, 5), 0)
cv2.imshow('mask', mask)

dst = cv2.bitwise_and(img, img, mask=mask)
cv2.imshow('dst', dst)

最后一步就是将掩码和原图像进行求与运算,即得到最终结果。

掩码图和结果如下所示:

opencv函数findcontours_OpenCV 中的轮廓应用_第9张图片

这里说个题外话,这里我们通过 edges = cv2.Canny(blur, 10, 200) 得到二值图,但是要手动设置两个阈值参数,不想手动设置的话可以使用如下函数来手动设置:

 def auto_canny(image, sigma=0.33):
    # compute the median of the single channel pixel intensities
    v = np.median(image)

    # construct two thresholds using the median
    lower = int(max(0, (1.0 - sigma) * v))
    upper = int(min(255, (1.0 + sigma) * v))
    edged = cv2.Canny(image, lower, upper)

    return edged

所以将 edges = cv2.Canny(blur, 10, 200) 替换为 edges = auto_canny(blur, sigma=0.33) 即可。

当需要框出目标的轮廓时,推荐使用 cv2.Canny() 边缘检测而非阈值处理生成二值图。理由:

  1. 提取边缘与阈值处理不同,边缘提取可以识别图片中目标的形状、轮廓,而不是简单的区分出图片中的高光与暗调,可以简单地提取图片中颜色分布位于中间调上的目标(中间调是指色阶值接近中值(128)的图像像素);
  2. 使用Canny边缘检测,提取结果的白点数量更少,对等高线检测的混淆因素减少;
  3. 不同边缘检测算法中,Canny边缘检测效果好(虽然对性能要求高);

参考:

边缘检测,框出物体的轮廓

几何形状识别与测量

Contour Features

remove the background of image:12


如果觉得有用,点个赞吧(ง •̀_•́)ง。

你可能感兴趣的:(叶片轮廓提取代码,平行轮廓填充代码,深入理解opencv)