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表示全部填充。
查找并绘制下图的轮廓:

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()


浙公网安备 33010602011771号