python数字图像处理基础(六)——模板匹配、直方图

发布时间:2024年01月18日

模板匹配

概念

模板匹配和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与图像被模板覆盖的地方的差别程度,这个差别程度的计算方法在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.查找直方图

1)OpenCV中的直方图计算

我们使用cv2.calcHist()函数来查找直方图。让我们熟悉这个函数及其参数:

Cv2.calcHist(image,channels,mask,hitSize,range[,hist[,accumulate]])

  • image(图像):它是类型为uint8或者float32的源图像。应该用方括号给出,即“[img]”.
  • .Channels(通道):它也是被放在方括号内。它是我们计算直方图的通道的索引。例如,如果输入是灰度图像,则其值为[0].对于彩色图像,可以分别通过[0],[1]或者[2]计算蓝色、绿色或者红色通道的直方图 。
  • Mask(掩膜):要查找完整图像的直方图,它会显示为“无”。但是,如果你想找到特定区域的图像直方图,你必须为此创建一个蒙版图像并将其作为蒙版
  • histSize:这代表我们的BIN数量。需要用方括号给出。对于全尺寸,我们传入[256].
  • Range(范围):通常情况下,它是[0,256].

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函数。

2.绘制直方图

两种方法:

? 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中可用。

3.掩膜的应用

我们使用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


文章来源:https://blog.csdn.net/SavEMyselF1/article/details/135662699
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。