Opencv-Python学习笔记(十):轮廓特征

本篇文章接上一篇轮廓检测继续学习,本篇主要记录轮廓特征的学习。

  • 查找轮廓的不同特征,例如面积,周长,质心,边界框等
  • 将会学到大量与轮廓有关的函数。

1.矩

图像的矩可以帮助我们计算图像的质心,面积等。函数 cv2.moments() 会将计算得到的矩以一个字典的形式返回。
根据这些矩的值,我们可以计算出对象的重心:C_{x}=\frac{M_{10}}{M_{00}}C_{y}=\tfrac{M_{01}}{M_{00}}

以下图为例:


代码如下:

# -*- coding: utf-8 -*-
# @Time    : 2019/11/3 14:04
# @Author  : MMagicLoren
# @Email   : [email protected]
# @File    : 轮廓特征.py
# @Software: PyCharm
import cv2 as cv
import numpy as np


if __name__ == '__main__':
    src = cv.imread("F:/Pycharm/opencv_exercises-master/images/contours.png")  # 读入图片放进src中
    cv.namedWindow("input image", cv.WINDOW_AUTOSIZE)  # 创建窗口, 窗口尺寸自动调整
    cv.imshow("input image", src)
    gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
    ret, thresh = cv.threshold(gray, 127, 255, 0)
    contours, hierarchy = cv.findContours(thresh, 1, 2)
    draw_img = src.copy()  # drawContours函数返回值会将原图像覆盖,因此在做处理时先copy一份原图像
    res = cv.drawContours(draw_img, contours, -1, (0, 0, 255), 2)  # 颜色通道为BGR
    cv.imshow("res", res)
    cnt = contours[7]
    # 0代表六边形内部,1代表外部 2代表圆的内部,3代表外部,4代表正方形的内部,6代表外部,5代表三角形外部,7代表外部
    M = cv.moments(cnt)
    print(M)
    cx = int(M['m10'] / M['m00'])
    cy = int(M['m01'] / M['m00'])
    print(cx)
    print(cy)
    cv.waitKey(0)  # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
    cv.destroyAllWindows()

结果:

{'m00': 10070.5, 'm10': 2980077.333333333, 'm01': 1221129.1666666665, 'm20': 892455332.25, 'm11': 366961743.9583333, 'm02': 159998709.75, 'm30': 270555133863.30002, 'm21': 111636390609.56667, 'm12': 48596875569.933334, 'm03': 22062772549.95, 'mu20': 10586416.838937044, 'mu11': 5603385.321169794, 'mu02': 11926971.33740604, 'mu30': 192928572.94073486, 'mu21': 102675369.13498163, 'mu12': -109091639.64499855, 'mu03': -230841098.3263588, 'nu20': 0.10438712167286807, 'nu11': 0.055252053097849176, 'nu02': 0.11760562871541187, 'nu30': 0.01895697194783025, 'nu21': 0.010088780851672374, 'nu12': -0.010719237285439939, 'nu03': -0.02268221942803347}
295
121

轮廓的面积可以使用函数 cv2.contourArea() 计算得到,也可以使用矩( 0 阶矩),M['m00']。

    # 求六边形的面积
    area = cv.contourArea(cnt)
    print(area)
    print(M['m00'])
    

轮廓周长也被称为弧长。可以使用函数 cv2.arcLength() 计算得到。这个函数的第二参数可以用来指定对象的形状是闭合的( True),还是打开的(一条曲线)。
 

perimeter = cv.arcLength(cnt,True)

轮廓近似
将轮廓形状近似到另外一种由更少点组成的轮廓形状,新轮廓的点的数目由我们设定的准确度来决定。使用的Douglas-Peucker算法,可以到维基百科获得更多此算法的细节。为了帮助理解,假设我们要在一幅图像中查找一个矩形,但是由于图像的种种原因,我们不能得到一个完美的矩形,而是一个“坏形状”(如下图所示)。现在我们就可以使用这个功能来近似这个形状了。在这种情况下,函数的第二个参数叫epsilon,它是从原始轮廓到近似轮廓的最大距离。它是一个准确度参数。选择一个好的 epsilon 对于得到满意结果非常重要。

