使用Python和OpenCV的图像拼接问题

gan*_*esh 3 python opencv image-processing computer-vision image-stitching

将 24 个拼接图像拼接到下一个第 25 个图像后,我得到如下输出。在此之前,缝合效果很好。

在此输入图像描述

有谁知道为什么/何时拼接输出会这样?这样的输出有什么可能性?这可能是什么原因?

拼接代码遵循标准拼接步骤,例如查找关键点、描述符,然后匹配点,计算单应性,然后扭曲图像。但我不明白为什么会有这样的输出。

拼接的核心部分如下:

detector = cv2.SIFT_create(400)
# find the keypoints and descriptors with SIFT
gray1 = cv2.cvtColor(image1,cv2.COLOR_BGR2GRAY)
ret1, mask1 = cv2.threshold(gray1,1,255,cv2.THRESH_BINARY)
kp1, descriptors1 = detector.detectAndCompute(gray1,mask1)

gray2 = cv2.cvtColor(image2,cv2.COLOR_BGR2GRAY)
ret2, mask2 = cv2.threshold(gray2,1,255,cv2.THRESH_BINARY)
kp2, descriptors2 = detector.detectAndCompute(gray2,mask2)

keypoints1Im = cv2.drawKeypoints(image1, kp1, outImage = cv2.DRAW_MATCHES_FLAGS_DEFAULT, color=(0,0,255))
keypoints2Im = cv2.drawKeypoints(image2, kp2, outImage = cv2.DRAW_MATCHES_FLAGS_DEFAULT, color=(0,0,255))

# BFMatcher with default params
matcher = cv2.BFMatcher()
matches = matcher.knnMatch(descriptors2,descriptors1, k=2)

# Apply ratio test
good = []
for m, n in matches:
    if m.distance < 0.75 * n.distance:
        good.append(m)

print (str(len(good)) + " Matches were Found")

if len(good) <= 10:
    return image1

matches = copy.copy(good)

matchDrawing = util.drawMatches(gray2,kp2,gray1,kp1,matches)

#Aligning the images
src_pts = np.float32([ kp2[m.queryIdx].pt for m in matches ]).reshape(-1,1,2)
dst_pts = np.float32([ kp1[m.trainIdx].pt for m in matches ]).reshape(-1,1,2)


H = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC,5.0)[0]

h1,w1 = image1.shape[:2]
h2,w2 = image2.shape[:2]
pts1 = np.float32([[0,0],[0,h1],[w1,h1],[w1,0]]).reshape(-1,1,2)
pts2 = np.float32([[0,0],[0,h2],[w2,h2],[w2,0]]).reshape(-1,1,2)
pts2_ = cv2.perspectiveTransform(pts2, H)
pts = np.concatenate((pts1, pts2_), axis=0)
# print("pts:", pts)
[xmin, ymin] = np.int32(pts.min(axis=0).ravel() - 0.5)
[xmax, ymax] = np.int32(pts.max(axis=0).ravel() + 0.5)
t = [-xmin,-ymin]
Ht = np.array([[1,0,t[0]],[0,1,t[1]],[0,0,1]]) # translate

result = cv2.warpPerspective(image2, Ht.dot(H), (xmax-xmin, ymax-ymin))

resizedB = np.zeros((result.shape[0], result.shape[1], 3), np.uint8)

