【问题标题】:Manually wirting code for warpAffine in python在python中手动编写warpAffine的代码
【发布时间】:2018-03-02 00:38:41
【问题描述】:

我想通过不使用库函数来实现仿射变换。 我有一个名为“transformed”的图像,我想应用逆变换来获得“img_org”图像。现在,我正在使用自己的基本 GetBilinearPixel 函数来设置强度值。但是,图像没有正确转换。这就是我想出的。 :

这是图片(“transformed.png”):

这是图片(“img_org.png”):

但我的目标是制作这张图片:

你可以在这里看到变换矩阵:

pts1 = np.float32( [[693,349] , [605,331] , [445,59]] )
pts2 = np.float32 ( [[1379,895] , [1213,970] ,[684,428]] )
Mat = cv2.getAffineTransform(pts2,pts1)
B=Mat

代码:

img_org=np.zeros(shape=(780,1050))
img_size=np.zeros(shape=(780,1050))

def GetBilinearPixel(imArr, posX, posY):
return imArr[posX][posY]

for i in range(1,img.shape[0]-1):
    for j in range(1,img.shape[1]-1):
        pos=np.array([[i],[j],[1]],np.float32)
        #print pos
        pos=np.matmul(B,pos)
        r=int(pos[0][0])
        c=int(pos[1][0])
        #print r,c
        if(c<=1024 and r<=768 and c>=0 and r>=0):
            img_size[r][c]=img_size[r][c]+1
            img_org[r][c] += GetBilinearPixel(img, i, j)

for i in range(0,img_org.shape[0]):
    for j in range(0,img_org.shape[1]):
        if(img_size[i][j]>0):
            img_org[i][j] = img_org[i][j]/img_size[i][j]

我的逻辑错了吗?我知道我应用了非常低效的算法。 有什么我缺少的见解吗? 或者你能给我任何其他可以正常工作的算法。

(请求)。我不想使用 warpAffine 函数。

【问题讨论】:

  • 您有什么特别的原因不想使用warpAffine()
  • 是的,我是我大学的助教。我正在编写自己的库,用于给学生分配作业,让他们了解 warpAffine 函数中的实现。
  • 积分从何而来?
  • 我认为矩阵是正确的。我希望“transformed.png”转换为“original.png”(转换-> 原始)。我计算的矩阵来自我共享的第一张和第三张图像。 pts2 代表第一张图像,pts1 代表第三张图像。我可以对图像进行矢量化,但我认为逻辑仍将保持不变。
  • 是的,我正在寻找答案,但我认为这需要我一点时间。不过我很确定你的观点是错误的。

标签: python image opencv image-processing opencv3.0


【解决方案1】:

所以我对代码进行了矢量化,这种方法有效——我找不到你的实现的确切问题,但也许这会有所启发(而且速度更快)。

矢量化的设置是创建一个包含图像中每个点的线性(同质)数组。我们想要一个看起来像

的数组
x0 x1 ... xN   x0 x1 ... xN   .....   x0 x1 ... xN
y0 y0 ... y0   y1 y1 ... y1   .....   yM yM ... yM
 1  1 ...  1    1  1 ...  1   .....    1  1 ...  1

这样每个点(xi, yi, 1) 都包括在内。然后转换只是与您的转换矩阵和这个数组的单个矩阵乘法。

为了简化问题(部分是因为您的图像命名约定让我感到困惑),我会说原始起始图像是“目标”或dst,因为我们想转换回“源”或src 图像.考虑到这一点,创建这个线性同质数组可能看起来像这样:

dst = cv2.imread('img.jpg', 0)
h, w = dst.shape[:2]
dst_y, dst_x = np.indices((h, w))  # similar to meshgrid/mgrid
dst_lin_homg_pts = np.stack((dst_x.ravel(), dst_y.ravel(), np.ones(dst_y.size)))

然后,要变换点,只需创建变换矩阵并相乘。我将四舍五入转换后的像素位置,因为我将它们用作索引而不用插值:

src_pts = np.float32([[693, 349], [605, 331], [445, 59]])
dst_pts = np.float32([[1379, 895], [1213, 970], [684, 428]])
transf = cv2.getAffineTransform(dst_pts, src_pts)
src_lin_pts = np.round(transf.dot(dst_lin_homg_pts)).astype(int)

现在这个转换会将一些像素发送到负索引,如果我们用这些索引,它会环绕图像——可能不是我们想要做的。当然,在 OpenCV 实现中,它只是完全切断了这些像素。但是我们可以只移动所有转换后的像素,使所有位置都是正的,并且我们不会截断任何位置(在这方面你当然可以做任何你想做的事情):

min_x, min_y = np.amin(src_lin_pts, axis=1)
src_lin_pts -= np.array([[min_x], [min_y]])

然后我们需要创建转换映射到的源图像src。我将使用灰色背景创建它,以便我们可以从dst 图像中看到黑色的范围。

trans_max_x, trans_max_y = np.amax(src_lin_pts, axis=1)
src = np.ones((trans_max_y+1, trans_max_x+1), dtype=np.uint8)*127

现在我们要做的就是将目标图像中的一些相应像素放入源图像中。由于我没有切断任何像素,并且两个线性点阵列中的像素数量相同,因此我可以将转换后的像素分配为它们在原始图像中的颜色。

src[src_lin_pts[1], src_lin_pts[0]] = dst.ravel()

现在,当然,这不是在图像上进行插值。但是 OpenCV 中没有用于插值的内置函数(有后端 C 函数供其他方法使用,但不能在 Python AFAIK 中访问)。但是,您拥有重要的部分——目标图像映射到的位置以及原始图像,因此您可以使用任意数量的库来插入该网格。或者只是自己实现线性插值,因为它不是太难。当然,在此之前,您可能希望取消圆整扭曲的像素位置。

cv2.imshow('src', src)
cv2.waitKey()

编辑:同样的方法也适用于warpPerspective,虽然你得到的矩阵乘法会给出一个三行(齐次)向量,你需要将前两行除以第三行来设置他们回到笛卡尔世界。除此之外,其他一切都保持不变。

【讨论】:

  • 非常感谢。我还认为,当点转换为负坐标时,它们将无法正确映射。想使用循环而不是内置函数。没关系。
  • 是的;在 OpenCV 实现中,它只是忽略了这些索引。它还要求您输入一个输出形状,这意味着它也不会自动计算范围。因此,我创建了一个小 python 模块,它执行 OpenCV warp 函数,但自动计算目标大小并将图像转换为正值,就像我在这里所做的那样。您可以查看here,如果它适合您正在做的任何事情。
  • 如果我想对这种技术应用插值怎么办?
  • 正如我在帖子中提到的,您拥有原始像素值和新位置,因此您拥有插值所需的一切。您可以创建一个线性插值方法(基本上,新映射点之间的所有像素都被分配了它们的邻居值的某种组合),或者自己创建一个最近邻方法,这还不错。但是如果你想要更好的插值,你应该使用另一个库,比如 scipy 或者带有 2D 插值方法的东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-02
  • 2022-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多