OpenCV-Python——第17.3章:轮廓形状拟合(边界矩形,最小外接圆...)及性质

本文详细介绍使用OpenCV进行图像处理中的轮廓分析方法,包括边界矩形、最小外接圆、椭圆拟合、直线拟合等技术,以及如何通过轮廓性质如长宽比、Extent和Solidity来理解图像中的对象。

目录

边界矩形    最小外接圆     椭圆拟合    直线拟合    轮廓性质    综合举例


轮廓形状拟合

1 边界矩形

1.1 直边界矩形

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

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

  • array:轮廓点,常用contours[0]
  • (x,y)为矩形左上角的坐标,(w,h)是矩形的宽和高。

注:例子在最后

1.2 旋转的边界矩形

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

rect = cv2.minAreaRect(points)

  • points:轮廓点,常用contours[0]
  • rect:返回的是一个 Box2D 结构,其中包含 矩形左上角角点的坐标(x,y),矩形的宽和高(w,h),以及旋转角度。但是 要绘制这个矩形需要矩形的 4 个角点,可以通过函数 cv2.boxPoints() 获得。

2 最小外接圆

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

(x, y), radius = cv2.minEnclosingCircle(points)

  • points:轮廓点,常用contours[0]
  • (x, y):圆心
  • radius:半径

3 椭圆拟合

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

ellipse = cv2.fitEllipse(points)    或  (x,y),(a,b),angle = cv2.fitEllipse(points)

  • points:轮廓点,常用contours[0]
  • ellipse:可直接使用cv2.ellipse()画出椭圆
  • (x,y):椭圆中心
  • (a,b):长轴短轴
  • angle:旋转角度

4 直线拟合

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

[vx, vy, x, y] = cv2.fitLine(points, distType, param, reps, aeps, line)

  • points:二维点的数组
  • distType:距离类型
  • param:距离参数
  • reps:径向的精度参数
  • aeps:角度精度参数
  • line:输出直线
  • vx, vy:直线的方向
  • x, y:直线上一点

 

轮廓性质

1 长宽比

边界矩形的宽高比

2 Extent

轮廓面积与边界矩形面积的比。

3 Solidity

 轮廓面积与凸包面积的比。

4 Equivalent Diameter

 与轮廓面积相等的圆形的直径

5 极点

leftmost = tuple(cnt[cnt[:,:,0].argmin()][0])
rightmost = tuple(cnt[cnt[:,:,0].argmax()][0])
topmost = tuple(cnt[cnt[:,:,1].argmin()][0])
bottommost = tuple(cnt[cnt[:,:,1].argmax()][0])

综合举例

程序如下:

import cv2
import numpy as np
from matplotlib import pyplot as plt

font = cv2.FONT_HERSHEY_SIMPLEX  # 设置字体样式

img = cv2.imread('test21_3.jpg')
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255, 0)
image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnt = contours[0]

# 极点
img0 = img.copy()
leftmost = tuple(cnt[cnt[:, :, 0].argmin()][0])
cv2.circle(img0, leftmost, 5, [0, 0, 255], -1)
rightmost = tuple(cnt[cnt[:, :, 0].argmax()][0])
cv2.circle(img0, rightmost, 5, [0, 0, 255], -1)
topmost = tuple(cnt[cnt[:, :, 1].argmin()][0])
cv2.circle(img0, topmost, 5, [0, 0, 255], -1)
bottommost = tuple(cnt[cnt[:, :, 1].argmax()][0])
cv2.circle(img0, bottommost, 5, [0, 0, 255], -1)
text1 = 'Leftmost: ' + str(leftmost) + ' Rightmost: ' + str(rightmost)
text2 = 'Topmost: ' + str(topmost) + ' Bottommost: ' + str(bottommost)
cv2.putText(img0, text1, (10, 30), font, 0.5, (0, 255, 0), 1, cv2.LINE_AA, 0)
cv2.putText(img0, text2, (10, 60), font, 0.5, (0, 255, 0), 1, cv2.LINE_AA, 0)

# 直边界矩形拟合
img1 = img.copy()
x, y, w, h = cv2.boundingRect(cnt)
area = cv2.contourArea(cnt)
aspect_ratio = float(w)/h  # 长宽比
rect_area = w*h
extent = float(area)/rect_area  # 轮廓面积与边界矩形面积的比。
hull = cv2.convexHull(cnt)
hull_area = cv2.contourArea(hull)
solidity = float(area)/hull_area  # 轮廓面积与凸包面积的比。
cv2.rectangle(img1, (x, y), (x+w, y+h), (0, 255, 0), 2)
text1 = 'Aspect Ration: ' + str(round(aspect_ratio, 4))
text2 = 'Extent:  ' + str(round(extent, 4))
text3 = 'Solidity: ' + str(round(solidity, 4))
cv2.putText(img1, text1, (10, 30), font, 0.5, (0, 255, 0), 1, cv2.LINE_AA, 0)
cv2.putText(img1, text2, (10, 60), font, 0.5, (0, 255, 0), 1, cv2.LINE_AA, 0)
cv2.putText(img1, text3, (10, 90), font, 0.5, (0, 255, 0), 1, cv2.LINE_AA, 0)

# 最小矩形拟合
img2 = img.copy()
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)  # 获得矩形角点
area = cv2.contourArea(box)
width = rect[1][0]
height = rect[1][1]
cv2.polylines(img2, [box], True, (0, 255, 0), 3)
text1 = 'Width: ' + str(int(width)) + ' Height: ' + str(int(height))
text2 = 'Rect Area: ' + str(area)
cv2.putText(img2, text1, (10, 30), font, 0.5, (0, 255, 0), 1, cv2.LINE_AA, 0)
cv2.putText(img2, text2, (10, 60), font, 0.5, (0, 255, 0), 1, cv2.LINE_AA, 0)

# 圆拟合
img3 = img.copy()
(x, y), radius = cv2.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)
area = cv2.contourArea(cnt)
equi_diameter = np.sqrt(4*area/np.pi)
cv2.circle(img3, center, radius, (0, 255, 0), 2)
text1 = 'Center: (' + str(int(x)) + ', ' + str(int(y)) + ') '
text2 = 'Diameter: ' + str(2*radius)
cv2.putText(img3, text1, (10, 30), font, 0.5, (0, 255, 0), 1, cv2.LINE_AA, 0)
cv2.putText(img3, text2, (10, 60), font, 0.5, (0, 255, 0), 1, cv2.LINE_AA, 0)

# 椭圆拟合
img4 = img.copy()
ellipse = cv2.fitEllipse(cnt)
(x, y), (a, b), angle = cv2.fitEllipse(cnt)
cv2.ellipse(img4, ellipse, (0, 255, 0), 2)
text1 = 'x: ' + str(int(x)) + ' y: ' + str(int(y))
text2 = 'a:  ' + str(int(a)) + ' b:  ' + str(int(b))
text3 = 'angle: ' + str(round(angle, 2))
cv2.putText(img4, text1, (10, 30), font, 0.5, (0, 255, 0), 1, cv2.LINE_AA, 0)
cv2.putText(img4, text2, (10, 60), font, 0.5, (0, 255, 0), 1, cv2.LINE_AA, 0)
cv2.putText(img4, text3, (10, 90), font, 0.5, (0, 255, 0), 1, cv2.LINE_AA, 0)

# 直线拟合
img5 = img.copy()
rows, cols = img.shape[:2]
[vx, vy, x, y] = cv2.fitLine(cnt, cv2.DIST_L2, 0, 0.01, 0.01)
slope = -float(vy)/float(vx)  # 直线斜率
lefty = int((x*slope) + y)
righty = int(((x-cols)*slope)+y)
cv2.line(img5, (cols-1, righty), (0, lefty), (0, 255, 0), 2)
text1 = 'Center: (' + str(int(x)) + ', ' + str(int(y)) + ') '
text2 = 'Slope: ' + str(round(slope, 2))
cv2.putText(img5, text1, (10, 30), font, 0.5, (0, 255, 0), 1, cv2.LINE_AA, 0)
cv2.putText(img5, text2, (10, 60), font, 0.5, (0, 255, 0), 1, cv2.LINE_AA, 0)

plt.subplot(231), plt.imshow(cv2.cvtColor(img0, cv2.COLOR_BGR2RGB)), plt.title('Pole')
plt.subplot(232), plt.imshow(cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)), plt.title('Rectangle')
plt.subplot(233), plt.imshow(cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)), plt.title('Rectangle')
plt.subplot(234), plt.imshow(cv2.cvtColor(img3, cv2.COLOR_BGR2RGB)), plt.title('Circle')
plt.subplot(235), plt.imshow(cv2.cvtColor(img4, cv2.COLOR_BGR2RGB)), plt.title('Ellipse')
plt.subplot(236), plt.imshow(cv2.cvtColor(img5, cv2.COLOR_BGR2RGB)), plt.title('Line')
plt.show()

结果如下,点击图片可放大

### 如何使用 OpenCV 计算椭圆的外接矩形OpenCV 中,可以通过 `cv2.fitEllipse` 函数拟合一个椭圆到一组点上,并返回该椭圆的相关参数。为了获取这个椭圆的外接矩形,可以利用这些参数进一步计算其边界框。 以下是具体实现方法: #### 1. 拟合椭圆并提取参数 函数 `cv2.fitEllipse(points)` 可以用于拟合椭圆,其中输入是一个二维点集数组。它会返回一个元组 `(center, axes, angle)`,分别表示椭圆中心坐标、轴长度以及旋转角度[^1]。 ```python import numpy as np import cv2 # 假设 points 是一组轮廓点 points = np.array([[x1, y1], [x2, y2], ...]) # 替换为实际数据 ellipse = cv2.fitEllipse(points) (center, (width, height), angle) = ellipse ``` #### 2. 构造外接矩形 一旦得到了椭圆的信息,就可以通过构造一个围绕它的最小面积矩形来获得外接矩形。这一步可以直接调用 `cv2.boundingRect()` 或者更精确地使用 `cv2.minAreaRect()` 来得到旋转后的矩形。 对于非旋转版本(直角矩形),可采用以下代码: ```python rect = cv2.boundingRect(np.int32([points])) (x, y, w, h) = rect print(f"Bounding Rectangle: Top-left corner ({x}, {y}), Width={w}, Height={h}") ``` 如果需要考虑旋转情况,则应改用 `cv2.minAreaRect()` 方法: ```python rotated_rect = cv2.minAreaRect(points) box_points = cv2.boxPoints(rotated_rect) box_points = np.int0(box_points) print("Rotated Bounding Box Points:", box_points) ``` 上述两部分结合起来能够满足不同场景下对外接矩形的需求。 #### 完整示例代码 下面提供了一个完整的例子展示如何从给定的一系列点出发找到它们所形成形状的最佳匹配椭圆及其对应的外接矩形。 ```python import cv2 import numpy as np def draw_ellipse_and_bounding_box(image_path): img = cv2.imread(image_path, 0) _, thresh = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) contours, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2:] if not contours: print("No contours detected.") return None cnt = max(contours, key=cv2.contourArea) ellipse = cv2.fitEllipse(cnt) center, axes, angle = ellipse result_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) cv2.ellipse(result_img, ellipse, (0, 255, 0), 2) # Draw fitted ellipse rotated_rect = cv2.minAreaRect(cnt) box_points = cv2.boxPoints(rotated_rect) box_points = np.int0(box_points) cv2.drawContours(result_img,[box_points],0,(255,0,0),2) # Draw min area rectangle straight_rect = cv2.boundingRect(cnt) x,y,w,h = straight_rect cv2.rectangle(result_img,(x,y),(x+w,y+h),(0,0,255),2) # Draw bounding rectangle return result_img result = draw_ellipse_and_bounding_box('your_image.png') if result is not None: cv2.imshow('Result', result) cv2.waitKey(0) cv2.destroyAllWindows() ``` 此脚本读入一幅灰度图,在二值化之后查找最大连通域作为目标对象,接着绘制出相应的椭圆与两种类型的包围盒。 ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值