将仿射变换应用于边界矩形

时间:2017-12-25 16:58:03

标签: python-3.x opencv image-processing

我正在使用Python3& amp; OpenCV的。

我们可以使用SIFT关键点作为帧上行人轮廓的标识符,然后在两组SIFT关键点之间(即在一帧和下一帧之间)执行强力匹配,以在下一帧中找到行人。 为了在帧序列上可视化,我们可以绘制一个界定行人的边界矩形。这就是它的样子:

tracking example

主要问题是使用关键点来表征行人的运动。这里的想法是使用2个连续帧上的关键点的坐标来找到仿射变换(即在x& y,旋转和缩放中的平移)。理想情况下,这种仿射变换在某种程度上对应于行人的运动。为了跟踪这个行人,我们只需要在边界矩形坐标上应用相同的仿射变换。 最后一部分效果不佳。如下图或上图所示,矩形在几帧内不断缩小,不可避免地消失或偏离行人:

tracking results

要指定,我们使用2个极值点来定义边界矩形:

bounding rectangle coordinates

有一些内置的cv2函数可以对图像应用仿射变换,比如cv2.warpAffine(),但是我想将它仅应用于边界矩形坐标(即2点或1点+宽度和放大器) ;身高)。

为了找到两组关键点之间的仿射变换,我编写了自己的函数(如果有帮助,我可以发布代码),但是我在使用cv2.getAffineTransform()时观察到类似的结果。

您知道如何将仿射变换正确应用于此边界矩形吗?

编辑:这里有一些解释&更好的上下文代码:

  • 行人检测是通过openCV中提供的预先训练的SVM分类器完成的:hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())& hog.detectMultiScale()

  • 一旦检测到第一个行人,SVM就会返回相关边界矩形(xA, yA, w, h)的坐标(我们在第一次检测后停止使用SVM,因为它很慢,我们专注于一个现在是行人)

  • 我们使用image[yA: yA+h, xA: xA+w]选择当前帧的相应区域,并使用surf.detectAndCompute()

  • 搜索SURF关键点
  • 这会返回keypoints&它们的相关描述符(每个关键点的64个特征数组)

  • 我们基于描述符之间的L2范数和关键点之间的像素距离来执行强力匹配,以构建当前帧和之间的关键点对。前一个。此函数的代码很长,但应与cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)

  • 类似
  • 一旦我们有了匹配的关键点对,我们就可以用它们来找到这个函数的仿射变换:

    previousKpts = previousKpts[:5]  # select 4 best matches
    currentKpts = currentKpts[:5]
    
    # build A matrix of shape [2 * Nb of keypoints, 4]
    A = np.ndarray(((2 * len(previousKpts), 4)))
    
    for idx, keypoint in enumerate(previousKpts):
        # Keypoint.pt = (x-coord, y-coord)
        A[2 * idx, :] = [keypoint.pt[0], -keypoint.pt[1], 1, 0]
        A[2 * idx + 1, :] = [keypoint.pt[1], keypoint.pt[0], 0, 1]
    
    # build b matrix of shape [2 * Nb of keypoints, 1]
    b = np.ndarray((2 * len(previousKpts), 1))
    
    for idx, keypoint in enumerate(currentKpts):
        b[2 * idx, :] = keypoint.pt[0]
        b[2 * idx + 1, :] = keypoint.pt[1]
    
    # convert the numpy.ndarrays to matrix :
    A = np.matrix(A)
    b = np.matrix(b)
    
    # solution of the form x = [x1, x2, x3, x4]' = ((A' * A)^-1) * A' * b
    x = np.linalg.inv(A.T * A) * A.T * b
    
    theta = math.atan2(x[1, 0], x[0, 0])  # outputs rotation angle in [-pi, pi]
    alpha = math.sqrt(x[0, 0] ** 2 + x[1, 0] ** 2)  # scaling parameter
    bx = x[2, 0]  # translation along x-axis
    by = x[3, 0]  # translation along y-axis
    
    
    return theta, alpha, bx, by
    
  • 然后我们必须将相同的仿射变换应用于边界矩形的角点:

    # define the 4 bounding points using xA, yA
    xB = xA + w
    yB = yA + h
    rect_pts = np.array([[[xA, yA]], [[xB, yA]], [[xA, yB]], [[xB, yB]]], dtype=np.float32)
    
    # warp the affine transform into a full perspective transform
    affine_warp = np.array([[alpha*np.cos(theta), -alpha*np.sin(theta), tx],
                            [alpha*np.sin(theta), alpha*np.cos(theta), ty],
                            [0, 0, 1]], dtype=np.float32)
    
    # apply affine transform
    rect_pts = cv2.perspectiveTransform(rect_pts, affine_warp)
    xA = rect_pts[0, 0, 0]
    yA = rect_pts[0, 0, 1]
    xB = rect_pts[3, 0, 0]
    yB = rect_pts[3, 0, 1]
    
    return xA, yA, xB, yB
    
  • 保存更新的矩形坐标(xA, yA, xB, yB),所有当前关键点和&描述符,并迭代下一帧:使用我们之前保存的image[yA: yB, xA: xA]选择(xA, yA, xB, yB),获取SURF关键点等。

1 个答案:

答案 0 :(得分:0)

正如Micka所说,cv2.perspectiveTransform()是实现这一目标的简单方法。您只需要通过在底部添加值为[0, 0, 1]的第三行,将您的仿射变形转换为全透视变换(单应性)。例如,让我们在点w, h = 100, 200放置(10, 20)的框,然后使用仿射变换来移动点,以便将框移动到(0, 0)(即移位)左边10个像素,向上20个像素):

>>> xA, yA, w, h = (10, 20, 100, 200)
>>> xB, yB = xA + w, yA + h
>>> rect_pts = np.array([[[xA, yA]], [[xB, yA]], [[xA, yB]], [[xB, yB]]], dtype=np.float32)
>>> affine_warp = np.array([[1, 0, -10], [0, 1, -20], [0, 0, 1]], dtype=np.float32)
>>> cv2.perspectiveTransform(rect_pts, affine_warp)
array([[[   0.,    0.]],

       [[ 100.,    0.]],

       [[   0.,  200.]],

       [[ 100.,  200.]]], dtype=float32)

这样可以完美地按预期工作。您也可以通过矩阵乘法简单地转换点:

>>> rect_pts.dot(affine_warp[:, :2]) + affine_warp[:, 2]
array([[[   0.,    0.]],

       [[ 100.,    0.]],

       [[   0.,  200.]],

       [[ 100.,  200.]]], dtype=float32)