Labels the connected components in a batch of images. A component is a set of pixels in a single input image, which are all adjacent and all have the same non-zero value. The components using a squared connectivity of one (all True entries are joined with their neighbors above, below, left,
(images)
| 467 | |
| 468 | |
| 469 | def connected_components(images): |
| 470 | """Labels the connected components in a batch of images. |
| 471 | |
| 472 | A component is a set of pixels in a single input image, which are all adjacent |
| 473 | and all have the same non-zero value. The components using a squared |
| 474 | connectivity of one (all True entries are joined with their neighbors above, |
| 475 | below, left, and right). Components across all images have consecutive ids 1 |
| 476 | through n. Components are labeled according to the first pixel of the |
| 477 | component appearing in row-major order (lexicographic order by |
| 478 | image_index_in_batch, row, col). Zero entries all have an output id of 0. |
| 479 | |
| 480 | This op is equivalent with `scipy.ndimage.measurements.label` on a 2D array |
| 481 | with the default structuring element (which is the connectivity used here). |
| 482 | |
| 483 | Args: |
| 484 | images: A 2D (H, W) or 3D (N, H, W) Tensor of boolean image(s). |
| 485 | |
| 486 | Returns: |
| 487 | Components with the same shape as `images`. False entries in `images` have |
| 488 | value 0, and all True entries map to a component id > 0. |
| 489 | |
| 490 | Raises: |
| 491 | TypeError: if `images` is not 2D or 3D. |
| 492 | """ |
| 493 | with ops.name_scope("connected_components"): |
| 494 | image_or_images = ops.convert_to_tensor(images, name="images") |
| 495 | if len(image_or_images.get_shape()) == 2: |
| 496 | images = image_or_images[None, :, :] |
| 497 | elif len(image_or_images.get_shape()) == 3: |
| 498 | images = image_or_images |
| 499 | else: |
| 500 | raise TypeError( |
| 501 | "images should have rank 2 (HW) or 3 (NHW). Static shape is %s" % |
| 502 | image_or_images.get_shape()) |
| 503 | components = gen_image_ops.image_connected_components(images) |
| 504 | |
| 505 | # TODO(ringwalt): Component id renaming should be done in the op, to avoid |
| 506 | # constructing multiple additional large tensors. |
| 507 | components_flat = array_ops.reshape(components, [-1]) |
| 508 | unique_ids, id_index = array_ops.unique(components_flat) |
| 509 | id_is_zero = array_ops.where_v2(math_ops.equal(unique_ids, 0))[:, 0] |
| 510 | # Map each nonzero id to consecutive values. |
| 511 | nonzero_consecutive_ids = math_ops.range( |
| 512 | array_ops.shape(unique_ids)[0] - array_ops.shape(id_is_zero)[0]) + 1 |
| 513 | |
| 514 | def no_zero(): |
| 515 | # No need to insert a zero into the ids. |
| 516 | return nonzero_consecutive_ids |
| 517 | |
| 518 | def has_zero(): |
| 519 | # Insert a zero in the consecutive ids where zero appears in unique_ids. |
| 520 | # id_is_zero has length 1. |
| 521 | zero_id_ind = math_ops.cast(id_is_zero[0], dtypes.int32) |
| 522 | ids_before = nonzero_consecutive_ids[:zero_id_ind] |
| 523 | ids_after = nonzero_consecutive_ids[zero_id_ind:] |
| 524 | return array_ops.concat([ids_before, [0], ids_after], axis=0) |
| 525 | |
| 526 | new_ids = control_flow_ops.cond( |