学习目标:
掌握模板匹配的原理,能完成魔板匹配的应用
理解霍夫线变换的原理,了解霍夫圆检测
知道OpenCV如何进行线和圆的检测
定义: 在给定的图片中,查找和模板最相似的区域,该算法的输入包括模板和图片,整个任务的思路就是按照滑动窗口的思路不断移动模板图片,计算与图像中对应区域的匹配度,最终将匹配度最高的区域选择为最终的结果。
实现流程:
(1)准备两张图像:一张原始图像,一张模板图像
(2)滑动模板图像与原图像进行比对:将模板每次移动一个像素(从左往右,从上往下),在每个位置,都计算与模板图像的相似程度。
(3)对于每个位置将计算的相似结果保存到矩阵R中,如果输入图像的大小(H×W)且模板图像的大小(w×h),则输出矩阵R的大小为(W-w+1,H-h+1),将R显示为图像为:
(4)获取到结果矩阵后,查找最大值所在的位置,那么该位置对应的区域被认为是最匹配的。对应的区域就是以该点为顶点,长宽和模板图像一样大小的矩阵。
(1)API: res=cv2.matchTemplate(img,template,method)
参数:
img:进行模板匹配的图像
template:模板
method:实现模板匹配的算法。主要有:
1.平方差匹配(CV_TM_SQDIFF):利用模板与图像之间的平方差进行匹配,最好的匹配是0,匹配越差,匹配的值越大。
2.相关匹配(CV_TM_CCORR):利用模板与图像间的乘法进行匹配,数值越大表示匹配程度越高,越小表示匹配效果差。
3. 利用相关系数匹配(CV_TM_CCOEFF):利用模板与图像之间的相关系数匹配,1表示匹配完美,1表示匹配最差。
(2)cv2.minMaxLoc()方法查找最大值所在的位置。 如果使用平方差比较方法,则最小值位置是最佳匹配的位置
import cv2
import matplotlib.pyplot as plt
import numpy as np
#1. 读取图像,灰度形式
img=cv2.imread('template.png',1)#灰度值的方式读取图像
tem=cv2.imread('temp.png',1)
#2.模板匹配
r=cv2.matchTemplate(img,tem,cv2.TM_CCORR)
#3.寻找匹配后的最小值,最大值,及其位置
min_val,max_val,min_loc,max_loc=cv2.minMaxLoc(r)
# 4.寻找最佳匹配位置
top_left=max_loc
h,w=tem.shape[:2]
bottom_right=(top_left[0]+w,top_left[1]+h)
cv2.rectangle(img,top_left,bottom_right,color=(0,255,255))
cv2.imshow('origin',img)
cv2.waitKey(0)
注意: 模板匹配不适合用于尺度变换、视角变换后的图像,这时需要用到关键点匹配算法.
经典的关键点检测算法: 包括SIFT和SURF,主要的思路是首先通过关键点检测算法获取模板和测试图像中的关键点;然后使用关键点匹配算法处理即可,这些关键点可以很好的处理尺度变化、视角变换、旋转变换、光照变换,就有很好的不变性。
在笛卡尔坐标系中,一条直线由两个点A=(x1,y1)和B(x2,y2)确定,如下图所示:
将直线y=kx+q可写成关于斜率和截距(k,q)的表达式:
通过图形的直观的表示如下:
(左为笛卡尔空间,右为霍夫空间)
笛卡尔坐标系中的一条直线,对应于霍夫空间中的一个点。反过来,霍夫空间中的一个直线,对应于笛卡尔坐标系中的一个点:
(1)AB两点,对应霍夫空间的情景:(AB两点在霍夫空间中的交点,代表由AB两点构成的直线的斜率和截距)
(2)ABC三点,,对应霍夫空间的情景:(ABC三点在霍夫空间中的交点,代表由ABC三点构成的直线的斜率和截距)
(3)多个点,可能存在多个直线时:选择尽可能多的直线汇成的点,构成笛卡尔坐标系中的直线
笛卡尔空间映射到霍夫空间的问题:
上述情况下,霍夫空间中难以确定直线的截距和斜率(k,q),因此考虑将笛卡尔坐标转换为极坐标。
笛卡尔坐标系和极坐标系:
在极坐标系中,极坐标中的点对应霍夫空间中的线,这时霍夫空间是(ρ,θ)的空间,ρ是原点到直线的垂直距离,θ代表直线与横轴顺时针方向的夹角,垂直线的角度为0度,水平线的角度是180度。
假设有一个大小为100×100的图片,使用霍夫变换检测图片中的直线,步骤如下:
(1)直线在霍夫空间中可以使用坐标(ρ,θ)表示,首先创建一个2D数组,我们叫做累加器,初始化所有值为0,行表示ρ,列表示θ
该数组的大小决定了结果的准确性,若希望角度的精度为1度,那就需要180列。对于ρ,最大值为图片对角线的距离,如果希望精度达到像素级别,行数应该与图像的对角线的距离相等。
(2)取直线上的第一个点(x,y),将其带入直线在极坐标中的公式中,然后遍历θ的取值:0、1、2…180,分别求出对应的ρ值,如果这个数字在上述累加器中存在相应的位置,则在该位置上的数值加1。
(3)取直线上的第二个点,重复上述步骤,更新累加器中的值,对图像中的直线上的每个店都执行以上步骤,每次更新累加器中的值。
(4)搜索累加器中的最大值,并找到其对应的(ρ,θ),就可以将图像中但是直线表示出来。
API: cv2.HoughLines(img,rho,theta,threshold)
参数:
img:检测的图像,要求是二值化图像,所以在调用霍夫变换之前首先要进行二值化,或者进行Canny边缘检测
rho、thrta:ρ和θ的精确度
threshold:阈值,只有累加器中的值高于该阈值时才认为是直线。
import cv2
import matplotlib.pyplot as plt
import numpy as np
#1. 读取图像,灰度形式
img2=cv2.imread('Hough_line.png')#
img=cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)#
#2.cannny边缘检测
canny=cv2.Canny(img,50,150)
#3.Hough直线检测,得到ρθ的矩阵
line=cv2.HoughLines(canny,0.8,np.pi/180,150)
print(line)
#4 绘制直线
for i in line:
rho,theta=i[0]
a=np.cos(theta)
b=np.sin(theta)
x=rho*a
y=rho*b
x1=int(x+1000*(-b))
y1=int(y+1000*a)
x2=int(x-1000*(-b))
y2=int(y-1000*a)
cv2.line(img2,(x1,y1),(x2,y2),(0,255,0))
cv2.imshow('origin',img2)
cv2.imshow('canny',canny)
cv2.waitKey(0)
圆的表达式是:
(x-a)2+(y-b)2=r
其中a和b表示圆心坐标,r表示圆半径,因此标准的霍夫圆检测就是在这三个参数组成的三维空间累加器上进行圆形检测,此时效率就会很低,所有OpenCV中使用Hough梯度法进行原型检测。
Huogh梯度法检测圆形流程:
(1)检测圆心:圆心是圆周法线的交汇处,设置一个阈值,在某点的相交的直线的条数大于这个阈值就认为该交汇点为圆心。
(2)源半径确定:圆心到圆周上的距离是相同的,确定一个阈值,只要相同的距离的数量大于阈值,就认为该距离是该圆心的半径。
原则上:霍夫变换可以检测任何形状,但复杂的形状需要的参数很多,霍夫空间的维数就多,因此在程序实现上所需的内存空间以及运行效率上都不利于把标准的霍夫变换应用于复杂图形的检测中。霍夫梯度法是霍夫变换的改进,它的目的是减小霍夫空间的维度,提高效率。
API: circle=cv2.HoughCircles(image,method,dp,minDist,param1=100,param2=100,minRadius=0,maxRadius=0)
参数:
img:输入图像,应输入灰度图像
method:使用霍夫变换圆检测的算法,它的参数是CV_HOUGH_GRADIENT
dp:霍夫空间的分辨率,dp=1时表示霍夫空间与输入图像空间的大小一致,dp=2时霍夫空间是输入图像空间的一般,以此类推
minDist:为圆心之间的最小距离,如果检测到两个圆心之间的距离小于该阈值,则认为他们是同一个源心
param1:边缘检测时使用Canny算子的高阈值,低阈值为高阈值的一半
param2:检测圆心和确定半径时所共有的阈值
minRadius和maxRadius为所检测到的圆半径的最小值和最大值。
import cv2
import matplotlib.pyplot as plt
import numpy as np
#1. 读取图像,灰度形式
img2=cv2.imread('circle.png')#
img=cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
#2.中值滤波
blur=cv2.medianBlur(img,7)
#3.Hough圆检测,
circles=cv2.HoughCircles(blur,cv2.HOUGH_GRADIENT,1,200,param1=100
,param2=50,minRadius=0,maxRadius=100)
print(circles)
#[
# [圆心x,圆心y,半径]
# ...
# ]
#4 绘制直线
for circle in circles[0,:]:
cv2.circle(img2,(circle[0],circle[1]),
int(circle[2]),(0,255,0),2)
cv2.imshow('origin',img2)
cv2.waitKey(0)