resizedB[t[1]:t[1]+h1,t[0]:w1+t[0]] = image1
# Now create a mask of logo and create its inverse mask also
img2gray = cv2.cvtColor(result,cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(img2gray, 0, 255, cv2.THRESH_BINARY)

kernel = np.ones((5,5),np.uint8)
k1 = (kernel == 1).astype('uint8')
mask = cv2.erode(mask, k1, borderType=cv2.BORDER_CONSTANT)

mask_inv = cv2.bitwise_not(mask)

difference = cv2.bitwise_or(resizedB, resizedB, mask=mask_inv)

result2 = cv2.bitwise_and(result, result, mask=mask)

result = cv2.add(result2, difference)
Run Code Online (Sandbox Code Playgroud)

编辑:

此图像显示了匹配绘图,同时将 25 个图像缝合到结果直到 24 个图像:
在此输入图像描述

在那场比赛之前:
在此输入图像描述

我总共有 97 张图像需要拼接。如果我分别缝合 24 和 25 图像,它们会正确缝合。如果我从第 23 张图像开始缝合,那么缝合也很好,但当我从第 1 张图像开始缝合时,它会给我带来问题。我无法理解这个问题。

第23张图像拼接后的结果:
在此输入图像描述

第24张图像拼接后的结果:
在此输入图像描述

第25张图像拼接后的结果如上,出错了。

奇怪的观察:如果我用相同的代码分别缝合 23,24,25 个图像,它就会缝合。如果我在 23 到 97 之后缝合图像,它就会缝合。但不知何故,如果我从第 1 张开始缝合图像,则在缝合第 25 张图像时它会破裂。我不明白为什么会发生这种情况。

我尝试过不同的组合,例如不同的关键点检测、提取方法、匹配方法、不同的单应性计算、不同的扭曲代码,但这些组合不起作用。步骤组合代码中缺少某些内容或存在错误。我无法弄清楚。

抱歉问这么长的问题。由于我对此完全陌生,因此我无法正确解释和理解这些事情。感谢您的帮助和指导。

使用相同代码分别拼接23、24、25张图像的结果:
在此输入图像描述

使用不同的代码(在缝合之间给出黑线),如果我缝合 97 个图像,则第 25 个图像会在缝合中上升,并且如下所示缝合(右角点):
在此输入图像描述

Rah*_*dia 7

首先,我无法重现您的问题并解决它,因为图像太大,我的系统无法处理。但是,我在全景拼接项目中遇到了同样的问题,因此我分享其背后的原因以及解决问题的方法。希望这也对您有帮助。

当我像你一样将 4 张图像拼接在一起时,我的问题如下所示。

我的问题

正如您所看到的,第四张图像扭曲了很多,这是不应该发生的。同样的事情也发生在你身上,但程度更高。

现在,这是我在经过一些图像预处理后拼接 8 个图像时的输出。

图像预处理后输出

对输入图像进行一些预处理后,我能够将 8 张图像完美地拼接在一起,没有任何失真。

要了解这种失真背后的确切原因,请观看Joseph Redmon 在 50:26 - 1:07:23 之间制作的这段视频。

正如视频中所建议的,我们首先必须将图像投影到圆柱体上,然后展开它们,然后将这些展开的图像缝合在一起。

下面是初始输入图像(左)和投影并展开到圆柱体上后的图像(右)。

预处理前后的图像

对于您的问题,当您使用卫星图像时,我想投影到球体上会比圆柱体效果更好,但是您必须尝试一下。

下面分享我的代码,用于将图像投影到圆柱体上并将其展开以供参考。其背后使用的数学与视频中给出的相同。


def Convert_xy(x, y):
    global center, f

    xt = ( f * np.tan( (x - center[0]) / f ) ) + center[0]
    yt = ( (y - center[1]) / np.cos( (x - center[0]) / f ) ) + center[1]
    
    return xt, yt


def ProjectOntoCylinder(InitialImage):
    global w, h, center, f
    h, w = InitialImage.shape[:2]
    center = [w // 2, h // 2]
    f = 1100       # 1100 field; 1000 Sun; 1500 Rainier; 1050 Helens
    
    # Creating a blank transformed image
    TransformedImage = np.zeros(InitialImage.shape, dtype=np.uint8)
    
    # Storing all coordinates of the transformed image in 2 arrays (x and y coordinates)
    AllCoordinates_of_ti =  np.array([np.array([i, j]) for i in range(w) for j in range(h)])
    ti_x = AllCoordinates_of_ti[:, 0]
    ti_y = AllCoordinates_of_ti[:, 1]
    
    # Finding corresponding coordinates of the transformed image in the initial image
    ii_x, ii_y = Convert_xy(ti_x, ti_y)

    # Rounding off the coordinate values to get exact pixel values (top-left corner)
    ii_tl_x = ii_x.astype(int)
    ii_tl_y = ii_y.astype(int)

    # Finding transformed image points whose corresponding 
    # initial image points lies inside the initial image
    GoodIndices = (ii_tl_x >= 0) * (ii_tl_x <= (w-2)) * \
                  (ii_tl_y >= 0) * (ii_tl_y <= (h-2))

    # Removing all the outside points from everywhere
    ti_x = ti_x[GoodIndices]
    ti_y = ti_y[GoodIndices]
    
    ii_x = ii_x[GoodIndices]
    ii_y = ii_y[GoodIndices]

    ii_tl_x = ii_tl_x[GoodIndices]
    ii_tl_y = ii_tl_y[GoodIndices]

    # Bilinear interpolation
    dx = ii_x - ii_tl_x
    dy = ii_y - ii_tl_y

    weight_tl = (1.0 - dx) * (1.0 - dy)
    weight_tr = (dx)       * (1.0 - dy)
    weight_bl = (1.0 - dx) * (dy)
    weight_br = (dx)       * (dy)
    
    TransformedImage[ti_y, ti_x, :] = ( weight_tl[:, None] * InitialImage[ii_tl_y,     ii_tl_x,     :] ) + \
                                      ( weight_tr[:, None] * InitialImage[ii_tl_y,     ii_tl_x + 1, :] ) + \
                                      ( weight_bl[:, None] * InitialImage[ii_tl_y + 1, ii_tl_x,     :] ) + \
                                      ( weight_br[:, None] * InitialImage[ii_tl_y + 1, ii_tl_x + 1, :] )


    # Getting x coorinate to remove black region from right and left in the transformed image
    min_x = min(ti_x)

    # Cropping out the black region from both sides (using symmetricity)
    TransformedImage = TransformedImage[:, min_x : -min_x, :]

    return TransformedImage, ti_x-min_x, ti_y

Run Code Online (Sandbox Code Playgroud)

您只需调用该函数ProjectOntoCylinder并向其传递一个图像即可获取结果图像以及蒙版图像中白色像素的坐标。使用下面的代码调用该函数并获取蒙版图像。

# Applying Cylindrical projection on Image
Image_Cyl, mask_x, mask_y = ProjectOntoCylinder(Image)

# Getting Image Mask
Image_Mask = np.zeros(Image_Cyl.shape, dtype=np.uint8)
Image_Mask[mask_y, mask_x, :] = 255
Run Code Online (Sandbox Code Playgroud)

以下是我的项目及其详细文档的链接,以供参考:

第 1 部分: 源代码文档

第 2 部分: 源代码文档