本章节介绍图像轮廓查找和绘制,图像轮廓的多边形逼近,凸包和外接矩形等。
图像轮廓是具有相同颜色或灰度的连续点的曲线,轮廓在形状分析和物体的检测和识别中很有用。
为了检测的准确性,需要先对图形进行二值化或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表示全部填充。
查找并绘制下图的轮廓:
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)。
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表示是否闭合
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()
也可以绘制比较复杂的图形的轮廓,比如绘制手的轮廓:
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 绘制。
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 可以直接用轮廓
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表示图形的轮廓
返回值是矩形的左上角坐标和矩形长宽
除了在原图上面绘制轮廓和外接矩形,也可以单独创建窗口绘制。
如下是绘制星星的最大外接矩形:
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型)。
绘制五角星的最小外接矩形:
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 把圆绘制出来。
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绘制椭圆
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()