OpenCV学习笔记10-图像轮廓的相关知识及代码实现

文章目录

1 什么是图像轮廓

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

轮廓的作用:

  • 用于图形分析
  • 物体的识别和检测

注意点:

  • 为了检测的准确性,需要先对图像进行 二值化Canny操作
  • 在OpenCV中,找到轮廓就像从黑色背景中找到白色物体。因此请记住, 要找到的对象应该是白色,背景应该是黑色。
  • 画轮廓时会修改输入的图像, 如果之后想继续使用原始图像,应该将原始图像储存到其他变量中。

2 查找轮廓

  • cv2.findContours(image, mode, method[, contours[, hierarchy[, offset]]])
  • image 寻找轮廓的图像(单通道图像矩阵,可以是灰度图, 但更常用的是二值图像,一般是经过Canny、拉普拉斯等边缘检测算子处理过的二值图像
  • mode 查找轮廓的模式
    • RETR_EXTERNAL , 表示 只检测最外围轮廓OpenCV学习笔记10-图像轮廓的相关知识及代码实现
    • RETR_LIST , 检测的轮廓 不建立等级关系, 即 检测所有轮廓 图像里面的数字是索引,从右到左,从里到外OpenCV学习笔记10-图像轮廓的相关知识及代码实现
    • RETR_CCOMP , 每层最多两级, 从小到大, 从里到外, 很少用.OpenCV学习笔记10-图像轮廓的相关知识及代码实现
    • RETR_TREE, 按照树型存储轮廓, 从外到里,从大到小, 从右到左最常用!.OpenCV学习笔记10-图像轮廓的相关知识及代码实现
  • method 轮廓近似方法也叫ApproximationMode
    • CHAIN_APPROX_NONE 保存所有轮廓上的点
    • CHAIN_APPROX_SIMPLE, 只保存 角点, 比如四边形, 只保留四边形的4个角, 存储信息少, 比较常用
  • 返回值 :contours和hierarchy 即 轮廓(旧版本是list形式,新版本是tuple形式) 和 层级
  • contours:轮廓点。 元组格式(不是ndarray),每一个元素为一个3维数组(其形状为(n,1,2),其中n表示轮廓点个数,2表示像素点坐标),表示一个轮廓。
  • hierarchy:轮廓间的层次关系,为三维数组,形状为(1,n,4),其中n表示轮廓总个数,4指的是用4个数表示各轮廓间的相互关系。第一个数表示同级轮廓的下一个轮廓编号,第二个数表示同级轮廓的上一个轮廓的编号,第三个数表示该轮廓下一级轮廓的编号,第四个数表示该轮廓的上一级轮廓的编号。
  • offset 轮廓点的偏移量,格式为tuple,如(-10,10)表示轮廓点沿X负方向偏移10个像素点,沿Y正方向偏移10个像素点。
import cv2
import numpy as np

img = cv2.imread('./contours1.jpeg')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

print(type(contours))
print(contours)

cv2.waitKey(0)
cv2.destroyAllWindows()

3 绘制轮廓

  • cv2.drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]])
  • 直接绘制轮廓时会对原图进行绘制,建议拷贝一份新的图像
  • image 指明在哪幅图像上绘制轮廓(只有三通道的图像才显示轮廓)
  • contours 轮廓点,传入的是list类型
  • contourIdx 要绘制的轮廓的编号. -1 表示绘制所有轮廓 0代表绘制最外面的轮廓,1代表绘制第二个轮廓(从外到内数),2代表绘制第三个轮廓(从外到内数),以此类推
  • color 轮廓的颜色(BGR), 如 (0, 0, 255)表示红色
  • thickness线宽, -1 表示全部填充 1代表线的宽度,1、2、3…线宽依次变粗
  • lineType (可选参数)轮廓线型,包括cv2.LINE_4,cv2.LINE_8(默认),cv2.LINE_AA,分别表示4邻域线,8领域线,抗锯齿线(可以更好地显示曲线)
  • hierarchy (可选参数)层级结构,上述函数cv2.findContours()的第二个返回值,配合maxLevel参数使用
  • maxLevel (可选参数)等于0表示只绘制指定的轮廓,等于1表示绘制指定轮廓及其下一级子轮廓,等于2表示绘制指定轮廓及其所有子轮廓
  • offset (可选参数)轮廓点的偏移量
import cv2
import numpy as np

img = cv2.imread('./contours1.jpeg')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

thresh, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

img_copy = img.copy()
cv2.drawContours(img_copy, contours, -1, (0, 0, 255), 1)

cv2.imshow('img_copy', img_copy)

cv2.waitKey(0)
cv2.destroyAllWindows()

OpenCV学习笔记10-图像轮廓的相关知识及代码实现

4 轮廓的面积和周长

轮廓面积是指每个轮廓中所有的像素点围成区域的面积,单位为像素。

轮廓面积是轮廓重要的统计特性之一,通过轮廓面积的大小可以进一步分析每个轮廓隐含的信息,例如通过轮廓面积区分物体大小识别不同的物体。

在查找到轮廓后, 可能会有很多细小的轮廓, 我们可以通过轮廓的面积进行过滤.

计算轮廓面积(不适用于具有自交点的轮廓,即重合),通常搭配findContours()函数使用:

  • 计算面积:cv2.contourArea(contour[, oriented])
  • contour 输入的图像轮廓点
  • oriented 有方向的区域标志,表示某一方向上的轮廓的面积值
    • 默认为False,表示返回不带方向的绝对值
    • True 依赖轮廓的方向(顺时针或逆时针),返回一个已标记区域的值
  • 计算周长:cv2.arcLength(curve, closed)
  • curve 即轮廓
  • closed 是否是闭合的轮廓
    • True 表示计算闭合的轮廓的周长
    • False表示计算不是闭合的轮廓的周长,此时的周长比闭合时少最后一条 (例如,计算正方形的周长时,用True时计算四条边的和;用False时计算三条边的和)

计算面积代码:

import cv2
import numpy as np

img = cv2.imread('./contours1.jpeg')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

thresh, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

img_copy = img.copy()
cv2.drawContours(img_copy, contours, 1, (255, 0, 0), 2)

area = cv2.contourArea(contours[1])
print('area:', area)

cv2.waitKey(0)
cv2.destroyAllWindows()

计算周长代码:

import cv2
import numpy as np

img = cv2.imread('./contours1.jpeg')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

thresh, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

img_copy = img.copy()
cv2.drawContours(img_copy, contours, 1, (255, 0, 0), 2)

perimeter = cv2.arcLength(contours[1], closed=True)
print('perimeter:', perimeter)

cv2.waitKey(0)
cv2.destroyAllWindows()

完整代码:

import cv2
import numpy as np

img = cv2.imread('./contours1.jpeg')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

cv2.drawContours(img, contours, 1, (0, 0, 255), 2)

area = cv2.contourArea(contours[1])
print('area: ', area)
cv2.imshow('img', img)

perimeter = cv2.arcLength(contours[1], True)
print('perimeter:', perimeter)

cv2.waitKey(0)
cv2.destroyAllWindows()

5 多边形逼近与凸包

findContours后的轮廓信息contours可能过于复杂不平滑,可以用approxPolyDP函数对该多边形曲线做适当近似,这就是轮廓的多边形逼近.

原图:

OpenCV学习笔记10-图像轮廓的相关知识及代码实现
多边形逼近动态平滑:
OpenCV学习笔记10-图像轮廓的相关知识及代码实现

理解Douglas-Peucker算法:

1、该算法从轮廓中挑出 两个最远的点,进行相连;

2、然后再原轮廓上寻找一个 离线段距离最远的点,将该点加入逼近后的新轮廓,即连接着三个点形成的三角型作为轮廓;

3、选择三角形的任意一条边出发,进行步骤2,将距离最远点加入新轮廓,直至满足输出的精度要求。

approxPolyDP就是以多边形去逼近轮廓,把一个连续光滑曲线折线化,采用的是Douglas-Peucker算法(方法名中的DP)

DP算法原理比较简单,核心就是不断找多边形最远的点加入形成新的多边形,直到最大距离小于指定的精度。(以最少的储存信息量反映最接近原来轮廓的样子)

OpenCV学习笔记10-图像轮廓的相关知识及代码实现
我们可以看看动态形成过程:
OpenCV学习笔记10-图像轮廓的相关知识及代码实现

如图,假设阈值为T,先处理AB段,在整个轮廓上找到与AB边最远的距离d1,若d1

  • 多边形逼近:cv2.approxPolyDP(curve, epsilon, closed[, approxCurve])
  • curve 要近似逼近的轮廓,类型是mat(即ndarray)
  • epsilon 判断点到相对应的line segment的距离,即DP算法使用的阈值(距离大于此阈值则舍弃,小于此阈值则保留,epsilon越小,折线的形状越”接近”曲线。)经过测试,该阈值设为0.02*perimeter比较合适。
  • closed轮廓是否闭合
    • True 近似曲线是闭合的(它的第一个和最后一个顶点是连接的)
    • False 表示不闭合
  • approxCurve 输出轮廓,类型应该与输入轮廓的类型相匹配(ndarray)。 *与cv2.drawContours搭配使用时,要把输出的approx转换成list类型传入到contours,详情看25行代码
import cv2
import numpy as np

img = cv2.imread('./hand.png')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

thresh, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

approx = cv2.approxPolyDP(contours[0], 20, closed=True)

print(type(approx))

cv2.drawContours(img, [approx], 0, (0, 255, 0), 2)

cv2.imshow('img', img)

cv2.waitKey(0)
cv2.destroyAllWindows()

OpenCV学习笔记10-图像轮廓的相关知识及代码实现

逼近多边形是轮廓的高度近似,但是有时候,我们希望使用一个多边形的凸包来简化它。凸包跟逼近多边形很像,只不过它是物体最外层的凸多边形。凸包指的是完全包含原有轮廓,并且仅由轮廓上的点所构成的多边形。凸包的每一处都是凸的,即在凸包内连接任意两点的直线都在凸包的内部。在凸包内,任意连续三个点的内角小于180°。寻找图像的凸包,能够让我们做一些有意思的事情,比如手势识别等。(简单来说外部凸起来的点连起来就是凸包)

  • cv2.convexHull(points[, hull[, clockwise[, returnPoints]]])
  • points 即原始轮廓,不要传入逼近的轮廓
  • hull 输出凸包结果(ndarray) 与cv2.drawContours搭配使用时,要把输出的hull转换成list类型传入到contours,详情看19行代码
  • colckwise 转动方向,默认为 True,为顺时针绘制,反之为逆时针绘制
  • returnPoints 默认为 True,返回凸包上点的坐标,如果设置为False,会返回与凸包点对应的轮廓上的点。
import cv2
import numpy as np

img =cv2.imread('./hand.png')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

thersh, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print('contours:',type(contours))

hull = cv2.convexHull(contours[0])
print('hull:',type(hull))

cv2.drawContours(img, [hull], 0, (255, 0, 0), 2)

cv2.imshow('img', img)

cv2.waitKey(0)
cv2.destroyAllWindows()

OpenCV学习笔记10-图像轮廓的相关知识及代码实现

绘制轮廓+多边形逼近+凸包的结合:

import cv2
import numpy as np

img =cv2.imread('./hand.png')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

thersh, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print(type(contours))

cv2.drawContours(img, contours, 0, (0, 0, 255), 2)

approx = cv2.approxPolyDP(contours[0], 20, True)

cv2.drawContours(img, [approx], 0, (0, 255, 0), 2)

hull = cv2.convexHull(contours[0])

cv2.drawContours(img, [hull], 0, (255, 0, 0), 2)

cv2.imshow('img', img)

cv2.waitKey(0)
cv2.destroyAllWindows()

OpenCV学习笔记10-图像轮廓的相关知识及代码实现

6 外接矩形

外接矩形分为 最小外接矩形最大外接矩形.

下图中红色矩形是最小外接矩形, 绿色矩形为最大外接矩形.

OpenCV学习笔记10-图像轮廓的相关知识及代码实现
  • cv2.minAreaRect(points) 最小外接矩阵(存在旋转)
  • points 即为轮廓(contours)
  • 返回元组,我们用rect接收, 内容是一个旋转矩形(RotatedRect)的参数: 矩形的起始坐标(x,y), 矩形的宽度和高度, 矩形的旋转角度.

返回的rect,我们可以通过cv2.boxPoints(box[, points])来自动获得矩形的四个顶点坐标

  • box 是rect返回的参数
  • points 返回矩形的四个坐标(二维的ndarray)

我们获得坐标后(用box接收),可以通过cv2.drawCounter( )将最小外接矩形画出来,但是通过cv2.drawCounters( )绘制外接矩形时, 要把box转化成list传入到counters参数里才能使用

坑:像素是通过整数的ndarray储存的,但是上面获得的box有可能是小数,我们要把小数位通过四舍五入重新算出来,再传入到counters,具体方法是box = np.round(box).astype(‘int64’)→看26行代码

  • cv2.boundingRect(points) 最大外接矩阵(方方正正的矩形,不存在旋转)
  • points 即为轮廓(contours)
  • 返回四个参数,起始坐标(x,y),宽高(w,h),用x,y,w,h接收, *起始坐标在左下角

获得x,y,w,h后,我们可以通过 cv2.rectangle( )将最大外接矩形画出来,具体看33行代码

import cv2
import numpy as np

img = cv2.imread('./hello.jpeg')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

(x, y), (w, h), angle = cv2.minAreaRect(contours[1])

print(x, y)
print(w, h)
print(angle)
r = cv2.minAreaRect(contours[1])

box = cv2.boxPoints(r)
print(box)

box = np.round(box).astype('int64')
print(box)

cv2.drawContours(img, [box], 0, (255, 0, 0), 2)

x,y, w, h = cv2.boundingRect(contours[1])
cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)

cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

OpenCV学习笔记10-图像轮廓的相关知识及代码实现

附OpenCV目录:OpenCV总目录学习笔记

OpenCV学习笔记10-图像轮廓的相关知识及代码实现

Original: https://blog.csdn.net/weixin_56197703/article/details/124179602
Author: Aaron-ywl
Title: OpenCV学习笔记10-图像轮廓的相关知识及代码实现

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/704739/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球