Advertisement
Guest User

Untitled

a guest
May 23rd, 2019
64
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 34.35 KB | None | 0 0
  1. """
  2. Mask R-CNN
  3. Common utility functions and classes.
  4. Copyright (c) 2017 Matterport, Inc.
  5. Licensed under the MIT License (see LICENSE for details)
  6. Written by Waleed Abdulla
  7. """
  8.  
  9. import sys
  10. import os
  11. import logging
  12. import math
  13. import random
  14. import numpy as np
  15. import tensorflow as tf
  16. import scipy
  17. import skimage.color
  18. import skimage.io
  19. import skimage.transform
  20. import urllib.request
  21. import shutil
  22. import warnings
  23. from distutils.version import LooseVersion
  24.  
  25. # URL from which to download the latest COCO trained weights
  26. COCO_MODEL_URL = "https://github.com/matterport/Mask_RCNN/releases/download/v2.0/mask_rcnn_coco.h5"
  27.  
  28.  
  29. ############################################################
  30. # Bounding Boxes
  31. ############################################################
  32.  
  33. def extract_bboxes(mask):
  34. """Compute bounding boxes from masks.
  35. mask: [height, width, num_instances]. Mask pixels are either 1 or 0.
  36. Returns: bbox array [num_instances, (y1, x1, y2, x2)].
  37. """
  38. boxes = np.zeros([mask.shape[-1], 4], dtype=np.int32)
  39. for i in range(mask.shape[-1]):
  40. m = mask[:, :, i]
  41. # Bounding box.
  42. horizontal_indicies = np.where(np.any(m, axis=0))[0]
  43. vertical_indicies = np.where(np.any(m, axis=1))[0]
  44. if horizontal_indicies.shape[0]:
  45. x1, x2 = horizontal_indicies[[0, -1]]
  46. y1, y2 = vertical_indicies[[0, -1]]
  47. # x2 and y2 should not be part of the box. Increment by 1.
  48. x2 += 1
  49. y2 += 1
  50. else:
  51. # No mask for this instance. Might happen due to
  52. # resizing or cropping. Set bbox to zeros
  53. x1, x2, y1, y2 = 0, 0, 0, 0
  54. boxes[i] = np.array([y1, x1, y2, x2])
  55. return boxes.astype(np.int32)
  56.  
  57.  
  58. def compute_iou(box, boxes, box_area, boxes_area):
  59. """Calculates IoU of the given box with the array of the given boxes.
  60. box: 1D vector [y1, x1, y2, x2]
  61. boxes: [boxes_count, (y1, x1, y2, x2)]
  62. box_area: float. the area of 'box'
  63. boxes_area: array of length boxes_count.
  64. Note: the areas are passed in rather than calculated here for
  65. efficiency. Calculate once in the caller to avoid duplicate work.
  66. """
  67. # Calculate intersection areas
  68. y1 = np.maximum(box[0], boxes[:, 0])
  69. y2 = np.minimum(box[2], boxes[:, 2])
  70. x1 = np.maximum(box[1], boxes[:, 1])
  71. x2 = np.minimum(box[3], boxes[:, 3])
  72. intersection = np.maximum(x2 - x1, 0) * np.maximum(y2 - y1, 0)
  73. union = box_area + boxes_area[:] - intersection[:]
  74. iou = intersection / union
  75. return iou
  76.  
  77.  
  78. def compute_overlaps(boxes1, boxes2):
  79. """Computes IoU overlaps between two sets of boxes.
  80. boxes1, boxes2: [N, (y1, x1, y2, x2)].
  81. For better performance, pass the largest set first and the smaller second.
  82. """
  83. # Areas of anchors and GT boxes
  84. area1 = (boxes1[:, 2] - boxes1[:, 0]) * (boxes1[:, 3] - boxes1[:, 1])
  85. area2 = (boxes2[:, 2] - boxes2[:, 0]) * (boxes2[:, 3] - boxes2[:, 1])
  86.  
  87. # Compute overlaps to generate matrix [boxes1 count, boxes2 count]
  88. # Each cell contains the IoU value.
  89. overlaps = np.zeros((boxes1.shape[0], boxes2.shape[0]))
  90. for i in range(overlaps.shape[1]):
  91. box2 = boxes2[i]
  92. overlaps[:, i] = compute_iou(box2, boxes1, area2[i], area1)
  93. return overlaps
  94.  
  95.  
  96. def compute_overlaps_masks(masks1, masks2):
  97. """Computes IoU overlaps between two sets of masks.
  98. masks1, masks2: [Height, Width, instances]
  99. """
  100.  
  101. # If either set of masks is empty return empty result
  102. if masks1.shape[-1] == 0 or masks2.shape[-1] == 0:
  103. return np.zeros((masks1.shape[-1], masks2.shape[-1]))
  104. # flatten masks and compute their areas
  105. masks1 = np.reshape(masks1 > .5, (-1, masks1.shape[-1])).astype(np.float32)
  106. masks2 = np.reshape(masks2 > .5, (-1, masks2.shape[-1])).astype(np.float32)
  107. area1 = np.sum(masks1, axis=0)
  108. area2 = np.sum(masks2, axis=0)
  109.  
  110. # intersections and union
  111. intersections = np.dot(masks1.T, masks2)
  112. union = area1[:, None] + area2[None, :] - intersections
  113. overlaps = intersections / union
  114.  
  115. return overlaps
  116.  
  117.  
  118. def non_max_suppression(boxes, scores, threshold):
  119. """Performs non-maximum suppression and returns indices of kept boxes.
  120. boxes: [N, (y1, x1, y2, x2)]. Notice that (y2, x2) lays outside the box.
  121. scores: 1-D array of box scores.
  122. threshold: Float. IoU threshold to use for filtering.
  123. """
  124. assert boxes.shape[0] > 0
  125. if boxes.dtype.kind != "f":
  126. boxes = boxes.astype(np.float32)
  127.  
  128. # Compute box areas
  129. y1 = boxes[:, 0]
  130. x1 = boxes[:, 1]
  131. y2 = boxes[:, 2]
  132. x2 = boxes[:, 3]
  133. area = (y2 - y1) * (x2 - x1)
  134.  
  135. # Get indicies of boxes sorted by scores (highest first)
  136. ixs = scores.argsort()[::-1]
  137.  
  138. pick = []
  139. while len(ixs) > 0:
  140. # Pick top box and add its index to the list
  141. i = ixs[0]
  142. pick.append(i)
  143. # Compute IoU of the picked box with the rest
  144. iou = compute_iou(boxes[i], boxes[ixs[1:]], area[i], area[ixs[1:]])
  145. # Identify boxes with IoU over the threshold. This
  146. # returns indices into ixs[1:], so add 1 to get
  147. # indices into ixs.
  148. remove_ixs = np.where(iou > threshold)[0] + 1
  149. # Remove indices of the picked and overlapped boxes.
  150. ixs = np.delete(ixs, remove_ixs)
  151. ixs = np.delete(ixs, 0)
  152. return np.array(pick, dtype=np.int32)
  153.  
  154.  
  155. def apply_box_deltas(boxes, deltas):
  156. """Applies the given deltas to the given boxes.
  157. boxes: [N, (y1, x1, y2, x2)]. Note that (y2, x2) is outside the box.
  158. deltas: [N, (dy, dx, log(dh), log(dw))]
  159. """
  160. boxes = boxes.astype(np.float32)
  161. # Convert to y, x, h, w
  162. height = boxes[:, 2] - boxes[:, 0]
  163. width = boxes[:, 3] - boxes[:, 1]
  164. center_y = boxes[:, 0] + 0.5 * height
  165. center_x = boxes[:, 1] + 0.5 * width
  166. # Apply deltas
  167. center_y += deltas[:, 0] * height
  168. center_x += deltas[:, 1] * width
  169. height *= np.exp(deltas[:, 2])
  170. width *= np.exp(deltas[:, 3])
  171. # Convert back to y1, x1, y2, x2
  172. y1 = center_y - 0.5 * height
  173. x1 = center_x - 0.5 * width
  174. y2 = y1 + height
  175. x2 = x1 + width
  176. return np.stack([y1, x1, y2, x2], axis=1)
  177.  
  178.  
  179. def box_refinement_graph(box, gt_box):
  180. """Compute refinement needed to transform box to gt_box.
  181. box and gt_box are [N, (y1, x1, y2, x2)]
  182. """
  183. box = tf.cast(box, tf.float32)
  184. gt_box = tf.cast(gt_box, tf.float32)
  185.  
  186. height = box[:, 2] - box[:, 0]
  187. width = box[:, 3] - box[:, 1]
  188. center_y = box[:, 0] + 0.5 * height
  189. center_x = box[:, 1] + 0.5 * width
  190.  
  191. gt_height = gt_box[:, 2] - gt_box[:, 0]
  192. gt_width = gt_box[:, 3] - gt_box[:, 1]
  193. gt_center_y = gt_box[:, 0] + 0.5 * gt_height
  194. gt_center_x = gt_box[:, 1] + 0.5 * gt_width
  195.  
  196. dy = (gt_center_y - center_y) / height
  197. dx = (gt_center_x - center_x) / width
  198. dh = tf.log(gt_height / height)
  199. dw = tf.log(gt_width / width)
  200.  
  201. result = tf.stack([dy, dx, dh, dw], axis=1)
  202. return result
  203.  
  204.  
  205. def box_refinement(box, gt_box):
  206. """Compute refinement needed to transform box to gt_box.
  207. box and gt_box are [N, (y1, x1, y2, x2)]. (y2, x2) is
  208. assumed to be outside the box.
  209. """
  210. box = box.astype(np.float32)
  211. gt_box = gt_box.astype(np.float32)
  212.  
  213. height = box[:, 2] - box[:, 0]
  214. width = box[:, 3] - box[:, 1]
  215. center_y = box[:, 0] + 0.5 * height
  216. center_x = box[:, 1] + 0.5 * width
  217.  
  218. gt_height = gt_box[:, 2] - gt_box[:, 0]
  219. gt_width = gt_box[:, 3] - gt_box[:, 1]
  220. gt_center_y = gt_box[:, 0] + 0.5 * gt_height
  221. gt_center_x = gt_box[:, 1] + 0.5 * gt_width
  222.  
  223. dy = (gt_center_y - center_y) / height
  224. dx = (gt_center_x - center_x) / width
  225. dh = np.log(gt_height / height)
  226. dw = np.log(gt_width / width)
  227.  
  228. return np.stack([dy, dx, dh, dw], axis=1)
  229.  
  230.  
  231. ############################################################
  232. # Dataset
  233. ############################################################
  234.  
  235. class Dataset(object):
  236. """The base class for dataset classes.
  237. To use it, create a new class that adds functions specific to the dataset
  238. you want to use. For example:
  239. class CatsAndDogsDataset(Dataset):
  240. def load_cats_and_dogs(self):
  241. ...
  242. def load_mask(self, image_id):
  243. ...
  244. def image_reference(self, image_id):
  245. ...
  246. See COCODataset and ShapesDataset as examples.
  247. """
  248.  
  249. def __init__(self, class_map=None):
  250. self._image_ids = []
  251. self.image_info = []
  252. # Background is always the first class
  253. self.class_info = [{"source": "", "id": 0, "name": "BG"}]
  254. self.source_class_ids = {}
  255.  
  256. def add_class(self, source, class_id, class_name):
  257. assert "." not in source, "Source name cannot contain a dot"
  258. # Does the class exist already?
  259. for info in self.class_info:
  260. if info['source'] == source and info["id"] == class_id:
  261. # source.class_id combination already available, skip
  262. return
  263. # Add the class
  264. self.class_info.append({
  265. "source": source,
  266. "id": class_id,
  267. "name": class_name,
  268. })
  269.  
  270. def add_image(self, source, image_id, path, **kwargs):
  271. image_info = {
  272. "id": image_id,
  273. "source": source,
  274. "path": path,
  275. }
  276. image_info.update(kwargs)
  277. self.image_info.append(image_info)
  278.  
  279. def image_reference(self, image_id):
  280. """Return a link to the image in its source Website or details about
  281. the image that help looking it up or debugging it.
  282. Override for your dataset, but pass to this function
  283. if you encounter images not in your dataset.
  284. """
  285. return ""
  286.  
  287. def prepare(self, class_map=None):
  288. """Prepares the Dataset class for use.
  289. TODO: class map is not supported yet. When done, it should handle mapping
  290. classes from different datasets to the same class ID.
  291. """
  292.  
  293. def clean_name(name):
  294. """Returns a shorter version of object names for cleaner display."""
  295. return ",".join(name.split(",")[:1])
  296.  
  297. # Build (or rebuild) everything else from the info dicts.
  298. self.num_classes = len(self.class_info)
  299. self.class_ids = np.arange(self.num_classes)
  300. self.class_names = [clean_name(c["name"]) for c in self.class_info]
  301. self.num_images = len(self.image_info)
  302. self._image_ids = np.arange(self.num_images)
  303.  
  304. # Mapping from source class and image IDs to internal IDs
  305. self.class_from_source_map = {"{}.{}".format(info['source'], info['id']): id
  306. for info, id in zip(self.class_info, self.class_ids)}
  307. self.image_from_source_map = {"{}.{}".format(info['source'], info['id']): id
  308. for info, id in zip(self.image_info, self.image_ids)}
  309.  
  310. # Map sources to class_ids they support
  311. self.sources = list(set([i['source'] for i in self.class_info]))
  312. self.source_class_ids = {}
  313. # Loop over datasets
  314. for source in self.sources:
  315. self.source_class_ids[source] = []
  316. # Find classes that belong to this dataset
  317. for i, info in enumerate(self.class_info):
  318. # Include BG class in all datasets
  319. if i == 0 or source == info['source']:
  320. self.source_class_ids[source].append(i)
  321.  
  322. def map_source_class_id(self, source_class_id):
  323. """Takes a source class ID and returns the int class ID assigned to it.
  324. For example:
  325. dataset.map_source_class_id("coco.12") -> 23
  326. """
  327. return self.class_from_source_map[source_class_id]
  328.  
  329. def get_source_class_id(self, class_id, source):
  330. """Map an internal class ID to the corresponding class ID in the source dataset."""
  331. info = self.class_info[class_id]
  332. assert info['source'] == source
  333. return info['id']
  334.  
  335. @property
  336. def image_ids(self):
  337. return self._image_ids
  338.  
  339. def source_image_link(self, image_id):
  340. """Returns the path or URL to the image.
  341. Override this to return a URL to the image if it's available online for easy
  342. debugging.
  343. """
  344. return self.image_info[image_id]["path"]
  345.  
  346. def load_image(self, image_id):
  347. """Load the specified image and return a [H,W,3] Numpy array.
  348. """
  349. # Load image
  350. image = skimage.io.imread(self.image_info[image_id]['path'])
  351. # If grayscale. Convert to RGB for consistency.
  352. if image.ndim != 3:
  353. image = skimage.color.gray2rgb(image)
  354. # If has an alpha channel, remove it for consistency
  355. if image.shape[-1] == 4:
  356. image = image[..., :3]
  357. return image
  358.  
  359. def load_mask(self, image_id):
  360. """Load instance masks for the given image.
  361. Different datasets use different ways to store masks. Override this
  362. method to load instance masks and return them in the form of am
  363. array of binary masks of shape [height, width, instances].
  364. Returns:
  365. masks: A bool array of shape [height, width, instance count] with
  366. a binary mask per instance.
  367. class_ids: a 1D array of class IDs of the instance masks.
  368. """
  369. # Override this function to load a mask from your dataset.
  370. # Otherwise, it returns an empty mask.
  371. logging.warning("You are using the default load_mask(), maybe you need to define your own one.")
  372. mask = np.empty([0, 0, 0])
  373. class_ids = np.empty([0], np.int32)
  374. return mask, class_ids
  375.  
  376.  
  377. def resize_image(image, min_dim=None, max_dim=None, min_scale=None, mode="square", padding=None):
  378. """Resizes an image keeping the aspect ratio unchanged.
  379. min_dim: if provided, resizes the image such that it's smaller
  380. dimension == min_dim
  381. max_dim: if provided, ensures that the image longest side doesn't
  382. exceed this value.
  383. min_scale: if provided, ensure that the image is scaled up by at least
  384. this percent even if min_dim doesn't require it.
  385. mode: Resizing mode.
  386. none: No resizing. Return the image unchanged.
  387. square: Resize and pad with zeros to get a square image
  388. of size [max_dim, max_dim].
  389. pad64: Pads width and height with zeros to make them multiples of 64.
  390. If min_dim or min_scale are provided, it scales the image up
  391. before padding. max_dim is ignored in this mode.
  392. The multiple of 64 is needed to ensure smooth scaling of feature
  393. maps up and down the 6 levels of the FPN pyramid (2**6=64).
  394. crop: Picks random crops from the image. First, scales the image based
  395. on min_dim and min_scale, then picks a random crop of
  396. size min_dim x min_dim. Can be used in training only.
  397. max_dim is not used in this mode.
  398. Returns:
  399. image: the resized image
  400. window: (y1, x1, y2, x2). If max_dim is provided, padding might
  401. be inserted in the returned image. If so, this window is the
  402. coordinates of the image part of the full image (excluding
  403. the padding). The x2, y2 pixels are not included.
  404. scale: The scale factor used to resize the image
  405. padding: Padding added to the image [(top, bottom), (left, right), (0, 0)]
  406. """
  407. # Keep track of image dtype and return results in the same dtype
  408. image_dtype = image.dtype
  409. # Default window (y1, x1, y2, x2) and default scale == 1.
  410. h, w = image.shape[:2]
  411. window = (0, 0, h, w)
  412. scale = 1
  413. padding = [(0, 0), (0, 0), (0, 0)]
  414. crop = None
  415.  
  416. if mode == "none":
  417. return image, window, scale, padding, crop
  418.  
  419. # Scale?
  420. if min_dim:
  421. # Scale up but not down
  422. scale = max(1, min_dim / min(h, w))
  423. if min_scale and scale < min_scale:
  424. scale = min_scale
  425.  
  426. # Does it exceed max dim?
  427. if max_dim and mode == "square":
  428. image_max = max(h, w)
  429. if round(image_max * scale) > max_dim:
  430. scale = max_dim / image_max
  431.  
  432. # Resize image using bilinear interpolation
  433. if scale != 1:
  434. image = resize(image, (round(h * scale), round(w * scale)),
  435. preserve_range=True)
  436.  
  437. # Need padding or cropping?
  438. if mode == "square":
  439. # Get new height and width
  440. h, w = image.shape[:2]
  441. top_pad = (max_dim - h) // 2
  442. bottom_pad = max_dim - h - top_pad
  443. left_pad = (max_dim - w) // 2
  444. right_pad = max_dim - w - left_pad
  445. padding = [(top_pad, bottom_pad), (left_pad, right_pad), (0, 0)]
  446. image = np.pad(image, padding, mode='constant', constant_values=0)
  447. window = (top_pad, left_pad, h + top_pad, w + left_pad)
  448. elif mode == "pad64":
  449. h, w = image.shape[:2]
  450. # Both sides must be divisible by 64
  451. assert min_dim % 64 == 0, "Minimum dimension must be a multiple of 64"
  452. # Height
  453. if h % 64 > 0:
  454. max_h = h - (h % 64) + 64
  455. top_pad = (max_h - h) // 2
  456. bottom_pad = max_h - h - top_pad
  457. else:
  458. top_pad = bottom_pad = 0
  459. # Width
  460. if w % 64 > 0:
  461. max_w = w - (w % 64) + 64
  462. left_pad = (max_w - w) // 2
  463. right_pad = max_w - w - left_pad
  464. else:
  465. left_pad = right_pad = 0
  466. padding = [(top_pad, bottom_pad), (left_pad, right_pad), (0, 0)]
  467. image = np.pad(image, padding, mode='constant', constant_values=0)
  468. window = (top_pad, left_pad, h + top_pad, w + left_pad)
  469. elif mode == "crop":
  470. # Pick a random crop
  471. h, w = image.shape[:2]
  472. y = random.randint(0, (h - min_dim))
  473. x = random.randint(0, (w - min_dim))
  474. crop = (y, x, min_dim, min_dim)
  475. image = image[y:y + min_dim, x:x + min_dim]
  476. window = (0, 0, min_dim, min_dim)
  477. elif mode == "mask":
  478. pass
  479. else:
  480. raise Exception("Mode {} not supported".format(mode))
  481. return image.astype(image_dtype), window, scale, padding, crop
  482.  
  483.  
  484. def resize_mask(mask, scale, padding, crop=None):
  485. """Resizes a mask using the given scale and padding.
  486. Typically, you get the scale and padding from resize_image() to
  487. ensure both, the image and the mask, are resized consistently.
  488. scale: mask scaling factor
  489. padding: Padding to add to the mask in the form
  490. [(top, bottom), (left, right), (0, 0)]
  491. """
  492. # Suppress warning from scipy 0.13.0, the output shape of zoom() is
  493. # calculated with round() instead of int()
  494. with warnings.catch_warnings():
  495. warnings.simplefilter("ignore")
  496. mask = scipy.ndimage.zoom(mask, zoom=[scale, scale, 1], order=0)
  497. if crop is not None:
  498. y, x, h, w = crop
  499. mask = mask[y:y + h, x:x + w]
  500. else:
  501. mask = np.pad(mask, padding, mode='constant', constant_values=0)
  502. return mask
  503.  
  504.  
  505. def minimize_mask(bbox, mask, mini_shape):
  506. """Resize masks to a smaller version to reduce memory load.
  507. Mini-masks can be resized back to image scale using expand_masks()
  508. See inspect_data.ipynb notebook for more details.
  509. """
  510. mini_mask = np.zeros(mini_shape + (mask.shape[-1],), dtype=bool)
  511. for i in range(mask.shape[-1]):
  512. # Pick slice and cast to bool in case load_mask() returned wrong dtype
  513. m = mask[:, :, i].astype(bool)
  514. y1, x1, y2, x2 = bbox[i][:4]
  515. m = m[y1:y2, x1:x2]
  516. if m.size == 0:
  517. raise Exception("Invalid bounding box with area of zero")
  518. # Resize with bilinear interpolation
  519. m = resize(m, mini_shape)
  520. mini_mask[:, :, i] = np.around(m).astype(np.bool)
  521. return mini_mask
  522.  
  523.  
  524. def expand_mask(bbox, mini_mask, image_shape):
  525. """Resizes mini masks back to image size. Reverses the change
  526. of minimize_mask().
  527. See inspect_data.ipynb notebook for more details.
  528. """
  529. mask = np.zeros(image_shape[:2] + (mini_mask.shape[-1],), dtype=bool)
  530. for i in range(mask.shape[-1]):
  531. m = mini_mask[:, :, i]
  532. y1, x1, y2, x2 = bbox[i][:4]
  533. h = y2 - y1
  534. w = x2 - x1
  535. # Resize with bilinear interpolation
  536. m = resize(m, (h, w))
  537. mask[y1:y2, x1:x2, i] = np.around(m).astype(np.bool)
  538. return mask
  539.  
  540.  
  541. # TODO: Build and use this function to reduce code duplication
  542. def mold_mask(mask, config):
  543. pass
  544.  
  545.  
  546. def unmold_mask(mask, bbox, image_shape, threshold):
  547. """Converts a mask generated by the neural network to a format similar
  548. to its original shape.
  549. mask: [height, width] of type float. A small, typically 28x28 mask.
  550. bbox: [y1, x1, y2, x2]. The box to fit the mask in.
  551. Returns a binary mask with the same size as the original image.
  552. """
  553. threshold = 0.5
  554. y1, x1, y2, x2 = bbox
  555. mask = resize(mask, (y2 - y1, x2 - x1))
  556. mask = np.where(mask >= threshold, 1, 0).astype(np.bool)
  557.  
  558. # Put the mask in the right location.
  559. full_mask = np.zeros(image_shape[:2], dtype=np.bool)
  560. full_mask[y1:y2, x1:x2] = mask
  561. return full_mask
  562.  
  563.  
  564. ############################################################
  565. # Anchors
  566. ############################################################
  567.  
  568. def generate_anchors(scales, ratios, shape, feature_stride, anchor_stride):
  569. """
  570. scales: 1D array of anchor sizes in pixels. Example: [32, 64, 128]
  571. ratios: 1D array of anchor ratios of width/height. Example: [0.5, 1, 2]
  572. shape: [height, width] spatial shape of the feature map over which
  573. to generate anchors.
  574. feature_stride: Stride of the feature map relative to the image in pixels.
  575. anchor_stride: Stride of anchors on the feature map. For example, if the
  576. value is 2 then generate anchors for every other feature map pixel.
  577. """
  578. # Get all combinations of scales and ratios
  579. scales, ratios = np.meshgrid(np.array(scales), np.array(ratios))
  580. scales = scales.flatten()
  581. ratios = ratios.flatten()
  582.  
  583. # Enumerate heights and widths from scales and ratios
  584. heights = scales / np.sqrt(ratios)
  585. widths = scales * np.sqrt(ratios)
  586.  
  587. # Enumerate shifts in feature space
  588. shifts_y = np.arange(0, shape[0], anchor_stride) * feature_stride
  589. shifts_x = np.arange(0, shape[1], anchor_stride) * feature_stride
  590. shifts_x, shifts_y = np.meshgrid(shifts_x, shifts_y)
  591.  
  592. # Enumerate combinations of shifts, widths, and heights
  593. box_widths, box_centers_x = np.meshgrid(widths, shifts_x)
  594. box_heights, box_centers_y = np.meshgrid(heights, shifts_y)
  595.  
  596. # Reshape to get a list of (y, x) and a list of (h, w)
  597. box_centers = np.stack(
  598. [box_centers_y, box_centers_x], axis=2).reshape([-1, 2])
  599. box_sizes = np.stack([box_heights, box_widths], axis=2).reshape([-1, 2])
  600.  
  601. # Convert to corner coordinates (y1, x1, y2, x2)
  602. boxes = np.concatenate([box_centers - 0.5 * box_sizes,
  603. box_centers + 0.5 * box_sizes], axis=1)
  604. return boxes
  605.  
  606.  
  607. def generate_pyramid_anchors(scales, ratios, feature_shapes, feature_strides,
  608. anchor_stride):
  609. """Generate anchors at different levels of a feature pyramid. Each scale
  610. is associated with a level of the pyramid, but each ratio is used in
  611. all levels of the pyramid.
  612. Returns:
  613. anchors: [N, (y1, x1, y2, x2)]. All generated anchors in one array. Sorted
  614. with the same order of the given scales. So, anchors of scale[0] come
  615. first, then anchors of scale[1], and so on.
  616. """
  617. # Anchors
  618. # [anchor_count, (y1, x1, y2, x2)]
  619. anchors = []
  620. for i in range(len(scales)):
  621. anchors.append(generate_anchors(scales[i], ratios, feature_shapes[i],
  622. feature_strides[i], anchor_stride))
  623. return np.concatenate(anchors, axis=0)
  624.  
  625.  
  626. ############################################################
  627. # Miscellaneous
  628. ############################################################
  629.  
  630. def trim_zeros(x):
  631. """It's common to have tensors larger than the available data and
  632. pad with zeros. This function removes rows that are all zeros.
  633. x: [rows, columns].
  634. """
  635. assert len(x.shape) == 2
  636. return x[~np.all(x == 0, axis=1)]
  637.  
  638.  
  639. def compute_matches(gt_boxes, gt_class_ids, gt_masks,
  640. pred_boxes, pred_class_ids, pred_scores, pred_masks,
  641. iou_threshold=0.5, score_threshold=0.0):
  642. """Finds matches between prediction and ground truth instances.
  643. Returns:
  644. gt_match: 1-D array. For each GT box it has the index of the matched
  645. predicted box.
  646. pred_match: 1-D array. For each predicted box, it has the index of
  647. the matched ground truth box.
  648. overlaps: [pred_boxes, gt_boxes] IoU overlaps.
  649. """
  650. # Trim zero padding
  651. # TODO: cleaner to do zero unpadding upstream
  652. gt_boxes = trim_zeros(gt_boxes)
  653. gt_masks = gt_masks[..., :gt_boxes.shape[0]]
  654. pred_boxes = trim_zeros(pred_boxes)
  655. pred_scores = pred_scores[:pred_boxes.shape[0]]
  656. # Sort predictions by score from high to low
  657. indices = np.argsort(pred_scores)[::-1]
  658. pred_boxes = pred_boxes[indices]
  659. pred_class_ids = pred_class_ids[indices]
  660. pred_scores = pred_scores[indices]
  661. pred_masks = pred_masks[..., indices]
  662.  
  663. # Compute IoU overlaps [pred_masks, gt_masks]
  664. overlaps = compute_overlaps_masks(pred_masks, gt_masks)
  665.  
  666. # Loop through predictions and find matching ground truth boxes
  667. match_count = 0
  668. pred_match = -1 * np.ones([pred_boxes.shape[0]])
  669. gt_match = -1 * np.ones([gt_boxes.shape[0]])
  670. for i in range(len(pred_boxes)):
  671. # Find best matching ground truth box
  672. # 1. Sort matches by score
  673. sorted_ixs = np.argsort(overlaps[i])[::-1]
  674. # 2. Remove low scores
  675. low_score_idx = np.where(overlaps[i, sorted_ixs] < score_threshold)[0]
  676. if low_score_idx.size > 0:
  677. sorted_ixs = sorted_ixs[:low_score_idx[0]]
  678. # 3. Find the match
  679. for j in sorted_ixs:
  680. # If ground truth box is already matched, go to next one
  681. if gt_match[j] > -1:
  682. continue
  683. # If we reach IoU smaller than the threshold, end the loop
  684. iou = overlaps[i, j]
  685. if iou < iou_threshold:
  686. break
  687. # Do we have a match?
  688. if pred_class_ids[i] == gt_class_ids[j]:
  689. match_count += 1
  690. gt_match[j] = i
  691. pred_match[i] = j
  692. break
  693.  
  694. return gt_match, pred_match, overlaps
  695.  
  696.  
  697. def compute_ap(gt_boxes, gt_class_ids, gt_masks,
  698. pred_boxes, pred_class_ids, pred_scores, pred_masks,
  699. iou_threshold=0.5):
  700. """Compute Average Precision at a set IoU threshold (default 0.5).
  701. Returns:
  702. mAP: Mean Average Precision
  703. precisions: List of precisions at different class score thresholds.
  704. recalls: List of recall values at different class score thresholds.
  705. overlaps: [pred_boxes, gt_boxes] IoU overlaps.
  706. """
  707. # Get matches and overlaps
  708. gt_match, pred_match, overlaps = compute_matches(
  709. gt_boxes, gt_class_ids, gt_masks,
  710. pred_boxes, pred_class_ids, pred_scores, pred_masks,
  711. iou_threshold)
  712.  
  713. # Compute precision and recall at each prediction box step
  714. precisions = np.cumsum(pred_match > -1) / (np.arange(len(pred_match)) + 1)
  715. recalls = np.cumsum(pred_match > -1).astype(np.float32) / len(gt_match)
  716.  
  717. # Pad with start and end values to simplify the math
  718. precisions = np.concatenate([[0], precisions, [0]])
  719. recalls = np.concatenate([[0], recalls, [1]])
  720.  
  721. # Ensure precision values decrease but don't increase. This way, the
  722. # precision value at each recall threshold is the maximum it can be
  723. # for all following recall thresholds, as specified by the VOC paper.
  724. for i in range(len(precisions) - 2, -1, -1):
  725. precisions[i] = np.maximum(precisions[i], precisions[i + 1])
  726.  
  727. # Compute mean AP over recall range
  728. indices = np.where(recalls[:-1] != recalls[1:])[0] + 1
  729. mAP = np.sum((recalls[indices] - recalls[indices - 1]) *
  730. precisions[indices])
  731.  
  732. return mAP, precisions, recalls, overlaps
  733.  
  734.  
  735. def compute_ap_range(gt_box, gt_class_id, gt_mask,
  736. pred_box, pred_class_id, pred_score, pred_mask,
  737. iou_thresholds=None, verbose=1):
  738. """Compute AP over a range or IoU thresholds. Default range is 0.5-0.95."""
  739. # Default is 0.5 to 0.95 with increments of 0.05
  740. iou_thresholds = iou_thresholds or np.arange(0.5, 1.0, 0.05)
  741.  
  742. # Compute AP over range of IoU thresholds
  743. AP = []
  744. for iou_threshold in iou_thresholds:
  745. ap, precisions, recalls, overlaps =\
  746. compute_ap(gt_box, gt_class_id, gt_mask,
  747. pred_box, pred_class_id, pred_score, pred_mask,
  748. iou_threshold=iou_threshold)
  749. if verbose:
  750. print("AP @{:.2f}:\t {:.3f}".format(iou_threshold, ap))
  751. AP.append(ap)
  752. AP = np.array(AP).mean()
  753. if verbose:
  754. print("AP @{:.2f}-{:.2f}:\t {:.3f}".format(
  755. iou_thresholds[0], iou_thresholds[-1], AP))
  756. return AP
  757.  
  758.  
  759. def compute_recall(pred_boxes, gt_boxes, iou):
  760. """Compute the recall at the given IoU threshold. It's an indication
  761. of how many GT boxes were found by the given prediction boxes.
  762. pred_boxes: [N, (y1, x1, y2, x2)] in image coordinates
  763. gt_boxes: [N, (y1, x1, y2, x2)] in image coordinates
  764. """
  765. # Measure overlaps
  766. overlaps = compute_overlaps(pred_boxes, gt_boxes)
  767. iou_max = np.max(overlaps, axis=1)
  768. iou_argmax = np.argmax(overlaps, axis=1)
  769. positive_ids = np.where(iou_max >= iou)[0]
  770. matched_gt_boxes = iou_argmax[positive_ids]
  771.  
  772. recall = len(set(matched_gt_boxes)) / gt_boxes.shape[0]
  773. return recall, positive_ids
  774.  
  775.  
  776. # ## Batch Slicing
  777. # Some custom layers support a batch size of 1 only, and require a lot of work
  778. # to support batches greater than 1. This function slices an input tensor
  779. # across the batch dimension and feeds batches of size 1. Effectively,
  780. # an easy way to support batches > 1 quickly with little code modification.
  781. # In the long run, it's more efficient to modify the code to support large
  782. # batches and getting rid of this function. Consider this a temporary solution
  783. def batch_slice(inputs, graph_fn, batch_size, names=None):
  784. """Splits inputs into slices and feeds each slice to a copy of the given
  785. computation graph and then combines the results. It allows you to run a
  786. graph on a batch of inputs even if the graph is written to support one
  787. instance only.
  788. inputs: list of tensors. All must have the same first dimension length
  789. graph_fn: A function that returns a TF tensor that's part of a graph.
  790. batch_size: number of slices to divide the data into.
  791. names: If provided, assigns names to the resulting tensors.
  792. """
  793. if not isinstance(inputs, list):
  794. inputs = [inputs]
  795.  
  796. outputs = []
  797. for i in range(batch_size):
  798. inputs_slice = [x[i] for x in inputs]
  799. output_slice = graph_fn(*inputs_slice)
  800. if not isinstance(output_slice, (tuple, list)):
  801. output_slice = [output_slice]
  802. outputs.append(output_slice)
  803. # Change outputs from a list of slices where each is
  804. # a list of outputs to a list of outputs and each has
  805. # a list of slices
  806. outputs = list(zip(*outputs))
  807.  
  808. if names is None:
  809. names = [None] * len(outputs)
  810.  
  811. result = [tf.stack(o, axis=0, name=n)
  812. for o, n in zip(outputs, names)]
  813. if len(result) == 1:
  814. result = result[0]
  815.  
  816. return result
  817.  
  818.  
  819. def download_trained_weights(coco_model_path, verbose=1):
  820. """Download COCO trained weights from Releases.
  821. coco_model_path: local path of COCO trained weights
  822. """
  823. if verbose > 0:
  824. print("Downloading pretrained model to " + coco_model_path + " ...")
  825. with urllib.request.urlopen(COCO_MODEL_URL) as resp, open(coco_model_path, 'wb') as out:
  826. shutil.copyfileobj(resp, out)
  827. if verbose > 0:
  828. print("... done downloading pretrained model!")
  829.  
  830.  
  831. def norm_boxes(boxes, shape):
  832. """Converts boxes from pixel coordinates to normalized coordinates.
  833. boxes: [N, (y1, x1, y2, x2)] in pixel coordinates
  834. shape: [..., (height, width)] in pixels
  835. Note: In pixel coordinates (y2, x2) is outside the box. But in normalized
  836. coordinates it's inside the box.
  837. Returns:
  838. [N, (y1, x1, y2, x2)] in normalized coordinates
  839. """
  840. h, w = shape
  841. scale = np.array([h - 1, w - 1, h - 1, w - 1])
  842. shift = np.array([0, 0, 1, 1])
  843. return np.divide((boxes - shift), scale).astype(np.float32)
  844.  
  845.  
  846. def denorm_boxes(boxes, shape):
  847. """Converts boxes from normalized coordinates to pixel coordinates.
  848. boxes: [N, (y1, x1, y2, x2)] in normalized coordinates
  849. shape: [..., (height, width)] in pixels
  850. Note: In pixel coordinates (y2, x2) is outside the box. But in normalized
  851. coordinates it's inside the box.
  852. Returns:
  853. [N, (y1, x1, y2, x2)] in pixel coordinates
  854. """
  855. h, w = shape
  856. scale = np.array([h - 1, w - 1, h - 1, w - 1])
  857. shift = np.array([0, 0, 1, 1])
  858. return np.around(np.multiply(boxes, scale) + shift).astype(np.int32)
  859.  
  860.  
  861. def resize(image, output_shape, order=1, mode='constant', cval=0, clip=True,
  862. preserve_range=False, anti_aliasing=False, anti_aliasing_sigma=None):
  863. """A wrapper for Scikit-Image resize().
  864. Scikit-Image generates warnings on every call to resize() if it doesn't
  865. receive the right parameters. The right parameters depend on the version
  866. of skimage. This solves the problem by using different parameters per
  867. version. And it provides a central place to control resizing defaults.
  868. """
  869. if LooseVersion(skimage.__version__) >= LooseVersion("0.14"):
  870. # New in 0.14: anti_aliasing. Default it to False for backward
  871. # compatibility with skimage 0.13.
  872. return skimage.transform.resize(
  873. image, output_shape,
  874. order=order, mode=mode, cval=cval, clip=clip,
  875. preserve_range=preserve_range, anti_aliasing=anti_aliasing,
  876. anti_aliasing_sigma=anti_aliasing_sigma)
  877. else:
  878. return skimage.transform.resize(
  879. image, output_shape,
  880. order=order, mode=mode, cval=cval, clip=clip,
  881. preserve_range=preserve_range)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement