opensfm/masking.py (84 lines of code) (raw):

import logging from typing import List, Tuple, Optional import cv2 import numpy as np from opensfm import upright from opensfm.dataset_base import DataSetBase logger = logging.getLogger(__name__) def mask_from_segmentation( segmentation: np.ndarray, ignore_values: List[int] ) -> np.ndarray: """Binary mask that is 0 for pixels with segmentation value to ignore.""" mask = np.ones(segmentation.shape, dtype=np.uint8) for value in ignore_values: mask &= segmentation != value return mask def combine_masks( mask1: Optional[np.ndarray], mask2: Optional[np.ndarray] ) -> Optional[np.ndarray]: """Combine two masks as mask1 AND mask2. Ignore any missing mask argument. """ if mask1 is None: if mask2 is None: return None else: return mask2 else: if mask2 is None: return mask1 else: mask1, mask2 = _resize_masks_to_match(mask1, mask2) return mask1 & mask2 def _resize_masks_to_match( im1: np.ndarray, im2: np.ndarray, ) -> Tuple[np.ndarray, np.ndarray]: h, w = max(im1.shape, im2.shape) if im1.shape != (h, w): im1 = cv2.resize(im1, (w, h), interpolation=cv2.INTER_NEAREST) if im2.shape != (h, w): im2 = cv2.resize(im2, (w, h), interpolation=cv2.INTER_NEAREST) return im1, im2 def load_features_mask( data: DataSetBase, image: str, points: np.ndarray, mask_image: Optional[np.ndarray] = None, ) -> np.ndarray: """Load a feature-wise mask. This is a binary array true for features that lie inside the combined mask. The array is all true when there's no mask. """ if points is None or len(points) == 0: return np.array([], dtype=bool) if mask_image is None: mask_image = _load_combined_mask(data, image) if mask_image is None: logger.debug("No segmentation for {}, no features masked.".format(image)) return np.ones((points.shape[0],), dtype=bool) exif = data.load_exif(image) width = exif["width"] height = exif["height"] orientation = exif["orientation"] new_height, new_width = mask_image.shape ps = upright.opensfm_to_upright( points[:, :2], width, height, orientation, new_width=new_width, new_height=new_height, ).astype(int) mask = mask_image[ps[:, 1], ps[:, 0]] n_removed = np.sum(mask == 0) logger.debug( "Masking {} / {} ({:.2f}) features for {}".format( n_removed, len(mask), n_removed / len(mask), image ) ) return np.array(mask, dtype=bool) def _load_segmentation_mask(data: DataSetBase, image: str) -> Optional[np.ndarray]: """Build a mask from segmentation ignore values. The mask is non-zero only for pixels with segmentation labels not in segmentation_ignore_values. """ ignore_values = data.segmentation_ignore_values(image) if not ignore_values: return None segmentation = data.load_segmentation(image) if segmentation is None: return None return mask_from_segmentation(segmentation, ignore_values) def _load_combined_mask(data: DataSetBase, image: str) -> Optional[np.ndarray]: """Combine binary mask with segmentation mask. Return a mask that is non-zero only where the binary mask and the segmentation mask are non-zero. """ mask = data.load_mask(image) smask = _load_segmentation_mask(data, image) return combine_masks(mask, smask)