Python OpenCV 轻松点,复习一下模板匹配吧

举报
梦想橡皮擦 发表于 2021/09/23 13:37:09 2021/09/23
【摘要】 Python OpenCV 365 天学习计划,与橡皮擦一起进入图像领域吧。本篇博客是这个系列的第 49 篇。 学在前面关于 OpenCV 中的模板匹配,在之前的博客 图像的模板匹配,Python OpenCV 取经之旅第 29 天。模板匹配就是在一个目标图像(大图)中检索模板图像(小图),进行该操作的核心是两个函数,一个是 cv2.matchTemplate 另一个是 cv2.minMax...

Python OpenCV 365 天学习计划,与橡皮擦一起进入图像领域吧。本篇博客是这个系列的第 49 篇。

学在前面

关于 OpenCV 中的模板匹配,在之前的博客 图像的模板匹配,Python OpenCV 取经之旅第 29 天

模板匹配就是在一个目标图像(大图)中检索模板图像(小图),进行该操作的核心是两个函数,一个是 cv2.matchTemplate 另一个是 cv2.minMaxLoc

模板匹配是将模板图像在目标图像上进行滑动,从左到右,从上到下,一个一个区域进行,类似卷积操作。

由上文提及的博客,我们也知道,如果模板图像的大小是 w*h,目标图像的大小是 W*H,那匹配到的图像大小是 W-w+1,H-h+1,这步骤的操作是由 cv2.matchTemplate 实现的,接下来在由 cv2.minMaxLoc 函数查找最大值和最小值的像素点位置,拿到这个位置会后就可以在目标图像上进行绘制了。

通过案例简单复盘

编写测试代码如下,代码中对于 method 参数进行了基本的罗列:

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt


def template_demo():
    tpl = cv.imread("./tpl.jpg")
    target = cv.imread("./t9.jpg")

    methods = [cv.TM_CCOEFF, cv.TM_CCORR, cv.TM_SQDIFF,
               cv.TM_CCORR_NORMED, cv.TM_CCOEFF_NORMED, cv.TM_SQDIFF_NORMED]
    th, tw = tpl.shape[:2]
    for md in methods:

        result = cv.matchTemplate(target, tpl, md)
        min_val, max_val, min_loc, max_loc = cv.minMaxLoc(result)
        # 如果方法是 TM_SQDIFF 或 TM_SQDIFF_NORMED,则取最小值
        if md == cv.TM_SQDIFF_NORMED or md == cv.TM_SQDIFF:
            tl = min_loc
        else:
            tl = max_loc
        br = (tl[0] + tw, tl[1] + th)
        cv.rectangle(target, tl, br, (0, 0, 255), 2)
        # cv.imshow("match-" + np.str(md), target)

        plt.subplot(121), plt.imshow(result, cmap='gray')
        plt.title('result'), plt.xticks([]), plt.yticks([])
        plt.subplot(122), plt.imshow(cv.cvtColor(
            target, cv.COLOR_BGR2RGB), cmap='gray')
        plt.title('target'), plt.xticks([]), plt.yticks([])

        plt.show()


if __name__ == "__main__":

    template_demo()
    cv.waitKey(0)
    cv.destroyAllWindows()

先展示的结果是没有进行归一化操作的,后展示的为归一化操作的结果。

cv.TM_CCOEFF 运行结果

cv.TM_CCORR

cv.TM_SQDIFF

使用 cv.TM_CCORR_NORMED, cv.TM_CCOEFF_NORMED, cv.TM_SQDIFF_NORMED 等参数值的结果不在展示,问题出现在模板图片的选择上,因为左上角出现相同的像素点,所以导致有的结果出现 2 个匹配结果,最神奇的是,我对 result 进行输出,发现使用 cv.minMaxLoc 函数之后,并未返回多个结果,以下是参考数据,都是单个坐标。

-20675626.0 56209900.0 (355, 415) (201, 259)
58169844.0 325979360.0 (346, 4) (201, 259)
38019264.0 216403984.0 (201, 259) (313, 3)
0.559607207775116 0.9503162503242493 (307, 1) (201, 259)
-0.4676389992237091 0.7111679315567017 (356, 415) (201, 259)
0.1108362227678299 1.0 (201, 259) (282, 0)

为了验证出现的原因,我将矩形绘制的地方增加了颜色变动代码。

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt


def template_demo():
    tpl = cv.imread("./tpl.jpg")
    target = cv.imread("./t9.jpg")
    tpl = cv.cvtColor(tpl,cv.COLOR_BGR2RGB)
    target = cv.cvtColor(target,cv.COLOR_BGR2RGB)

    # methods = [cv.TM_CCOEFF, cv.TM_CCORR, cv.TM_SQDIFF,
    #            cv.TM_CCORR_NORMED, cv.TM_CCOEFF_NORMED, cv.TM_SQDIFF_NORMED]
	# 每次绘制的时候,都展示不同的颜色代码
    colors = [(0, 0, 255),(255, 0, 255),(0, 255, 255),(0, 0, 0),(255, 0, 0),(0,255, 0)]
    methods = [cv.TM_CCOEFF, cv.TM_CCORR, cv.TM_SQDIFF,
               cv.TM_CCORR_NORMED, cv.TM_CCOEFF_NORMED, cv.TM_SQDIFF_NORMED]
    th, tw = tpl.shape[:2]
    color_num = 0
    for md in methods:

        result = cv.matchTemplate(target, tpl, md)
        # print(result.shape)
        # cv.normalize(result, result, 0, 1, cv.NORM_MINMAX, -1)
        min_val, max_val, min_loc, max_loc = cv.minMaxLoc(result)
        print(min_val, max_val, min_loc, max_loc)
        # 如果方法是 TM_SQDIFF 或 TM_SQDIFF_NORMED,则取最小值
        if md == cv.TM_SQDIFF_NORMED or md == cv.TM_SQDIFF:
            tl = min_loc
        else:
            tl = max_loc
        br = (tl[0] + tw, tl[1] + th)
        print(colors[color_num])
        cv.rectangle(target, tl, br, colors[color_num], 5)
        color_num+=1
        # cv.imshow("match-" + np.str(md), target)
        plt.cla()
        plt.subplot(121), plt.imshow(result, cmap='gray')
        plt.title('result'), plt.xticks([]), plt.yticks([])
        plt.subplot(122), plt.imshow(target, cmap='gray')
        plt.title('target'), plt.xticks([]), plt.yticks([])

        plt.show()


if __name__ == "__main__":

    template_demo()
    cv.waitKey(0)
    cv.destroyAllWindows()

经过实验确定绘制的图像是上次绘制的残留,因为出现了如下内容,两次绘制的矩形边框颜色不一致问题。

知道原因了修改就比较容易了,只需要在循环的时候重新加载一下原图即可。

for md in methods:
      target = cv.imread("./t9.jpg")
       result = cv.matchTemplate(target, tpl, md)

在实际应用的时候,尽量使用带有归一化的参数,但是对于每个图像不同的参数都会导致不同的结果,需要给予实际情况进行判断,没有标准解。其它内容还可以去官网继续学习 地址

匹配到多个图像区域

在上述的案例中一直都是匹配到一个目标图像区域,如果希望在一幅图像中匹配到多个目标图像,使用下述代码即可。

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

def main():
    tpl = cv.imread("./big_tpl.jpg")
    target = cv.imread("./big.jpg")
    th, tw = tpl.shape[:2]
    result = cv.matchTemplate(target, tpl, cv.TM_CCOEFF_NORMED)
    threshold = 0.8
    loc = np.where(result >= threshold)

    for pt in zip(*loc[::-1]):
        cv.rectangle(target, pt, (pt[0] + tw,
                                   pt[1] + th), (0, 0, 255), 1)
    cv.imshow("img", target)
    cv.waitKey()
    cv.destroyAllWindows()

if __name__ == '__main__':
    main()

上述代码函数使用的是原函数 cv.matchTemplate 没有难度,重点在下面的代码部分出现的差异:

threshold = 0.8
loc = np.where(result >= threshold)

for pt in zip(*loc[::-1]):
    cv.rectangle(target, pt, (pt[0] + tw,
                               pt[1] + th), (0, 0, 255), 1)

其中 threshold=0.8 相当于临界值的概念,用于筛选匹配到的结果大于 0.8 的坐标,相当于拿模板图像与目标图像去匹配,匹配度告诉 80%,表示匹配上了,否则没有匹配上,所以降低这个值会导致匹配的结果变多,你可以自行尝试一下。

