第五章 形状识别

5.1 简介

通过前面的学习我们基本掌握了常用的图像处理的方法,这对于我们今天要学习的内容有很大的帮助。我们要识别出图像中的形状(直线,圆形,四边形等等)都涉及到了前面的图像处理。首先我们要使用滤波器消除图像中的噪声,去掉干扰的元素。再次使用边缘检测,绘制出图像中所有元素的边缘。然后通过使用霍夫变换得到我们想要的形状。最后我们把识别出的形状绘制出来。这样就完成了整个识别过程。

【知识目标】

【技能目标】 - 会用Canney边缘检测 - 会用霍夫变换 - 会用高斯滤波方法

【项目描述】 通过学习边缘检测,霍夫变换和滤波器,可以识别出图像中的形状,并进一步熟悉图像处理的步骤和相关原理。

5.2 知识点

图像平滑处理

大家都能知道什么是模糊。当使用相机拍摄一张照片时没有在焦点范围的图像里产生的效果会有模糊的效果。实际上,这是图像中的每个像素都与其周围的像素混合在一起了。这种像素的“混合”就变成了我们的模糊像素,虽然这种效果在我们的照片中通常是不需要的,但在执行图像处理任务时,它实际上是非常有用的。 许多图像处理和计算机视觉功能,如阈值化和边缘检测,如果首先对图像进行平滑或模糊处理,则性能会更好。因为这种处理会在尽量保留图像原有信息的情况下,过滤掉图像内部的噪声(指存在于图像数据中的不必要的或多余的干扰信息),它会对图像中与周围像素点的像素值差异较大的像素点进行处理,将其值调整为周围像素点像素值的近似值。图像的这种平滑处理,也可以叫模糊处理,或者图像滤波操作。常用的方法有均值滤波,方框滤波,高斯滤波,中值滤波,双边滤波等。 在不知道用什么滤波器好的时候,优先使用高斯滤波,然后均值滤波;去除斑点和椒盐噪声优先使用中值滤波;要去除噪点的同时尽可能保留更多的边缘信息,使用双边滤波;均值滤波、方框滤波、高斯滤波属于线性滤波方式,速度相对快;中值滤波、双边滤波属于非线性滤波方式,速度相对慢一些。 后面会以常用高斯滤波为例子,讲解高斯滤波的使用。 我们将在图像的顶部定义一个k×k滑动窗口,其中k总是奇数。 这个窗口将从左到右,从上到下滑动。 这个矩阵中心的像素(我们必须使用奇数,否则就不会有真正的“中心”)然后被设置为围绕它的所有其他像素的平均值。我们称这个滑动窗口为“卷积核”,或者仅仅是“核”。随着内核大小的增加,我们的图像将变得越来越模糊。如下图片动态的展示了卷积过程。

在这里插入图片描述

边缘检测

边缘检测是用数学方法在图像中找到像素亮度变化明显的点。而Canny边缘检测方法常被誉为边缘检测的最优方法。 Canny边缘检测分为一下几个步骤:

霍夫变换

霍夫变换是在图像中寻找直线、圆形以及其他简单形状的方法。霍夫变换的方法是Paul Hough与1962年首次提出。最初霍夫变换只能检测直线,随着发展还能识别其他简单的图形结构了,比如圆形,椭圆等。 我们主要学习霍夫变换的直线检测和圆形检测。 在霍夫变换中我们常用公式ρ = xcosθ + ysinθ,表示直线,其中ρ是原点到直线的距离即原点到直线的垂线的长度,θ是直线与水平线所成的角度(0~180°),确定了它们,也就确定一条直线了,并且同一条直线上的点必然会有同样的(ρ,θ)。

在这里插入图片描述

5.3 平滑处理

关键方法介绍

OpenCV提供了方法cv2.GaussianBlur实现高斯滤波,该方法的语法格式为: dst=cv2.GaussianBlur(src,ksize,sigmaX,sigmaY,borderType) 式中:

全部代码如下所示。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cv2
import numpy as np

image = cv2.imread("/home/yangzl/pic/picgaosi.png")
cv2.imshow('Image window', image)

blurred = np.hstack([
cv2. GaussianBlur(image, (3, 3), 0),
cv2. GaussianBlur(image, (5, 5), 0),
cv2. GaussianBlur(image, (7, 7), 0)] )
cv2.imshow("Gaussian", blurred)

cv2.waitKey()
cv2.destroyAllWindows()

第4行引用OpenCV的库cv2和numpy。第6行到第7行调用imread方法读取本地图片并调用imshow方法显示 第10行使用了numpy库的hstack方法,np. hstack函数将输出图像堆叠在一起。这种方法将我们的三幅图像“水平堆叠”成一行。 这很有用,因为我们不想使用cv2.imshow函数创建三个单独的窗口。这个方法的参数是数组所以用[]把我们的输出图像组合在了一起。

