OpenCV图像轮廓提取与处理实战:从基础到高级应用
本文详细讲解OpenCV中图像轮廓的提取与处理方法。涵盖轮廓查找函数cv2.findContours()的参数详解、轮廓绘制cv2.drawContours()的使用,以及最小外接圆和凸包(convexHull)的计算。通过完整代码示例,演示如何从二值化图像中提取轮廓、计算几何属性并提取构成对象的所有像素点坐标,适用于图像处理入门及进阶开发者。
OpenCV图像轮廓基础与注意事项
图像轮廓
在OpenCV中,轮廓被定义为一组连续的点,这些点构成了物体边缘的边界。简单来说,轮廓是将检测到的离散边缘点连接起来形成的闭合或开放曲线。边缘检测算法虽然能识别出图像中的边缘像素,但这些像素往往是不连续的,因此需要通过轮廓提取将它们连接成有意义的几何形状。

在使用轮廓查找功能时,需注意以下关键事项: 1. 输入图像必须是二值图像。在查找轮廓前,需先进行阈值分割或边缘检测,将图像转换为黑白二值形式。 2. 查找轮廓的操作会修改原始图像数据,因此建议先复制一份原始图像用于后续绘制或显示,避免原始数据被破坏。 3. OpenCV的轮廓查找机制默认在黑色背景中寻找白色前景对象。因此,确保目标物体为白色(像素值高),背景为黑色(像素值低)是获取正确结果的前提。
核心方法
- cv2.findContours()
- cv2.drawContours()
轮廓查找与绘制核心函数
处理图像轮廓的标准流程分为两步:首先调用cv2.findContours()函数在图像中定位并提取所有轮廓,获取轮廓的几何信息;随后调用cv2.drawContours()函数,将提取到的轮廓数据绘制回图像中,以便可视化展示或进一步分析。
cv2.findContours()函数的基本语法如下: contours, hierarchy = cv2.findContours(image, mode, method) 各参数含义如下: - contours:返回的轮廓列表,每个轮廓是一个Numpy数组,包含边界点的坐标。 - hierarchy:可选返回值,表示图像的拓扑结构信息,记录了轮廓之间的层级关系(如父轮廓、子轮廓、下一个同级轮廓等)。 - image:输入的原始图像,必须为二值图像。 - mode:轮廓检索模式,指定如何提取轮廓(如只检索外部轮廓或所有嵌套轮廓)。 - method:轮廓近似方法,指定如何存储轮廓点(如精确存储所有点或仅存储关键点)。


r=cv2.drawContours(image, contours, contourIdx, color[, thickness])
cv2.drawContours()函数用于在图像上绘制轮廓,其参数说明如下: - image:目标输出图像,将在该图像上绘制轮廓。 - contours:包含所有检测到的轮廓的列表。 - contourIdx:指定绘制哪个轮廓的索引。若为-1,则绘制所有轮廓;若指定具体数字(如0、1),则绘制对应的第N个轮廓。 - color:绘制颜色,格式为BGR(蓝、绿、红)元组,例如(0, 255, 0)表示绿色。 - thickness:可选参数,指定轮廓线条的粗细程度。若设为-1,则填充轮廓内部区域。
import cv2
import numpy as np
o = cv2.imread('lena256.bmp')
gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY)#BGR-灰度
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)#二值图像
contours, hierarchy = cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
co=o.copy()#对原始图像进行绘制
r=cv2.drawContours(co,contours,-1,(0,127,127),4)#co为复制图像,轮廓会修改原始图像
cv2.imshow("original",o)
cv2.imshow("contours",r)
cv2.waitKey()

在进行轮廓处理前,通常需要进行颜色空间转换。cv2.cvtColor(input_image, flag)函数用于实现不同颜色空间之间的转换。 - input_image:需要转换的源图像。 - flag:指定的转换类型常量。 常用转换标志包括: - cv2.COLOR_BGR2GRAY:将BGR图像转换为灰度图像。 - cv2.COLOR_BGR2RGB:将BGR顺序转换为RGB顺序(常用于显示或与其他库交互)。 - cv2.COLOR_BGR2HSV:将BGR图像转换为HSV(色相、饱和度、明度)颜色空间,常用于基于颜色的分割。
最小外接圆计算实战
最小外接圆
cv2.minEnclosingCircle()函数用于计算包含对象的最小外接圆。该函数返回一个圆,其圆心坐标和半径使得该圆能够完全覆盖对象的所有点,且面积在所有可能的覆盖圆中最小。这在需要估算物体大小或进行圆形特征匹配时非常有用。
以下案例展示如何从一张包含花朵的图片中,识别并标记出位于图片中心的花朵区域。

