引言

轮廓近似(Contour Approximation)是指对轮廓进行逼近或拟合,得到近似的轮廓。在图像处理中,轮廓表示了图像中物体的边界,因此轮廓近似可以用来描述和识别物体的形状。

多边形拟合

多边形拟合(Approximating Polygons)是将轮廓逼近成一个由直线段构成的多边形。常见的有最小包围矩形、最小包围圆形、最小二乘法椭圆等。使用 cv2.approxPolyDP() 函数可以实现多边形拟合。

轮廓近似可以减少轮廓点数,简化轮廓信息,为后续轮廓特征提取和物体识别等任务提供便利。

方法详解

approx = cv2.approxPolyDP(curve, epsilon, closed)

参数说明:

  • curve:输入轮廓。
  • epsilon:近似精度,即两个轮廓之间最大的欧式距离。该参数越小,得到的近似结果越接近实际轮廓;反之,得到的近似结果会更加粗略。
  • closed:布尔类型的参数,表示是否封闭轮廓。如果是 True,表示输入轮廓是封闭的,近似结果也会是封闭的;否则表示输入轮廓不是封闭的,近似结果也不会是封闭的。

返回值:

  • approx:近似结果,是一个ndarray 数组,包含了被近似出来的轮廓上的点的坐标。

需要注意的是,在使用 cv2.approxPolyDP() 函数之前,需要先使用 cv2.findContours() 函数对图像进行轮廓提取,从而得到输入轮廓。此外,当对轮廓进行近似时,需要根据具体场景选择合适的近似精度,这要根据实际需要来决定。

代码实操:

此次用的实验数据是这张图 :random.png ,我直接用画图软件瞎画的

承接前文轮廓检测,首先把图像的轮廓搞出来,然后对轮廓进行拟合

import cv2
import numpy as np

#写一个方法用于展示图像,展示后按任意键继续下行代码
def cv_show(title,img):
    cv2.imshow(title,img)
    cv2.waitKey(0) 
    cv2.destroyAllWindows()
    return

img = cv2.imread('random.png')

#就是上一章的内容,具体就是会输出一个轮廓图像并返回一个轮廓数据
def draw_contour(img,color,width):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#转换灰度图
    ret , binary = cv2.threshold(gray,10,255,cv2.THRESH_BINARY)#转换成二值图
    contour,hierarchy = cv2.findContours(binary, cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)#寻找轮廓,都储存在contour中
    copy = img.copy()
    resoult = cv2.drawContours(copy,contour[:],-1,color,width)
    cv_show('resoult',resoult)
    return contour


contour = draw_contour(img,(0,0,255),2)
print(type(contour))    
epsilon = 0.01*cv2.arcLength(contour[0],True)
approx = cv2.approxPolyDP(contour[0], epsilon, True)
print(type(approx),approx.shape)

res = cv2.drawContours(img.copy(),[approx],-1,(0,0,255),2)
cv_show('res',res)

在 draw_contour() 方法中,首先将彩色图像转换为灰度图像,然后通过 cv2.threshold() 函数将其转换为二值图像。接着,调用 cv2.findContours() 函数寻找图像中的轮廓,并使用 cv2.drawContours() 函数将轮廓绘制出来。最后返回得到的轮廓数据。

在主函数中,先调用 draw_contour() 方法获取图像的轮廓数据,然后通过 cv2.approxPolyDP() 函数对轮廓进行近似,并使用 cv2.drawContours() 函数绘制近似后的轮廓。最终再通过 cv_show() 方法展示结果。

需要注意的是,cv2.findContours() 函数返回的轮廓数据是一个包含所有轮廓的列表,因此在调用 cv2.approxPolyDP() 函数时需要选择要处理的轮廓,只能输入一个轮廓。这里测试图本身就一个轮廓所以直接切片[0]完事了。

结果:

边界矩形

cv2.boundingRect() 函数是 OpenCV 中常用的一个函数,用于计算轮廓的垂直边界矩形(也称包围矩形或外接矩形)。

该函数的语法如下:

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

其中,contour 表示输入的轮廓数据,可以是一个单独的轮廓或者包含多个轮廓的列表。

返回值包含四个参数,分别表示矩形左上角点的 x 坐标、y 坐标以及矩形的宽度和高度。

在这个代码中,cv2.boundingRect() 函数被用于获取输入轮廓的边界矩形的位置与大小,返回值中的 xy 分别表示边界矩形左上角点的坐标,wh 分别表示矩形的宽度和高度。这一行代码将计算得到的边界矩形的位置和大小分别赋值给 xywh 四个变量。

代码实操:

此次用的测试图是这个:contour.png 也是我随便用画图工具画的

 和多边形拟合类似,先算出边界,然后用cv2.boundingRect()计算边界矩形 

