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

问题描述

将 24 个拼接图像的结果拼接到下一个 25 个图像后,我得到了如下输出。在那之前缝合很好。

enter image description here

有没有人知道为什么/什么时候拼接输出是这样的?输出的可能性有多大?这可能是什么原因?

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

拼接的核心部分如下:

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,cv2.THRESH_BINARY)
kp2,descriptors2 = detector.detectAndCompute(gray2,mask2)

keypoints1Im = cv2.drawKeypoints(image1,kp1,outimage = cv2.DRAW_MATCHES_FLAGS_DEFAULT,color=(0,255))
keypoints2Im = cv2.drawKeypoints(image2,kp2,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,gray1,matches)

#Aligning the images
src_pts = np.float32([ kp2[m.queryIdx].pt for m in matches ]).reshape(-1,2)
dst_pts = np.float32([ kp1[m.trainIdx].pt for m in matches ]).reshape(-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,0]]).reshape(-1,2)
pts2 = np.float32([[0,h2],[w2,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,t[0]],t[1]],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,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)

编辑:

此图显示匹配绘图,同时拼接 25 到结果直到 24 图像:

enter image description here

在那场比赛之前:

enter image description here

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

拼接第23张图片后的结果:

enter image description here

拼接第24张图片后的结果:

enter image description here

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

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

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

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

23、24、25张图片的拼接结果,代码相同:

enter image description here

使用不同的代码(在拼接之间给出黑线),如果我拼接了 97 个图像,那么第 25 个在拼接和拼接中上升,如下所示(右角点):

enter image description here

解决方法

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

当我像你一样将 4 张图像拼接在一起时,我的问题是这样的。

My problem

如您所见,第 4 幅图像严重失真,这是绝对不能发生的。同样的事情也发生在你身上,但程度更高。

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

Output after image pre-processing

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

要了解这种失真背后的确切原因,请观看 Joseph Redmon 在 50:26 - 1:07:23 之间的 this video

按照视频中的建议,我们首先必须将图像投影到圆柱体上,然后展开它们,然后将这些展开的图像拼接在一起。

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

Image before and after pre-processing

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

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


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[:,ii_tl_x + 1,:] ) + \
                                      ( weight_bl[:,None] * InitialImage[ii_tl_y + 1,:] ) + \
                                      ( weight_br[:,:] )


    # 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

您只需调用函数 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,:] = 255

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

第 1 部分: Source CodeDocumentation

第 2 部分: Source CodeDocumentation