(八)OpenCV-Python学习—轮廓查找,绘制和拟合

 针对物体轮廓,opencv还提供了一些相关的函数,来处理轮廓查找,绘制,拟合,以及计算轮廓周长和面积等,详细介绍如下:

1. 寻找和绘制轮廓

  opencv的findContours()能寻找图片中的轮廓,实现的是下面论文的算法:

    Satoshi Suzuki and others. Topological structural analysis of digitized binary images by border following. Computer Vision, Graphics, and Image Processing, 30(1):32–46, 1985.

  函数对应的参数如下: 

contours, hierarchy    =cv2.findContours(image, mode, method, offset=None)
    image: 单通道的二值图(若输入灰度图,非零像素点会被设成1,0像素点设成0)
    
    mode: 轮廓检索模式,包括RETR_EXTERNAL,RETR_LIST,RETR_CCOMP,RETR_TREE,RETR_FLOODFILL
        RETR_EXTERNAL: 只检索最外层的轮廓 (返回值会设置所有hierarchy[i][2]=hierarchy[i][3]=-1)
        RETR_LIST: 检索所有的轮廓,但不建立轮廓间的层次关系(hierarchy relationship)
        RETR_CCOMP:  检测所有的轮廓,但只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层,只有内围轮廓不再包含子轮廓时,其为内层。
        RETR_TREE:检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。
        RETR_FLOODFILL:
        
    method: 轮廓的近似方法,包括CHAIN_APPROX_NONE,CHAIN_APPROX_SIMPLE,CHAIN_APPROX_TC89_L1,CHAIN_APPROX_TC89_KCOS
        CHAIN_APPROX_NONE: 保存物体边界上所有连续的轮廓点到contours中,即点(x1,y1)和点(x2,y2),满足max(abs(x1-x2),abs(y2-y1))==1,则认为其是连续的轮廓点
        CHAIN_APPROX_SIMPLE: 仅保存轮廓的拐点信息到contours,拐点与拐点之间直线段上的信息点不予保留
        CHAIN_APPROX_TC89_L1: 采用Teh-Chin chain近似算法 
        CHAIN_APPROX_TC89_KCOS:采用Teh-Chin chain近似算法
        
    offset:偏移量,所有的轮廓信息相对于原始图像的偏移量,相当于在每一个检测出的轮廓点上加上该偏移量(在图片裁剪时比较有用)

    
返回值:
    contours:返回的轮廓点集合,一个list,每一个元素是一个轮廓,轮廓是一个N*1*2的ndarray
    hierarchy: 轮廓之间的层次关系,每一个元素对应contours中相应索引轮廓的层次关系,是一个N*4的array,hierarchy[i][0]~hierarchy[i][3]分别表示第i个轮廓的后一个轮廓,
        前一个轮廓,第一个内嵌轮廓(子轮廓),父轮廓的索引编号,如果当前轮廓没有对应的后一个轮廓、前一个轮廓、内嵌轮廓或父轮廓,则hierarchy[i][0]~hierarchy[i][3]的相应位被设置为默认值-1

  Teh-Chin chain近似算法: C-H Teh and Roland T. Chin. On the detection of dominant points on digital curves. Pattern Analysis and Machine Intelligence, IEEE Transactions on, 11(8):859–872, 1989.

 

  opencv还提供drawContours()函数来绘制检测到的轮廓,其对应参数如下:

image=cv2.drawContours(image, contours, contourIdx, color, thickness=None, lineType=None, hierarchy=None, maxLevel=None, offset=None

    image: 绘制的轮廓的图像矩阵
    contours: 所有的轮廓集合(findContours()返回值)
    contourIdx: 轮廓集合的索引,表示指定一个轮廓进行绘制;若为负数,表示绘制所有轮廓
    color: 绘制使用的颜色
    thickness:线的粗细
    lineType: 线的类型,包括FILLED,LINE_4,LINE_8,LINE_AA
    hierarchy: 轮廓的层次关系(findContours()返回值)
    maxLevel: 0表示只有指定的轮廓被绘制,1表示绘制指定的轮廓和其第一层内嵌轮廓,2表示绘制指定轮廓和其所有的内嵌轮廓(只有hierarchy部位None时,才起作用)
    offset: 绘制轮廓时的偏移量

一般轮廓寻找和拟合,能解决一些简单的目标定位问题,其大致流程如下:

  • 对图像边缘检测或阈值分割得到二值图(适当的形态学处理)

  • 利用findContours()函数寻找二值图中多个轮廓

  • 对于每一个轮廓采用boundingRect(), minAreaRect()等进行拟合得到目标位置框

findContours()函数使用示例代码及结果如下:

#coding:utf-8

import cv2

img = cv2.imread(r"D:\data\receipt.jpg")
img1 = img.copy()
img2 = img.copy()
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_gaussian = cv2.GaussianBlur(img_gray, (3, 3), 1)
edge = cv2.Canny(img_gaussian, 100, 300)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10, 1))
edge = cv2.dilate(edge, kernel, iterations=2) #横向的形态学膨胀
# thre, edge = cv2.threshold(img_gaussian, 0, 255, cv2.THRESH_OTSU+cv2.THRESH_BINARY)

#寻找轮廓
contours, hierarchy = cv2.findContours(edge, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img1, contours, -1, (0,0,255))

#轮廓拟合
num = len(contours)
for i in range(num):
    area = cv2.contourArea(contours[i], oriented=False)
    if 30 < area < 8000:  #限定轮廓的面积
        rect = cv2.boundingRect(contours[i])
        print(rect)
        cv2.rectangle(img2, (rect[0], rect[1]), (rect[0]+rect[2], rect[1]+rect[3]), (0, 255, 0))


# cv2.imshow("img_gray", img_gray)
cv2.imshow("img", img)
cv2.imshow("img_contour", img1)
cv2.imshow("img_rect", img2)
cv2.imshow("edge", edge)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.findContours()

 

2. 轮廓周长和面积

  opencv提供函数arcLength()来计算点集所围区域的周长,其参数如下:

retval=    cv2.arcLength(curve, closed)
    curve: 坐标点集,n*2的array
    closed: 点集所围区域是否时封闭的

  opencv提供函数contourArea() 来计算点集所围区域的面积,其参数如下:

retval=cv2.contourArea(contour, oriented=False)
    contour: 组成轮廓的坐标点集
    oriented: 为True时,返回的面积会带有符号,正数表示轮廓点顺时针排列,负数表示逆时针排列;为False时,返回面积的绝对值,不带符号

  arcLength() 和contourArea()函数使用示例如下:

#coding:utf-8
import cv2
import numpy as np

img = np.ones((400, 400,3), dtype=np.uint8)*255
points = np.array([[120, 120],[150, 170],[120, 220],[220,220],[200,170],[220,120]], np.int32)

length1 = cv2.arcLength(points, False)  #首尾不相连
length2 = cv2.arcLength(points, True)  #首尾相连
print(length1, length2)  #324.3223342895508 424.3223342895508

area1 = cv2.contourArea(points, oriented=True)  #返回点集排列顺序
area2 = cv2.contourArea(points, oriented=False)
print(area1, area2)  #-7500.0 7500.0


rows, cols = points.shape
for i in range(rows):
    point1 = tuple(points[i])
    point2 = tuple(points[(i+1)%rows])
    cv2.circle(img, point1, radius=2, color=(55, 55, 55), thickness=2)
    cv2.line(img, point1, point2, color=(0, 255, 0), thickness=1, lineType=cv2.LINE_4)

cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
arcLength()和contourArea()

3. 点和轮廓的位置关系

  opencv提供函数pointPolygonTest()来计算坐标点和一个轮廓的位置关系,其参数如下:

retval=cv.pointPolygonTest(contour, pt, measureDist)
    contour: 组成轮廓的点集
    pt: 坐标点
    measureDist: 为False时,返回值为1,-10(1表示点在轮廓内,-1表示点在轮廓外面,0在轮廓边缘上);为True时,返回坐标点离轮廓边缘的最小距离(带符号,分别表示轮廓内和轮廓外)

  pointPolygonTest()函数的使用示例代码如下:

#coding:utf-8
import cv2
import numpy as np

img = np.ones((400, 400,3), dtype=np.uint8)*255
points = np.array([[120, 120],[150, 170],[120, 220],[220,220],[200,170],[220,120]], np.int32)

p1 = (100, 100)
p1_ret = cv2.pointPolygonTest(points, p1, measureDist=False)
p1_ret2 = cv2.pointPolygonTest(points, p1, measureDist=True)
print(p1_ret, p1_ret2) # -1.0 -28.284271247461902

p2 = (160, 200)
p2_ret = cv2.pointPolygonTest(points, p2, measureDist=False)
p2_ret2 = cv2.pointPolygonTest(points, p2, measureDist=True)
print(p2_ret, p2_ret2)  #1.0 20.0

cv2.circle(img, p1, radius=2, color=(0, 0, 255), thickness=2)
cv2.circle(img, p2, radius=2, color=(0, 0, 255), thickness=2)

rows, cols = points.shape
for i in range(rows):
    point1 = tuple(points[i])
    point2 = tuple(points[(i+1)%rows])
    cv2.circle(img, point1, radius=2, color=(55, 55, 55), thickness=2)
    cv2.line(img, point1, point2, color=(0, 255, 0), thickness=1, lineType=cv2.LINE_4)

cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.pointPolygonTest()

 

4. 轮廓的凸包缺陷

  convexHull()函数能检测出点集的最小凸包,opencv还提供了函数convexityDefects()来检测凸包的缺陷,这里缺陷指凸包的内陷处,如下图所示:

 

   convexityDefects()函数的参数如下:

convexityDefects=cv2.convexityDefects(contour, convexhull)
    contour: 组成轮廓的点集(有序的点集)
    convexhull: convexHull()的返回值,代表组成凸包的的坐标点集索引

返回值:    
    convexityDefects:n*1*4的array,每一个元素代表一个缺陷,缺陷包括四个值:缺陷的起点,终点和最远点的索引,最远点到凸包的距离 (返回的距离值放大了256倍,所以除以256才是实际的距离)

  convexityDefects()函数使用代码和结果如下:

#coding:utf-8

import cv2
import numpy as np
img = np.ones((400, 400,3), dtype=np.uint8)*255
points = np.array([[120, 120],[150, 170],[120, 220],[220,220],[200,170],[220,120]], np.int32)
hull = cv2.convexHull(points,returnPoints=False)  # 返回索引
defects = cv2.convexityDefects(points, hull)
print(hull)
print(defects)
rows, cols = points.shape
for i in range(rows):
    point1 = tuple(points[i])
    point2 = tuple(points[(i+1)%rows])
    cv2.circle(img, point1, radius=2, color=(55, 55, 55), thickness=2)
    cv2.line(img, point1, point2, color=(0, 255, 0), thickness=1, lineType=cv2.LINE_4)
rows2, _ = hull.shape
for j in range(rows2):
    index1 = hull[j][0]
    index2 = hull[(j+1)%rows2][0]
    point1 = tuple(points[index1])
    point2 = tuple(points[index2])
    cv2.line(img, point1, point2, color=(0, 0, 255), thickness=1, lineType=cv2.LINE_4)


cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.convexityDefects()

 

 

参考:https://zhuanlan.zhihu.com/p/107257870

posted @ 2020-09-20 16:09  silence_cho  阅读(9723)  评论(0编辑  收藏  举报