OpenCV-Python图像处理学习笔记(五)——Canny 边缘检测、图像金字塔、轮廓检测(一)

本篇学习笔记主要内容在图像的Canny 边缘检测、图像金字塔、轮廓检测内容。

获取更多可以查看本栏目其他文章。

 往期内容:

OpenCV-Python图像处理学习笔记(一)——认识、安装、环境测试

OpenCV-Python图像处理学习笔记(二)——图像/视频读取保存、分割及边界填充

OpenCV-Python图像处理学习笔记(三)——数值运算、图像阈值、图像平滑(滤波)

OpenCV-Python图像处理学习笔记(四)——形态学操作、图像梯度
OpenCV-Python图像处理学习笔记(五)——Canny 边缘检测、图像金字塔、轮廓检测(一)

OpenCV-Python图像处理学习笔记(六)——轮廓检测(二)、模板匹配
OpenCV-Python图像处理学习笔记(七)——直方图、图像变换

目录

 1  Canny 边缘检测

 1.1  梯度强度和方向

1.2  非极大值抑制

1.3  双阈值检测

 1.4  边缘检测代码实现

 2  图像金字塔

2.1  高斯金字塔

2.2  拉普拉斯金字塔

3  图像轮廓检测

3.1  绘制图像轮廓

3.2  轮廓近似方法

3.3  轮廓特征

3.3.1  矩

3.3.2  轮廓面积

3.3.3  轮廓周长

3.3.4  轮廓近似

 3.3.5  凸包

3.3.6  凸性检测

3.3.7  边界矩形

 3.3.8  最小外接圆

 3.3.9  椭圆拟合

 3.3.10  直线拟合

4  小结


导入必要Python包

import cv2
import numpy as np
import matplotlib.pyplot as plt

 1  Canny 边缘检测

Canny 边缘检测是一种非常流行的边缘检测算法,是 John F.Canny 在 1986 年提出的,它是一个有很多步构成的算法。

边缘检测流程

  1. 使用高斯滤波器,平滑图像,去除噪声;
  2. 计算图像中每个像素点的梯度强度和方向;
  3. 应用非极大值抑制(Non-Maximum Suooression),消除边缘检测带来的杂散响应;
  4. 应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘;
  5. 通过一直孤立的弱边缘最终完成边缘检测。

 1.1  梯度强度和方向

平滑操作在之前的博文中讲到过,这里不再重复。这里使用 Sobel 算子计算水平方向和竖直方向的一阶导数(图像梯度)(Gx 和 Gy)。根据得到的这两幅梯度图(Gx 和 Gy)找到边界的梯度和方向,公式如下:

Edge Gradient(G) = \sqrt{G_{x}^{2} + G_{y}^{2}}

Angle(\theta ) = tan^{-1}(\frac{G_{x}}{G_{y}}) 

梯度的方向一般总是与边界垂直。梯度方向被归为四类:垂直,水平,和 两个对角线。

1.2  非极大值抑制

在获得梯度的方向和大小之后,应该对整幅图像做一个扫描,去除那些非边界上的点。对每一个像素进行检查,看这个点的梯度是不是周围具有相同梯度方向的点中最大的。如下图所示:

 通俗理解为比较当前点梯度值与周围点梯度值大小,保存最大值的点,抑制非最大的点。

1.3  双阈值检测

双阈值检测是确定那些边界才是真正的边界。需要设置两个阈值:

minValmaxVal

这里需要判断图像的灰度梯度与 maxVal 和 minVal 的大小:

  • 梯度值 > maxVal : 视为边界
  • minVal < 梯度值 < maxVal : 是否与某个被确定为真正的边界点相连,如果是就认为它也是边界点,如果不是就抛弃
  • 梯度值 < minVal :舍弃

 例如下图举例说明:

A 高于阈值 maxVal 所以是真正的边界点,C 虽然低于 maxVal 但高于 minVal 并且与 A 相连,所以也被认为是真正的边界点。而 B 就会被抛弃,因为他不仅低于 maxVal 而且不与真正的边界点相连。所以选择合适的 maxVal 和 minVal 对于能否得到好的结果非常重要。 在这一步一些小的噪声点也会被除去,因为已经假设边界都是一些长的线段。

 1.4  边缘检测代码实现

需要用到 cv2.Canny() 函数来完成边缘检测。

img = cv2.imread('.jpg', 0)

edges = cv2.Canny(img, 100, 200)

plt.subplot(121)
plt.imshow(img, cmap = 'gray')
plt.title('Original Image')
plt.xticks([])
plt.yticks([])

plt.subplot(122)
plt.imshow(edges, cmap = 'gray')
plt.title('Edge Image')
plt.xticks([])
plt.yticks([])

plt.show()

其中 cv2.Canny() 函数涉及到的参数如下:

  • 第一个参数是输入图像;
  • 第二和第三个分别是 minVal 和 maxVal;
  • 第三个参数用来计算图像梯度的 Sobel 卷积核的大小,默认值为 3;
  • 第四个参数是 L2gradient,用来设定求梯度大小的方程。如果设为 True,就会使用我们上面提到过的方程,否则使用方程:Edge Gradient(G) = |{G_{x}^{2} |+ |G_{y}^{2}} |代替,默认值为 False。

 2  图像金字塔

一般情况下,我们要处理是一副具有固定分辨率的图像。但是有些情况下, 我们需要对同一图像的不同分辨率的子图像进行处理。比如,我们要在一幅图 像中查找某个目标,比如脸,我们不知道目标在图像中的尺寸大小。这种情况 下,我们需要创建创建一组图像,这些图像是具有不同分辨率的原始图像。我 们把这组图像叫做图像金字塔(简单来说就是同一图像的不同分辨率的子图集 合)。如果我们把最大的图像放在底部,最小的放在顶部,看起来像一座金字塔,故而得名图像金字塔。

有两类图像金字塔:高斯金字塔拉普拉斯金字塔

高斯金字塔的顶部是通过将底部图像中的连续的行和列去除得到的。顶部图像中的每个像素值等于下一层图像中 5 个像素的高斯加权平均值。这样操作一次一个M x N的图像就变成了一个M/2 x N/2 的图像。所以这幅图像的面积就变为原来图像面积的四分之一。

2.1  高斯金字塔

向下采样方法(缩小)

\frac{1}{16}\left [ \begin{matrix} 1& 4& 4& 1\\ 4 & 16& 16& 4 \\ 4& 16& 16& 4\\ 1& 4& 4& 1\end{matrix} \right ]

  • G_{i}与高斯内核卷积
  • 将所有偶数行和列去除

向上采样方法(放大)

\left [ \begin{matrix} 10& 30\\ 56& 96\\ \end{matrix} \right ]\Rightarrow \left [ \begin{matrix} 10& 0& 30& 0\\ 0 & 0& 0& 0 \\ 56& 0& 96& 0\\ 0& 0& 0& 0\end{matrix} \right ]

 通过下列图像进行代码操作示例:

 原图像大小为(442,340,3)

img = cv2.imread('.jpg')

# 上采样
up = cv2.pyrUp(img)

# 下采样
down = cv2.pyrDown(img)

 分别进行上采样和下采样原图像大小分别变为如下:

(884,680,3)和(221,170,3)

使用cv2.pyrDown(),图像的分辨率就会降低,信息就会被丢失。

2.2  拉普拉斯金字塔

拉普拉斯金字塔可以有高斯金字塔计算得来,公式如下:

L_{i} = G_{i} - PyrUp (PyrDown (G_{i}))

拉普拉金字塔的图像看起来就像边界图,其中很多像素都是 0。经常被用在图像压缩中。

具体计算方式如下:

down = cv2.pyrDown(img)
down_up = cv2.pyrUp(down)
l_1 = img - down_up
cv_show(l_1, 'l_1')

3  图像轮廓检测

轮廓可以简单认为成将连续的点(连着边界)连在一起的曲线,具有相同的颜色或者灰度。

  • 为了更加准确,要使用二值化图像。在寻找轮廓之前,要进行阈值化处理或者 Canny 边界检测。
  • 查找轮廓的函数会修改原始图像。
  • 在 OpenCV 中,查找轮廓就像在黑色背景中找白色物体。要找的物体应该是白色,而背景应该是黑色。

通过cv2.findContours()函数遭到图像轮廓,具体常用参数如下:

mode:轮廓检索模式

  • RETR_EXTERNAL:只检索最外面的轮廓;
  • RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中;
  • RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界;
  • RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次;

method:轮廓近似方法

  • CHAIN_APPROX_NONE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列);
  • CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分;

通过cv2.drawContours()函数遭到图像轮廓,具体常用参数如下:

  • 第一个参数:图像;
  • 第二个参数:轮廓本身,在Python中是一个list;
  • 第三个参数:指定绘制轮廓list中的哪条轮廓,如果是-1,则绘制其中的所有轮廓;
  • 其他参数:包括线条粗细、颜色等。

更详细说明可参考:OpenCV: Modules

3.1  绘制图像轮廓

通过以下代码找到并绘制图像轮廓:

im = cv2.imread('test.jpg')
imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)

# 二值图像
ret, thresh = cv2.threshold(imgray, 127, 255, cv2.THRESH_BINARY)

# 找到图像轮廓
image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
'''
image:二值结果图像
contours:图像轮廓点,list
hierarchy:轮廓结果层级信息
'''

# 尽量进行copy
img = imgray .copy()

# 绘制图像轮廓
# img = cv2.drawContour(img, contours, -1, (0, 255, 0), 3)
img = cv2.drawContours(img, contours, 3, (0, 255, 0), 3)
'''
(0, 255, 0):bgr颜色
3:线条宽度
'''

3.2  轮廓近似方法

轮廓是一个形状具有相同灰度值的边界,它会存储形状边界上所有的(x,y)坐标。实际上我们不需要所有的点,当需要直线时,找到两个端点即可。cv2.CHAIN_APPROX_SIMPLE可以实现。它会将轮廓上的冗余点去掉,压缩轮廓,从而节省内存开支。

下面第一个显示使用cv2.CHAIN_APPROX_NONE的效果,一共734个点,第二个图是使用cv2.CHAIN_APPROX_SIMPLE的结果,只有4个点。

3.3  轮廓特征

轮廓的不同特征,例如面积,周长,重心,边界框等。

3.3.1  矩

图像的矩可以帮助我们计算图像的质心,面积等。

函数 cv2.moments() 会将计算得到的矩以一个字典的形式返回:

img = cv2.imread('.jpg', 0)

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

image, contours, hierarchy = cv2.findContours(thresh, 1, 2)

cnt = contours[0]
M = cv2.moments(cnt)

print(M)

根据这些矩值,可以计算出图像的重心:

C_{x}=\frac{M_{10}}{M_{00}} , C_{y}=\frac{M_{01}}{M_{00}}

cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])

3.3.2  轮廓面积

可以使用函数cv2.contourArea()计算得到,也可以用矩(0阶矩),M['m00']。

area = cv2.contourArea(cnt)

3.3.3  轮廓周长

也被称为弧长。可以使用函数cv2.arcLength()计算得到。这个函数的第二参数可以用来指定对象的形状是闭合的(True),还是打开的(一条曲线)。

perimeter = cv2.arcLength(cnt, True)

3.3.4  轮廓近似

将轮廓形状近似到另外一种由更少点组成的轮廓形状,新轮廓的点的数目 由我们设定的准确度来决定,使用的Douglas-Peucker算法。

如下图所示,现在可以使用这个函数来近似这个形状。cv2.approxPolyDP()函数的第二个参数叫 epsilon,它是从原始轮廓到近似轮廓的最大距离。它是一个准确度参数。