epsilon = 0.1*cv.arcLength(cnt,True)
approx = cv.approxPolyDP(cnt,epsilon,True)

近似的方法为:如图所示,假设一条曲线为ACB,我们想用直线进行近似,先连接AB,在曲线上找一点作AB的垂线,使得距离d最大,假设这一点为C。如果d<T(T为我们设定的阈值)代表我们可以做近似,如果d>T,就不能用一条直线做近似。

当d>T时,我们连接AC,BC,如图所示,同理,看是否可以用AC直线代替曲线,BC直线代替曲线。如果还不满足,还是用这样的方法进行逼近。

扫描二维码关注公众号,回复: 9780042 查看本文章

我们来看一下效果:

epsilon = 0.1 * cv.arcLength(cnt, True)  # 一般情况下用周长的百分比作为阈值 
    approx = cv.approxPolyDP(cnt, epsilon, True)
    draw_img = src.copy()  # drawContours函数返回值会将原图像覆盖,因此在做处理时先copy一份原图像
    res = cv.drawContours(draw_img, [approx], -1, (0, 0, 255), 2)  # 颜色通道为BGR
    cv.imshow("res", res)

当我们改变阈值时:

epsilon = 0.01 * cv.arcLength(cnt, True)  # 一般情况下用周长的百分比作为阈值
    approx = cv.approxPolyDP(cnt, epsilon, True)
    draw_img = src.copy()  # drawContours函数返回值会将原图像覆盖,因此在做处理时先copy一份原图像
    res = cv.drawContours(draw_img, [approx], -1, (0, 0, 255), 2)  # 颜色通道为BGR
    cv.imshow("res", res)

边界矩形
直边界矩形: 一个直矩形(就是没有旋转的矩形)。它不会考虑对象是否旋转。所以边界矩形的面积不是最小的。可以使用函数 cv2.boundingRect() 查找得到。( x, y)为矩形左上角的坐标,( w, h)是矩形的宽和高。
 

x, y, w, h = cv.boundingRect(cnt)
    src = cv.rectangle(src, (x, y), (x + w, y + h), (0, 255, 0), 2)
    cv.imshow('直边界矩形', src)

旋转的边界矩形: 这个边界矩形是面积最小的,因为它考虑了对象的旋转。用到的函数为 cv2.minAreaRect()。返回的是一个 Box2D 结构,其中包含矩形左上角角点的坐标( x, y),矩形的宽和高( w, h),以及旋转角度。但是要绘制这个矩形需要矩形的 4 个角点,可以通过函数 cv2.boxPoints() 获得。
 

#  直边界矩形
    x, y, w, h = cv.boundingRect(cnt)
    src = cv.rectangle(src, (x, y), (x + w, y + h), (0, 255, 0), 2)
    cv.imshow("src", src)
    #  旋转边界矩形
    rect = cv.minAreaRect(cnt)
    box = cv.boxPoints(rect)
    box = np.int0(box)
    src = cv.drawContours(src, [box], 0, (0, 0, 255), 2)
    cv.imshow("src", src)

最小外接圆
函数 cv2.minEnclosingCircle() 可以帮我们找到一个对象的外切圆。它是所有能够包括对象的圆中面积最小的一个。
 

#  最小外接圆
    (x, y), radius = cv.minEnclosingCircle(cnt)
    center = (int(x), int(y))
    radius = int(radius)
    src = cv.circle(src, center, radius, (0, 255, 0), 2)
    cv.imshow("src", src)

椭圆拟合
使用的函数为 cv2.ellipse(),返回值其实就是旋转边界矩形的内切圆。
 

#  椭圆拟合
    ellipse = cv.fitEllipse(cnt)
    src = cv.ellipse(src, ellipse, (0, 255, 0), 2)
    cv.imshow("src", src)

