by Hieu Minh Nguyen on May 25, 2016 | Comment Count
Image blending is the process of combining multiple images together. It truly is an artform. And Margot Robbie is a masterpiece. So naturally, I had to bring them together for this project.
I settled on creating a sort of hero/villain blended headshot. The black image I chose was of Harley Quinn from the upcoming movie, Suicide Squad. The white image I chose was of the actress who plays her, Margot Robbie. I used Gimp to adjust the orientation and make both images have the same dimensions. I also used Gimp to create a black and white image mask, drawn freehand using the eraser tool. This mask is unique because it isn’t just a half/half face. I was able to take interesting features from both images and perform a smooth blending.
To perform the image blending, I used SciPy and OpenCV. The Python methods are detailed below.
This function takes an image, convolves it with 5x5 kernel a=0.4, and then subsamples it down to a quarter of the size (dividing the height and width by two). I took advantage of numpy indexing techniques to index every other row and column.
def reduce(image): # Convolve input image with kernel for Gaussian smoothing kernel = generatingKernel(0.4) convolved = scipy.signal.convolve2d(image, kernel, mode='same') # Subsample with numpy indexing to take every other row/column reduced = convolved[::2, ::2] return reduced
This function takes an image and supersamples it to four times the size (multiplying the height and width by two). First, I created a zeros numpy array of twice the input size, then used indexing techniques to assign every other row and column to the output. The output is then convolved with a 5x5 kernel a=0.4 and multiplied by 4 to scale the image intensities to the proper values. This scaling is needed because of the resulting convolution output on an image with “empty” rows and columns. The pixel values are effectively divided by 4 over the kernel space, so they must be scaled up to obtain the appropriate pixel intensity.
def expand(image): # Create image twice the size of input upsampled = np.zeros((2*len(image), 2*len(image))) # Assign every other row/col of input to output upsampled[::2, ::2] = image # Convolve input image with kernel for Gaussian smoothing kernel = generatingKernel(0.4) convolved = scipy.signal.convolve2d(upsampled, kernel, mode='same') # Multiply output by 4 to scale image back up expanded = convolved * 4 return expanded
This function takes an image and builds a Gaussian pyramid out of it. This is accomplished by recursively appending the reduced images.
def gaussPyramid(image, levels): # Iteratively call the reduce function to build a pyramid output = [image] for i in xrange(levels): output.append(reduce(output[i])) return output
This function takes a Gaussian pyramid constructed by the previous function, and turns it into a Laplacian pyramid, which is essentially the difference of Gaussian pyramid levels. For each level, the immediate lower level is expanded. A check is done to ensure the expanded image dimensions are identical to the target image, otherwise cropping is performed (removing excessive rows/columns). The Laplacian level is then calculated by taking the difference. This continues until the top level, which is identical to the top level of the Gaussian pyramid because no further difference can be done.
def laplPyramid(gaussPyr): # Iterate over Gaussian pyramid levels output =  for i in xrange(len(gaussPyr)-1): # Expand lower level of pyramid expanded = expand(gaussPyr[i+1]) # Crop expanded image if wrong target dimensions if (len(expanded) != len(gaussPyr[i])): expanded = expanded[0:len(gaussPyr[i]), :] if (len(expanded) != len(gaussPyr[i])): expanded = expanded[:, 0:len(gaussPyr[i])] # Calculate Laplacian pyramid output.append(gaussPyr[i] - expanded) # Last element of Laplacian pyramid is identical to input output.append(gaussPyr[len(gaussPyr)-1]) return output
This function takes three pyramids (white Laplacian, black Laplacian, mask Gaussian) and performs an alpha-blend of the two Laplacian pyramids according to the mask pyramid. This is done recursively for each level of the pyramid.
def blend(laplPyrWhite, laplPyrBlack, gaussPyrMask): # Iterate over each level to calculate the blended pyramid blended_pyr =  for i in xrange(len(gaussPyrMask)): blended_pyr.append(gaussPyrMask[i]*laplPyrWhite[i] + (1 - gaussPyrMask[i])*laplPyrBlack[i]) return blended_pyr
This function flattens a given Laplacian pyramid into an image. It starts from the smallest level and recursively expands and adds the expanded level to the next level. There is also a check performed to ensure the dimensions are correct, otherwise cropping is performed. This continues until the penultimate level is flattened onto the base level.
def collapse(pyramid): # Iterate over pyramid levels starting from smallest (in reverse) output = pyramid[len(pyramid)-1] for i in xrange(len(pyramid)-1, 0, -1): # Expand smaller level to next level expanded = expand(output) # Crop expanded image if wrong target dimensions if (len(expanded) != len(pyramid[i-1])): expanded = expanded[0:len(pyramid[i-1]), :] if (len(expanded) != len(pyramid[i-1])): expanded = expanded[:, 0:len(pyramid[i-1])] # Flatten expanded onto next level output = expanded + pyramid[i - 1] return output
Here is the final result! Almost as good as the movie!