实现代码:
import numpy as np
import cv2 as cv
img=cv.imread("image.jpg",0)
#为了显示方便,这里将图片进行缩放
x,y=img.shape
img=cv.resize(img,(y//2,x//2))
#将图片二值化,由于前景物体是黑色的,因此在二值化时采用cv.THRESH_TOZERO_INV这种方式
ret,thresh=cv.threshold(img,127,255,cv.THRESH_TOZERO_INV)
#寻找图片中的轮廓,mode=cv.RETR_EXTERNAL,这是为了寻找最外层的轮廓
im,contour,hierarchy=cv.findContours(thresh,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
#cv.minEnclosingCircle函数的参数要求是ndarray类型,因此这里将找到的
# 轮廓中的所有的点存放在一个列表中,然后使用这个列表创建数组
point_list=[]
for i in contour:
for j in i:
point_list.append(j[0])
point_array=np.array(point_list)
#使用最小外接圆函数,返回值为这个圆的圆心坐标和圆半径长度
(x,y),radius=cv.minEnclosingCircle(point_array)
#图片上的坐标均为整数,圆的半径也要求是整数,因此将它们强制转换为int类型
center=(int(x),int(y))
color=cv.cvtColor(img,cv.COLOR_GRAY2BGR)
color=cv.circle(color,center,radius=int(radius),color=(0,0,255),thickness=2)
#显示图片
cv.imshow("color",color)
cv.waitKey(0)
cv.destroyAllWindows()
程序运行结果展示:

凸包检测与性质分析
凸包(Convex Hull)
凸包与轮廓概念相似但存在区别。虽然某些情况下两者的结果可能一致,但凸包专门用于处理曲线的凸性。函数cv2.convexHull()可用于检测曲线是否存在凸性缺陷(即凹陷部分),并可选地纠正这些缺陷。一般来说,凸包形成的曲线总是向外凸出或保持平直,不会向内凹陷。在OpenCV中,使用cv2.convexHull()函数来计算轮廓的凸包。
cv2.convexHull()函数的定义如下: hull = cv2.convexHull(points, hull, clockwise, returnPoints)
该函数的参数说明如下:
points:输入参数,即需要计算凸包的轮廓点集(Numpy数组)。
hull:输出参数,通常由函数自动返回,无需手动指定。
clockwise:方向标志。若为True,凸包的点按顺时针方向排列;若为False,则按逆时针方向排列。
returnPoints:布尔值,默认为True。若为True,返回凸包上点的实际坐标;若为False,返回与凸包点对应的原始轮廓中的点索引。
沿用之前的图片案例,对代码进行适当修改以计算并绘制凸包,具体代码如下:
import numpy as np
import cv2 as cv
img=cv.imread("image.jpg",0)
#为了显示方便,这里将图片进行缩放
x,y=img.shape
img=cv.resize(img,(y//2,x//2))
#将图片二值化,由于前景物体是黑色的,因此在二值化时采用cv.THRESH_TOZERO_INV这种方式
ret,thresh=cv.threshold(img,127,255,cv.THRESH_TOZERO_INV)
#寻找图片中的轮廓,mode=cv.RETR_EXTERNAL,这是为了寻找最外层的轮廓
im,contour,hierarchy=cv.findContours(thresh,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
#cv.minEnclosingCircle函数的参数要求是ndarray类型,因此这里将找到的
# 轮廓中的所有的点存放在一个列表中,然后使用这个列表创建数组
point_list=[]
for i in contour:
for j in i:
point_list.append(j[0])
point_array=np.array(point_list)
#寻找凸包,返回值是凸包上的点
hull=cv.convexHull(point_array,returnPoints=True)
color=cv.cvtColor(img,cv.COLOR_GRAY2BGR)
#将凸包绘制出来,需要注意的是:这里需要将凸包上点的坐标写成一个
#列表传入函数cv.ploylines,否则绘制出来的只是凸包上的一系列点
color=cv.polylines(color,[hull],True,(0,0,255),2)
#显示图片
cv.imshow("color",color)
cv.waitKey(0)
cv.destroyAllWindows()
程序运行结果展示:

基于掩模的像素点提取方法
图像掩模与像素点提取
有时我们需要获取构成对象的所有像素点坐标。实现方法是:首先提取图像的所有轮廓,然后使用cv2.drawContours()函数将轮廓内部区域填充为指定颜色(如白色),生成一个掩模图像。接着,使用cv2.findNonZero()函数从掩模中提取所有非零像素点的坐标,从而得到构成对象的确切像素点集合。以下代码演示了这一过程:
import numpy as np
import cv2 as cv
img=cv.imread("image.jpg",0)
#为了显示方便,这里将图片进行缩放
x,y=img.shape
img=cv.resize(img,(y//2,x//2))
#将图片二值化,由于前景物体是黑色的,因此在二值化时采用cv.THRESH_TOZERO_INV这种方式
ret,thresh=cv.threshold(img,127,255,cv.THRESH_TOZERO_INV)
#寻找图片中的轮廓,mode=cv.RETR_EXTERNAL,这是为了寻找最外层的轮廓
im,contour,hierarchy=cv.findContours(thresh,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
#创建一个填充轮廓内像素点的画板,背景颜色为黑色,这里我们使用numpy创建一个全零的二维数组
mask=np.zeros(img.shape,dtype=np.uint8)
#将参数thickness设置为-1,这样cv.drawContours函数就会将轮廓内的像素点填充为指定的颜色
mask=cv.drawContours(mask,contour,contourIdx=-1,color=(255,255,255),thickness=-1)
#寻找mask内非零像素点,将其存放为一个numpy数组
NonZeroPoints=np.array(cv.findNonZero(mask))
#形状变换,将其改变为一个二维数组,数组的每一行存放一个非零像素点的坐标
NonZeroPoints=NonZeroPoints.reshape((-1,2))
#验证我们提取出来的像素点坐标是否正确,我们使用变量
#column和row分别存放非零像素点在图像中坐标的列数和行数
column=NonZeroPoints[:,0]
row=NonZeroPoints[:,1]
#在新的画板上将这些点绘制出来,将这些坐标对应的像素点的值设为255
mask1=np.zeros(img.shape)
mask1[row,column]=255
#显示结果
cv.imshow("mask",mask)
cv.imshow("mask1",mask1)
cv.waitKey(0)
cv.destroyAllWindows()
程序运行结果展示:

通过对比上述两幅结果图,可以清晰看到:通过掩模提取并重新绘制的像素点坐标,与原对象的几何形状完全一致,证明像素点提取正确无误。
至此,关于OpenCV图像轮廓提取与处理的示例讲解完毕。如需了解更多OpenCV轮廓相关技术,请查阅本站历史文章或继续关注后续更新。感谢大家的支持!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