第11行到第13行使用的cv2的GaussianBlur方法进行了高斯滤波,第一个参数是我们想要模糊的图像,第二个参数是提供了一个表示我们卷积核大小的元组,最后一个参数是x轴方向的标准偏差,通过将该值设置为0,我们指示OpenCV根据卷积核大小自动计算它们,参数3的值越大,模糊效果越明显。实际GaussianBlur还有第4和第5个参数,但是这两个参数可以不赋值使用默认值就可以。

执行程序后的效果图如下所示。三个不同卷积核的效果从左到右排列3×3, 5×5,7×7 在这里插入图片描述

5.4 Canny边缘检测

关键方法介绍

OpenCV提供了方法cv2.Canny实现Canny边缘检测,该方法的语法格式为: edges=cv2.Canny(image,threshold1,threshold2[,apertureSize[,L2gradient]]) 式中:

全部代码如下所示。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cv2
import numpy as np

image = cv2.imread("/home/yangzl/pic/picgaosi.png")
cv2.imshow('Image window', image)

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

gaussian = cv2. GaussianBlur(gray, (5, 5), 0)

canny = cv2. Canny(gaussian, 30, 150)

blurred = np.hstack([
    gray,
    gaussian,
    canny
] )

cv2.imshow("Result", blurred)

cv2.waitKey()
cv2.destroyAllWindows()

第4行引用OpenCV的库cv2和numpy。首先我们要执行灰度处理,目的是方便后面的高斯滤波去除噪声。所以第10行使用cvtColor执行灰度图转换。第12行使用高斯方法模糊方法执行平滑处理。通过在边缘检测之前应用模糊,我们将帮助去除图像中我们不感兴趣的“噪声”边缘。我们使用了5*5的卷积核。第14行使用Canny边缘检测器,我们设置了最小阈值30和最大阈值150,可以根据实际情况进行适当调节。任何大于threshold2的梯度值都被认为是边。任何低于threshold1的值都被认为不是边沿。介于threshold1和threshold2之间的值根据它们的强度如何“连接”被分类为边缘或非边缘。在这种情况下,任何低于30的梯度值都被认为是非边缘,而任何高于150的梯度值都被认为是边缘。最后我们在第22行显示我们的边缘检测结果。

运行程序后的效果如下图所示。

在这里插入图片描述

5.5 霍夫变换

关键方法介绍

OpenCV提供了方法cv2.HoughLines实现霍夫直线变换,该方法要求所操作的源图像是一个二值化图像,所以在进行霍夫变换之前要先做二值化处理或边缘检测。该方法的语法格式为: lines=cv2.HoughLines(image,rho,theta,threshold) 式中:

全部代码如下所示。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cv2
import numpy as np

image = cv2.imread("/home/yangzl/pic/pic1.png")
cv2.imshow('Image window', image)

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

gaussian = cv2. GaussianBlur(gray, (5, 5), 0)

edges = cv2.Canny(gaussian, 30, 70)
cv2.imshow('Edges', edges)

lines = cv2.HoughLines(edges, 1, np.pi / 180, 160)

for line in lines:
    rho,theta = line[0]
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = a * rho
    y0 = b * rho
    x1 = int(x0 - 1000 * (-b))
    y1 = int(y0 - 1000 * (a))
    x2 = int(x0 + 1000 * (-b))
    y2 = int(y0 + 1000 * (a))
    cv2.line(image, (x1, y1), (x2, y2), (255, 0, 0), 1)

cv2.imshow('Result', image)

cv2.waitKey()
cv2.destroyAllWindows()

第4行引用OpenCV的库cv2和numpy。首先我们还是要执行灰度处理,以便后面的高斯滤波去除噪声。所以第10行使用cvtColor方法执行灰度图转换。第12行使用了5*5的卷积核的高斯模糊方法做平滑处理。第14行应用Canny边缘检测器完成边缘检测后在第15行显示出检测的结果图。第17行使用OpenCV的方法HoughLines执行直线检测,参数1是我们要检测的输入图像。参数2是使用精度1.参数3是使用精度π/180,表示要搜索所有可能的角度。参数4是阈值160。该值越小,判定出的直线就越多。返回值lines中的每个元素都是一对浮点数,表示检测到的直线参数即(ρ,θ),是numpy.ndarray类型。通过直线检测出的是图像中的是直线而不是线段,因此检测都的直线没有端点,所以我们在绘制直线时都是穿过整幅图像的。第20行到第30行根据返回值计算直线的两个端点并绘制。实现的原理就是先计算出直线的中心(x0,y0),再计算出直线的两端的点(x1,y1)(x2,y2),最后使用画线方法line连接两个点,完成直线的绘制。算法原理如下图解释。

