fr/fr_env/lib/python3.8/site-packages/skimage/restoration/inpaint.py

153 lines
5.1 KiB
Python

import numpy as np
from scipy import sparse
from scipy.sparse.linalg import spsolve
import scipy.ndimage as ndi
from scipy.ndimage.filters import laplace
import skimage
from ..measure import label
def _get_neighborhood(nd_idx, radius, nd_shape):
bounds_lo = (nd_idx - radius).clip(min=0)
bounds_hi = (nd_idx + radius + 1).clip(max=nd_shape)
return bounds_lo, bounds_hi
def _inpaint_biharmonic_single_channel(mask, out, limits):
# Initialize sparse matrices
matrix_unknown = sparse.lil_matrix((np.sum(mask), out.size))
matrix_known = sparse.lil_matrix((np.sum(mask), out.size))
# Find indexes of masked points in flatten array
mask_i = np.ravel_multi_index(np.where(mask), mask.shape)
# Find masked points and prepare them to be easily enumerate over
mask_pts = np.stack(np.where(mask), axis=-1)
# Iterate over masked points
for mask_pt_n, mask_pt_idx in enumerate(mask_pts):
# Get bounded neighborhood of selected radius
b_lo, b_hi = _get_neighborhood(mask_pt_idx, 2, out.shape)
# Create biharmonic coefficients ndarray
neigh_coef = np.zeros(b_hi - b_lo)
neigh_coef[tuple(mask_pt_idx - b_lo)] = 1
neigh_coef = laplace(laplace(neigh_coef))
# Iterate over masked point's neighborhood
it_inner = np.nditer(neigh_coef, flags=['multi_index'])
for coef in it_inner:
if coef == 0:
continue
tmp_pt_idx = np.add(b_lo, it_inner.multi_index)
tmp_pt_i = np.ravel_multi_index(tmp_pt_idx, mask.shape)
if mask[tuple(tmp_pt_idx)]:
matrix_unknown[mask_pt_n, tmp_pt_i] = coef
else:
matrix_known[mask_pt_n, tmp_pt_i] = coef
# Prepare diagonal matrix
flat_diag_image = sparse.dia_matrix((out.flatten(), np.array([0])),
shape=(out.size, out.size))
# Calculate right hand side as a sum of known matrix's columns
matrix_known = matrix_known.tocsr()
rhs = -(matrix_known * flat_diag_image).sum(axis=1)
# Solve linear system for masked points
matrix_unknown = matrix_unknown[:, mask_i]
matrix_unknown = sparse.csr_matrix(matrix_unknown)
result = spsolve(matrix_unknown, rhs)
# Handle enormous values
result = np.clip(result, *limits)
result = result.ravel()
# Substitute masked points with inpainted versions
for mask_pt_n, mask_pt_idx in enumerate(mask_pts):
out[tuple(mask_pt_idx)] = result[mask_pt_n]
return out
def inpaint_biharmonic(image, mask, multichannel=False):
"""Inpaint masked points in image with biharmonic equations.
Parameters
----------
image : (M[, N[, ..., P]][, C]) ndarray
Input image.
mask : (M[, N[, ..., P]]) ndarray
Array of pixels to be inpainted. Have to be the same shape as one
of the 'image' channels. Unknown pixels have to be represented with 1,
known pixels - with 0.
multichannel : boolean, optional
If True, the last `image` dimension is considered as a color channel,
otherwise as spatial.
Returns
-------
out : (M[, N[, ..., P]][, C]) ndarray
Input image with masked pixels inpainted.
References
----------
.. [1] N.S.Hoang, S.B.Damelin, "On surface completion and image inpainting
by biharmonic functions: numerical aspects",
:arXiv:`1707.06567`
.. [2] C. K. Chui and H. N. Mhaskar, MRA Contextual-Recovery Extension of
Smooth Functions on Manifolds, Appl. and Comp. Harmonic Anal.,
28 (2010), 104-113,
:DOI:`10.1016/j.acha.2009.04.004`
Examples
--------
>>> img = np.tile(np.square(np.linspace(0, 1, 5)), (5, 1))
>>> mask = np.zeros_like(img)
>>> mask[2, 2:] = 1
>>> mask[1, 3:] = 1
>>> mask[0, 4:] = 1
>>> out = inpaint_biharmonic(img, mask)
"""
if image.ndim < 1:
raise ValueError('Input array has to be at least 1D')
img_baseshape = image.shape[:-1] if multichannel else image.shape
if img_baseshape != mask.shape:
raise ValueError('Input arrays have to be the same shape')
if np.ma.isMaskedArray(image):
raise TypeError('Masked arrays are not supported')
image = skimage.img_as_float(image)
mask = mask.astype(bool)
# Split inpainting mask into independent regions
kernel = ndi.morphology.generate_binary_structure(mask.ndim, 1)
mask_dilated = ndi.morphology.binary_dilation(mask, structure=kernel)
mask_labeled, num_labels = label(mask_dilated, return_num=True)
mask_labeled *= mask
if not multichannel:
image = image[..., np.newaxis]
out = np.copy(image)
for idx_channel in range(image.shape[-1]):
known_points = image[..., idx_channel][~mask]
limits = (np.min(known_points), np.max(known_points))
for idx_region in range(1, num_labels+1):
mask_region = mask_labeled == idx_region
_inpaint_biharmonic_single_channel(mask_region,
out[..., idx_channel], limits)
if not multichannel:
out = out[..., 0]
return out