Poisson Image Editing

Our goal is to extract the source image: sun

And merge it to the target image: ocean

In [2]:
ls pics/
test1_mask.jpg    test1_target.jpg  test2_src.png
test1_src.jpg     test2_mask.png    test2_target.png

Instead of copy-and-past, we interpolate the copied region by solving the Poisson equation. More specifically, Eq. (10) in [Pérez 2003] is used here for seamless cloning. Let $\mathbf{f}$ be the pixel values in the region to paste (which is what we're trying to solve), let $\mathbf{g}$ be the corresponding region in the source image. Instead of letting $\mathbf{f}=\mathbf{g}$, we let $\nabla \mathbf{f} = \nabla \mathbf{g}$, and let the pixel values on the boundary to be the same as those from the target image. In this way, the pixel values $\mathbf{f}$'s distribution is "guided" by the source image, and gradually blends to the target image on the boundary. More rigorous derivations can be found in [Pérez 2003], the main idea is to let the pixel values be the same on the boundary, and find a distribution that resembles the source image and changes to the target image smoothly.

The only technical difficulty is to implement the Laplacian operator. This is a good reference. We first build the matrix $\mathbf{D}$, then build matrix $\mathbf{A}$ by setting $\mathbf{D}$ as block diagonal element, and set the $\mathbf{I}$'s:

Now our equation becomes:
$\mathbf{Af} = \mathbf{Ag}$, inside the region;
$\mathbf{f} = \mathbf{t}$, outside the region.

where $\mathbf{g}$ is the pixel value of the source image, $\mathbf{t}$ is the pixel value of the target image.

Now we are ready to implement our Poisson editting algorithm. First, we load the images and the masks:

Note the mask tells us what region to extract from the source image, when we insert to the target image, we may want to translate it, so we need an offset parameter. In here, I set the offset value directly, I'll talk about how to find the desired value later.

Now we translate the source image according to the offset:

Now we need to generate the matrix $\mathbf{A}$. First, apply our function to get the Laplacian matrix:

We only want to apply the Laplacian operator inside the blending region, so for the outside part, we set it to identity. Note for each row in mat_A, if it takes the Laplacian, then the row will have a "4" on the diagonal and four "-1", so to set it to identity, we want to set the "4" to "1", and the rest to "0":

In [1]:
pwd
Out[1]:
'/Users/james/Desktop/四春/A5图像处理/大作业1/第一次大作业/image_fusion'
In [3]:
from scipy import sparse
from os import path
import cv2
import numpy as np
import matplotlib.pyplot as plt
%pylab inline
from tqdm import tqdm_notebook as tqdm
import sys
sys.path.append('bin/')
Populating the interactive namespace from numpy and matplotlib
In [4]:
from poisson_img_editing import laplacian_matrix, poisson_edit
# setup figure template
figure_template_path = 'bin/'
if figure_template_path not in sys.path:
    sys.path.append(figure_template_path)
from importlib import reload
import utils
# force reload of the module
reload(utils)
from utils import std_plot,display_dataframe, embed_pdf_figure, embed_pdf_pages
fontlegend = {'family':'Arial',
                  'weight' : 'normal', 
              #'linewidth':0.5,
                  'size' : 6.5*1}
In [5]:
source = cv2.imread("pics/test1_src.jpg")
target = cv2.imread("pics/test1_target.jpg")
mask = cv2.imread("pics/test1_mask.jpg", cv2.IMREAD_GRAYSCALE)

fig,ax=plt.subplots(1,3,figsize=(15,5))
ax[0].imshow(source[:,:,::-1]) 
ax[2].imshow(target[:,:,::-1])
ax[1].imshow(mask, cmap='gray')

    
fig.tight_layout()
embed_pdf_figure() 

print('src shape:', source.shape[:-1])
print('target shape:', target.shape[:-1])
print('mask shape:', mask.shape)
src shape: (360, 266)
target shape: (427, 770)
mask shape: (360, 266)
In [6]:
permc_spec = ['NATURAL', 'MMD_ATA', 'MMD_AT_PLUS_A', 'COLAMD']
In [7]:
%time fusion_1 = poisson_edit(50, 100,3,permc_spec[0],-2,2,-2,-2,source, target, mask)
CPU times: user 10.1 s, sys: 493 ms, total: 10.5 s
Wall time: 11.1 s
In [8]:
fig,ax=plt.subplots(1,3,figsize=(15,5))
ax[0].imshow(source[:,:,::-1]) 
ax[1].imshow(target[:,:,::-1])
ax[2].imshow(fusion_1[:,:,::-1])
  
fig.tight_layout()
embed_pdf_figure() 
In [9]:
source = cv2.imread("pics/test2_src.png")
target = cv2.imread("pics/test2_target.png")
mask = cv2.imread("pics/test2_mask.png", cv2.IMREAD_GRAYSCALE)
In [10]:
fig,ax=plt.subplots(1,3,figsize=(15,5))
ax[0].imshow(source[:,:,::-1]) 
ax[1].imshow(mask, cmap='gray')
ax[2].imshow(target[:,:,::-1])

fig.tight_layout()
embed_pdf_figure() 

print('src shape:', source.shape[:-1])
print('target shape:', target.shape[:-1])
print('mask shape:', mask.shape)
src shape: (120, 180)
target shape: (356, 418)
mask shape: (120, 180)
In [11]:
%time fusion_2 = poisson_edit(150, 150,2,permc_spec[2],4,4,4,3,source, target, mask)
CPU times: user 5.03 s, sys: 231 ms, total: 5.26 s
Wall time: 6.05 s
In [12]:
fig,ax=plt.subplots(1,3,figsize=(15,5))
ax[0].imshow(source[:,:,::-1]) 
ax[1].imshow(target[:,:,::-1])
ax[2].imshow(fusion_2[:,:,::-1])

fig.tight_layout()
embed_pdf_figure()

Reference

Patrick Pérez, Michel Gangnet, and Andrew Blake. 2003. Poisson image editing. ACM Trans. Graph. 22, 3 (July 2003), 313-318. DOI: https://doi.org/10.1145/882262.882269

In [ ]: