gpt4 book ai didi

opencv - 使用warpAffine一起显示拼接图像而不会中断

转载 作者:行者123 更新时间:2023-12-02 16:37:41 34 4
gpt4 key购买 nike

我正在尝试通过模板匹配将2个图像拼接在一起,找到传递给cv2.getAffineTransform()的3组点,获得一个扭曲矩阵,该矩阵我传递给cv2.warpAffine()来对齐我的图像。

但是,当我加入图像时,大部分仿射图像都不会显示。我尝试使用不同的技术来选择点,更改顺序或自变量等,但是我只能获得一幅薄薄的仿射图像来显示。

有人可以告诉我我的方法是否有效,并建议我可能会出错的地方吗?对于可能导致问题的任何猜测,将不胜感激。提前致谢。

这是我得到的final result。以下是原始图像(12)和我使用的代码:

编辑:这是变量trans的结果

array([[  1.00768049e+00,  -3.76690353e-17,  -3.13824885e+00],
[ 4.84461775e-03, 1.30769231e+00, 9.61912797e+02]])

这是传递给 cv2.getAffineTransform的要点: unified_pair1
array([[  671.,  1024.],
[ 15., 979.],
[ 15., 962.]], dtype=float32)
unified_pair2
array([[ 669.,   45.],
[ 18., 13.],
[ 18., 0.]], dtype=float32)
import cv2
import numpy as np


def showimage(image, name="No name given"):
cv2.imshow(name, image)
cv2.waitKey(0)
cv2.destroyAllWindows()
return

image_a = cv2.imread('image_a.png')
image_b = cv2.imread('image_b.png')


def get_roi(image):
roi = cv2.selectROI(image) # spacebar to confirm selection
cv2.waitKey(0)
cv2.destroyAllWindows()
crop = image_a[int(roi[1]):int(roi[1]+roi[3]), int(roi[0]):int(roi[0]+roi[2])]
return crop
temp_1 = get_roi(image_a)
temp_2 = get_roi(image_a)
temp_3 = get_roi(image_a)

def find_template(template, search_image_a, search_image_b):
ccnorm_im_a = cv2.matchTemplate(search_image_a, template, cv2.TM_CCORR_NORMED)
template_loc_a = np.where(ccnorm_im_a == ccnorm_im_a.max())

ccnorm_im_b = cv2.matchTemplate(search_image_b, template, cv2.TM_CCORR_NORMED)
template_loc_b = np.where(ccnorm_im_b == ccnorm_im_b.max())
return template_loc_a, template_loc_b


coord_a1, coord_b1 = find_template(temp_1, image_a, image_b)
coord_a2, coord_b2 = find_template(temp_2, image_a, image_b)
coord_a3, coord_b3 = find_template(temp_3, image_a, image_b)

def unnest_list(coords_list):
coords_list = [a[0] for a in coords_list]
return coords_list

coord_a1 = unnest_list(coord_a1)
coord_b1 = unnest_list(coord_b1)
coord_a2 = unnest_list(coord_a2)
coord_b2 = unnest_list(coord_b2)
coord_a3 = unnest_list(coord_a3)
coord_b3 = unnest_list(coord_b3)

def unify_coords(coords1,coords2,coords3):
unified = []
unified.extend([coords1, coords2, coords3])
return unified

# Create a 2 lists containing 3 pairs of coordinates
unified_pair1 = unify_coords(coord_a1, coord_a2, coord_a3)
unified_pair2 = unify_coords(coord_b1, coord_b2, coord_b3)

# Convert elements of lists to numpy arrays with data type float32
unified_pair1 = np.asarray(unified_pair1, dtype=np.float32)
unified_pair2 = np.asarray(unified_pair2, dtype=np.float32)

# Get result of the affine transformation
trans = cv2.getAffineTransform(unified_pair1, unified_pair2)

# Apply the affine transformation to original image
result = cv2.warpAffine(image_a, trans, (image_a.shape[1] + image_b.shape[1], image_a.shape[0]))
result[0:image_b.shape[0], image_b.shape[1]:] = image_b

showimage(result)
cv2.imwrite('result.png', result)

来源:基于从文档中收到的 here,此 tutorial和此 example的建议的方法。

最佳答案

7月12日编辑:

这篇文章启发了GitHub存储库,提供了完成此任务的功能;一个用于填充的warpAffine(),另一个用于填充的warpPerspective()。 fork Python versionC++ version

转换会移动像素的位置

任何转换所做的就是将您的点坐标(x, y)映射到新位置(x', y'):

s*x'    h1 h2 h3     x
s*y' = h4 h5 h6 * y
s h7 h8 1 1

其中 s是一些缩放因子。您必须将新坐标除以比例因子,以获取正确的像素位置 (x', y')。从技术上讲,这仅适用于同形异义词- (3, 3)转换矩阵--您无需为仿射变换定标(您甚至不需要使用齐次坐标...但最好还是使讨论更笼统) 。

然后将实际像素值移动到那些新位置,然后对颜色值进行插值以适合新像素网格。因此,在此过程中,这些新位置会在某个时刻记录下来。我们需要这些位置来查看像素相对于其他图像实际移动到的位置。让我们从一个简单的示例开始,看看点在哪里映射。

假设您的变换矩阵只是将像素向左移动十个像素。翻译由最后一栏处理;第一行是 x中的翻译,第二行是 y中的翻译。因此,我们将有一个单位矩阵,但第一行第三列带有 -10。像素 (0,0)将映射到哪里?希望,如果逻辑有意义,请使用 (-10,0)。实际上,它确实:
transf = np.array([[1.,0.,-10.],[0.,1.,0.],[0.,0.,1.]])
homg_pt = np.array([0,0,1])
new_homg_pt = transf.dot(homg_pt))
new_homg_pt /= new_homg_pt[2]
# new_homg_pt = [-10. 0. 1.]

完善!因此,我们可以找出所有点都映射有线性代数的位置。我们将需要获取所有 (x,y)点,并将它们放入一个巨大的数组中,以便每个点都在其自己的列中。让我们假装我们的图像只是 4x4
h, w = src.shape[:2] # 4, 4
indY, indX = np.indices((h,w)) # similar to meshgrid/mgrid
lin_homg_pts = np.stack((indX.ravel(), indY.ravel(), np.ones(indY.size)))

这些 lin_homg_pts现在具有所有同质点:
[[ 0.  1.  2.  3.  0.  1.  2.  3.  0.  1.  2.  3.  0.  1.  2.  3.]
[ 0. 0. 0. 0. 1. 1. 1. 1. 2. 2. 2. 2. 3. 3. 3. 3.]
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]]

然后我们可以做矩阵乘法以获得每个点的映射值。为简单起见,让我们继续使用先前的单应性。
trans_lin_homg_pts = transf.dot(lin_homg_pts)
trans_lin_homg_pts /= trans_lin_homg_pts[2,:]

现在我们有了转换点:
[[-10. -9. -8. -7. -10. -9. -8. -7. -10. -9. -8. -7. -10. -9. -8. -7.]
[ 0. 0. 0. 0. 1. 1. 1. 1. 2. 2. 2. 2. 3. 3. 3. 3.]
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]]

如我们所见,一切都按预期进行:我们仅将 x的值移动了 -10

像素可以移动到图像范围之外

请注意,这些像素位置为负-位于图像边界之外。如果我们做一些更复杂的操作并将图像旋转45度,我们将获得一些超出原始范围的像素值。不过,我们并不在乎每个像素值,我们只需要知道原始图像像素位置之外最远的像素有多远,这样我们就可以在显示变形图像之前将原始图像垫得很远。 。
theta = 45*np.pi/180
transf = np.array([
[ np.cos(theta),np.sin(theta),0],
[-np.sin(theta),np.cos(theta),0],
[0.,0.,1.]])
print(transf)
trans_lin_homg_pts = transf.dot(lin_homg_pts)
minX = np.min(trans_lin_homg_pts[0,:])
minY = np.min(trans_lin_homg_pts[1,:])
maxX = np.max(trans_lin_homg_pts[0,:])
maxY = np.max(trans_lin_homg_pts[1,:])
# minX: 0.0, minY: -2.12132034356, maxX: 4.24264068712, maxY: 2.12132034356,

因此,我们看到可以在正向和负向两个方向上将像素位置定位在原始图像之外。最小 x值不会改变,因为当单应性应用旋转时,它将从左上角开始旋转。现在要注意的一件事是,我已将变换应用于图像中的所有像素。但这实际上是不必要的,您只需将四个角点翘曲并查看它们的降落位置即可。

填充目标图像

请注意,在调用 cv2.warpAffine()时,必须输入目标大小。这些转换后的像素值参考该大小。因此,如果像素映射到 (-10,0),它将不会显示在目标图像中。这意味着我们将不得不制作另一个单应性,其平移会使所有像素位置偏移为正,然后我们可以填充图像矩阵以补偿偏移。如果单应性移动的点也指向大于图像的位置,我们还必须在原始图像的底部和右侧填充。

在最近的示例中,最小 x值是相同的,因此我们不需要水平移动。但是,最小 y值下降了大约两个像素,因此我们需要将图像向下移动两个像素。首先,让我们创建填充的目标图像。
pad_sz = list(src.shape) # in case three channel
pad_sz[0] = np.round(np.maximum(pad_sz[0], maxY) - np.minimum(0, minY)).astype(int)
pad_sz[1] = np.round(np.maximum(pad_sz[1], maxX) - np.minimum(0, minX)).astype(int)
dst_pad = np.zeros(pad_sz, dtype=np.uint8)
# pad_sz = [6, 4, 3]

我们可以看到,高度从原始高度增加了两个像素,以说明该偏移。

在转换中添加平移,以将所有像素位置都移到正值

现在,我们需要创建一个新的单应矩阵,以将变形后的图像平移相同的数量。而要应用这两种变换(原始变换和新变换),我们必须组成两个单应性(对于仿射变换,您可以简单地添加翻译,但不能用于单应性)。另外,我们需要除以最后一个条目,以确保比例尺仍然正确(再次,仅对于单应性):
anchorX, anchorY = 0, 0
transl_transf = np.eye(3,3)
if minX < 0:
anchorX = np.round(-minX).astype(int)
transl_transf[0,2] -= anchorX
if minY < 0:
anchorY = np.round(-minY).astype(int)
transl_transf[1,2] -= anchorY
new_transf = transl_transf.dot(transf)
new_transf /= new_transf[2,2]

我还在此处创建了将目标图像放置到填充矩阵中的 anchor ;它的移动量与单应性将移动图像的量相同。因此,我们将目标图像放置在填充矩阵内:
dst_pad[anchorY:anchorY+dst_sz[0], anchorX:anchorX+dst_sz[1]] = dst

将新的变形扭曲为填充图像

我们剩下要做的就是将新的变换应用于源图像(带有填充的目标大小),然后我们可以覆盖两个图像。
warped = cv2.warpPerspective(src, new_transf, (pad_sz[1],pad_sz[0]))

alpha = 0.3
beta = 1 - alpha
blended = cv2.addWeighted(warped, alpha, dst_pad, beta, 1.0)

全部放在一起

让我们为此创建一个函数,因为我们创建了很多我们不需要的变量。对于输入,我们需要源图像,目标图像和原始单应性。对于输出,我们只需要填充的目标图像和变形的图像。请注意,在示例中,我们使用了 3x3单应性,因此,最好确保我们发送 3x3变换,而不是使用 2x3仿射或欧几里得经线。您只需将 [0,0,1]行添加到底部的任何仿射扭曲中,就可以了。
def warpPerspectivePadded(img, dst, transf):

src_h, src_w = src.shape[:2]
lin_homg_pts = np.array([[0, src_w, src_w, 0], [0, 0, src_h, src_h], [1, 1, 1, 1]])

trans_lin_homg_pts = transf.dot(lin_homg_pts)
trans_lin_homg_pts /= trans_lin_homg_pts[2,:]

minX = np.min(trans_lin_homg_pts[0,:])
minY = np.min(trans_lin_homg_pts[1,:])
maxX = np.max(trans_lin_homg_pts[0,:])
maxY = np.max(trans_lin_homg_pts[1,:])

# calculate the needed padding and create a blank image to place dst within
dst_sz = list(dst.shape)
pad_sz = dst_sz.copy() # to get the same number of channels
pad_sz[0] = np.round(np.maximum(dst_sz[0], maxY) - np.minimum(0, minY)).astype(int)
pad_sz[1] = np.round(np.maximum(dst_sz[1], maxX) - np.minimum(0, minX)).astype(int)
dst_pad = np.zeros(pad_sz, dtype=np.uint8)

# add translation to the transformation matrix to shift to positive values
anchorX, anchorY = 0, 0
transl_transf = np.eye(3,3)
if minX < 0:
anchorX = np.round(-minX).astype(int)
transl_transf[0,2] += anchorX
if minY < 0:
anchorY = np.round(-minY).astype(int)
transl_transf[1,2] += anchorY
new_transf = transl_transf.dot(transf)
new_transf /= new_transf[2,2]

dst_pad[anchorY:anchorY+dst_sz[0], anchorX:anchorX+dst_sz[1]] = dst

warped = cv2.warpPerspective(src, new_transf, (pad_sz[1],pad_sz[0]))

return dst_pad, warped

运行功能的例子

最后,我们可以使用一些真实的图像和单应性来调用此函数,并查看它如何平移。我将从 LearnOpenCV借用示例:
src = cv2.imread('book2.jpg')
pts_src = np.array([[141, 131], [480, 159], [493, 630],[64, 601]], dtype=np.float32)
dst = cv2.imread('book1.jpg')
pts_dst = np.array([[318, 256],[534, 372],[316, 670],[73, 473]], dtype=np.float32)

transf = cv2.getPerspectiveTransform(pts_src, pts_dst)

dst_pad, warped = warpPerspectivePadded(src, dst, transf)

alpha = 0.5
beta = 1 - alpha
blended = cv2.addWeighted(warped, alpha, dst_pad, beta, 1.0)
cv2.imshow("Blended Warped Image", blended)
cv2.waitKey(0)

我们最终得到了这个填充后的扭曲图像:



与通常会得到的 typical cut off warp相反。

关于opencv - 使用warpAffine一起显示拼接图像而不会中断,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51269822/

34 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com