使用 OpenCV SIFT实现标记定位

以下程序的目的很简单:就是在屏幕空间中查找视图质心坐标。然后用这个坐标来定位粘贴靶位点。

# Finds view centroid coordinates in screen space.
    logging.info(' > finding projected point...')
    view_arr = np.array(view.convert('L'))
    screen_arr = np.array(screen.convert('L'))##把两张图片存储成数组,并传入函数
    # logging.info(f'{view_arr.shape}, {screen_arr.shape}')
    x, y = screenpoint.project(view_arr, screen_arr, False)

调用logging写错误日志就显得很专业……

import numpy as np
import cv2
import logging

MIN_MATCH_COUNT = 6#定义全局变量
FLANN_INDEX_KDTREE = 0 # kdtree建立索引方式的常量参数,获取flann匹配器

sift = cv2.xfeatures2d.SIFT_create()#实例化的sift函数


def project(view, screen, debug=False):
    kp_screen, des_screen = sift.detectAndCompute(screen, None)#找出图像中的关键点同时计算关键点对应的sift特征向量, kp表示生成的关键点,screen表示输入的灰度图,dst表示输出的sift特征向量,通常是128维的。
    ##检测关键点 计算描述符
    kp_view, des_view = sift.detectAndCompute(view, None)

    index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
    #第一个是index-Params。配置要使用的算法。
    ##使用ELANN算法&KDtree(K-NN匹配近邻法)
    ###使用5棵k-d 
    search_params = dict(checks=50)
    #第二个是Search-Params。指定递归遍历的次数。
    ##checks指定索引树要被遍历的次数。值越高结果越准确,但是消耗的时间也越多。
    flann = cv2.FlannBasedMatcher(index_params, search_params)
    matches = flann.knnMatch(des_screen, des_view, k=2)

Lowe’s algorithm

**Lowe’s算法:**为了进一步筛选匹配点,来获取优秀的匹配点,这就是所谓的“去粗取精”。一般会采用Lowe’s算法来进一步获取优秀匹配点。
为了排除因为图像遮挡和背景混乱而产生的无匹配关系的关键点,SIFT的作者Lowe提出了比较最近邻距离与次近邻距离的SIFT匹配方式:取一幅图像中的一个SIFT关键点,并找出其与另一幅图像中欧式距离最近的前两个关键点,在这两个关键点中,如果最近的距离除以次近的距离得到的比率ratio少于某个阈值T,则接受这一对匹配点。因为对于错误匹配,由于特征空间的高维性,相似的距离可能有大量其他的错误匹配,从而它的ratio值比较高。显然降低这个比例阈值T,SIFT匹配点数目会减少,但更加稳定,反之亦然。
Lowe推荐ratio的阈值为0.8,但作者对大量任意存在尺度、旋转和亮度变化的两幅图片进行匹配,结果表明ratio取值在0. 4~0. 6 之间最佳,小于0. 4的很少有匹配点,大于0. 6的则存在大量错误匹配点,所以建议ratio的取值原则如下:

  • ratio=0. 4:对于准确度要求高的匹配;
  • ratio=0. 6:对于匹配点数目要求比较多的匹配;
  • ratio=0. 5:一般情况下。

Fast Library for Approximate Nearest Neighbors

FLANN它是一个对大数据集和高维特征进行最近邻搜索的算法的集合,而且这些算法都已经被优化过了。在面对大数据集是它的效果要好于BFMatcher。
KDtree
随机k-d树算法(The Randomized k-dimensionality Tree Algorithm)属于快速近似NN匹配(FAST APPROXIMATE NN MATCHING)的一种。随机k-d森林在许多情形下都很有效,其中的具体划分树有两种。
a. Classic k-d tree
找出数据集中方差最高的维度,利用这个维度的数值将数据划分为两个部分,对每个子集重复相同的过程。
b. Randomized k-d tree
建立多棵随机k-d树,从具有最高方差的N_d维中随机选取若干维度,用来做划分。在对随机k-d森林进行搜索时候,所有的随机k-d树将共享一个优先队列。增加树的数量能加快搜索速度,但由于内存负载的问题,树的数量只能控制在一定范围内,比如20,如果超过一定范围,那么搜索速度不会增加甚至会减慢。
5在这里插入图片描述
一般遵循以下规律构造:
①各层节点交替划分各维空间
根节点划分x坐标
其儿子划分y坐标
其孙子划分z坐标