epsilon = 0.1 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)

 下边,第二幅图中的绿线是当 epsilon = 10% 时得到的近似轮廓,第三幅 图是当 epsilon = 1% 时得到的近似轮廓。第三个参数设定弧线是否闭合。

 3.3.5  凸包

函数 cv2.convexHull() 可以用来检测一个曲线是否具有凸性缺陷,并能纠正缺陷。一般来说,如果图像有地方凸出或凹陷就被叫做凸性缺陷。例如下图中。红色曲线显示了凸性缺陷被双箭头标出来了。

hull = cv2.convexHull(points, hull, clockwise, returnPoints)
'''
points:要传入的轮廓
hull:输出,通常不需要
clockwise:方向标志,如果设置为True,输出的凸包是顺时针方向的,否则为逆时针方向。
returnPoints:默认值为True。它会返回凸包上点的坐标,如果设置为False,就会返回与凸包点对应的轮廓上的点。
'''

# 寻找凸包,得到凸包的角点
img = cv2.imread('.jpg', 0)
ret, thresh = cv2.threshold(img, 127, 255, 0)
image, contours, hierarchy = cv2.findContours(thresh, 1, 2)
cnt = contours[0]
hull = cv2.convexHull(cnt)

# 绘制凸包
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
cv2.polylines(image, [hull], True, (0, 255, 0), 2)

3.3.6  凸性检测

函数cv2.isContourConvex()可以检测一个曲线是不是凸的。它只能返回True或者False。

k = cv2.isContourConvex(cnt)

3.3.7  边界矩形

有两类边界矩形:直边界矩形和旋转的边界矩形

直边界矩形是一个直矩形(就是没有旋转的矩形)。它不会考虑对象是否旋转。 所以边界矩形的面积不是最小的。可以使用函数 cv2.boundingRect() 得到。

(x,y)为矩形左上角的坐标,(w,h)是矩形的宽和高。

x, y, w, h = cv2.boundingRect(cnt)

# 绘制矩形框
img = cv2.rectangle(img, 
                    (x,y), 
                    (x + w, y + h), 
                    (0, 255, 0), 
                    2)
'''
参数表示依次为:(图片,长方形框左上角坐标, 长方形框右下角坐标,字体颜色,字体粗细)
'''

旋转的边界矩形是面积最小的,因为它考虑了对象的旋转。用函数cv2.minAreaRect()。返回的是一个 Box2D 结构,其中包含矩形左上角角点的坐标(x,y),矩形的宽和高(w,h),以及旋转角度。但是要绘制这个矩形需要矩形的 4 个角点,可以通过函数 cv2.boxPoints() 获得。

# 得到最小外接矩形的(中心(x,y), (宽,高), 旋转角度)
rect = cv2.minAreaRect(contours[0]) 

# 获取最小外接矩形的4个顶点坐标
box = cv2.boxPoints(rect) 

# 画出来
cv2.drawContours(img, [box], 0, (0, 0, 255), 5)
plt.imshow(img)
plt.show()

 下图其中绿色的为直矩形,红的为旋转矩形。

 3.3.8  最小外接圆

函数 cv2.minEnclosingCircle() 可以找到一个对象的外切圆。 它是所有能够包括对象的圆中面积最小的一个。

(x, y), radius = cv2.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)

# 绘制圆形
img = cv2.circle(img, center, radius, (0, 255, 0), 2)
'''
img:输入的图片data
center:圆心位置
radius:圆的半径
color:圆的颜色
thickness:圆形轮廓的粗细(如果为正)。负厚度表示要绘制实心圆。
lineType: 圆边界的类型。
shift:中心坐标和半径值中的小数位数。
'''

 得到下列结果:

 3.3.9  椭圆拟合

使用的函数为 cv2.ellipse(),返回值是旋转边界矩形的内切圆。

# 提取轮廓椭圆拟合后的椭圆参数
ellipse = cv2.fitEllipse(cnt)
'''
返回值:ellipse = [ (x, y) , (a, b), angle ]
(x, y):代表椭圆中心点的位置
(a, b):代表长短轴长度,应注意a、b为长短轴的直径,而非半径
angle:代表了中心旋转的角度
'''