在这里插入图片描述

最后运行程序效果图如下显示。左侧为边缘检测结果图,右侧为检测直线的结果图。

在这里插入图片描述

5.6 霍夫变换 直线检测优化

概率霍夫变换是对基本霍夫变换的算法进行了一些修改,为了更好的判断直线,概率霍夫变换算法对直线的选取做了两点改进。所接受的直线的最小长度。如果有超过阈值个数的像素点构成了一条直线,但是它很短,就不会接受这个直线。接受直线时允许的最大像素点间距。如果有超过阈值个数的像素点构成了一条直线,但像素点之间的距离很远,就不接受这个直线。

关键方法介绍

OpenCV提供了方法cv2.HoughLinesP实现。该方法的语法格式为: lines=cv2.HoughLinesP(image,rho,theta,threshold,minLineLength,maxLineGap) 式中:

全部代码如下所示。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cv2
import numpy as np

image = cv2.imread("/home/yangzl/pic/pic1.png")
cv2.imshow('Image window', image)

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

gaussian = cv2. GaussianBlur(gray, (5, 5), 0)

edges = cv2.Canny(gaussian, 30, 70)
cv2.imshow('Edges', edges)

lines  =  cv2.HoughLinesP(edges,1,np.pi/180,1,minLineLength=100,maxLineGap=30)

for  line  in  lines:
    x1, y1, x2, y2 = line[0]
    cv2.line(image,(x1,y1),(x2,y2),(255,0,255),1)

cv2.imshow('Result', image)

cv2.waitKey()
cv2.destroyAllWindows()

与前一个代码不同的就是我们在17行使用的方法是HoughLinesP我们最小线段距离为100,最大间隔为30,其他参数与HoughLines里的参数类似。第19行到21行获取了HoughLinesP返回值里的两点坐标,直接使用line方法绘制出相应的直线。

运行程序后效果图如下所示。

在这里插入图片描述

5.7 霍夫变换 圆检测

霍夫变换除了可以用来检测直线外,也能检测图像中的圆形,在圆检测中需要考虑圆半径,圆心,以及接受直线的最小长度,接受直线的最大像素点间距等要素。

关键方法介绍

OpenCV提供了方法cv2.HoughCircles实现圆检测。该方法的语法格式为: circles=cv2.HoughCircles(image,method,dp,minDist,param1,param2,minRadius,maxRadius) 式中:

全部代码如下所示。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cv2
import numpy as np

image = cv2.imread("/home/yangzl/pic/pic13.png")
cv2.imshow('Image window', image)

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

gaussian = cv2. GaussianBlur(gray, (7, 7), 0)

edges = cv2.Canny(gaussian, 30, 60)
cv2.imshow('Edges', edges)

circles = cv2.HoughCircles(edges, cv2.HOUGH_GRADIENT,1,120, 
                        param1=60,param2=125,minRadius=20,maxRadius=220)

# 确保至少发现一个圆
if circles is not None:
    # 进行取整操作
    circles = np.int0(np.around(circles))
    # 循环遍历所有的坐标和半径
    for i in circles[0, :]:
        cv2.circle(image, (i[0], i[1]), i[2], (0, 0, 255), 2)  # 画出外圆
        cv2.circle(image, (i[0], i[1]), 2, (0, 0, 255), 3)  # 画出圆心

cv2.imshow('Result', image)

cv2.waitKey()
cv2.destroyAllWindows()

第4行引用OpenCV的库cv2和numpy。首先我们还是要执行灰度处理,以便后面的高斯滤波去除噪声。所以第10行使用cvtColor方法执行灰度图转换。第12行使用了7*7的卷积核的高斯模糊方法做平滑处理。第14行应用Canny边缘检测器完成边缘检测后在第15行显示出检测的结果图。第17行使用OpenCV的方法HoughCircles执行圆形检测,参数1是我们要检测的输入图像,需要单通道灰度图像。参数2使用cv2.HOUGH_GRADIENT.参数3设置1就表示他们具有相同的分辨率。参数4最小距离为120。参数5第一个特定参数设置为60。参数6第二个特定参数设置为125。参数7的最小半径设置为20。参数8最大半径设置为220。以上这些参数根据实际情况适当调整。 第21行判断返回的数据是不是为空。 第23行因为返回的数据是浮点数,所以需要转换成整数,在第25行到第27行画出圆形和圆心。

运行程序后,左侧为边缘检测效果图,右侧为最终画圆的效果图。 在这里插入图片描述

5.8 挑战任务

1. 识别给定的样例图片中的直线。 2. 识别给定的样例图片中的圆形。 3. 使用摄像头识别直线和圆形