Python 中用于多图像的快速且鲁棒的图像拼接算法?

yol*_*oli 9 python opencv image-processing computer-vision image-stitching

我有一个固定相机,可以快速拍摄连续移动的产品的照片,但处于相同角度(平移视角)的固定位置。我需要将所有图像拼接成全景图。我尝试过使用 Stitcher 类。它有效,但计算时间很长。我还尝试使用另一种方法,即使用 SIFT 检测器 FNNbasedMatcher,查找单应性,然后扭曲图像。如果我只使用两个图像,此方法效果很好。对于多个图像,它仍然无法正确缝合它们。有谁知道这种情况下最好、最快的图像拼接算法?

这是我使用 Stitcher 类的代码。

import time
import cv2
import os
import numpy as np
import sys

def main():
    # read input images
    imgs = []
    path = 'pics_rotated/'
    i = 0
    for (root, dirs, files) in os.walk(path):
        images = [f for f in files]
        print(images)
        for i in range(0,len(images)):
            curImg = cv2.imread(path + images[i])
            imgs.append(curImg)

    stitcher = cv2.Stitcher.create(mode= 0)
    status ,result = stitcher.stitch(imgs)
    if status != cv2.Stitcher_OK:
        print("Can't stitch images, error code = %d" % status)
        sys.exit(-1)
    cv2.imwrite("imagesout/output.jpg", result)
    cv2.waitKey(0)


if __name__ == '__main__':
    start = time.time()
    main()
    end = time.time()
    print("Time --->>>>>", end - start)
    cv2.destroyAllWindows()enter code here
Run Code Online (Sandbox Code Playgroud)

Bur*_*rak 24

简报

尽管OpenCVStitcher提供了很多执行拼接的方法和选项,但由于其复杂性,我发现很难使用它。因此,我会尽力提供最少且最快的方式来执行拼接。如果您想知道更复杂的方法(例如曝光补偿),我强烈建议您查看详细的示例代码。作为旁注,如果有人可以将以下函数转换为使用 Stitcher 类,我将不胜感激。

介绍

为了将多个图像组合成同一视角,需要进行以下操作:

  • 检测并匹配特征。
  • 计算单应性(帧之间的透视变换)。
  • 将一个图像扭曲到另一个视角。
  • 结合基础图像和扭曲图像,同时跟踪原点的变化。
  • 给定组合模式,拼接多个图像。

特征检测与匹配

什么是特点? 它们是可区分的部分,例如正方形的角,在图像中保留。为了获得这些特征点,提出了不同的算法,例如 Harris、ORB、SIFT、SURF 等。cv::Feature2d完整列表请参见。我将使用 SIFT,因为它准确且足够快。

特征由关键点(图像中的位置)和描述符组成,描述符是表示特征属性的一组数字(例如 128 维向量)。

找到图像中的不同点后,我们需要匹配相应的点对。看cv::DescriptionMatcher。我将使用基于 Flann 的描述符匹配器。


首先,我们初始化描述符和匹配器类。

descriptor = cv.SIFT.create()
matcher = cv.DescriptorMatcher.create(cv.DescriptorMatcher.FLANNBASED)
Run Code Online (Sandbox Code Playgroud)

然后,我们找到每张图像中的特征。

(kps, desc) = descriptor.detectAndCompute(image, mask=None)
Run Code Online (Sandbox Code Playgroud)

现在我们找到对应的点对。

if (desc1 is not None and desc2 is not None and len(desc1) >=2 and len(desc2) >= 2):
    rawMatch = matcher->knnMatch(desc2, desc1, k=2)
matches = []
# ensure the distance is within a certain ratio of each other (i.e. Lowe's ratio test)
ratio = 0.75
for m in rawMatch:
    if len(m) == 2 and m[0].distance < m[1].distance * ratio:
        matches.append((m[0].trainIdx, m[0].queryIdx))
Run Code Online (Sandbox Code Playgroud)

单应性计算

单应性是从一种视图到另一种视图的透视变换。一个视图中的平行线在另一个视图中可能不平行,就像通往日落的道路一样。我们需要至少有 4 个对应点对。越多意味着必须分解或消除冗余数据。

将初始视图中的点转换为其扭曲位置的单应性矩阵。它是通过直接线性变换算法计算的 3x3 矩阵。有 8 个 DoF,矩阵中最后一个元素为 1。

[pt2] = H * [pt1]
Run Code Online (Sandbox Code Playgroud)

现在我们有了对应的点匹配,我们计算单应性。我们处理冗余数据的方法是RANSAC,它随机选择4个点对并使用最佳拟合结果。请参阅cv::findHomography参考资料 了解更多选项。