其中 np.where(condition) 函数表示输出满足 condition 的坐标,注意不是值,是坐标。
接下来的操作其实是对图像坐标的一些细节处理了,如果你对代码不清楚,可以按照下述输出进行比对

    threshold = 0.8
    loc = np.where(result >= threshold)
    print(loc)
    print(loc[::-1])
    print(list(zip(*loc[::-1])))
    for pt in zip(*loc[::-1]):
        cv.rectangle(target, pt, (pt[0] + tw,
                                   pt[1] + th), 255, 1)

输出结果如下:

(array([146, 146, 147, 147, 147, 147, 147, 147, 148, 148, 148, 148, 148,
       148, 148, 148, 148, 148, 149, 149, 149, 149, 149, 149, 149, 149,
       149, 149, 150, 150, 150, 150], dtype=int64), array([692, 979, 118, 405, 691, 692, 978, 979, 117, 118, 404, 405, 691,
       692, 693, 978, 979, 980, 117, 118, 119, 404, 405, 406, 692, 693,
       979, 980, 118, 119, 405, 406], dtype=int64))
(array([692, 979, 118, 405, 691, 692, 978, 979, 117, 118, 404, 405, 691,
       692, 693, 978, 979, 980, 117, 118, 119, 404, 405, 406, 692, 693,
       979, 980, 118, 119, 405, 406], dtype=int64), array([146, 146, 147, 147, 147, 147, 147, 147, 148, 148, 148, 148, 148,
       148, 148, 148, 148, 148, 149, 149, 149, 149, 149, 149, 149, 149,
       149, 149, 150, 150, 150, 150], dtype=int64))
[(692, 146), (979, 146), (118, 147), (405, 147), (691, 147), (692, 147), (978, 147), (979, 147), (117, 148), (118, 148), (404, 148), (405, 148), (691, 148), (692, 148), (693, 148), (978, 148), (979, 148), (980, 148), (117, 149), (118, 149), (119, 149), (404, 149), (405, 149), (406, 149), (692, 149), (693, 149), (979, 149), (980, 149), (118, 150), (119, 150), (405, 150), (406, 150)]

使用 loc = np.where(result >= threshold) 之后,得到了所有大于 0.8 的坐标点,但是这些点都是横纵坐标分开顺序排列,并且是孤立的,而且图像在绘制的时候,横纵坐标顺序是反着的,先高即纵,后宽即横,捣鼓清楚了,其他的都是 Python 基本操作。

对坐标理解清楚之后,完全可以修改代码如下,不需要这些变换,得到的结果是一致的。

for pt in zip(*loc):
      cv.rectangle(target, (pt[1],pt[0]), (pt[1] + th,
                                 pt[0] + tw), 255, 1)

为了提高效率,可以匹配灰度图,然后在彩色图像绘制即可。

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
def main():
    tpl = cv.imread("./big_tpl.jpg")
    target = cv.imread("./big.jpg")
    tpl_gray = cv.cvtColor(tpl, cv.COLOR_BGR2GRAY)
    target_gray = cv.cvtColor(target, cv.COLOR_BGR2GRAY)
    th, tw = tpl_gray.shape[:]
    result = cv.matchTemplate(target_gray, tpl_gray, cv.TM_CCOEFF_NORMED)
    print(result)
    threshold = 0.8
    loc = np.where(result >= threshold)
    for pt in zip(*loc[::-1]):
        cv.rectangle(target, pt, (pt[0] + tw,
                                   pt[1] + th), 255, 1)

    cv.imshow("target", target)

    cv.waitKey()
    cv.destroyAllWindows()

if __name__ == '__main__':
    main()

橡皮擦的小节

希望今天的 1 个小时你有所收获,我们下篇博客见~

相关阅读


技术专栏

  1. Python 爬虫 100 例教程,超棒的爬虫教程,立即订阅吧
  2. Python 爬虫小课,精彩 9 讲

今天是持续写作的第 92 / 100 天。
如果你想跟博主建立亲密关系,可以关注同名公众号 梦想橡皮擦,近距离接触一个逗趣的互联网高级网虫。
博主 ID:梦想橡皮擦,希望大家点赞评论收藏

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。