直线拟合
我们可以根据一组点拟合出一条直线,同样我们也可以为图像中的白色点拟合出一条直线。
 

 #  直线拟合
    rows, cols = src.shape[:2]
    [vx, vy, x, y] = cv.fitLine(cnt, cv.DIST_L2, 0, 0.01, 0.01)
    lefty = int((-x * vy / vx) + y)
    righty = int(((cols - x) * vy / vx) + y)
    src = cv.line(src, (cols - 1, righty), (0, lefty), (0, 255, 0), 2)
    cv.imshow("src", src)

完整代码:

# -*- coding: utf-8 -*-
# @Time    : 2019/11/3 14:04
# @Author  : MMagicLoren
# @Email   : [email protected]
# @File    : 轮廓特征.py
# @Software: PyCharm
import cv2 as cv
import numpy as np


if __name__ == '__main__':
    src = cv.imread("F:/Pycharm/opencv_exercises-master/images/22.png")  # 读入图片放进src中
    cv.namedWindow("input image", cv.WINDOW_AUTOSIZE)  # 创建窗口, 窗口尺寸自动调整
    cv.imshow("input image", src)
    gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
    ret, thresh = cv.threshold(gray, 127, 255, 0)
    contours, hierarchy = cv.findContours(thresh, 1, 2)
    # draw_img = src.copy()  # drawContours函数返回值会将原图像覆盖,因此在做处理时先copy一份原图像
    # res = cv.drawContours(draw_img, contours, -1, (0, 0, 255), 2)  # 颜色通道为BGR
    # cv.imshow("res", res)
    cnt = contours[0]
    # 0代表六边形内部,1代表外部 2代表圆的内部,3代表外部,4代表正方形的内部,6代表外部,5代表三角形外部,7代表外部
    M = cv.moments(cnt)
    print(M)
    #  求六边形的质心
    # cx = int(M['m10'] / M['m00'])
    # cy = int(M['m01'] / M['m00'])
    # print(cx)
    # print(cy)
    # 求六边形的面积
    # area = cv.contourArea(cnt)
    # print(area)
    # print(M['m00'])
    #  求轮廓的周长
    # perimeter = cv.arcLength(cnt, True)
    # print(perimeter)
    #  轮廓近似
    # epsilon = 0.01 * cv.arcLength(cnt, True)  # 一般情况下用周长的百分比作为阈值
    # approx = cv.approxPolyDP(cnt, epsilon, True)
    # draw_img = src.copy()  # drawContours函数返回值会将原图像覆盖,因此在做处理时先copy一份原图像
    # res = cv.drawContours(draw_img, [approx], -1, (0, 0, 255), 2)  # 颜色通道为BGR
    # cv.imshow("res", res)
    #  直边界矩形
    # x, y, w, h = cv.boundingRect(cnt)
    # src = cv.rectangle(src, (x, y), (x + w, y + h), (0, 255, 0), 2)
    # cv.imshow("src", src)
    #  旋转边界矩形
    # rect = cv.minAreaRect(cnt)
    # box = cv.boxPoints(rect)
    # box = np.int0(box)
    # src = cv.drawContours(src, [box], 0, (0, 0, 255), 2)
    # cv.imshow("src", src)
    #  最小外接圆
    # (x, y), radius = cv.minEnclosingCircle(cnt)
    # center = (int(x), int(y))
    # radius = int(radius)
    # src = cv.circle(src, center, radius, (0, 255, 0), 2)
    # cv.imshow("src", src)
    #  椭圆拟合
    # ellipse = cv.fitEllipse(cnt)
    # src = cv.ellipse(src, ellipse, (0, 255, 0), 2)
    # cv.imshow("src", src)
    #  直线拟合
    rows, cols = src.shape[:2]
    [vx, vy, x, y] = cv.fitLine(cnt, cv.DIST_L2, 0, 0.01, 0.01)
    lefty = int((-x * vy / vx) + y)
    righty = int(((cols - x) * vy / vx) + y)
    src = cv.line(src, (cols - 1, righty), (0, lefty), (0, 255, 0), 2)
    cv.imshow("src", src)
    cv.waitKey(0)  # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
    cv.destroyAllWindows()
发布了29 篇原创文章 · 获赞 83 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/l59565455/article/details/102881964
今日推荐