②每一层的区域中,按该层划分的坐标排序,选取其中位数作为划分点进行划分
切点作为父节点,左边的点划分到左子树中,右边的点划分到右子树中

③逐层划分,直至划分区域无节点
类似于一种快排。
(感谢博主的解释)

另:FlannBasedMatcher简称最近邻近似匹配。是一种近似匹配方法,并不追求完美!,因此速度更快。当然也可以通过调整FlannBasedMatcher的参数来提高匹配的精度或者提高算法速度,但是相应地算法速度或者算法精度会受到影响。

    # Store all good matches as per Lowe's ration test按照劳氏定量测试储存所有好的集合
    ## Lowe's algorithm,获取优秀匹配点
    #### 寻找距离近的放入good列表
    good = []
    for m, n in matches:
        if m.distance < 0.7 * n.distance:
            good.append(m)
 	# 如果足够多good  就筛选
    if len(good) < MIN_MATCH_COUNT:
        logging.debug("ScreenPoint: Not enough matches.")
        return -1, -1
	# 通过距离近的描述符 找到两幅图片的关键点。后有数据存储的解释。
    screen_pts = np.float32([kp_screen[m.queryIdx].pt
                             for m in good]).reshape(-1, 1, 2)
    view_pts = np.float32([kp_view[m.trainIdx].pt
                           for m in good]).reshape(-1, 1, 2)

Homography 单应性
此处引用我的另一篇笔记
计算多个二维点对之间的最优单映射变换矩阵,找到并返回源平面和目标平面之间的转换矩阵H,以便于反向投影错误率达到最小。

    h, w = view.shape
    M, mask = cv2.findHomography(view_pts, screen_pts, cv2.RANSAC, 5.0)

得到两帧图像中的特征点和单应性矩阵M后,可以
(1)根据相应的计算方法,由前一帧图像四个角的位置,就可以得到变换后四个角对应点的位置。
(2)直接用perspectiveTransform(透视变换)来得到四个角的位置。

在这里插入图片描述
透视变换示例图

cv2.perspectiveTransform

Python: cv2.perspectiveTransform(src, m[, dst]) → dst
Parameters:
src – input two-channel or three-channel floating-point array; each element is a 2D/3D vector to be transformed.
dst – output array of the same size and type as src.
m – 3x3 or 4x4 floating-point transformation matrix.

详细参考opencv文档

    pts = np.float32([[(w - 1) * 0.5, (h - 1) * 0.5]]).reshape(-1, 1, 2)
    ##使用reshape方法把32 位 float 型的二进制存储数据改为一个不限定行,一列,两层的三维数组!!32位存取数据!!怪不得叫我准备好6.4G内存
    ###使用三维数组是因为后面需要整理好的数据类型。
    dst = cv2.perspectiveTransform(pts, M)# 计算第二张图相对于第一张图的该函数透视变换二维或三维向量。
    x, y = np.int32(dst[0][0])

    if debug:
        img_debug = draw_debug_(x, y, view, screen, M, mask, kp_screen,
                                kp_view, good)
        return x, y, img_debug
    else:
        return x, y


def draw_debug_(x, y, view, screen, M, mask, kp_screen, kp_view, good):

    matchesMask = mask.ravel().tolist()
    draw_params = dict(
        matchColor=(0, 255, 0),  # draw matches in green color
        singlePointColor=None,
        matchesMask=matchesMask,  # draw only inliers
        flags=2)
    img_debug = cv2.drawMatches(screen, kp_screen, view, kp_view, good, None,
                                **draw_params)
	#获取img_debug空间中的视图质心坐标。
    # Get view centroid coordinates in img_debug space.
    cx = int(view.shape[1] * 0.5 + screen.shape[1])
    cy = int(view.shape[0] * 0.5)
    #绘制视图网络
    # Draw view outline.
    cv2.rectangle(img_debug, (screen.shape[1], 0),
                  (img_debug.shape[1] - 2, img_debug.shape[0] - 2),
                  (0, 0, 255), 2)
    #绘制连接线
    # draw connecting line.
    cv2.polylines(img_debug, [np.int32([(x, y), (cx, cy)])], True,
                  (100, 100, 255), 1, cv2.LINE_AA)
    # Draw query/match markers.
    cv2.drawMarker(img_debug, (cx, cy), (0, 0, 2
    55), cv2.MARKER_CROSS, 30, 2)
    cv2.circle(img_debug, (x, y), 10, (0, 0, 255), -1)

    return img_debug

在这里插入图片描述


版权声明:本文为weixin_43266423原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。