All Downloads are FREE. Search and download functionalities are using the official Maven repository.

boofcv.alg.segmentation.ms.ClusterLabeledImage Maven / Gradle / Ivy

Go to download

BoofCV is an open source Java library for real-time computer vision and robotics applications.

The newest version!
/*
 * Copyright (c) 2021, Peter Abeles. All Rights Reserved.
 *
 * This file is part of BoofCV (http://boofcv.org).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package boofcv.alg.segmentation.ms;

import boofcv.alg.misc.ImageMiscOps;
import boofcv.struct.ConnectRule;
import boofcv.struct.image.GrayS32;
import georegression.struct.point.Point2D_I32;
import org.ddogleg.struct.DogArray_I32;

/**
 * 

* Given a labeled image in which pixels that contains the same label may or may not be connected to each other, * create a new labeled image in which only connected pixels have the same label. * A two pass algorithm is used. In the first pass pixels are examined from top to bottom, left to right. For * each pixel (the target), the input image and output image labels of its adjacent pixels are examined. If an adjacent * pixel has the same input label as the target then it is either assigned the same output label or marked for being * merged. Depending if it is not labeled or has an output label already, respectively. After all the pixels are process * the merge requests are examined and a new set of output labels is created. A pass across the output image is * done to relabel the inputs. *

* *

* Clustering can be done using 4 or 8 connect, which defines what an adjacent pixel is. 4-connect just considers * pixels which are (+1,0) (0,1) (-1,0) (0,-1). 8-connect considers (+1,0) (0,1) (-1,0) (0,-1) and * (1,1) (-1,1) (-1,-1) (1,-1). *

* * @author Peter Abeles */ @SuppressWarnings({"NullAway.Init"}) public class ClusterLabeledImage extends RegionMergeTree { // which connectivity rule is used. 4 or 8. protected ConnectRule connectRule; // offset in pixel indices for adjacent pixels protected int[] edgesIn; protected int[] edgesOut; // relative coordinates of adjacent pixels protected Point2D_I32[] edges; // contains the number of pixels in each output label protected DogArray_I32 regionMemberCount; /** * Configures labeling * * @param connectRule Which connectivity rule to use. 4 or 8 */ public ClusterLabeledImage( ConnectRule connectRule ) { this.connectRule = connectRule; if (connectRule == ConnectRule.EIGHT) { edgesIn = new int[4]; edgesOut = new int[4]; edges = new Point2D_I32[4]; } else if (connectRule == ConnectRule.FOUR) { edgesIn = new int[2]; edgesOut = new int[2]; edges = new Point2D_I32[2]; } else { throw new IllegalArgumentException("connectRule must be 4 or 8"); } for (int i = 0; i < edges.length; i++) edges[i] = new Point2D_I32(); } /** * Declares lookup tables for neighbors */ protected void setUpEdges( GrayS32 input, GrayS32 output ) { if (connectRule == ConnectRule.EIGHT) { setUpEdges8(input, edgesIn); setUpEdges8(output, edgesOut); edges[0].setTo(1, 0); edges[1].setTo(1, 1); edges[2].setTo(0, 1); edges[3].setTo(-1, 0); } else { setUpEdges4(input, edgesIn); setUpEdges4(output, edgesOut); edges[0].setTo(1, 0); edges[1].setTo(0, 1); } } protected void setUpEdges8( GrayS32 image, int[] edges ) { edges[0] = 1; edges[1] = 1 + image.stride; edges[2] = +image.stride; edges[3] = -1 + image.stride; } protected void setUpEdges4( GrayS32 image, int[] edges ) { edges[0] = 1; edges[1] = +image.stride; } /** * Relabels the image such that all pixels with the same label are a member of the same graph. * * @param input Labeled input image. * @param output Labeled output image. * @param regionMemberCount (Input/Output) Number of pixels which belong to each group. */ public void process( GrayS32 input, GrayS32 output, DogArray_I32 regionMemberCount ) { // initialize data structures this.regionMemberCount = regionMemberCount; regionMemberCount.reset(); setUpEdges(input, output); ImageMiscOps.fill(output, -1); // this is a bit of a hack here. Normally you call the parent's init function. // since the number of regions is not initially known this will grow mergeList.reset(); connectInner(input, output); connectLeftRight(input, output); connectBottom(input, output); // Merge together all the regions that are connected in the output image performMerge(output, regionMemberCount); } /** * Examines pixels inside the image without the need for bounds checking */ protected void connectInner( GrayS32 input, GrayS32 output ) { int startX = connectRule == ConnectRule.EIGHT ? 1 : 0; for (int y = 0; y < input.height - 1; y++) { int indexIn = input.startIndex + y*input.stride + startX; int indexOut = output.startIndex + y*output.stride + startX; for (int x = startX; x < input.width - 1; x++, indexIn++, indexOut++) { int inputLabel = input.data[indexIn]; int outputLabel = output.data[indexOut]; if (outputLabel == -1) { // see if it needs to create a new output segment output.data[indexOut] = outputLabel = regionMemberCount.size; regionMemberCount.add(1); mergeList.add(outputLabel); } for (int i = 0; i < edgesIn.length; i++) { if (inputLabel == input.data[indexIn + edgesIn[i]]) { int outputAdj = output.data[indexOut + edgesOut[i]]; if (outputAdj == -1) { // see if not assigned regionMemberCount.data[outputLabel]++; output.data[indexOut + edgesOut[i]] = outputLabel; } else if (outputLabel != outputAdj) { // see if assigned to different regions markMerge(outputLabel, outputAdj); } // do nothing, same input and output labels } } } } } /** * Examines pixels along the left and right border */ protected void connectLeftRight( GrayS32 input, GrayS32 output ) { for (int y = 0; y < input.height; y++) { int x = input.width - 1; int inputLabel = input.unsafe_get(x, y); int outputLabel = output.unsafe_get(x, y); if (outputLabel == -1) { // see if it needs to create a new output segment outputLabel = regionMemberCount.size; output.unsafe_set(x, y, outputLabel); regionMemberCount.add(1); mergeList.add(outputLabel); } // check right first for (int i = 0; i < edges.length; i++) { Point2D_I32 offset = edges[i]; // make sure it is inside the image if (!input.isInBounds(x + offset.x, y + offset.y)) continue; if (inputLabel == input.unsafe_get(x + offset.x, y + offset.y)) { int outputAdj = output.unsafe_get(x + offset.x, y + offset.y); if (outputAdj == -1) { // see if not assigned regionMemberCount.data[outputLabel]++; output.unsafe_set(x + offset.x, y + offset.y, outputLabel); } else if (outputLabel != outputAdj) { // see if assigned to different regions markMerge(outputLabel, outputAdj); } // do nothing, same input and output labels } } // skip check of left of 4-connect if (connectRule != ConnectRule.EIGHT) continue; x = 0; inputLabel = input.unsafe_get(x, y); outputLabel = output.unsafe_get(x, y); if (outputLabel == -1) { // see if it needs to create a new output segment outputLabel = regionMemberCount.size; output.unsafe_set(x, y, outputLabel); regionMemberCount.add(1); mergeList.add(outputLabel); } for (int i = 0; i < edges.length; i++) { Point2D_I32 offset = edges[i]; // make sure it is inside the image if (!input.isInBounds(x + offset.x, y + offset.y)) continue; if (inputLabel == input.unsafe_get(x + offset.x, y + offset.y)) { int outputAdj = output.unsafe_get(x + offset.x, y + offset.y); if (outputAdj == -1) { // see if not assigned regionMemberCount.data[outputLabel]++; output.unsafe_set(x + offset.x, y + offset.y, outputLabel); } else if (outputLabel != outputAdj) { // see if assigned to different regions markMerge(outputLabel, outputAdj); } // do nothing, same input and output labels } } } } /** * Examines pixels along the bottom border */ protected void connectBottom( GrayS32 input, GrayS32 output ) { for (int x = 0; x < input.width - 1; x++) { int y = input.height - 1; int inputLabel = input.unsafe_get(x, y); int outputLabel = output.unsafe_get(x, y); if (outputLabel == -1) { // see if it needs to create a new output segment outputLabel = regionMemberCount.size; output.unsafe_set(x, y, outputLabel); regionMemberCount.add(1); mergeList.add(outputLabel); } // for 4 and 8 connect the check is only +1 x and 0 y if (inputLabel == input.unsafe_get(x + 1, y)) { int outputAdj = output.unsafe_get(x + 1, y); if (outputAdj == -1) { // see if not assigned regionMemberCount.data[outputLabel]++; output.unsafe_set(x + 1, y, outputLabel); } else if (outputLabel != outputAdj) { // see if assigned to different regions markMerge(outputLabel, outputAdj); } // do nothing, same input and output labels } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy