6

I'm currently using the opencv (CV2) and Python Pillow image library to try and take an image of arbitrary phones and replace the screen with a new image. I've gotten to the point where I can take an image and identify the screen of the phone and get all the coordinates for the corner, but I'm having a really hard time replacing that area in the image with a new image.

The code I have so far:

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)

This will give me an image that looks like this:enter image description here

I can also get the coordinates of the screen corners using this line of code:

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

However I can't figure out for the life of me how to take a new image and scale it into the shape of the coordinate space I've found and overlay the image ontop.

My guess is that I can do this using an image transform in Pillow using this function that I adapted from this stackoverflow question:

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) 

However I'm in over my head a bit, and I can't get the details right. Could someone give me some help?

EDIT

Ok now that I'm using the getPerspectiveTransform and warpPerspective functions, I've got the following additional code:

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)

The image is getting rotated and skewed properly (I think), but its not the same size as the screen. Its "shorter":

enter image description here

and if I use a different image that is very tall vertically I end up with something that is too "long":

enter image description here

Do I need to apply an additional transformation to scale the image? Not sure whats going on here, I thought the perspective transform would make the image automatically scale out to the provided coordinates.

Community
  • 1
  • 1
Richard Artoul
  • 346
  • 4
  • 9

2 Answers2

7

I downloaded your image data and reproduced the problem in my local machine to find out the solution. Also downloaded lenna.png to fit inside the phone screen.

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)

enter image description here

ZdaR
  • 22,343
  • 7
  • 66
  • 87
  • I'm surprised this worked for you. I tried the exact same code (without the hard-coded values) and I continue to get miscaled images (see the pictures in the edited version of my question). I think it only worked for you because you hard-coded values. – Richard Artoul Jul 13 '16 at 03:53
  • Aaaah....I had a bug in my code. You're right, I was wrong. Will post full solution soon. – Richard Artoul Jul 13 '16 at 04:11
  • Yeah sure, you can mark this as answer, for any future visitors. Also edit your question to make it small and precise. So that it may help community. – ZdaR Jul 13 '16 at 05:50
0

You can overlay the new image (rotated to screen orientation of the phone) on to the original image by

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

You can have the new image appropriately rotated with alpha channel if required.

As you mentioned in the comment below (under the heading perspective transformation) the new image need to be perspective transformed (distorted). Look in the link below for how perspective transformation works to fix the distorted image to straight (you want the reverse of this).

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

You basically need to provide 4 points in the original and distorted space (pts1 and pts2) for the transformation.

I think you might be able to use the four corners of the original image to be inserted (pts1) and the corners of contours (pts2) should work.

user3404344
  • 1,707
  • 15
  • 13
  • I dont think this completely solves the problem though. The screen I'm finding in the image will usually not be a perfect rectangle so rotating the image is not sufficient, I also need to transform the perspective so that it matches the contour I found. Does that make sense? – Richard Artoul Jul 12 '16 at 06:04
  • Could you give me some more detail? I'm a complete noob to opencv, image-processing, and vector math – Richard Artoul Jul 12 '16 at 06:18
  • You're the man/woman! Its so close now. Could you take a look at my edited question and let me know if you know how to solve the last problem I have? The image is rotated and skewed (I think) appropriately, but its not scaled out to the right size. Do I need another transform? – Richard Artoul Jul 12 '16 at 07:12
  • I think you need to scale the image to appropriate dimensions prior to the perspective transformation. Try uniform scaling (length or width based scale factor that is closest to 1) or different scaling to ht and wd based on contour and image (being overlayed) dimensions. – user3404344 Jul 12 '16 at 07:23