模板匹配和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与图像被模板覆盖的地方的差别程度,这个差别程度的计算方法在opencv里有6种,然后将每次计算的结果放入一个矩阵里,作为结果输出。假如原图是AxB大小,而模板是axb大小,则输出结果的矩阵是(A-a+1)x(B-b+1) (通过.shape查看验证)
匹配算法 | method |
---|---|
平方差匹配法:计算平方不同,计算出来的值越小,越相关 | cv2.TM_SQDIFF |
归一化平方差匹配法:计算归一化平方不同,计算出来的值越接近0,越相关 | cv2.TM_SQDIFF_NORMED |
相关匹配法:计算相关性,计算出来的值越大,越相关 | cv2.TM_CCORR |
归一化相关匹配法:计算归一化相关性,计算出来的值越接近1,越相关 | cv2.TM_CCORR_NORMED |
相关系数匹配法:计算相关系数,计算出来的值越大,越相关 | cv2.TM_CCOEFF |
归一化相关系数匹配法:计算归一化相关系数,计算出来的值越接近1,越相关 | cv2.TM_CCOEFF_NORMED |
通常来讲,随着从简单测量方法(平方差)到更复杂的测量方法(相关系数法),我们可以获得越来越准确的匹配。然而这同时也会以越来越大的计算量为代价。对于选取何种方法,针对不同的匹配情况进行对此分析比较,选取更适合自己应用场景同时兼顾速度和精度的最佳方案。一般使用归一化的。
cv2.minMaxLoc()函数会返回四个值——最小值及其位置、最大值及其位置(这里的位置是匹配框左上角顶点的坐标位置)
import cv2
img = cv2.imread('./image/img1.jpg', 0)
template = cv2.imread('./image/template.png', 0)
h, w = template.shape[:2]
# print(img.shape)
# print(template.shape)
# (225, 203)
# (82, 100)
res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
# print(res.shape) --> (144, 104)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
理解:利用这个函数找最大值最小值,以及从六种中选择出的匹配算法,两者结合,得到最匹配的点的坐标。由于这个点是匹配框的左上顶点,我们再求得模板图像的长和宽,有左上顶点、长、宽,即可得到与模板匹配的图像
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('./image/img1.jpg', 0)
template = cv2.imread('./image/template.png', 0)
h, w = template.shape[:2] # 切片,取shape的前两个值代表模板长宽,不取第三个值(代表BGR)
# 六种匹配方法
methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR',
'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']
for meth in methods:
img2 = img.copy() # 不然原图会被覆盖
# eval 语句用来计算存储在字符串中的有效 Python 表达式
method = eval(meth)
# 模板匹配
res = cv2.matchTemplate(img, template, method)
# 寻找最值
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# 使用不同的比较方法,对结果的解释不同
# 如果是平方差匹配或归一化平方差匹配,取最小值
if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
top_left = min_loc
else:
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
# 画矩形
cv2.rectangle(img, top_left, bottom_right, 255, 2)
# 展示
plt.subplot(121), plt.imshow(res, cmap='gray')
plt.title('Matching Result'), plt.xticks([]), plt.yticks([]) # 隐藏坐标轴
plt.subplot(122), plt.imshow(img, cmap='gray')
plt.title('Detected Point'), plt.xticks([]), plt.yticks([])
plt.suptitle(meth)
plt.show()
注意到其中有一个匹配算法的结果匹配的不好
import cv2
import numpy as np
img_rgb = cv2.imread('./image/img1.jpg')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread('./image/template.png', 0)
h, w = template.shape[:2]
# 选择的匹配算法是相关系数法
res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
threshold = 0.4
# 取匹配程度大于%40的坐标
loc = np.where(res >= threshold)
for pt in zip(*loc[::-1]): # *号表示可选参数
bottom_right = (pt[0] + w, pt[1] + h)
# 画矩形
cv2.rectangle(img_rgb, pt, bottom_right, (0, 0, 255), 2)
cv2.imshow("img", img_rgb)
cv2.waitKey(0)
什么是直方图?你可以将直方图视为图形或绘图,从而为你提供有关图像强度分布的总体思路。它是X轴上的像素值(范围从0到255,并非总是)和Y轴上图像中相应像素数量的绘图。
一些直方图相关的术语
BINS:上面的直方图显示每个像素值的像素数量,即从0到255。也就是说,你需要256个值来显示上述的直方图。但是请考虑,如果您不需要分别查找所有像素值的像素数量,但是像素值的间隔中的像素数量是多少?例如,你需要找到位于10到15之间,然后是16到31,…,240到255之间的像素值。你只需要16个值来表示直方图。这就是OpenCV教程中关于直方图的例子。
因此,你所做的只是将整个直方图拆分成16个子部分,每个子部分的值是其中所有像素数的总和。这个子部分被称为“BIN”。在第一种情况下,BINS中的每组的像素数目都是256,而在第二种情况下,它仅为16个。在OpenCV中,BINS由术语hitSize表示。
DIMS:这是我们收集数据的参数数量。在这种情况下,我们只收集强度值的数量,所以这里是1.
RANGE:这是你要测量的强度值的范围。通常,它是[0,256],即所有强度值。
1)OpenCV中的直方图计算
我们使用cv2.calcHist()函数来查找直方图。让我们熟悉这个函数及其参数:
Cv2.calcHist(image,channels,mask,hitSize,range[,hist[,accumulate]])
img = cv2.imread('1.jpg', 0)
hist = cv2.calcHist([img], [0], None, [256], [0, 256])
#hist是一个256*1的数组,每个值对应于图像中具有相应像素值的像素数
print(hist)
2)Numpy中的直方图计算
img = cv2.imread('1.jpg', 0)
hist, bins = np.histogram(img.ravel(), 256, [0, 256])
print("hist", hist)
print("bins", bins)
hist与我们之前计算的相同。但是bins将有257个元素因为Numpy计算bins为0-0.99,1-1.99,2-2.99等。为了表示这一点,他们还在bins的末尾加上256.但我们不需要高达256,255就够了。OpenCV函数比np.histogram()要快(大约40倍)。所以坚持使用OpenCV函数。
两种方法:
? Shortway:使用matplotlib绘图函数
? Long way:使用OpenCV绘图函数
1)使用Matplotlib(主要)
Matplotlib带有一个直方图绘制函数:matplot.pyplot.hist()
它直接找到直方图并绘制它.不需要使用calcHist()或者np.histogram()函数来查找直方图。
`import cv2`
`from matplotlib import pyplot as plt`
`img = cv2.imread('home.jpg',0)`
`plt.hist(img.ravel(),256,[0,256]); plt.show()`
或者你可以使用matplotlib的正常绘制方式,这对BGR绘制是有利的.为此,你首先需要查找直方图数据。
`import cv2`
`from matplotlib import pyplot as plt`
`img = cv2.imread('8.jpg')`
`color = ('b', 'g', 'r')`
`for i, col in enumerate(color): # 枚举`
`histr = cv2.calcHist([img], [i], None, [256], [0, 256])`
`plt.plot(histr, color=col)`
`plt.xlim([0, 256])`
`plt.show()`
2)使用OpenCV
用OpenCV的话,你可以将直方图的值与其二进制一起调整为x,y坐标,以便你可以使用cv2.line()或cv2.polyline()函数绘制它以生成与上面相同的图像。这已经在OpenCV-Python2官方demo中可用。
我们使用cv2.calcHist()来查找完整图像的直方图。如果你想查找图像中某些区域的直方图,该怎么办?只需在想查找直方图的区域创建一个带白色的蒙版图像,否则就是黑色。然后将它作为掩膜。
例子:
img = cv2.imread('2.jpg')`
#创建一个掩膜`
mask = np.zeros(img.shape[:2], dtype='uint8')`
mask[100:300, 100:400] = 255`
masked_img = cv2.bitwise_and(img, img, mask=mask)`
#计算有掩膜和没有掩膜时的直方图`
#只需改变第三个参数`
hist_full = cv2.calcHist([img], [0], None, [256], [0, 256])`
hist_mask = cv2.calcHist([img], [0], mask, [256], [0, 256])`
plt.subplot(221), plt.imshow(img, 'gray'), plt.title("origianl")`
plt.subplot(222), plt.imshow(mask, 'gray'), plt.title('mask')`
plt.subplot(223), plt.imshow(masked_img, 'gray'), plt.title('masked_img')`
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask), plt.title('hist')`
plt.xlim([0, 256])`
plt.show()`
解释代码:
mask = np.zeros(img.shape[:2], dtype=‘uint8’)
用法:zeros(shape, dtype=float, order=‘C’)
返回:返回来一个给定形状和类型的用0填充的数组;
参数:shape:形状
? dtype:数据类型,可选参数,默认np.float64
? order:可选参数,c代表与c语言类似,行优先;F代表列优先
plt.title(‘hist’)
将该figure对象的表头名命名为hist
plt.subplot(221)
subplot()函数则用来实现,在一个大图中,出现多个小的子图。
处理哪个figure,则选择哪个figure,再进行画图。
221表示是一个两行两列布局的图,且现在画的是右上角的小图
同理,236表示画的2行3列布局的最右下角的图
hist_full = cv2.calcHist([img], [0], None, [256], [0, 256])
plt.plot(hist_full)
hist_full是一个shape为(256,1)的数组,表示0-255每个像素值对应的像素个数,下标即为相应的像素值
plt.plot()一般需要输入x,y,若只输入一个参数,那么默认x为range(n),n为y的长度,在这里即表示图像x轴为0-255像素点灰度值,y轴为对应灰度值的像素点数量
一个plt.plot()代表该图像中的一条图线
plt.imshow()
负责对图像进行处理,并显示其格式,但是不能显示。
plt.show()
显示图像
plt.xlim([0,256])
x轴上的值的取值范围为0-256