如何在opencv中对图像及其注释进行图像配准?

1 投票
1 回答
57 浏览
提问于 2025-04-14 16:01

我想把一个源图像上的多边形标签转移到一个目标图像上。目标图像其实就是源图像,只是稍微移动了一下。我找到了一段代码,可以用来把源图像和目标图像对齐。把这段代码写成一个函数后,变成了:

import numpy as np
import cv2

def register_images(
        align: np.ndarray,
        reference: np.ndarray,
):
    """
    Registers two RGB images with each other.

    Args:
        align: Image to be aligned. 
        reference: Reference image to be used for alignment.

    Returns:
        Registered image and transformation matrix.
    """
    # Convert to grayscale if needed
    _align = align.copy()
    _reference = reference.copy()
    if _align.shape[-1] == 3:
        _align = cv2.cvtColor(_align, cv2.COLOR_RGB2GRAY)
    if _reference.shape[-1] == 3:
        _reference = cv2.cvtColor(_reference, cv2.COLOR_RGB2GRAY)

    height, width = _reference.shape

    # Create ORB detector with 5000 features
    orb_detector = cv2.ORB_create(500)

    # Find the keypoint and descriptors
    # The first arg is the image, second arg is the mask (not required in this case).
    kp1, d1 = orb_detector.detectAndCompute(_align, None)
    kp2, d2 = orb_detector.detectAndCompute(_reference, None)

    # Match features between the two images
    # We create a Brute Force matcher with Hamming distance as measurement mode.
    matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)

    # Match the two sets of descriptors
    matches = list(matcher.match(d1, d2))

    # Sort matches on the basis of their Hamming distance and select the top 90 % matches forward
    matches.sort(key=lambda x: x.distance)
    matches = matches[:int(len(matches) * 0.9)]
    no_of_matches = len(matches)

    # Define empty matrices of shape no_of_matches * 2
    p1 = np.zeros((no_of_matches, 2))
    p2 = np.zeros((no_of_matches, 2))
    for i in range(len(matches)):
        p1[i, :] = kp1[matches[i].queryIdx].pt
        p2[i, :] = kp2[matches[i].trainIdx].pt

    # Find the homography matrix and use it to transform the colored image wrt the reference
    homography, mask = cv2.findHomography(p1, p2, cv2.RANSAC)
    transformed_img = cv2.warpPerspective(align, homography, (width, height))

    return transformed_img, homography

现在,我可以获取到变换后的图像和用来对齐这两张图像的变换矩阵。不过,我不太明白的是,怎么把同样的变换应用到用来标注图像的多边形和边界框上。

具体来说,标注是用COCO格式的,这意味着你可以这样访问坐标:

x0, y0, width, height = bounding_box

而标注是一个多边形坐标的列表:

segmentations = [poly1, poly2, poly3, ...]  # segmentations are a list of polygons
for poly in segmentations:
    x_coords = poly[0::2]  # x coordinates are integer values on the even index in the poly list
    y_coords = poly[1::2]  # y coordinates are integer values on the odd index in the poly list

一旦我获取到x和y坐标,怎么才能把变换矩阵应用上去呢?

1 个回答

3

多边形

对于任何多边形,只需将其通过 perspectiveTransform() 函数和一个变换矩阵一起处理就可以了。这就是全部。

perspectiveTransform() 会处理所有的数学运算,包括将 (x,y) 点扩展为 (x,y,1),进行矩阵乘法,最后再通过添加的 w 维度进行除法,并去掉这个多余的维度。

确保多边形以 numpy 数组的形式提供。如果 OpenCV 出现问题,确保数组的形状是 (N, 1, 2),其中 N 是 (x,y) 坐标的点数。dtype 也可能导致问题,它可能需要浮点数,或者特定的宽度。

盒子

无论你开始时的盒子是什么类型,你都需要计算出它的角点。现在它就变成了一个 多边形。接下来:参考上面的内容。

如果你以这种方式变换一个轴对齐的边界框,它可能会因为变换(透视、剪切、旋转等)而不再是轴对齐的。如果你需要一个围绕变换后盒子或多边形的轴对齐框,可以对这些点调用 boundingRect()

仅仅变换盒子的左上角和右下角是不够的。如果变换是一般的旋转(或其他任何非平移的变换),而你将变换后的点当作新的轴对齐盒子的角点,那么这个盒子就会 失效。它将无法与原来描述的图像部分对齐。

想象一下丘比特的脚把它压扁了,蒙提·派森风格

第一张盒子图片来自 Creativity103 在 Flickr第二张盒子图片的缩略图来自某个 Getty 用户


OpenCV 的变换矩阵是以 正向 的方式给出的。它们可以直接在点上使用 perspectiveTransform()

你可能需要,也可能 不需要 反转变换矩阵。这取决于你是如何计算它的。可以用 np.linalg.inv() 来反转,OpenCV 里可能也有类似的函数。

warp...() 函数会隐式地反转它们给出的变换矩阵,因为采样算法需要这样做。当给定 WARP_INVERSE_MAP 标志时,传入的变换矩阵被认为已经反转,因此不会被隐式反转,而是直接用于“拉取”式的采样。

撰写回答