概念
从第一个关键点集中提取一组包含三个索引的集合,这些索引会在两个关键点集中被索引时形成三角形。使用这些索引,我们可以从两个关键点集中获取相应的三角形,从而允许我们通过每个三角形逐个构建扭曲图像 (有关详细信息,请参见使用OpenCV将一个三角形扭曲到另一个三角形):
image1.png
(添加了点):
![enter image description here](https://istack.dev59.com/KxweE.webp)
image2.png
(添加了点):
![enter image description here](https://istack.dev59.com/JaBTa.webp)
结果 (添加了点)
![enter image description here](https://istack.dev59.com/t0Z4P.gif)
代码
import cv2
import numpy as np
def triangles(points):
points = np.where(points, points, 1)
subdiv = cv2.Subdiv2D((*points.min(0), *points.max(0)))
subdiv.insert(list(points))
for pts in subdiv.getTriangleList().reshape(-1, 3, 2):
yield [np.where(np.all(points == pt, 1))[0][0] for pt in pts]
def crop(img, pts):
x, y, w, h = cv2.boundingRect(pts)
img_cropped = img[y: y + h, x: x + w]
pts[:, 0] -= x
pts[:, 1] -= y
return img_cropped, pts
def warp(img1, img2, pts1, pts2):
for indices in triangles(pts1):
img1_cropped, triangle1 = crop(img1, pts1[indices])
img2_cropped, triangle2 = crop(img2, pts2[indices])
transform = cv2.getAffineTransform(np.float32(triangle1), np.float32(triangle2))
img2_warped = cv2.warpAffine(img1_cropped, transform, img2_cropped.shape[:2][::-1], None, cv2.INTER_LINEAR, cv2.BORDER_REFLECT_101)
mask = np.zeros_like(img2_cropped)
cv2.fillConvexPoly(mask, np.int32(triangle2), (1, 1, 1), 16, 0)
img2_cropped *= 1 - mask
img2_cropped += img2_warped * mask
img1 = cv2.imread("image1.png")
img2 = cv2.imread("image2.png")
pts1 = np.array([[0, 0], [286, 0], [286, 198], [174, 198], [158, 116], [0, 97]])
pts2 = np.array([[80, 37], [409, 42], [416, 390], [331, 384], [291, 119], [111, 311]])
warp(img1, img2, pts1, pts2)
for pt in pts2:
cv2.circle(img2, tuple(pt), 15, (0, 0, 255), -1)
cv2.imshow("Original", img1)
cv2.imshow("Transformed", img2)
cv2.waitKey(0)
cv2.destroyAllWindows()
输出
![在此输入图像描述](https://istack.dev59.com/9bIg9.webp)
解释
- 导入必要的库:
import cv2
import numpy as np
定义一个函数
triangles
,它将接收一个坐标数组
points
,并生成由原始坐标数组的三个索引构成的三角形列表以覆盖该坐标数组的区域。
def triangles(points):
points = np.where(points, points, 1)
subdiv = cv2.Subdiv2D((*points.min(0), *points.max(0)))
subdiv.insert(list(points))
for pts in subdiv.getTriangleList().reshape(-1, 3, 2):
yield [np.where(np.all(points == pt, 1))[0][0] for pt in pts]
定义一个函数,名为
crop
,它将接受一个图像数组
img
和一个由三个坐标
pts
组成的数组。它会返回一个矩形片段,其大小恰好足以适应由这三个点形成的三角形,并将三个坐标转换为图像左上角的位置,并返回此新坐标数组。
def crop(img, pts):
x, y, w, h = cv2.boundingRect(pts)
img_cropped = img[y: y + h, x: x + w]
pts[:, 0] -= x
pts[:, 1] -= y
return img_cropped, pts
定义一个名为
warp
的函数,该函数将接受两个图片数组
img1
和
img2
,以及两个坐标数组
pts1
和
pts2
。它将利用之前定义的
triangles
函数来迭代第一个坐标数组中的三角形,使用之前定义的
crop
函数在对应于三角形索引的坐标处裁剪两张图片,并使用
cv2.warpAffine()
方法来扭曲当前迭代中的三角形图像。
def warp(img1, img2, pts1, pts2):
for indices in triangles(pts1):
img1_cropped, triangle1 = crop(img1, pts1[indices])
img2_cropped, triangle2 = crop(img2, pts2[indices])
transform = cv2.getAffineTransform(np.float32(triangle1), np.float32(triangle2))
img2_warped = cv2.warpAffine(img1_cropped, transform, img2_cropped.shape[:2][::-1], None, cv2.INTER_LINEAR, cv2.BORDER_REFLECT_101)
mask = np.zeros_like(img2_cropped)
cv2.fillConvexPoly(mask, np.int32(triangle2), (1, 1, 1), 16, 0)
img2_cropped *= 1 - mask
img2_cropped += img2_warped * mask
- 读取图像。在这种情况下,
img1
是我们想要变形的图像,img2
是空白的500 x 500图像。同时,定义两个坐标数组作为图像的关键点:
img1 = cv2.imread("image1.png")
img2 = cv2.imread("image2.png")
pts1 = np.array([[0, 0], [286, 0], [286, 198], [174, 198], [158, 116], [0, 97]])
pts2 = np.array([[80, 37], [409, 42], [416, 390], [331, 384], [291, 119], [111, 311]])
最后,使用先前定义的
warp
函数将
img1
扭曲,使其关键点与
img2
的关键点重叠,并显示结果图像。我将第二个坐标数组中的点绘制到结果扭曲图像上,以便更容易地可视化扭曲过程:
warp(img1, img2, pts1, pts2)
for pt in pts2:
cv2.circle(img2, tuple(pt), 15, (0, 0, 255), -1)
cv2.imshow("Original", img1)
cv2.imshow("Transformed", img2)
cv2.waitKey(0)
cv2.destroyAllWindows()