【youcans 的 OpenCV 例程200篇】200.轮廓的基本属性

OpenCV 例程200篇 总目录-202206更新


【youcans 的 OpenCV 例程200篇】200.轮廓的基本属性


在对实际图像进行轮廓查找时,得到的轮廓数量很多。获取轮廓后,通常基于轮廓的特征进行筛选、识别和处理。例如,基于轮廓的周长和面积对轮廓进行筛选,然后绘制筛选的目标轮廓或其最小外接矩形。


2.4 轮廓的基本属性

2.4.1 轮廓的宽高比(Aspect Ratio)

对象的宽高比(Aspect Ratio),是指对象垂直边界矩形的宽度与高度的比值,是对象/轮廓的重要特征。

通过对象/轮廓的垂直矩形边界框,可以计算宽高比:

A s p e c t R a t i o = W i d t h ( B o u n d i n g R e c t ) H e i g h t ( B o u n d i n g R e c t ) AspectRatio = \frac{Width_{(BoundingRect)}}{Height_{(BoundingRect)}} AspectRatio=Height(BoundingRect)Width(BoundingRect)

例程:

    # 轮廓的宽高比 (Aspect Ratio)
    x, y, wv, hv = cv2.boundingRect(cnt)  # 轮廓外接垂直矩形的左上顶点 (x,y), 宽度 w, 高度 h
    aspect_ratio = round(wv/hv, 2)  # 轮廓外接垂直矩形的宽高比

注意:

  1. 在 OpenCV 中使用垂直边界矩形计算宽高比,而不是用最小边界矩形计算。
  2. 有的资料中用“长宽比”描述,但从 OpenCV 的定义应为“宽高比”。

2.4.2 轮廓的面积比(Extent)

对象的面积(Extent),是指对象面积与垂直边界矩形面积的比值。

通过轮廓面积和垂直边界矩形面积,可以计算面积比:
E x t e n t = A r e a ( O b j e c t ) A r e a ( B o u n d i n g R e c t ) Extent = \frac{Area_{(Object)}}{Area_{(BoundingRect)}} Extent=Area(BoundingRect)Area(Object)
例程:

    # 轮廓的面积比 (Extent)
    x, y, wv, hv = cv2.boundingRect(cnt)  # 轮廓外接垂直矩形的左上顶点 (x,y), 宽度 w, 高度 h
    rect_area = wv * hv  # 轮廓外接垂直矩形的面积
    cnt_area = cv2.contourArea(cnt)  # 轮廓的面积
    extent = round(cnt_area/rect_area, 2)  # 轮廓的面积比

注意:

  1. 在 OpenCV 中的使用外接垂直边界矩形计算面积比,而不是用最小矩形边界计算。
  2. 有的资料中将 Extent 译为“占空比”、“范围”,皆供参考。

2.4.3 轮廓的坚实度(Solidity)

对象的坚实度(Solidity),是指对象面积与其凸包面积的比值。

通过轮廓面积和凸包面积,可以计算坚实度:
S o l i d i t y = A r e a ( O b j e c t ) A r e a ( C o n v e x H u l l ) Solidity = \frac{Area_{(Object)}}{Area_{(ConvexHull)}} Solidity=Area(ConvexHull)Area(Object)
例程:

    # 轮廓的坚实度 (Solidity)
    cnt_area = cv2.contourArea(cnt)  # 轮廓的面积
    hull = cv2.convexHull(cnt)  # 轮廓的凸包,返回凸包顶点集
    hull_area = cv2.contourArea(hull)  # 凸包的面积
    solidity = round(cnt_area/hull_area, 2)  # 轮廓的坚实度

2.4.4 轮廓的等效直径(Equivalent diameter)

轮廓的等效直径(Equivalent diameter), 是指与轮廓面积相等的圆形的直径 。

通过轮廓面积,可以计算轮廓的等效直径:
D e q u = 4 ∗ A r e a ( O b j e c t ) / π D_{equ} = \sqrt{4 * Area_{(Object)} / {\pi}} Dequ=4Area(Object)/π

例程:

    # 轮廓的等效直径 (Equivalent diameter)
    cnt_area = cv2.contourArea(cnt)  # 轮廓的面积
    dEqu = round(np.sqrt(4*cnt_area/np.pi), 2)  # 轮廓的等效直径

注意:

  1. 轮廓的等效直径,也称为当量直径,注意不是轮廓外接圆/内接圆的直径。

2.4.5 轮廓的方向(Orientation)

轮廓的方向(Orientation), 是指物体指向的角度。

通过函数 cv2.fitEllipse() 可以得到轮廓的最优拟合椭圆,并返回椭圆的中心点、轴长和旋转角度:

  • retval = [(x,y), (a,b), ang],椭圆中心点坐标 (x,y),椭圆长轴、短轴长度 (a,b),旋转角度 angle

例程:

    # 轮廓的方向 (Orientation)
    ellipse = cv2.fitEllipse(cnt)  # 椭圆中心点 (x,y), 长轴短轴长度 (a,b), 旋转角度 ang
    angle = round(ellipse[2], 1)  # 轮廓的方向, 指椭圆长轴 a 与水平方向的夹角

注意:

  1. 旋转角度 angle 表示轮廓的方向,指椭圆长轴与水平方向的夹角,顺时针为正。

2.4.6 轮廓的掩模(Mask)

图像掩模(Mask)也常被写成“图像掩膜”,或称为掩码、掩像、模板、遮罩,有的资料中甚至译成“面具”。图像处理时,可以只对掩模区域进行处理,或者只对非掩模区域进行处理。

轮廓的掩模是轮廓区域的掩模图像,背景为黑色、轮廓区域为白色。
轮廓的掩模可以通过绘制轮廓 cv2.drawContours() 函数的内部填充功能(“CV_FILLED”)实现。

轮廓的像素点(Pixelpoints),是指轮廓区域内的所有像素点。
轮廓的像素点可以由轮廓掩模的非 0 筛选(切片)获得,如通过 Numpy 函数 np.nonzero() 或 OpenCV 函数 cv2.findNonZero() 实现。

例程:

    # 轮廓的掩模和像素点 (Mask)
    maskCnt = np.zeros(gray.shape, np.uint8)  # 背景区域置为黑色
    cv2.drawContours(maskCnt, [cnt], 0, 255, -1)  # 轮廓区域置为白色
    pixelsNP = np.transpose(np.nonzero(maskCnt))  # (15859, 2)
    pixelsCV = cv2.findNonZero(maskCnt)  # (15859, 1, 2)

注意:

  1. 通过函数 np.nonzero() 获得的像素点集形状为 (len,2),像素点的坐标格式为行-列:(y, x)。
  2. 通过函数 cv.findNonZero() 获得的像素点集形状为 (len,1,2),像素点的坐标格式为列-行:(x, y)。

2.4.7 最大值/最小值及其位置

轮廓中像素点的灰度值的最大值、最小值及其位置,可以由函数 **cv.minMaxLoc() ** 通过掩模图像获得。

函数说明:

cv.minMaxLoc(src[, mask]) → minVal, maxVal, minLoc, maxLoc

对于灰度图像或二维数组,查找最大值、最小值及其位置。

参数说明:

  • src:单通道图像或二维数组
  • mask:掩模图像,可选项,只对掩模区域查找最大值、最小值
  • minVal: 最小值,浮点数
  • maxVal: 最大值,浮点数
  • minLoc: 最小值像素的位置,像素坐标 (x, y)
  • maxLoc: 最大值像素的位置,像素坐标 (x, y)

例程:

    # 轮廓的掩模
    maskCnt = np.zeros(gray.shape, np.uint8)  # 背景区域置为黑色
    cv2.drawContours(maskCnt, [cnt], 0, 255, -1)  # 轮廓区域置为白色
    # 轮廓的最大值/最小值及其位置
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(gray, mask=maskCnt)  # 必须用灰度图像
    print(min_val, max_val, min_loc, max_loc)  # 0.0 255.0 (468, 291) (379, 357)

注意:

  1. 最大值、最小值是针对灰度图像的像素值而言的,对于多通道的彩色图像是无所谓最大值最小值的。
  2. 最大值、最小值像素点坐标的格式为列-行:(x, y)。
  3. 如果存在多个最大值/最小值的像素,只返回其中一个最大值/最小值像素点的位置。

2.4.8 灰度均值和颜色均值

轮廓中像素点的颜色均值或灰度值均值,可以由函数 **cv.mean() ** 对掩模图像计算。

函数说明:

cv.mean(src[, mask]) → retval

对于单通道或多通道图像,计算整个图像或掩模区域的像素值的均值。

参数说明:

  • src:单通道或多通道图像
  • mask:掩模图像,可选项
  • retval:4 个元素的元组,元素值为浮点数

例程:

    # 轮廓的掩模
    maskCnt = np.zeros(gray.shape, np.uint8)  # 背景区域置为黑色
    cv2.drawContours(maskCnt, [cnt], 0, 255, -1)  # 轮廓区域置为白色
    # 轮廓的灰度均值和颜色均值
    gray_mean = cv2.mean(gray, maskCnt)  # (mg, 0.0, 0.0, 0.0)
    img_mean = cv2.mean(img, maskCnt)  # (mR, mG, mB, 0.0)

注意:

  1. 函数允许 1~4 通道的图像输入,返回值是一个元组,包含 4个元素,是图像对应通道的均值。如果该通道不存在,则输出的均值为 0.0。例如输入为灰度图像时输出为 (mg, 0.0, 0.0, 0.0)。

2.4.9 极端点位置

极点是指对象的最左侧、最右侧、最顶部、最底部的点。

查找轮廓的极端点,可以通过轮廓边界点集 contours[i],对所有边界点的横坐标、纵坐标分别查找最大值、最小值及其位置。

由轮廓上下左右的极端点位置,就可以得到轮廓的垂直矩形边界框的顶点坐标和宽度高度。

例程:

    # 轮廓的极端点位置
    leftmost = tuple(cnt[cnt[:, :, 0].argmin()][0])  # cnt[:,:,0], 所有边界点的横坐标
    rightmost = tuple(cnt[cnt[:, :, 0].argmax()][0])
    topmost = tuple(cnt[cnt[:, :, 1].argmin()][0])  # cnt[:,:,1], 所有边界点的纵坐标
    bottommost = tuple(cnt[cnt[:, :, 1].argmax()][0])

例程 12.6:轮廓的基本属性

    # 12.6 轮廓的基本属性
    img = cv2.imread("../images/seagull01.png", flags=1)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 灰度图像

    # HSV 色彩空间图像分割
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)  # 将图片转换到 HSV 色彩空间
    lowerBlue, upperBlue = np.array([100, 43, 46]), np.array([124, 255, 255])  # 蓝色阈值
    segment = cv2.inRange(hsv, lowerBlue, upperBlue)  # 背景色彩图像分割
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))  # (5, 5) 结构元
    binary = cv2.dilate(cv2.bitwise_not(segment), kernel=kernel, iterations=3)  # 图像膨胀

    # 寻找二值化图中的轮廓
    # binary, contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)  # OpenCV3
    contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)  # OpenCV4~
    #  绘制全部轮廓,contourIdx=-1 绘制全部轮廓
    imgCnts = img.copy()
    imgCnts = cv2.drawContours(imgCnts, contours, -1, (255,255,255), -1)  # 绘制全部轮廓, 内部填充
    print("len(contours) =", len(contours))  # 所有轮廓的列表

    cnt = contours[1]
    # 轮廓的垂直矩形边界框
    # boundingBoxes = [cv2.boundingRect(cnt) for cnt in contours]  # 所有轮廓的外接垂直矩形
    xv, yv, wv, hv = cv2.boundingRect(cnt)  # 矩形左上顶点的坐标 x, y, 矩形宽度 w, 高度 h
    print("Vertical rectangle: (x,y)={}, (w,h)={}".format((xv, yv), (wv, hv)))
    cv2.rectangle(imgCnts, (xv,yv), (xv+wv,yv+hv), (0, 0, 255), 2)  # 绘制垂直矩形边界框

    # 轮廓的宽高比 (Aspect Ratio)
    xv, yv, wv, hv = cv2.boundingRect(cnt)  # 轮廓外接垂直矩形的左上顶点 (x,y), 宽度 w, 高度 h
    aspect_ratio = round(wv/hv, 2)  # 轮廓外接垂直矩形的宽高比
    print("Vertical rectangle: w={}, h={}".format(wv,hv))
    print("Aspect ratio:", aspect_ratio)

    # 轮廓的面积比 (Extent)
    xv, yv, wv, hv = cv2.boundingRect(cnt)  # 轮廓外接垂直矩形的左上顶点 (x,y), 宽度 w, 高度 h
    rect_area = wv * hv  # 轮廓外接垂直矩形的面积
    cnt_area = cv2.contourArea(cnt)  # 轮廓的面积
    extent = round(cnt_area/rect_area, 2)  # 轮廓的面积比
    print("Area of cnt:", cnt_area)
    print("Area of VertRect:", rect_area)
    print("Extent(area ratio):", extent)

    # 轮廓的坚实度 (Solidity)
    cnt_area = cv2.contourArea(cnt)  # 轮廓的面积
    hull = cv2.convexHull(cnt)  # 轮廓的凸包,返回凸包顶点集
    hull_area = cv2.contourArea(hull)  # 凸包的面积
    solidity = round(cnt_area/hull_area, 2)  # 轮廓的坚实度
    print("Area of cnt:", cnt_area)
    print("Area of convex hull:", hull_area)
    print("Solidity(area ratio):", solidity)

    # 轮廓的等效直径 (Equivalent diameter)
    cnt_area = cv2.contourArea(cnt)  # 轮廓的面积
    dEqu = round(np.sqrt(4*cnt_area/np.pi), 2)  # 轮廓的等效直径
    print("Area of cnt:", cnt_area)
    print("Equivalent diameter:", dEqu)

    # 轮廓的方向 (Orientation)
    ellipse = cv2.fitEllipse(cnt)  # 椭圆中心点 (x,y), 长轴短轴长度 (a,b), 旋转角度 ang
    angle = round(ellipse[2], 1)  # 轮廓的方向, 指椭圆长轴与水平方向的夹角
    print("Orientation of cnt: {}".format(angle))

    # 轮廓的掩模和像素点 (Mask)
    maskCnt = np.zeros(gray.shape, np.uint8)  # 背景区域置为黑色
    cv2.drawContours(maskCnt, [cnt], 0, 255, -1)  # 轮廓区域置为白色
    pixelsNP = np.transpose(np.nonzero(maskCnt))  # (15859, 2): (y, x)
    pixelsCV = cv2.findNonZero(maskCnt)  # (15859, 1, 2): (x, y)
    print("pixelsNP: {}, pixelsCV: {}".format(pixelsNP.shape, pixelsCV.shape))

    # 轮廓的最大值/最小值及其位置
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(gray, mask=maskCnt)  # 必须用灰度图像
    print("Minimum value is {} at Pos{}".format(min_val, min_loc))
    print("Maximum value is {} at Pos{}".format(max_val, max_loc))

    # 轮廓的灰度均值和颜色均值
    gray_mean = cv2.mean(gray, maskCnt)  # (mg, 0, 0, 0)
    img_mean = cv2.mean(img, maskCnt)  # (mR, mG, mB, 0)
    print("gray_mean: {:.1f} \nimg_mean: ({:.1f}, {:.1f}, {:.1f})"
          .format(gray_mean[0], img_mean[0], img_mean[1], img_mean[2]))

    # 轮廓的极端点位置
    leftmost = tuple(cnt[cnt[:, :, 0].argmin()][0])  # cnt[:,:,0], 所有边界点的横坐标
    rightmost = tuple(cnt[cnt[:, :, 0].argmax()][0])
    topmost = tuple(cnt[cnt[:, :, 1].argmin()][0])  # cnt[:,:,1], 所有边界点的纵坐标
    bottommost = tuple(cnt[cnt[:, :, 1].argmax()][0])
    print("Left most is {} at Pos{}".format(leftmost[0], leftmost))
    print("Right most is {} at Pos{}".format(rightmost[0], rightmost))
    print("Top most is {} at Pos{}".format(topmost[1], topmost))
    print("Bottom most is {} at Pos{}".format(bottommost[1], bottommost))
    for point in [leftmost, rightmost, topmost, bottommost]:
        cv2.circle(imgCnts, point, 5, (0, 0, 255), -1)  # 在轮廓的极端点上绘制圆点

    plt.figure(figsize=(9, 6))
    plt.subplot(131), plt.axis('off'), plt.title("Origin")
    plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    plt.subplot(132), plt.axis('off'), plt.title("Tree contour")
    plt.imshow(cv2.cvtColor(imgCnts, cv2.COLOR_BGR2RGB))
    plt.subplot(133), plt.axis('off'), plt.title("Contour mask")
    plt.imshow(cv2.cvtColor(maskCnt, cv2.COLOR_BGR2RGB))
    plt.tight_layout()
    plt.show()

