如何使用 Python 用新图像替换图像中的轮廓(矩形)?

Posted

技术标签:

【中文标题】如何使用 Python 用新图像替换图像中的轮廓(矩形)?【英文标题】:How to replace a contour (rectangle) in an image with a new image using Python? 【发布时间】:2016-11-14 05:20:19 【问题描述】:

我目前正在使用 opencv (CV2) 和 Python Pillow 图像库来尝试拍摄任意手机的图像并用新图像替换屏幕。我已经到了可以拍摄图像并识别手机屏幕并获取角落的所有坐标的地步,但是我很难用新图像替换图像中的那个区域。

我目前的代码:

import cv2
from PIL import Image

image = cv2.imread('mockup.png')
edged_image = cv2.Canny(image, 30, 200)

(contours, _) = cv2.findContours(edged_image.copy(), cv2.RETR_TREE,     cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key = cv2.contourArea, reverse = True)[:10]
screenCnt = None

for contour in contours:
    peri = cv2.arcLength(contour, True)
    approx = cv2.approxPolyDP(contour, 0.02 * peri, True)

    # if our approximated contour has four points, then
    # we can assume that we have found our screen
    if len(approx) == 4:
        screenCnt = approx
        break

cv2.drawContours(image, [screenCnt], -1, (0, 255, 0), 3)
cv2.imshow("Screen Location", image)
cv2.waitKey(0)

这会给我一个看起来像这样的图像:

我也可以使用这行代码获取屏幕角的坐标:

screenCoords = [x[0].tolist() for x in screenCnt] 
// [[398, 139], [245, 258], [474, 487], [628, 358]]

但是,我终其一生都无法弄清楚如何拍摄新图像并将其缩放为我找到的坐标空间的形状并将图像覆盖在上面。

我的猜测是,我可以使用我改编自 this *** question 的函数在 Pillow 中使用图像转换来做到这一点:

def find_transform_coefficients(pa, pb):
"""Return the coefficients required for a transform from start_points to end_points.

    args:
        start_points -> Tuple of 4 values for start coordinates
        end_points --> Tuple of 4 values for end coordinates
"""
matrix = []
for p1, p2 in zip(pa, pb):
    matrix.append([p1[0], p1[1], 1, 0, 0, 0, -p2[0]*p1[0], -p2[0]*p1[1]])
    matrix.append([0, 0, 0, p1[0], p1[1], 1, -p2[1]*p1[0], -p2[1]*p1[1]])

A = numpy.matrix(matrix, dtype=numpy.float)
B = numpy.array(pb).reshape(8)

res = numpy.dot(numpy.linalg.inv(A.T * A) * A.T, B)
return numpy.array(res).reshape(8) 

但是,我有点不知所措,我无法正确了解详细信息。有人可以帮帮我吗?

编辑

好的,现在我正在使用 getPerspectiveTransform 和 warpPerspective 函数,我有以下附加代码:

screenCoords = numpy.asarray(
    [numpy.asarray(x[0], dtype=numpy.float32) for x in screenCnt],
    dtype=numpy.float32
)

overlay_image = cv2.imread('123.png')
overlay_height, overlay_width = image.shape[:2]

input_coordinates = numpy.asarray(
    [
        numpy.asarray([0, 0], dtype=numpy.float32),
        numpy.asarray([overlay_width, 0], dtype=numpy.float32),
        numpy.asarray([overlay_width, overlay_height],     dtype=numpy.float32),
        numpy.asarray([0, overlay_height], dtype=numpy.float32)
    ],
    dtype=numpy.float32,
)

transformation_matrix = cv2.getPerspectiveTransform(
    numpy.asarray(input_coordinates),
    numpy.asarray(screenCoords),
)

warped_image = cv2.warpPerspective(
    overlay_image,
    transformation_matrix,
    (background_width, background_height),
)
cv2.imshow("Overlay image", warped_image)
cv2.waitKey(0)

图像正在正确旋转和倾斜(我认为),但它与屏幕的大小不同。它的“更短”:

如果我使用不同的垂直高度非常高的图像,我最终会得到一些太“长”的图像:

我是否需要应用额外的转换来缩放图像?不知道这里发生了什么,我认为透视变换会使图像自动缩放到提供的坐标。

【问题讨论】:

【参考方案1】:

您可以将新图像(旋转到手机的屏幕方向)叠加到原始图像上

import cv2
A_img = cv2.imread("new_image.png")
B_img = cv2.imread("larger_image.jpg")
x_offset=y_offset=50
B_img[y_offset:y_offset+A_img.shape[0], x_offset:x_offset+A_img.shape[1]] = A_img

如果需要,您可以使用 Alpha 通道适当地旋转新图像。

正如您在下面的评论中提到的(在标题透视变换下),新图像需要进行透视变换(扭曲)。查看下面的链接,了解透视变换如何将扭曲的图像修复为直线(您想要相反的)。

http://docs.opencv.org/master/da/d6e/tutorial_py_geometric_transformations.html#gsc.tab=0

你基本上需要在原始和扭曲的空间(pts1和pts2)中提供4个点来进行变换。

我认为您可以使用要插入的原始图像的四个角 (pts1) 并且轮廓的角 (pts2) 应该可以工作。

【讨论】:

我认为这并不能完全解决问题。我在图像中找到的屏幕通常不是一个完美的矩形,因此旋转图像是不够的,我还需要转换透视图,使其与我找到的轮廓相匹配。这有意义吗? 你能告诉我更多细节吗?我是 opencv、图像处理和矢量数学的菜鸟 你是男人/女人!它现在如此接近。你能看看我编辑的问题,如果你知道如何解决我遇到的最后一个问题,请告诉我?图像被适当地旋转和倾斜(我认为),但它没有缩放到正确的大小。我需要另一个转换吗? 我认为您需要在透视变换之前将图像缩放到适当的尺寸。尝试统一缩放(基于长度或宽度的最接近 1 的缩放因子)或根据轮廓和图像(被覆盖)尺寸对 ht 和 wd 进行不同缩放。【参考方案2】:

我下载了您的图像数据并在我的本地机器上重现了该问题以找出解决方案。还下载了lenna.png 以适应手机屏幕。

import cv2
import numpy as np

# Template image of iPhone
img1 = cv2.imread("/Users/anmoluppal/Downloads/46F1U.jpg")
# Sample image to be used for fitting into white cavity
img2 = cv2.imread("/Users/anmoluppal/Downloads/Lenna.png")

rows,cols,ch = img1.shape

# Hard coded the 3 corner points of white cavity labelled with green rect.
pts1 = np.float32([[201, 561], [455, 279], [742, 985]])
# Hard coded the same points on the reference image to be fitted.
pts2 = np.float32([[0, 0], [512, 0], [0, 512]])

# Getting affine transformation form sample image to template.
M = cv2.getAffineTransform(pts2,pts1)

# Applying the transformation, mind the (cols,rows) passed, these define the final dimensions of output after Transformation.
dst = cv2.warpAffine(img2,M,(cols,rows))

# Just for Debugging the output.
final = cv2.addWeighted(dst, 0.5, img1, 0.5, 1)
cv2.imwrite("./garbage.png", final)

【讨论】:

我很惊讶这对你有用。我尝试了完全相同的代码(没有硬编码值),但我继续得到错误缩放的图像(请参阅我的问题的编辑版本中的图片)。我认为它只对您有用,因为您对值进行了硬编码。 Aaaah....我的代码中有一个错误。你是对的,我错了。很快就会发布完整的解决方案。 是的,您可以将此标记为答案,以供任何未来的访问者使用。还要编辑您的问题以使其小而精确。以便它可以帮助社区。​​span>

以上是关于如何使用 Python 用新图像替换图像中的轮廓(矩形)?的主要内容,如果未能解决你的问题,请参考以下文章

PDFbox 1.7.0 - 如何在使用PDFBox添加新图像时保留现有图像?

Flask - 使用相同名称覆盖图像并显示新图像

无法通过替换Finder中的图像文件来替换图像

我想在实时数据库中保存多个图像 url,但每次旧图像 url 都在颤振中替换为新图像 url

从轮廓位置生成边界框

图像处理 - 查找轮廓 EmguCv