123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- import numpy as np
- from numba import njit, prange
- # The foreground estimation refer to pymatting [https://github.com/pymatting/pymatting/blob/master/pymatting/foreground/estimate_foreground_ml.py]
- @njit("void(f4[:, :, :], f4[:, :, :])", cache=True, nogil=True, parallel=True)
- def _resize_nearest_multichannel(dst, src):
- """
- Internal method.
- Resize image src to dst using nearest neighbors filtering.
- Images must have multiple color channels, i.e. :code:`len(shape) == 3`.
- Parameters
- ----------
- dst: numpy.ndarray of type np.float32
- output image
- src: numpy.ndarray of type np.float32
- input image
- """
- h_src, w_src, depth = src.shape
- h_dst, w_dst, depth = dst.shape
- for y_dst in prange(h_dst):
- for x_dst in range(w_dst):
- x_src = max(0, min(w_src - 1, x_dst * w_src // w_dst))
- y_src = max(0, min(h_src - 1, y_dst * h_src // h_dst))
- for c in range(depth):
- dst[y_dst, x_dst, c] = src[y_src, x_src, c]
- @njit("void(f4[:, :], f4[:, :])", cache=True, nogil=True, parallel=True)
- def _resize_nearest(dst, src):
- """
- Internal method.
- Resize image src to dst using nearest neighbors filtering.
- Images must be grayscale, i.e. :code:`len(shape) == 3`.
- Parameters
- ----------
- dst: numpy.ndarray of type np.float32
- output image
- src: numpy.ndarray of type np.float32
- input image
- """
- h_src, w_src = src.shape
- h_dst, w_dst = dst.shape
- for y_dst in prange(h_dst):
- for x_dst in range(w_dst):
- x_src = max(0, min(w_src - 1, x_dst * w_src // w_dst))
- y_src = max(0, min(h_src - 1, y_dst * h_src // h_dst))
- dst[y_dst, x_dst] = src[y_src, x_src]
- # TODO
- # There should be an option to switch @njit(parallel=True) on or off.
- # parallel=True would be faster, but might cause race conditions.
- # User should have the option to turn it on or off.
- @njit(
- "Tuple((f4[:, :, :], f4[:, :, :]))(f4[:, :, :], f4[:, :], f4, i4, i4, i4, f4)",
- cache=True,
- nogil=True)
- def _estimate_fb_ml(
- input_image,
- input_alpha,
- regularization,
- n_small_iterations,
- n_big_iterations,
- small_size,
- gradient_weight, ):
- h0, w0, depth = input_image.shape
- dtype = np.float32
- w_prev = 1
- h_prev = 1
- F_prev = np.empty((h_prev, w_prev, depth), dtype=dtype)
- B_prev = np.empty((h_prev, w_prev, depth), dtype=dtype)
- n_levels = int(np.ceil(np.log2(max(w0, h0))))
- for i_level in range(n_levels + 1):
- w = round(w0**(i_level / n_levels))
- h = round(h0**(i_level / n_levels))
- image = np.empty((h, w, depth), dtype=dtype)
- alpha = np.empty((h, w), dtype=dtype)
- _resize_nearest_multichannel(image, input_image)
- _resize_nearest(alpha, input_alpha)
- F = np.empty((h, w, depth), dtype=dtype)
- B = np.empty((h, w, depth), dtype=dtype)
- _resize_nearest_multichannel(F, F_prev)
- _resize_nearest_multichannel(B, B_prev)
- if w <= small_size and h <= small_size:
- n_iter = n_small_iterations
- else:
- n_iter = n_big_iterations
- b = np.zeros((2, depth), dtype=dtype)
- dx = [-1, 1, 0, 0]
- dy = [0, 0, -1, 1]
- for i_iter in range(n_iter):
- for y in prange(h):
- for x in range(w):
- a0 = alpha[y, x]
- a1 = 1.0 - a0
- a00 = a0 * a0
- a01 = a0 * a1
- # a10 = a01 can be omitted due to symmetry of matrix
- a11 = a1 * a1
- for c in range(depth):
- b[0, c] = a0 * image[y, x, c]
- b[1, c] = a1 * image[y, x, c]
- for d in range(4):
- x2 = max(0, min(w - 1, x + dx[d]))
- y2 = max(0, min(h - 1, y + dy[d]))
- gradient = abs(a0 - alpha[y2, x2])
- da = regularization + gradient_weight * gradient
- a00 += da
- a11 += da
- for c in range(depth):
- b[0, c] += da * F[y2, x2, c]
- b[1, c] += da * B[y2, x2, c]
- determinant = a00 * a11 - a01 * a01
- inv_det = 1.0 / determinant
- b00 = inv_det * a11
- b01 = inv_det * -a01
- b11 = inv_det * a00
- for c in range(depth):
- F_c = b00 * b[0, c] + b01 * b[1, c]
- B_c = b01 * b[0, c] + b11 * b[1, c]
- F_c = max(0.0, min(1.0, F_c))
- B_c = max(0.0, min(1.0, B_c))
- F[y, x, c] = F_c
- B[y, x, c] = B_c
- F_prev = F
- B_prev = B
- w_prev = w
- h_prev = h
- return F, B
- def estimate_foreground_ml(
- image,
- alpha,
- regularization=1e-5,
- n_small_iterations=10,
- n_big_iterations=2,
- small_size=32,
- return_background=False,
- gradient_weight=1.0, ):
- """Estimates the foreground of an image given its alpha matte.
- See :cite:`germer2020multilevel` for reference.
- Parameters
- ----------
- image: numpy.ndarray
- Input image with shape :math:`h \\times w \\times d`
- alpha: numpy.ndarray
- Input alpha matte shape :math:`h \\times w`
- regularization: float
- Regularization strength :math:`\\epsilon`, defaults to :math:`10^{-5}`.
- Higher regularization results in smoother colors.
- n_small_iterations: int
- Number of iterations performed on small scale, defaults to :math:`10`
- n_big_iterations: int
- Number of iterations performed on large scale, defaults to :math:`2`
- small_size: int
- Threshold that determines at which size `n_small_iterations` should be used
- return_background: bool
- Whether to return the estimated background in addition to the foreground
- gradient_weight: float
- Larger values enforce smoother foregrounds, defaults to :math:`1`
- Returns
- -------
- F: numpy.ndarray
- Extracted foreground
- B: numpy.ndarray
- Extracted background
- Example
- -------
- >>> from pymatting import *
- >>> image = load_image("data/lemur/lemur.png", "RGB")
- >>> alpha = load_image("data/lemur/lemur_alpha.png", "GRAY")
- >>> F = estimate_foreground_ml(image, alpha, return_background=False)
- >>> F, B = estimate_foreground_ml(image, alpha, return_background=True)
- See Also
- ----
- stack_images: This function can be used to place the foreground on a new background.
- """
- foreground, background = _estimate_fb_ml(
- image.astype(np.float32),
- alpha.astype(np.float32),
- regularization,
- n_small_iterations,
- n_big_iterations,
- small_size,
- gradient_weight, )
- if return_background:
- return foreground, background
- return foreground
|