import cv2
import numpy as np

#写一个方法用于展示图像,展示后按任意键继续下行代码
def cv_show(title,img):
    cv2.imshow(title,img)
    cv2.waitKey(0) 
    cv2.destroyAllWindows()
    return

img = cv2.imread('contour.png')

def draw_contour(img,color,width):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#转换灰度图
    ret , binary = cv2.threshold(gray,10,255,cv2.THRESH_BINARY)#转换成二值图
    contour,hierarchy = cv2.findContours(binary, cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)#寻找轮廓,都储存在contour中
    copy = img.copy()
    resoult = cv2.drawContours(copy,contour[:],-1,color,width)
    cv_show('resoult',resoult)
    return contour

contour = draw_contour(img,(0,255,255),2)
#print(np.array(contour).shape)

x,y,w,h = cv2.boundingRect(contour[0])
img_rectangle = cv2.rectangle(img.copy(),(x,y),(x+w,y+h),(255,255,0),2)
cv_show('img',img_rectangle)

这段代码中,调用 draw_contour() 方法获取图像的轮廓数据,并将其传递给 cv2.boundingRect() 函数计算得到轮廓的边界矩形位置和大小。然后,使用 cv2.rectangle() 函数在图像上绘制一个矩形,矩形左上角的坐标由 xy 指定,宽度和高度由 wh 指定,颜色为 (255, 255, 0),线条宽度为 2。最后使用 cv_show() 方法展示结果。

这里使用了 img.copy() 方法来复制原始图像,然后将矩形绘制在复制后的图像上。这样做可以防止直接修改原始图像;同时,使用复制后的图像展示结果也更加规范。

边界圆形

cv2.minEnclosingCircle() 是 OpenCV 中的一个函数,用于计算二维点集的最小外接圆。

函数原型为:

center, radius = cv2.minEnclosingCircle(points)

其中,points 参数为输入的二维点集,一般是一个 numpy 数组,形状为 (N,2)center 为输出的圆心坐标,形状为 (2,)radius 为输出的圆的半径。

该函数的实现原理是通过对输入的点集进行求解来获取能够覆盖所有点集的最小圆形。在实现过程中,该函数会利用旋转卡壳(Rotating Caliper)算法来寻找最小圆。

旋转卡壳算法是一种计算二维凸包及其性质的有效算法,在计算最小外接圆时也常常被使用。算法基本思路是首先计算出点集的凸包,然后在凸包边界上旋转两个切线,分别作为圆的直径,寻找最小的圆。

需要注意的是,cv2.minEnclosingCircle() 函数仅适用于二维点集,且在输入点集不完全符合条件时,该函数求得的最小外接圆可能并不是唯一的。

代码实操

import cv2
import numpy as np

#写一个方法用于展示图像,展示后按任意键继续下行代码
def cv_show(title,img):
    cv2.imshow(title,img)
    cv2.waitKey(0) 
    cv2.destroyAllWindows()
    return

img = cv2.imread('contour.png')

def draw_contour(img,color,width):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#转换灰度图
    ret , binary = cv2.threshold(gray,10,255,cv2.THRESH_BINARY)#转换成二值图
    contour,hierarchy = cv2.findContours(binary, cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)#寻找轮廓,都储存在contour中
    copy = img.copy()
    resoult = cv2.drawContours(copy,contour[:],-1,color,width)
    cv_show('resoult',resoult)
    return contour

contour = draw_contour(img,(0,255,255),2)
print(np.array(contour).shape)

(x,y),radius = cv2.minEnclosingCircle(contour[0])
center = (int(x),int(y))
radius = int(radius)
img_circle = cv2.circle(img.copy(),center,radius,(25,0,255),2)
cv_show('img',img_circle)

使用 cv2.minEnclosingCircle() 函数计算轮廓 contour[0] 的最小外接圆,返回圆心坐标 (x, y) 和半径 radius,此方法只能输入一个边界。
将圆心坐标 (x, y) 转换为整型坐标 (int(x), int(y)),并将半径 radius 强制转换为整型。
使用 cv2.circle() 函数在原始图像的复制版本 img.copy() 上绘制一个圆形区域,圆心坐标为 (int(x), int(y)),半径为 radius,颜色为 (25, 0, 255)(BGR 格式),线条宽度为 2。
调用 cv_show() 函数展示绘制后的图像。

结果:

注意,cv2.circle()和cv2.rectangle()一样会改变输入对象,所以要copy一下。

Logo

纵情码海钱塘涌,杭州开发者创新动! 属于杭州的开发者社区!致力于为杭州地区的开发者提供学习、合作和成长的机会;同时也为企业交流招聘提供舞台!

更多推荐