if len(matches) > 4:
    (H, status) = cv.findHomography(pts1, pts2, cv.RANSAC)
Run Code Online (Sandbox Code Playgroud)

扭曲到透视

通过计算单应性,我们知道源图像中的哪个点对应于目标图像中的哪个点。 为了不丢失源图像中的信息,我们需要将目标图像填充变换点落入负区域的量。 同时,我们需要跟踪原点的偏移量以拼接多个图像。

辅助功能

descriptor = cv.SIFT.create()
matcher = cv.DescriptorMatcher.create(cv.DescriptorMatcher.FLANNBASED)
Run Code Online (Sandbox Code Playgroud)
(kps, desc) = descriptor.detectAndCompute(image, mask=None)
Run Code Online (Sandbox Code Playgroud)
if (desc1 is not None and desc2 is not None and len(desc1) >=2 and len(desc2) >= 2):
    rawMatch = matcher->knnMatch(desc2, desc1, k=2)
matches = []
# ensure the distance is within a certain ratio of each other (i.e. Lowe's ratio test)
ratio = 0.75
for m in rawMatch:
    if len(m) == 2 and m[0].distance < m[1].distance * ratio:
        matches.append((m[0].trainIdx, m[0].queryIdx))
Run Code Online (Sandbox Code Playgroud)
[pt2] = H * [pt1]
Run Code Online (Sandbox Code Playgroud)

翘曲功能

if len(matches) > 4:
    (H, status) = cv.findHomography(pts1, pts2, cv.RANSAC)
Run Code Online (Sandbox Code Playgroud)

组合扭曲图像和目标图像

这是涉及曝光补偿等图像增强的步骤。为了简单起见,我们将使用均值混合。最简单的解决方案是覆盖目标图像中的现有数据,但平均操作对我们来说不是负担。

# find the ROI of a transformation result
def warpRect(rect, H):
    x, y, w, h = rect
    corners = [[x, y], [x, y + h - 1], [x + w - 1, y], [x + w - 1, y + h - 1]]
    extremum = cv.transform(corners, H)
    minx, miny = np.min(extremum[:,0]), np.min(extremum[:,1])
    maxx, maxy = np.max(extremum[:,0]), np.max(extremum[:,1])
    xo = int(np.floor(minx))
    yo = int(np.floor(miny))
    wo = int(np.ceil(maxx - minx))
    ho = int(np.ceil(maxy - miny))
    outrect = (xo, yo, wo, ho)
    return outrect
Run Code Online (Sandbox Code Playgroud)
# homography matrix is translated to fit in the screen
def coverH(rect, H):
    # obtain bounding box of the result
    x, y, _, _ = warpRect(rect, H)
    # shift amount to the first quadrant
    xpos = int(-x if x < 0 else 0)
    ypos = int(-y if y < 0 else 0)
    # correct the homography matrix so that no point is thrown out
    T = np.array([[1, 0, xpos], [0, 1, ypos], [0, 0, 1]])
    H_corr = T.dot(H)
    return (H_corr, (xpos, ypos))
Run Code Online (Sandbox Code Playgroud)

拼接给定组合模式的多个图像

# pad image to cover ROI, return the shift amount of origin
def addBorder(img, rect):
    x, y, w, h = rect
    tl = (x, y)    
    br = (x + w, y + h)
    top = int(-tl[1] if tl[1] < 0 else 0)
    bottom = int(br[1] - img.shape[0] if br[1] > img.shape[0] else 0)
    left = int(-tl[0] if tl[0] < 0 else 0)
    right = int(br[0] - img.shape[1] if br[0] > img.shape[1] else 0)
    img = cv.copyMakeBorder(img, top, bottom, left, right, cv.BORDER_CONSTANT, value=[0, 0, 0])
    orig = (left, top)
    return img, orig
Run Code Online (Sandbox Code Playgroud)

上述方法将先前组合的图像(称为全景)扭曲到随后的下一个图像上。然而,图案可能具有最佳缝合视图的连接点。

例如

1 2 3
4 5 6
Run Code Online (Sandbox Code Playgroud)

组合这些图像的最佳模式是

1 -> 2 <- 3
     |
     V
4 -> 5 <- 6
Run Code Online (Sandbox Code Playgroud)

1 & 2因此,我们需要最后一个函数来与2 & 31235456节点结合5

def size2rect(size):
    return (0, 0, size[1], size[0])
Run Code Online (Sandbox Code Playgroud)

要进行快速演示,您可以运行GitHub 中的Python 代码。如果你想在C++中使用上述方法,你可以看看Stitch库。欢迎对这篇文章进行任何 PR 或编辑。