运行结果:

len(contours) = 6
Vertical rectangle: (x,y)=(168, 173), (w,h)=(132, 156)
Vertical rectangle: w=132, h=156
Aspect ratio: 0.85
Area of cnt: 9302.5
Area of VertRect: 20592
Extent(area ratio): 0.45
Area of cnt: 9302.5
Area of convex hull: 12546.5
Solidity(area ratio): 0.74
Area of cnt: 9302.5
Equivalent diameter: 108.83
Orientation of cnt: 155.4
pixelsNP: (9551, 2), pixelsCV: (9551, 1, 2)
Minimum value is 0.0 at Pos(204, 179)
Maximum value is 255.0 at Pos(233, 220)
gray_mean: 111.2 
img_mean: (127.8, 113.6, 100.1)
Left most is 168 at Pos(168, 256)
Right most is 299 at Pos(299, 322)
Top most is 173 at Pos(203, 173)
Bottom most is 328 at Pos(288, 328)

在这里插入图片描述



(本节完)


版权声明:
youcans@xupt 原创作品,转载必须标注原文链接:(https://blog.csdn.net/youcans/article/details/125112262)
Copyright 2022 youcans, XUPT
Crated:2022-5-28
欢迎关注 『youcans 的 OpenCV 例程 200 篇』 系列,持续更新中
欢迎关注 『youcans 的 OpenCV学习课』 系列,持续更新中
更多内容,请见:
【OpenCV 例程200篇 总目录-202206更新】

194.寻找图像轮廓(cv.findContours)
195.绘制图像轮廓(cv.drawContours)
196.图像的矩和不变矩(cv.moments)
197.轮廓的基本特征
198.基于不变矩的形状相似性检测
199.轮廓的外接边界框
200.轮廓的基本属性

猜你喜欢

转载自blog.csdn.net/youcans/article/details/125112262