Machine Learning——CV系列(一)——Python+OpenCV核心操作(3)——边缘与轮廓

15 篇文章 0 订阅
13 篇文章 3 订阅

四、边缘与轮廓

4.1 图像梯度(见梯度算子)

4.2 Canny边缘提取算法

4.2.1 原理与流程

即便边缘与轮廓没关系,我们也先要根据边缘算轮廓。
Canny算法首先需要经过高斯模糊去噪,减少不重要特征,然后再调用Canny方法。
步骤:
在这里插入图片描述

4.2.2 非极大值抑制

这是一种边缘稀疏技术,作用在于"瘦"边。可以想象成我找了很多很多的结果,然后通过非极大值抑制,找到了相对最好的结果,而其他一般的结果我就丢掉了,达到"瘦"和"稀疏"的效果。具体如下:

1.将当前像素的梯度强度与沿正负梯度方向上的两个像素进行比较。
2.如果当前像素梯度强度与另外两个像素相比最大,则保留为边缘点;否则该像素点将被抑制。
在这里插入图片描述

4.2.3 双阈值边缘连接处理

双阈值边缘连接决定了哪些是边缘,哪些是真正的边,哪些不是边。阈值是颜色值/梯度值。(梯度大的应该被保留
这个是判断哪些些边缘需要保留,哪些边缘需要丢弃的。cv2.Canny中两个阈值就是梯度大小。假设有个坐标系,横轴代表像素[0-255],纵轴代表梯度大小,则两个阈值(maxVal,minVal)就相当于两条平行横轴的直线,然后连接每一个像素当前的梯度值,就形成了一条一条的梯度曲线。然后根据以下规则判断:

1.保留梯度曲线大于maxVal的部分对应的边缘。
2.如果曲线全部大于minVal,且有部分在maxVal和minVal之间,但是两头都大于maxVal,即"出头了",则保留全部曲线对应的边缘。
3.曲线一部分大于maxVal,一部分在minVal之下,则小于MaxVal的部分全不要。
在这里插入图片描述

4.2.4 代码

import cv2
img = cv2.imread("1.jpg", 0)

img = cv2.GaussianBlur(img, (3, 3), 0)
canny = cv2.Canny(img, 50, 150)

cv2.imshow('Canny', canny)
cv2.waitKey(0)

r'''
img = cv2.GaussianBlur(img, (5, 5), 0)
canny = cv2.Canny(img, threshold1, threshold2)
注意到Canny有两个阈值属性:tr1和tr2
其中较大的tr2用于检测图像中明显的边缘,但一般情况下检测的效果不会那么完美,
可能是断断续续的。所以这时候用较小的第一个阈值tr1用于将这些间断的边缘连接起来。
函数返回的是二值图,包含检测出的边缘
'''

在这里插入图片描述
canny_ct

import cv2

img = cv2.imread("25.jpg", 0)
cv2.imshow('Canny2', img)

img = cv2.convertScaleAbs(img, alpha=6, beta=0)
cv2.imshow('Abs', img)
img = cv2.GaussianBlur(img, (5, 5), 1)
canny = cv2.Canny(img, 100, 150)
canny = cv2.resize(canny, dsize=(500, 500))
cv2.imshow('Canny', canny)
cv2.waitKey(0)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们看到图像内部还是有很多噪点,可以用闭操作来进行补洞操作。代码如下:

import cv2

img = cv2.imread("25.jpg", 0)
cv2.imshow('Canny2', img)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)  # 闭

img = cv2.convertScaleAbs(img, alpha=6, beta=0)
cv2.imshow('Abs', img)
img = cv2.GaussianBlur(img, (5, 5), 1)

canny = cv2.Canny(img, 100, 150)
canny = cv2.resize(canny, dsize=(500, 500))
cv2.imshow('Canny', canny)
cv2.waitKey(0)

效果:
在这里插入图片描述
在这里插入图片描述

4.3 轮廓

轮廓和边缘不同,边缘厚度一般为1,即不能再薄了。可能是很多线条构成的,更有可能是断开的。而轮廓肯定是一些闭合的线条,更具有层次感。

4.3.0 轮廓与边缘区别

在这里插入图片描述

4.3.1 轮廓查找与绘制

import cv2

img = cv2.imread('14.jpg')
cv2.imshow("src", img)
imggray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imggray, 127, 255, 0)

contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)# 简单的点
# contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)# 尽可能多的点
print(len(contours[0])) # cv2.CHAIN_APPROX_SIMPLE:4 cv2.CHAIN_APPROX_NONE:720
img_contour = cv2.drawContours(img, contours, -1, (0, 255, 0), 2)# 2变-1表示填充
# 用途:unet做样本,切割细胞,拿到掩码图
cv2.imshow("img_contour", img_contour)
cv2.waitKey(0)
r'''
首先,该操作会修改原图,不想改原图需要拷贝一份
三个输入参数:输入的(二值)图像,轮廓检索方式,轮廓近似方法
=====================================================================================
1.轮廓检索方式
cv2.RETR_EXTERNAL	只检测外轮廓
cv2.RETR_LIST		检测的轮廓不建立等级关系
cv2.RETR_CCOMP		建立两个等级的轮廓,上面一层为外边界,里面一层为内孔的边界信息
cv2.RETR_TREE		建立一个等级树结构的轮廓
-------------------------------------------------------------------------------------
2.轮廓近似办法
cv2.CHAIN_APPROX_NONE		存储所有边界点
cv2.CHAIN_APPROX_SIMPLE		压缩垂直、水平、对角方向,只保留端点
cv2.CHAIN_APPROX_TX89_L1	使用teh-Chini近似算法
cv2.CHAIN_APPROX_TC89_KCOS	使用teh-Chini近似算法
=====================================================================================
返回的是轮廓的点contours和轮廓直接的从属关系hierarchy
'''

img_contour = cv2.drawContours(img, contours, -1, (0, 255, 0), 3)
r'''
参数说明:
    img		    表示输入的需要画的图片
    contours	表示轮廓值
    -1		    表示轮廓的索引,-1为全部都画
    (0, 0, 255)  表示颜色(这是红色)
    3            表示线条粗细,-1表示用颜色填充整个轮廓
'''

在这里插入图片描述

4.3.2 面积、周长和重心

在这里插入图片描述

import cv2

img = cv2.imread('m.jpg', 0)
ret, thresh = cv2.threshold(img, 127, 255, 0)# # 得到二值图
contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)# #得到轮廓
M = cv2.moments(contours[0])  # 矩,参数是一个数组,此时选择一组轮廓传入,返回一个字典
cx, cy = int(M['m10'] / M['m00']), int(M['m01'] / M['m00'])
print("重心:", cx, cy)
# 重心: 144 140
area = cv2.contourArea(contours[0])# 传入轮廓,返回面积。(上面的M['m00']的值也是面积)
print("面积:", area)
# 面积: 14207.5
perimeter = cv2.arcLength(contours[0], True)# 传入轮廓,返回周长。参数2表示轮廓是否封闭
print("周长:", perimeter)
# 周长: 797.3940051794052

关于矩:
在这里插入图片描述

4.3.3 轮廓近似

得到一个大体的轮廓,需要定义一个精度参数。

步骤:

1.得到二值图
2.找到轮廓的点
3.设定精度
4.轮廓近似
5.绘制轮廓

import cv2

img = cv2.imread('26.jpg')

imggray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imggray,127,255,0)## 得到二值图

contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)# #得到轮廓

epsilon = 40 # 设定一个精度
approx = cv2.approxPolyDP(contours[0],epsilon,True)
r'''
第一个参数是传入一个轮廓
第二个参数是精度,它是从原始轮廓到近似轮廓的最大距离,是一个准确度参数
第三个参数表示轮廓是否封闭
'''
img_contour= cv2.drawContours(img, [approx], -1, (0, 0, 255), 3)

cv2.imshow("img_contour", img_contour)
cv2.waitKey(0)

在这里插入图片描述

4.3.4 凸包和凸性检测

凸包与轮廓近似相似,但不同,虽然有些情况下他们给出的结果是一样的。
函数cv2.convexHull()可以用来检测一个曲线是否具有凸性缺陷,并能纠正缺陷。一般来说,凸性曲线总是凸出来的,至少是平的。如果有地方凹进去了就被叫做凸性缺陷。
函数cv2.isContourConvex()可以用来检测一个曲线是不是凸的。它只能返回True或False。
月牙形:非凸。正方形:凸。
凸包检测碰撞。圆最好检测,矩阵次之,凸包最后。

import cv2

img = cv2.imread('m.jpg')

imggray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imggray,127,255,0)# 得到二值图

contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)#得到轮廓

hull = cv2.convexHull(contours[0])# 获取当前图片轮廓的最小凸集
r'''
参数解释:[一般用到第一个参数就行]
Points:是传入的轮廓;
Hull:是输出,通常我们避免它;
Clockwise:方向标志。如果是true,则顺时针输出凸包,否则逆时针方式输出;
ReturnPoints:默认true时,返回hull点的坐标;如果是false,则返回与hull点对应的轮廓点的索引。
'''
print(cv2.isContourConvex(contours[0]), cv2.isContourConvex(hull))
#False True
#说明轮廓曲线是非凸的,凸包曲线是凸的
r'''
1.输出当前图片轮廓是否是凸的
2.输出当前图片轮廓的最小凸集是否是凸的[肯定是True] True代表凸包;False代表非凸
'''
img_contour= cv2.drawContours(img, [hull], -1, (0, 0, 255), 3)

cv2.imshow("img_contour", img_contour)
cv2.waitKey(0)

在这里插入图片描述

4.3.5 边界检测

常用的有:边界矩形、最小矩形、最小外切圆等。[可用来检测碰撞]

import cv2
import numpy as np

img = cv2.imread('16.jpg')

imggray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imggray, 127, 255, 0)

contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 边界矩形
x, y, w, h = cv2.boundingRect(contours[0])
img_contour = cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)

# 最小矩形
rect = cv2.minAreaRect(contours[0])
box = cv2.boxPoints(rect)
box = np.int0(box)
img_contour = cv2.drawContours(img, [box], 0, (0, 0, 255), 2)

# 最小外切圆
(x, y), radius = cv2.minEnclosingCircle(contours[0])
center = (int(x), int(y))
radius = int(radius)
img_contour = cv2.circle(img, center, radius, (255, 0, 0), 2)

cv2.imshow("img_contour", img_contour)
cv2.waitKey(0)

在这里插入图片描述

4.3.5.1 方向性判断

图像的最小椭圆和拟合直线:可用于方向性的判断。
应用:检测电力系统的闸刀开闭的姿态。

import cv2
import numpy as np

img = cv2.imread('16.jpg')

imggray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imggray, 127, 255, 0)

contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 椭圆拟合
ellipse = cv2.fitEllipse(contours[0])#返回的ellipse是椭圆的[中心坐标,短轴长轴(也就是2b,2a),旋转角度]
cv2.ellipse(img, ellipse, (255, 0, 0), 2)
r'''
返回的ellipse是椭圆的[中心坐标,短轴长轴(也就是2b,2a),旋转角度]
'''
cv2.ellipse(img, ellipse, (255, 0, 0), 2)
r'''
cv2.ellipse(img, center, axes, angle, startAngle, endAngle, color, thickness, lineType, shift),可见这个方法参数是很多的,在此说明一下:
====================================================================================
    img:需要绘图的图像
    ---------------------------------------------------------------
    center:椭圆中心点坐标,int
    axes:椭圆尺寸(即长短轴),int
    angle:旋转角度(顺时针方向),可以为小数
    ---------------------[ellipse参数包含了上面三个参数]------------
    startAngle:绘制的起始角度(顺时针方向)
    endAngle:绘制的终止角度(如绘制整个椭圆是0,360;绘制下半椭圆就是0,180)
    color:线条颜色(BGR)
    thickness:线条粗细(默认值=1)
    lineType:线条类型(默认值=8),本案例没设置
    shift:圆心坐标点和数轴的精度(默认值=0),本案例没设置
====================================================================================
但是本文案例没有写这么多参数,经过本人测试,cv2.ellipse(img, ellipse, (255, 0, 0), 2)
自动将ellipse解包成了center, axes, angle参数并且没有报数据类型错误[全是float]
还将axes参数自动/2了,起始终止也默认设为0和360。可以说是简化了参数吧。
'''
# 直线拟合
h, w, _ = img.shape
[vx, vy, x, y] = cv2.fitLine(contours[0], cv2.DIST_L2, 0, 0.01, 0.01)
lefty = int((-x * vy / vx) + y)
righty = int(((w - x) * vy / vx) + y)
cv2.line(img, (w - 1, righty), (0, lefty), (0, 0, 255), 2)

cv2.imshow("img_contour", img)
cv2.waitKey(0)

r'''
h, w, _ = img.shape
[vx, vy, x, y] = cv2.fitLine(contours[0], cv2.DIST_L2, 0, 0.01, 0.01)

points:一组轮廓
distType:距离类型==>
-----------------------------------------------------------------------
    cv2.DIST_USER:	 User defined distance
    cv2.DIST_L1: 	 distance = |x1-x2| + |y1-y2|
    cv2.DIST_L2: 	 欧式距离,此时与最小二乘法相同
    cv2.DIST_C:		 distance = max(|x1-x2|,|y1-y2|)
    cv2.DIST_L12:	 L1-L2 metric: distance = 2(sqrt(1+x*x/2) - 1))
    cv2.DIST_FAIR:	 distance = c^2(|x|/c-log(1+|x|/c)), c = 1.3998
    cv2.DIST_WELSCH: distance = c2/2(1-exp(-(x/c)2)), c = 2.9846
    cv2.DIST_HUBER:	 distance = |x|<c ? x^2/2 : c(|x|-c/2), c=1.345
-----------------------------------------------------------------------
param:距离参数,跟所选的距离类型有关,值可以设置为0。
reps、aeps:用于表示拟合直线所需要的径向和角度精度,通常情况下两个值均被设定为0.01
输出值:
对于二维直线,输出output为4维,前两维代表拟合出的直线的方向,后两位代表直线上的一点。
[即通常说的点斜式直线]
'''

在这里插入图片描述

4.3.6 轮廓性质

ret, thresh = cv2.threshold(imggray, 127, 255, cv2.THRESH_BINARY) # 得到二值图
contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)#得到轮廓
cnt = contours[0]
注意:下面 *cnt* 都是从上面两步得来的某一个轮廓
4.3.6.1 边界矩形的宽高比

在这里插入图片描述

x, y, w, h = cv2.boundingRect(cnt)
aspect_ratio = float(w) / h
4.3.6.2 轮廓面积与边界矩形面积的比

在这里插入图片描述

area = cv2.contourArea(cnt)
x, y, w, h = cv2.boundingRect(cnt)
rect_area = w * h
extent = float(area) / rect_area
4.3.6.3 轮廓面积与凸包面积的比

在这里插入图片描述

area = cv2.contourArea(cnt)
hull = cv2.convexHull(cnt)
hull_area = cv2.contourArea(hull)
extent = float(area) / hull_area
4.3.6.4 与轮廓面积相等的圆的直径

在这里插入图片描述

area = cv2.contourArea(cnt)
equi_diameter = np.sqrt(4 * area / np.pi)
4.3.6.5 图像的方向

返回椭圆长轴和短轴的长度

(x,y),(MA,ma),angle = cv2.fitEllipse(cnt)# 详见上文"图像的最小椭圆"

4.3.7 对象掩码

用于获取构成对象的所有像素点,即 3-1:轮廓查找和绘制 中最后参数取-1的情况

4.3.8 形状匹配

比较两个形状或轮廓的相似度。返回值越小,匹配越好。
在这里插入图片描述
在这里插入图片描述

import cv2

img1 = cv2.imread('16.jpg', 0)
img2 = cv2.imread('17.jpg', 0)

ret, thresh = cv2.threshold(img1, 127, 255, 0)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnt1 = contours[0]# 得到图1的一条轮廓

ret, thresh2 = cv2.threshold(img2, 127, 255, 0)
contours, hierarchy = cv2.findContours(thresh2, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnt2 = contours[0]# 得到图2的一条轮廓

ret = cv2.matchShapes(cnt1, cnt2, cv2.CONTOURS_MATCH_I2, 0.0)
print(ret)# 输出匹配度[0表示完全相同,1表示完全不同] # 0.6912157417129932
r'''
前两个参数分别是两个图的轮廓,第三个参数是匹配模式:
-----------------------------------------------------------------------------------
    CONTOURS_MATCH_I1 => 模式1
    CONTOURS_MATCH_I2 => 模式2
    CONTOURS_MATCH_I3 => 模式3
-----------------------------------------------------------------------------------
第四个参数是保留参数,目前无用,但是必须得写一个数:int
'''
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wa1tzy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值