jjzjj

openCV-python模板匹配(旋转)

萌萌糕~ 2024-04-24 原文

本文将介绍使用OpenCV实现多角度模板匹配的详细步骤 + 代码。

背景介绍

熟悉OpenCV的朋友肯定都知道OpenCV自带的模板匹配matchTemplate方法是不支持旋转的,也就是说当目标和模板有角度差异时匹配常常会失败,可能目标只是轻微的旋转,匹配分数就会下降很多,导致匹配精度下降甚至匹配出错。本文介绍基于matchTemplate + 旋转 + 金字塔下采样实现多角度的模板匹配,返回匹配结果(坐标、角度)。

实现思路

【1】如何适应目标的角度变化?我们可以将模板旋转,从0~360°依次匹配找到最佳的匹配位置;

【2】如何提高匹配速度?使用金字塔下采样,将模板和待匹配图均缩小后匹配;加大匹配搜寻角度的步长,比如从每1°匹配一次改为每5°匹配一次等。

实现步骤

【1】旋转模板图像。旋转图像本身比较简单,下面是代码:

# 图片旋转函数
def ImageRotate(img, angle):   # img:输入图片;newIm:输出图片;angle:旋转角度(°)
    height, width = img.shape[:2]  # 输入(H,W,C),取 H,W 的值
    center = (width // 2, height // 2)  # 绕图片中心进行旋转
    M = cv.getRotationMatrix2D(center, angle, 1.0)
    image_rotation = cv.warpAffine(img, M, (width, height))
    return image_rotation

但需要注意,很多时候按照上面方法旋转时,会丢失模板信息产生黑边,这里是进行裁剪模板为圆形ROI

# 取圆形ROI区域函数:具体实现功能为输入原图,取原图最大可能的原型区域输出
def circle_tr(src):
    dst = np.zeros(src.shape, np.uint8)  # 感兴趣区域ROI
    mask = np.zeros(src.shape, dtype='uint8')  # 感兴趣区域ROI
    (h, w) = mask.shape[:2]
    (cX, cY) = (w // 2, h // 2)  # 是向下取整
    radius = int(min(h, w) / 2)
    cv.circle(mask, (cX, cY), radius, (255, 255, 255), -1)
    # 以下是copyTo的算法原理:
    # 先遍历每行每列(如果不是灰度图还需遍历通道,可以事先把mask图转为灰度图)
    for row in range(mask.shape[0]):
        for col in range(mask.shape[1]):
            # 如果掩图的像素不等于0,则dst(x,y) = scr(x,y)
            if mask[row, col] != 0:
                # dst_image和scr_Image一定要高宽通道数都相同,否则会报错
                dst[row, col] = src[row, col]
                # 如果掩图的像素等于0,则dst(x,y) = 0
            elif mask[row, col] == 0:
                dst[row, col] = 0
    return dst

【2】图像金字塔下采样。什么是图像金字塔?什么是上下采样?直接百度。

减小图像分辨率提高图像匹配速度,代码如下:

# 金字塔下采样
def ImagePyrDown(image,NumLevels):
    for i in range(NumLevels):
        image = cv.pyrDown(image)       #pyrDown下采样
    return image

【3】0~360°各角度匹配。旋转模板图像,依次调用matchTemplate在目标图中匹配,记录最佳匹配分数,以及对应的角度。旋转匹配代码:

# 旋转匹配函数(输入参数分别为模板图像、待匹配图像)
def RatationMatch(modelpicture, searchpicture):
    searchtmp = []
    modeltmp = []

    searchtmp = ImagePyrDown(searchpicture, 3)
    modeltmp = ImagePyrDown(modelpicture, 3)

    newIm = circle_tr(modeltmp)
    # 使用matchTemplate对原始灰度图像和图像模板进行匹配
    res = cv.matchTemplate(searchtmp, newIm, cv.TM_SQDIFF_NORMED)
    min_val, max_val, min_indx, max_indx = cv.minMaxLoc(res)
    location = min_indx
    temp = min_val
    angle = 0     # 当前旋转角度记录为0

    tic = time.time()
    # 以步长为5进行第一次粗循环匹配
    for i in range(-180, 181, 5):
        newIm = ImageRotate(modeltmp, i)
        newIm = circle_tr(newIm)
        res = cv.matchTemplate(searchtmp, newIm, cv.TM_SQDIFF_NORMED)
        min_val, max_val, min_indx, max_indx = cv.minMaxLoc(res)
        if min_val < temp:
            location = min_indx
            temp = min_val
            angle = i
    toc = time.time()
    print('第一次粗循环匹配所花时间为:' + str(1000*(toc - tic)) + 'ms')

    tic = time.time()
    # 在当前最优匹配角度周围10的区间以1为步长循环进行循环匹配计算
    for j in range(angle-5, angle+6):
        newIm = ImageRotate(modeltmp, j)
        newIm = circle_tr(newIm)
        res = cv.matchTemplate(searchtmp, newIm, cv.TM_SQDIFF_NORMED)
        min_val, max_val, min_indx, max_indx = cv.minMaxLoc(res)
        if min_val < temp:
            location = min_indx
            temp = min_val
            angle = j
    toc = time.time()
    print('在当前最优匹配角度周围10的区间以1为步长循环进行循环匹配所花时间为:' + str(1000*(toc - tic)) + 'ms')

    tic = time.time()
    # 在当前最优匹配角度周围2的区间以0.1为步长进行循环匹配计算
    k_angle = angle - 0.9
    for k in range(0, 19):
        k_angle = k_angle + 0.1
        newIm = ImageRotate(modeltmp, k_angle)
        newIm = circle_tr(newIm)
        res = cv.matchTemplate(searchtmp, newIm, cv.TM_SQDIFF_NORMED)
        min_val, max_val, min_indx, max_indx = cv.minMaxLoc(res)
        if min_val < temp:
            location = min_indx
            temp = min_val
            angle = k_angle
    toc = time.time()
    print('在当前最优匹配角度周围2的区间以0.1为步长进行循环匹配所花时间为:' + str(1000*(toc - tic)) + 'ms')

    # 用下采样前的图片来进行精匹配计算
    k_angle = angle - 0.1
    newIm = ImageRotate(modelpicture, k_angle)
    newIm = circle_tr(newIm)
    res = cv.matchTemplate(searchpicture, newIm, cv.TM_CCOEFF_NORMED)
    min_val, max_val, min_indx, max_indx = cv.minMaxLoc(res)
    location = max_indx
    temp = max_val
    angle = k_angle
    for k in range(1, 3):
        k_angle = k_angle + 0.1
        newIm = ImageRotate(modelpicture, k_angle)
        newIm = circle_tr(newIm)
        res = cv.matchTemplate(searchpicture, newIm, cv.TM_CCOEFF_NORMED)
        min_val, max_val, min_indx, max_indx = cv.minMaxLoc(res)
        if max_val > temp:
            location = max_indx
            temp = max_val
            angle = k_angle

    location_x = location[0] + 50
    location_y = location[1] + 50

    # 前面得到的旋转角度是匹配时模板图像旋转的角度,后面需要的角度值是待检测图像应该旋转的角度值,故需要做相反数变换
    angle = -angle

    match_point = {'angle': angle, 'point': (location_x, location_y)}
    return match_point

【4】标注匹配结果。根据模板图大小、匹配结果角度画出匹配框,代码如下:

# 画图
def draw_result(src, temp, match_point):
    cv.rectangle(src, match_point,
                  (match_point[0] + temp.shape[1], match_point[1] + temp.shape[0]),
                  (0, 255, 0), 2)
    cv.imshow('result', src)
    cv.waitKey()

【5】调用。对输入图像做预处理,并输出匹配到的结果,代码如下:

def get_realsense(src, temp):
    ModelImage = temp
    SearchImage = srcx
    ModelImage_edge = cv.GaussianBlur(ModelImage, (5, 5), 0)
    ModelImage_edge = cv.Canny(ModelImage_edge, 10, 200, apertureSize=3)
    SearchImage_edge = cv.GaussianBlur(SearchImage, (5, 5), 0)

    (h1, w1) = SearchImage_edge.shape[:2]
    SearchImage_edge = cv.Canny(SearchImage_edge, 10, 180, apertureSize=3)
    serch_ROIPart = SearchImage_edge[50:h1 - 50, 50:w1 - 50]  # 裁剪图像

    tic = time.time()
    match_points = RatationMatch(ModelImage_edge, serch_ROIPart)
    toc = time.time()
    print('匹配所花时间为:' + str(1000 * (toc - tic)) + 'ms')
    print('匹配的最优区域的起点坐标为:' + str(match_points['point']))
    print('相对旋转角度为:' + str(match_points['angle']))
    TmpImage_edge = ImageRotate(SearchImage_edge, match_points['angle'])
    cv.imshow("TmpImage_edge", TmpImage_edge)
    cv.waitKey()
    draw_result(SearchImage, ModelImage_edge, match_points['point'])
    return match_points

【6】举例演示。模板图从下图中截取并保存 template.png:

测试图像6张,匹配结果:

角度误差在正负1度左右。

后记

可以在此基础上添加匹配分数阈值和NMS实现多目标匹配。

有关openCV-python模板匹配(旋转)的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  3. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

  4. ruby - 匹配未转义的平衡定界符对 - 2

    如何匹配未被反斜杠转义的平衡定界符对(其本身未被反斜杠转义)(无需考虑嵌套)?例如对于反引号,我试过了,但是转义的反引号没有像转义那样工作。regex=/(?!$1:"how\\"#expected"how\\`are"上面的正则表达式不考虑由反斜杠转义并位于反引号前面的反斜杠,但我愿意考虑。StackOverflow如何做到这一点?这样做的目的并不复杂。我有文档文本,其中包括内联代码的反引号,就像StackOverflow一样,我想在HTML文件中显示它,内联代码用一些spanMaterial装饰。不会有嵌套,但转义反引号或转义反斜杠可能出现在任何地方。

  5. ruby - 匹配大写字母并用后续字母填充,直到一定的字符串长度 - 2

    我有一个驼峰式字符串,例如:JustAString。我想按照以下规则形成长度为4的字符串:抓取所有大写字母;如果超过4个大写字母,只保留前4个;如果少于4个大写字母,则将最后大写字母后的字母大写并添加字母,直到长度变为4。以下是可能发生的3种情况:ThisIsMyString将产生TIMS(大写字母);ThisIsOneVeryLongString将产生TIOV(前4个大写字母);MyString将生成MSTR(大写字母+tr大写)。我设法用这个片段解决了前两种情况:str.scan(/[A-Z]/).first(4).join但是,我不太确定如何最好地修改上面的代码片段以处理最后一种

  6. ruby-on-rails - Rails 3,嵌套资源,没有路由匹配 [PUT] - 2

    我真的为这个而疯狂。我一直在搜索答案并尝试我找到的所有内容,包括相关问题和stackoverflow上的答案,但仍然无法正常工作。我正在使用嵌套资源,但无法使表单正常工作。我总是遇到错误,例如没有路线匹配[PUT]"/galleries/1/photos"表格在这里:/galleries/1/photos/1/edit路线.rbresources:galleriesdoresources:photosendresources:galleriesresources:photos照片Controller.rbdefnew@gallery=Gallery.find(params[:galle

  7. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

  8. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  9. 旋转矩阵的几何意义 - 2

    点向量坐标矩阵的几何意义介绍旋转矩阵的几何含义之前,先介绍一下点向量坐标矩阵的几何含义点:在一维空间下就是一个标量,如同一条直线上,以任意某一个位置为0点,以一定的尺度间隔为1,2,3...,相反方向为-1,-2,-3...;如此就形成了一维坐标系,这时候任何一个点都可以用一个数值表示,如点p1=5,即即从原点出发沿着x轴正方向移动5个尺度;点p2=-3,负方向移动3个尺度;     在一维坐标系上过原点做垂直于一维坐标系的直线,则形成了二维坐标系,此时描述一个点需要两个数值来表示点p3=(3,2),即从原点出发沿着x轴正方向移动3个尺度,在此基础上沿着y轴正方向移动两个尺度的位置就是点p3。

  10. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

随机推荐