opencv-python图像轮廓
本章节介绍图像轮廓查找和绘制,图像轮廓的多边形逼近,凸包和外接矩形等。
图像轮廓是具有相同颜色或灰度的连续点的曲线,轮廓在形状分析和物体的检测和识别中很有用。
为了检测的准确性,需要先对图形进行二值化或canny操作。
提取轮廓时会修改原图像,如果要继续使用原图像,应该先把原图像存入其他变量中。
1 查找并绘制轮廓
opencv中查找轮廓的函数
findContours(image, mode, method[, contours[, hierarchy[, offset]]]) -> image, contours, hierarchy
参数说明:
mode 查找轮廓的模式:
RETR_EXTERNAL=0,只检测外围轮廓;
RETR_LIST=1,检测的轮廓不建立等级关系,检测所有轮廓(常用);
RETR_CCOMP=2,每层最多两级,从小到大,从里到外;
RETR_TREE=3,按照树型存储轮廓,层级从右到左,从外到内。
method 轮廓近似方法也叫 ApproximationMode:
CHAIN_APPROX_NONE,保留轮廓上的所有点;
CHAIN_APPROX_SIMPLE,只保留边角的点,存储信息较少,比较常用。
返回值:返回img, contours和hierarchy(图像,轮廓和层级),返回的轮廓是最常用的,contours是list类型,表示所有轮廓,由不同层级的ndarray轮廓组成,每个轮廓保存其轮廓的坐标点。
opencv中绘制轮廓的函数:
drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]]) -> image
img:要绘制的轮廓图像;
contours:轮廓点;
contourldx:要绘制的轮廓编号,1表示绘制所有轮廓;
color:绘制轮廓颜色;
thickness:线宽,-1表示全部填充。
查找并绘制下图的轮廓:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import cv2 import numpy as np img = cv2.imread( './contours.png' ) gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #binary_img = cv2.adaptiveThreshold(gray_img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,3,0) #自适应阈值二值化 thresh,binary_img = cv2.threshold(gray_img, 130 , 255 , type = cv2.THRESH_TOZERO) image,contours,hierarchy = cv2.findContours(binary_img,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) img_copy = img.copy() cv2.drawContours(img_copy,contours, - 1 ,[ 0 , 0 , 255 ], 1 ) #绘制轮廓,绘制轮廓会改变输入的图像,最好备份一份原图。 #print(len(contours)) cv2.imshow( 'img' ,img) #cv2.imshow('binary_img',binary_img) cv2.imshow( 'img_copy' ,img_copy) cv2.waitKey( 0 ) cv2.destroyAllWindows() |
如果把thickness线宽,改为-1会进行填充。
多边形填充:
如果想要对上面的某些多边形进行填充,可以用 fillPoly(img,pts,color) pts 表示多边形数组,其中每个多边形均表示为顶点数组。单个多边形填充可以用 fillConvexPoly(img,points,color)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import cv2 import numpy as np img = cv2.imread( './contours.png' ) gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) thresh,binary_img = cv2.threshold(gray_img, 127 , 255 ,cv2.THRESH_BINARY) #图像二值化 image,contours,hierarchy = cv2.findContours(binary_img,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) #检测轮廓 # poly = np.array([[300,50],[50,250],[300,300]]) #定义多边形的顶点 img_copy = img.copy() #多边形填充也会改变原图,需要原图的话可以拷贝一下再填充 fill_img = cv2.fillPoly(img_copy,[contours[ 1 ],contours[ 3 ],contours[ 10 ]],( 0 , 0 , 255 )) #多边形填充,可以直接用检测轮廓返回的轮廓坐标点来填充,也可以自定义顶点 #fill_img = cv2.fillPoly(img,[contours[1],poly],(0,0,255)) cv2.imshow( 'img' ,img) cv2.imshow( 'fill_img' ,fill_img) cv2.waitKey( 0 ) cv2.destroyAllWindows() |
计算轮廓的面积和周长:
计算面积:contourArea(contour[, oriented]) -> retval contour表是需要计算轮廓
计算周长:arcLength(curve, closed) -> retval curve表示需要计算的轮廓,closed表示是否闭合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import cv2 import numpy as np img = cv2.imread( './contours.png' ) gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #binary_img = cv2.adaptiveThreshold(gray_img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,3,0) #自适应阈值二值化 thresh,binary_img = cv2.threshold(gray_img, 130 , 255 , type = cv2.THRESH_TOZERO) image,contours,hierarchy = cv2.findContours(binary_img,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) img_copy = img.copy() cv2.drawContours(img_copy,contours, 0 ,[ 0 , 0 , 255 ], - 1 ) #绘制轮廓,绘制轮廓会改变输入的图像,最好备份一份原图。 #print(len(contours)) #轮廓面积:指每个轮廓中所有的像素点围成区域的面积,单位是像素。可以用来分析每个轮廓的隐含信息,比如通过轮廓面积区分物体大小来识别物体。 # 在查找到轮廓后,可能会有很多细小轮廓,可以通过面积来过滤。contourArea(contour) arcLength(curve,closed) =(轮廓,是否闭合) area = cv2.contourArea(contours[ 0 ]) #计算某个轮廓的面积,contours轮廓是list类型 length = cv2.arcLength(contours[ 0 ],closed = True ) #计算某个轮廓的周长 print ( 'area:' ,area, 'length:' ,length) cv2.imshow( 'img' ,img) #cv2.imshow('binary_img',binary_img) cv2.imshow( 'img_copy' ,img_copy) cv2.waitKey( 0 ) cv2.destroyAllWindows() |
也可以绘制比较复杂的图形的轮廓,比如绘制手的轮廓:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import cv2 #绘制轮廓 import numpy as np img = cv2.imread( './hand.jpg' ) gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #binary_img = cv2.adaptiveThreshold(gray_img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,5,0) #自适应阈值二值化 这种方式二值化后噪点多 thresh,binary_img = cv2.threshold(gray_img, 40 , 255 , type = cv2.THRESH_BINARY) image,contours,hierarchy = cv2.findContours(binary_img,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) print ( len (contours)) img_copy = img.copy() cv2.drawContours(img_copy,contours, - 1 ,[ 0 , 0 , 255 ], 2 ) #绘制轮廓,绘制轮廓会改变输入的图像,最好备份一份原图。 cv2.imshow( 'img' ,img) cv2.imshow( 'binary_img' ,binary_img) cv2.imshow( 'img_copy' ,img_copy) cv2.waitKey( 0 ) cv2.destroyAllWindows() |
2 图像轮廓的多边形逼近
findContours后轮廓的信息可能比较复杂不平滑,可以用approxPolyDP对轮廓用多边形来近似拟合,即多边形逼近(采用的Douglas-Peucker方法)。
DP原理:在轮廓曲线上面,不断找多边形最远的点加入形成新的多边形,直到最短距离小于指定的精度。
opencv中的函数:
approxPolyDP(curve, epsilon, closed[, approxCurve]) -> approxCurve
curve 表示需要近似逼近的轮廓;
epsilon 表示DP算法使用的阈值,可以根据阈值的调整控制多边形逼近的形状。
closed 表示轮廓是否闭合。
返回值是个ndarray,表示轮廓近似逼近后的点的坐标,这些坐标点可以直接用drawContours来绘制,也可以用 polylines 绘制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import cv2 #绘制轮廓 import numpy as np img = cv2.imread( './hand.jpg' ) gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) thresh,binary_img = cv2.threshold(gray_img, 40 , 255 , type = cv2.THRESH_BINARY) image,contours,hierarchy = cv2.findContours(binary_img,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) print ( len (contours)) img_copy = img.copy() cv2.drawContours(img_copy,contours, - 1 ,[ 0 , 0 , 255 ], 2 ) #绘制轮廓,绘制轮廓会改变输入的图像,最好备份一份原图。 img_copy2 = img.copy() approx = cv2.approxPolyDP(contours[ 0 ], 20 ,closed = True ) #多边形逼近,返回值是个ndarray cv2.drawContours(img_copy2,[approx], - 1 ,[ 0 , 255 , 0 ], 2 ) #绘制多边形轮廓,轮廓类型要是list才行 #cv2.polylines(img_copy,[approx],True,[0,255,0],2) #也可以直接用polylines绘制 cv2.imshow( 'img' ,img) cv2.imshow( 'binary_img' ,binary_img) cv2.imshow( 'img_copy' ,img_copy) cv2.imshow( 'img_copy2' ,img_copy2) cv2.waitKey( 0 ) cv2.destroyAllWindows() |
3 凸包
凸包指的是完全包含原有轮廓,并且仅由轮廓上的点构成的多边形。凸包的每一处都是凸的,即在凸包内连接任意两点的直线都在凸包内。
opencv中凸包的函数
convexHull(points[, hull[, clockwise[, returnPoints]]]) -> hull
points 可以直接用轮廓
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import cv2 #绘制轮廓 import numpy as np img = cv2.imread( './hand.jpg' ) gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) thresh,binary_img = cv2.threshold(gray_img, 40 , 255 , type = cv2.THRESH_BINARY) image,contours,hierarchy = cv2.findContours(binary_img,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) img_copy = img.copy() cv2.drawContours(img_copy,contours, - 1 ,[ 0 , 0 , 255 ], 2 ) #绘制轮廓,绘制轮廓会改变输入的图像,最好备份一份原图。 hull = cv2.convexHull(contours[ 0 ]) #凸包 #cv2.drawContours(img_copy,[hull],-1,[0,225,0],2) #绘制凸包 cv2.polylines(img_copy,[hull], True ,[ 0 , 255 , 0 ], 2 ) #也可以直接用polylines绘制 cv2.imshow( 'img' ,img) cv2.imshow( 'binary_img' ,binary_img) cv2.imshow( 'img_copy' ,img_copy) cv2.waitKey( 0 ) cv2.destroyAllWindows() |
4 最大外接矩形和最小外接矩形
1)最大外接矩形
opencv中图形的最大外接矩形的函数
boundingRect(points) -> retval
points表示图形的轮廓
返回值是矩形的左上角坐标和矩形长宽
除了在原图上面绘制轮廓和外接矩形,也可以单独创建窗口绘制。
如下是绘制星星的最大外接矩形:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import cv2 #图像的最大外接矩形 boundingRect(contours) 参数是轮廓坐标点,返回矩形的左上角坐标和长宽 import numpy as np img = cv2.imread( './contours.png' ) gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) thresh,binary_img = cv2.threshold(gray_img, 40 , 255 , type = cv2.THRESH_BINARY) image,contours,hierarchy = cv2.findContours(binary_img,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) contours_img = np.zeros(img.shape[:],np.uint8) #创建白底图像画布绘制需要的轮廓 contours_img[:] = 255 img_copy = img.copy() cv2.drawContours(contours_img,contours, 10 ,[ 0 , 0 , 255 ], 2 ) #绘制轮廓,只单独绘制第二层轮廓 rect = cv2.boundingRect(contours[ 10 ]) # 矩形边框,返回值是矩形的左上角坐标和矩形长宽 x,y,w,h = rect print ( 'rect:' ,rect) img_copy2 = cv2.rectangle(contours_img,(x,y),(x + w, y + h),( 0 , 255 , 0 ), 3 ) #绘制矩形,注意绘制坐标为左上角和右下角坐标。 cv2.imshow( 'img' ,img) cv2.imshow( 'img_copy' ,img_copy) cv2.imshow( 'contours_img' ,contours_img) cv2.waitKey( 0 ) cv2.destroyAllWindows() |
2)最小外接矩形
opencv中最小外接矩形的函数是
minAreaRect(points) -> retval
返回最小外接矩形,是一个旋转的矩形,返回矩形的起始坐标(x,y),矩形的长宽,矩形旋转角度。
可以用 boxPoints(min_rect) 函数把旋转矩形的四个顶点坐标计算出来(注意坐标点应该是整型才行,这里返回的是float型)。
绘制五角星的最小外接矩形:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import cv2 import numpy as np img = cv2.imread( './contours.png' ) gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) thresh,binary_img = cv2.threshold(gray_img, 127 , 255 ,cv2.THRESH_BINARY) #二值化 image,contours,hierarchy = cv2.findContours(binary_img,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) #检测轮廓 img_copy = img.copy() cv2.drawContours(img_copy,contours, 10 ,[ 0 , 0 , 255 ], 2 ) #绘制轮廓 min_rect = cv2.minAreaRect(contours[ 10 ]) #返回最小外接矩形,是一个旋转的矩形,返回矩形的起始坐标(x,y),矩形的长宽,矩形旋转角度 print ( 'min_rect' ,min_rect) points = cv2.boxPoints(min_rect) #这个函数可以把旋转矩形的四个顶点坐标计算出来(注意坐标点应该是整型才行,这里返回的是float型) print ( 'point:\n' ,points) rect_points = np. round (points).astype( 'int64' ) #把坐标点类型转换为整数,如果直接int转的话会直接扔掉小数点后的数值,round可以四舍五入,再用astype转整数 print ( 'rect_points:\n' ,rect_points) cv2.drawContours(img_copy,[rect_points], 0 ,[ 0 , 255 , 0 ], 2 ) #根据坐标点绘制最小外接矩形 cv2.imshow( 'img' ,img) cv2.imshow( 'img_copy' ,img_copy) cv2.waitKey( 0 ) cv2.destroyAllWindows() |
5 最小外接圆和椭圆
1)最小外接圆
opencv中最小外接圆函数是
minEnclosingCircle(points) -> center, radius
返回最小外接圆的圆心,半径 == ((x,y),radius)
然后可以根据返回的圆心和半径用 circle 把圆绘制出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import cv2 import numpy as np img = cv2.imread( './contours.png' ) gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) thresh,binary_img = cv2.threshold(gray_img, 127 , 255 ,cv2.THRESH_BINARY) #二值化 image,contours,hierarchy = cv2.findContours(binary_img,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) #检测轮廓 img_copy = img.copy() cv2.drawContours(img_copy,contours, 10 ,[ 0 , 0 , 255 ], 2 ) #绘制轮廓 (x,y),radius = cv2.minEnclosingCircle(contours[ 10 ]) #返回最小外接圆,返回圆心,半径 == ((x,y),radius) print ((x,y),radius) cir_center = ( int (x), int (y)) #把圆心半径转换为整数 cir_radius = np. round (radius).astype( 'int64' ) cv2.circle(img_copy,cir_center,cir_radius,[ 0 , 255 , 0 ], 2 ) cv2.imshow( 'img' ,img) cv2.imshow( 'img_copy' ,img_copy) cv2.waitKey( 0 ) cv2.destroyAllWindows() |
2)椭圆拟合
opencv中轮廓椭圆拟合的函数
fitEllipse(points) -> retval
返回椭圆的圆心,长短半轴,旋转角度参数,根据返回的参数直接用ellipse绘制椭圆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import cv2 import numpy as np img = cv2.imread( './contours2.png' ) gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) thresh,binary_img = cv2.threshold(gray_img, 127 , 255 ,cv2.THRESH_BINARY) #二值化 image,contours,hierarchy = cv2.findContours(binary_img,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) #检测轮廓 img_copy = img.copy() cv2.drawContours(img_copy,contours, 1 ,[ 0 , 0 , 255 ], 2 ) #绘制轮廓 ellipse = cv2.fitEllipse(contours[ 1 ]) #椭圆拟合 print ( 'ellipse' ,ellipse) cv2.ellipse(img_copy,ellipse,[ 0 , 255 , 0 ]) #绘制椭圆 cv2.imshow( 'img' ,img) cv2.imshow( 'img_copy' ,img_copy) cv2.waitKey( 0 ) cv2.destroyAllWindows() |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?