opencv-python图像轮廓

发布时间 2023-08-10 14:30:17作者: 寒水浮云

本章节介绍图像轮廓查找和绘制,图像轮廓的多边形逼近,凸包和外接矩形等。

图像轮廓是具有相同颜色或灰度的连续点的曲线,轮廓在形状分析和物体的检测和识别中很有用。

为了检测的准确性,需要先对图形进行二值化或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()