# 绘制拟合的椭圆
im = cv2.ellipse(im, ellipse, (0, 255, 0), 2)
'''
im:原始图像
ellipse:拟合的椭圆信息
(0, 255, 0):颜色,服从BGR的规律
2:所绘制轮廓的宽度
'''

得到如下结果:

 3.3.10  直线拟合

可以根据一组点拟合出一条直线,同样我们也可以为图像中的白色点拟合出一条直线。

# 取彩色图片的长、宽
rows, cols = img.shape[:2]

# 拟合直线
[vx, vy, x, y] = cv2.fitLine(cnt, cv2.DIST_L2, 0, 0.01, 0.01)
'''
cnt: 待拟合的直线的集合,必须是矩阵形式;这里是选定的轮廓
cv2.DIST_L2: 距离类型。fitline为距离最小化函数,拟合直线时,要使输入点到拟合直线的距离和最小化。类型有以下几种:
    cv2.DIST_USER: 自定义
    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:第5/6个参数用于表示拟合直线所需要的径向和角度精度,通常情况下两个值均被设定为1e-2.

返回值:对于二维直线,输出为4维,前两维代表拟合出的直线的方向,后两位代表直线上的一点。
'''

lefty = int((x * vy / vx) + y)
righty = int(((cols - x) * vy / vx) + y)
img = cv2.line(img, (cols-1, righty), (0, lefty), (0, 255, 0), 2)
'''
img: 背景图
(cols-1, righty): 直线起点坐标
(0, lefty): 直线终点坐标
(0, 255, 0): 当前绘画的颜色。如在BGR模式下,传递(255,0,0)表示蓝色画笔。灰度图下,只需要传递亮度值即可。
2: 画笔的粗细,线宽。若是-1表示画封闭图像,如填充的圆。默认值是1.
lineType: 线条的类型,如8-connected类型、anti-aliased线条(反锯齿),默认情况下是8-connected样式ide,cv2.LINE_AA表示反锯齿线条,在曲线的时候视觉效果更佳。
'''

得到如下结果:

4  小结

下期将继续同步OpenCV轮廓性质部分内容以及模板匹配等部分内容。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
非常好的问题!我可以告诉你一些关于使用opencv-python进行图像处理的基本知识。 首先,确保你已经安装了opencv-python库。你可以使用以下命令在Python中安装它: ``` pip install opencv-python ``` 导入库后,你可以开始处理图像。下面是一个简单的例子,展示了如何读取、显示和保存图像: ```python import cv2 # 读取图像 image = cv2.imread('image.jpg') # 显示图像 cv2.imshow('Image', image) cv2.waitKey(0) # 保存图像 cv2.imwrite('image_output.jpg', image) ``` 在这个例子中,`imread`函数用于从文件中读取图像。`imshow`函数用于显示图像,`waitKey`函数可以等待按键输入。最后,`imwrite`函数用于将图像保存到文件中。 除了基本操作,opencv-python还提供了许多图像处理函数,如图像缩放、旋转、裁剪、滤波、边缘检测等。下面是一个简单的例子,展示了如何进行图像缩放和灰度转换: ```python import cv2 # 读取图像 image = cv2.imread('image.jpg') # 缩放图像 resized_image = cv2.resize(image, (800, 600)) # 灰度转换 gray_image = cv2.cvtColor(resized_image, cv2.COLOR_BGR2GRAY) # 显示图像 cv2.imshow('Resized Image', resized_image) cv2.imshow('Gray Image', gray_image) cv2.waitKey(0) ``` 在这个例子中,`resize`函数用于缩放图像,`cvtColor`函数用于将图像转换为灰度图像。 希望这些例子能帮助你入门opencv-python图像处理!如果你有更具体的问题,欢迎继续提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

熊仔阿大

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

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

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

打赏作者

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

抵扣说明:

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

余额充值