我正在尝试为项目编写自己的Watershed Segmentation实现。我有一个版本,返回类似于正确分割的东西给出非常琐碎的图片。不幸的是,它超级慢/效率低,它可能会或可能不会在所有情况下终止。
我一直在使用Woods和Gonzales的“数字图像处理”中的描述,以及Watershed Wikipedia页面。一般算法编码并包含在下面,但我有一种感觉,我循环了很多我不需要的东西。我很感激这里的任何和所有帮助,提前谢谢。
public static Set<Point> watershed(double[][] im) {
//Get the Gradient Magnitude to use as our "topographic map."
double t1 = System.currentTimeMillis();
double[][] edges = EdgeDetection.sobelMagnitude(im);
//Only iterate over values in the gradient magnitude to speed up.
double[] histogram = Image.getHistogram(edges);
Image.drawHistogram(histogram, Color.red);
int minPixelValue = 0;
for (int i = 0; i < histogram.length; i++) {
if (histogram[i] > 0) {
minPixelValue = i;
break;
}
}
int h = im.length;
int w = im[0].length;
//SE is a 3x3 structuring element for morphological dilation.
boolean[][] SE = {{true, true, true}, {true, true, true}, {true, true, true}};
//Keeping track of last iteration's components to see if two flooded together.
ArrayList<Set<Point>> lastComponents = connectedComponents(getSet(EdgeDetection.underThreshold(edges, minPixelValue + 1)));
ArrayList<Set<Point>> components;
Set<Point> boundary = new HashSet<Point>();
for (int i = minPixelValue + 1; i < 256; i++) {
if (histogram[i] != 0) {
System.out.println("BEHHH " + i);
t1 = System.currentTimeMillis();
ArrayList<Integer> damLocations = new ArrayList<Integer>();
HashMap<Integer, ArrayList<Integer>> correspondingSets = new HashMap<Integer, ArrayList<Integer>>();
//Figure out which of the old sets the new sets incorporated.
//Here is where we check if sets flooded into eachother.
//System.out.println("Checking for flooding");
components = connectedComponents(getSet(EdgeDetection.underThreshold(edges, i)));
for (int nc = 0; nc < components.size(); nc++) {
//System.out.println("Checking component " + nc);
Set<Point> newComponent = components.get(nc);
for (int oc = 0; oc < lastComponents.size(); oc++) {
//System.out.println(" Against component " + oc);
Set<Point> oldComponent = lastComponents.get(oc);
if (numberInCommon(newComponent, oldComponent) > 0) {
//System.out.println(" In there.");
ArrayList<Integer> oldSetsContained;
if (correspondingSets.containsKey(nc)) {
oldSetsContained = correspondingSets.get(nc);
damLocations.add(nc);
} else {
//System.out.println(" Nope.");
oldSetsContained = new ArrayList<Integer>();
}
oldSetsContained.add(oc);
correspondingSets.put(nc, oldSetsContained);
}
}
}
System.out.println("Calculating overlapping sets: " + (System.currentTimeMillis() - t1));
//System.out.println("Check done.");
for (int key : correspondingSets.keySet()) {
Integer[] cs = new Integer[correspondingSets.get(key).size()];
correspondingSets.get(key).toArray(cs);
if (cs.length == 1) {
//System.out.println("Set " + cs[0] + " has grown without flooding.");
} else {
//System.out.println("The following sets have flooded together: " + Arrays.toString(cs));
}
}
//Build Damns to prevent flooding
for (int c : damLocations) {
System.out.println("Building dam for component " + c);
Set<Point> bigComponent = components.get(c);
System.out.println("Total size: " + bigComponent.size());
ArrayList<Set<Point>> littleComponent = new ArrayList<Set<Point>>();
for (int lcindex : correspondingSets.get(c)) {
littleComponent.add(lastComponents.get(lcindex));
}
Set<Point> unionSet = new HashSet<Point>(boundary);
for (Set<Point> lc : littleComponent) {
unionSet = union(unionSet, lc);
}
System.out.println("Building union sets: " + (System.currentTimeMillis() - t1));
while (intersection(unionSet, bigComponent).size() < bigComponent.size()) {
for (int lIndex = 0; lIndex < littleComponent.size(); lIndex++) {
Set<Point> lc = littleComponent.get(lIndex);
Set<Point> lcBoundary = extractBoundaries(lc, SE, h, w);
Set<Point> toAdd = new HashSet<Point>();
Set<Point> otherComponents = new HashSet<Point>(unionSet);
otherComponents.removeAll(lc);
otherComponents.removeAll(boundary);
otherComponents = extractBoundaries(otherComponents, SE, h, w);
for (Point pt : lcBoundary) {
Set<Point> eightNbrs = get8Neighborhood(pt);
for (Point nbr : eightNbrs) {
if (bigComponent.contains(nbr) & !boundary.contains(nbr)) {
Set<Point> neighborNbr = get8Neighborhood(nbr);
if (intersection(neighborNbr, otherComponents).size() > 0) {
boundary.add(nbr);
edges[nbr.y][nbr.x] = 256;
break;
} else if (!lc.contains(nbr)) {
toAdd.add(nbr);
//if(i==65)System.out.println("Adding point " + nbr.y + " " + nbr.x);
} else {
//if(i==65)System.out.println("Already in here " + nbr.y + " " + nbr.x);
}
}
}
}
t1 = System.currentTimeMillis();
lc.addAll(toAdd);
toAdd.removeAll(toAdd);
littleComponent.set(lIndex, lc);
unionSet = new HashSet<Point>(boundary);
for (Set<Point> ltc : littleComponent) {
unionSet = union(unionSet, ltc);
}
System.out.println("This is a donk " + intersection(unionSet, bigComponent).size());
otherComponents = new HashSet<Point>(unionSet);
otherComponents.removeAll(lc);
otherComponents.removeAll(boundary);
}
}
}
}
}
boundary = close(boundary,h,w);
Image.drawSet(boundary, h, w);
return boundary;
}
答案 0 :(得分:3)
看起来算法最多为O(n ^ 2)。 你有很多嵌套循环..我无法找到伍兹的描述。 Christopher Mei的这段代码实现了算法:它是一个非常简单的实现。
WatershedPixel.java
/*
* Watershed algorithm
*
* Copyright (c) 2003 by Christopher Mei (christopher.mei@sophia.inria.fr)
*
* This plugin is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this plugin; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
import java.lang.*;
import java.util.*;
import ij.*;
/**
* The aim of WatershedPixel is to enable
* sorting the pixels of an Image according
* to their grayscale value.
*
* This is the first step of the Vincent
* and Soille Watershed algorithm (1991)
*
**/
public class WatershedPixel implements Comparable {
/** Value used to initialise the image */
final static int INIT = -1;
/** Value used to indicate the new pixels that
* are going to be processed (intial value
* at each level)
**/
final static int MASK = -2;
/** Value indicating that the pixel belongs
* to a watershed.
**/
final static int WSHED = 0;
/** Fictitious pixel **/
final static int FICTITIOUS = -3;
/** x coordinate of the pixel **/
private int x;
/** y coordinate of the pixel **/
private int y;
/** grayscale value of the pixel **/
private byte height;
/** Label used in the Watershed immersion algorithm **/
private int label;
/** Distance used for working on pixels */
private int dist;
/** Neighbours **/
private Vector neighbours;
public WatershedPixel(int x, int y, byte height) {
this.x = x;
this.y = y;
this.height = height;
label = INIT;
dist = 0;
neighbours = new Vector(8);
}
public WatershedPixel() {
label = FICTITIOUS;
}
public void addNeighbour(WatershedPixel neighbour) {
/*IJ.write("In Pixel, adding :");
IJ.write(""+neighbour);
IJ.write("Add done");
*/
neighbours.add(neighbour);
}
public Vector getNeighbours() {
return neighbours;
}
public String toString() {
return new String("("+x+","+y+"), height : "+getIntHeight()+", label : "+label+", distance : "+dist);
}
public final byte getHeight() {
return height;
}
public final int getIntHeight() {
return (int) height&0xff;
}
public final int getX() {
return x;
}
public final int getY() {
return y;
}
/** Method to be able to use the Collections.sort static method. **/
public int compareTo(Object o) {
if(!(o instanceof WatershedPixel))
throw new ClassCastException();
WatershedPixel obj = (WatershedPixel) o;
if( obj.getIntHeight() < getIntHeight() )
return 1;
if( obj.getIntHeight() > getIntHeight() )
return -1;
return 0;
}
public void setLabel(int label) {
this.label = label;
}
public void setLabelToINIT() {
label = INIT;
}
public void setLabelToMASK() {
label = MASK;
}
public void setLabelToWSHED() {
label = WSHED;
}
public boolean isLabelINIT() {
return label == INIT;
}
public boolean isLabelMASK() {
return label == MASK;
}
public boolean isLabelWSHED() {
return label == WSHED;
}
public int getLabel() {
return label;
}
public void setDistance(int distance) {
dist = distance;
}
public int getDistance() {
return dist;
}
public boolean isFICTITIOUS() {
return label == FICTITIOUS;
}
public boolean allNeighboursAreWSHED() {
for(int i=0 ; i<neighbours.size() ; i++) {
WatershedPixel r = (WatershedPixel) neighbours.get(i);
if( !r.isLabelWSHED() )
return false;
}
return true;
}
}
WatershedFIFO.java
/*
* Watershed plugin
*
* Copyright (c) 2003 by Christopher Mei (christopher.mei@sophia.inria.fr)
*
* This plugin is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this plugin; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
import java.util.*;
import ij.*;
/** This class implements a FIFO queue that
* uses the same formalism as the Vincent
* and Soille algorithm (1991)
**/
public class WatershedFIFO {
private LinkedList watershedFIFO;
public WatershedFIFO() {
watershedFIFO = new LinkedList();
}
public void fifo_add(WatershedPixel p) {
watershedFIFO.addFirst(p);
}
public WatershedPixel fifo_remove() {
return (WatershedPixel) watershedFIFO.removeLast();
}
public boolean fifo_empty() {
return watershedFIFO.isEmpty();
}
public void fifo_add_FICTITIOUS() {
watershedFIFO.addFirst(new WatershedPixel());
}
public String toString() {
StringBuffer ret = new StringBuffer();
for(int i=0; i<watershedFIFO.size(); i++) {
ret.append( ((WatershedPixel)watershedFIFO.get(i)).toString() );
ret.append( "\n" );
}
return ret.toString();
}
}
WatershedStructure.java
/*
* Watershed algorithm
*
* Copyright (c) 2003 by Christopher Mei (christopher.mei@sophia.inria.fr)
*
* This plugin is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this plugin; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
import java.lang.*;
import java.util.*;
import ij.process.*;
import ij.*;
import java.awt.*;
/**
* WatershedStructure contains the pixels
* of the image ordered according to their
* grayscale value with a direct access to their
* neighbours.
*
**/
public class WatershedStructure {
private Vector watershedStructure;
public WatershedStructure(ImageProcessor ip) {
byte[] pixels = (byte[])ip.getPixels();
Rectangle r = ip.getRoi();
int width = ip.getWidth();
int offset, topOffset, bottomOffset, i;
watershedStructure = new Vector(r.width*r.height);
/** The structure is filled with the pixels of the image. **/
for (int y=r.y; y<(r.y+r.height); y++) {
offset = y*width;
IJ.showProgress(0.1+0.3*(y-r.y)/(r.height));
for (int x=r.x; x<(r.x+r.width); x++) {
i = offset + x;
int indiceY = y-r.y;
int indiceX = x-r.x;
watershedStructure.add(new WatershedPixel(indiceX, indiceY, pixels[i]));
}
}
/** The WatershedPixels are then filled with the reference to their neighbours. **/
for (int y=0; y<r.height; y++) {
offset = y*width;
topOffset = offset+width;
bottomOffset = offset-width;
IJ.showProgress(0.4+0.3*(y-r.y)/(r.height));
for (int x=0; x<r.width; x++) {
WatershedPixel currentPixel = (WatershedPixel)watershedStructure.get(x+offset);
if(x+1<r.width) {
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+1+offset));
if(y-1>=0)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+1+bottomOffset));
if(y+1<r.height)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+1+topOffset));
}
if(x-1>=0) {
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x-1+offset));
if(y-1>=0)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x-1+bottomOffset));
if(y+1<r.height)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x-1+topOffset));
}
if(y-1>=0)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+bottomOffset));
if(y+1<r.height)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+topOffset));
}
}
Collections.sort(watershedStructure);
//IJ.showProgress(0.8);
}
public String toString() {
StringBuffer ret = new StringBuffer();
for(int i=0; i<watershedStructure.size() ; i++) {
ret.append( ((WatershedPixel) watershedStructure.get(i)).toString() );
ret.append( "\n" );
ret.append( "Neighbours :\n" );
Vector neighbours = ((WatershedPixel) watershedStructure.get(i)).getNeighbours();
for(int j=0 ; j<neighbours.size() ; j++) {
ret.append( ((WatershedPixel) neighbours.get(j)).toString() );
ret.append( "\n" );
}
ret.append( "\n" );
}
return ret.toString();
}
public int size() {
return watershedStructure.size();
}
public WatershedPixel get(int i) {
return (WatershedPixel) watershedStructure.get(i);
}
}
Watershed_Algorithm.java
/*
* Watershed algorithm
*
* Copyright (c) 2003 by Christopher Mei (christopher.mei@sophia.inria.fr)
*
* This plugin is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this plugin; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
import ij.*;
import ij.plugin.filter.PlugInFilter;
import ij.process.*;
import ij.gui.*;
import ij.plugin.frame.PlugInFrame;
import java.awt.*;
import java.util.*;
/**
* This algorithm is an implementation of the watershed immersion algorithm
* written by Vincent and Soille (1991).
*
* @Article{Vincent/Soille:1991,
* author = "Lee Vincent and Pierre Soille",
* year = "1991",
* keywords = "IMAGE-PROC SKELETON SEGMENTATION GIS",
* institution = "Harvard/Paris+Louvain",
* title = "Watersheds in digital spaces: An efficient algorithm
* based on immersion simulations",
* journal = "IEEE PAMI, 1991",
* volume = "13",
* number = "6",
* pages = "583--598",
* annote = "Watershed lines (e.g. the continental divide) mark the
* boundaries of catchment regions in a topographical map.
* The height of a point on this map can have a direct
* correlation to its pixel intensity. WIth this analogy,
* the morphological operations of closing (or opening)
* can be understood as smoothing the ridges (or filling
* in the valleys). Develops a new algorithm for obtaining
* the watershed lines in a graph, and then uses this in
* developing a new segmentation approach based on the
* {"}depth of immersion{"}.",
* }
*
* A review of Watershed algorithms can be found at :
* http://www.cs.rug.nl/~roe/publications/parwshed.pdf
*
* @Article{RoeMei00,
* author = "Roerdink and Meijster",
* title = "The Watershed Transform: Definitions, Algorithms and
* Parallelization Strategies",
* journal = "FUNDINF: Fundamenta Informatica",
* volume = "41",
* publisher = "IOS Press",
* year = "2000",
* }
**/
public class Watershed_Algorithm implements PlugInFilter {
private int threshold;
final static int HMIN = 0;
final static int HMAX = 256;
public int setup(String arg, ImagePlus imp) {
if (arg.equals("about"))
{showAbout(); return DONE;}
return DOES_8G+DOES_STACKS+SUPPORTS_MASKING+NO_CHANGES;
}
public void run(ImageProcessor ip) {
boolean debug = false;
IJ.showStatus("Sorting pixels...");
IJ.showProgress(0.1);
/** First step : the pixels are sorted according to increasing grey values **/
WatershedStructure watershedStructure = new WatershedStructure(ip);
if(debug)
IJ.write(""+watershedStructure.toString());
IJ.showProgress(0.8);
IJ.showStatus("Start flooding...");
if(debug)
IJ.write("Starting algorithm...\n");
/** Start flooding **/
WatershedFIFO queue = new WatershedFIFO();
int curlab = 0;
int heightIndex1 = 0;
int heightIndex2 = 0;
for(int h=HMIN; h<HMAX; h++) /*Geodesic SKIZ of level h-1 inside level h */ {
for(int pixelIndex = heightIndex1 ; pixelIndex<watershedStructure.size() ; pixelIndex++) /*mask all pixels at level h*/ {
WatershedPixel p = watershedStructure.get(pixelIndex);
if(p.getIntHeight() != h) {
/** This pixel is at level h+1 **/
heightIndex1 = pixelIndex;
break;
}
p.setLabelToMASK();
Vector neighbours = p.getNeighbours();
for(int i=0 ; i<neighbours.size() ; i++) {
WatershedPixel q = (WatershedPixel) neighbours.get(i);
if(q.getLabel()>=0) {/*Initialise queue with neighbours at level h of current basins or watersheds*/
p.setDistance(1);
queue.fifo_add(p);
break;
} // end if
} // end for
} // end for
int curdist = 1;
queue.fifo_add_FICTITIOUS();
while(true) /** extend basins **/{
WatershedPixel p = queue.fifo_remove();
if(p.isFICTITIOUS())
if(queue.fifo_empty())
break;
else {
queue.fifo_add_FICTITIOUS();
curdist++;
p = queue.fifo_remove();
}
if(debug) {
IJ.write("\nWorking on :");
IJ.write(""+p);
}
Vector neighbours = p.getNeighbours();
for(int i=0 ; i<neighbours.size() ; i++) /* Labelling p by inspecting neighbours */{
WatershedPixel q = (WatershedPixel) neighbours.get(i);
if(debug)
IJ.write("Neighbour : "+q);
/* Original algorithm :
if( (q.getDistance() < curdist) &&
(q.getLabel()>0 || q.isLabelWSHED()) ) {*/
if( (q.getDistance() <= curdist) &&
(q.getLabel()>=0) ) {
/* q belongs to an existing basin or to a watershed */
if(q.getLabel() > 0) {
if( p.isLabelMASK() )
// Removed from original algorithm || p.isLabelWSHED() )
p.setLabel(q.getLabel());
else
if(p.getLabel() != q.getLabel())
p.setLabelToWSHED();
} // end if lab>0
else
if(p.isLabelMASK())
p.setLabelToWSHED();
}
else
if( q.isLabelMASK() &&
(q.getDistance() == 0) ) {
if(debug)
IJ.write("Adding value");
q.setDistance( curdist+1 );
queue.fifo_add( q );
}
} // end for, end processing neighbours
if(debug) {
IJ.write("End processing neighbours");
IJ.write("New val :\n"+p);
IJ.write("Queue :\n"+queue);
}
} // end while (loop)
/* Detect and process new minima at level h */
for(int pixelIndex = heightIndex2 ; pixelIndex<watershedStructure.size() ; pixelIndex++) {
WatershedPixel p = watershedStructure.get(pixelIndex);
if(p.getIntHeight() != h) {
/** This pixel is at level h+1 **/
heightIndex2 = pixelIndex;
break;
}
p.setDistance(0); /* Reset distance to zero */
if(p.isLabelMASK()) { /* the pixel is inside a new minimum */
curlab++;
p.setLabel(curlab);
queue.fifo_add(p);
while(!queue.fifo_empty()) {
WatershedPixel q = queue.fifo_remove();
Vector neighbours = q.getNeighbours();
for(int i=0 ; i<neighbours.size() ; i++) /* inspect neighbours of p2*/{
WatershedPixel r = (WatershedPixel) neighbours.get(i);
if( r.isLabelMASK() ) {
r.setLabel(curlab);
queue.fifo_add(r);
}
}
} // end while
} // end if
} // end for
} /** End of flooding **/
IJ.showProgress(0.9);
IJ.showStatus("Putting result in a new image...");
/** Put the result in a new image **/
int width = ip.getWidth();
ImageProcessor outputImage = new ByteProcessor(width, ip.getHeight());
byte[] newPixels = (byte[]) outputImage.getPixels();
for(int pixelIndex = 0 ; pixelIndex<watershedStructure.size() ; pixelIndex++) {
WatershedPixel p = watershedStructure.get(pixelIndex);
if(p.isLabelWSHED() && !p.allNeighboursAreWSHED())
newPixels[p.getX()+p.getY()*width] = (byte)255;
}
IJ.showProgress(1);
IJ.showStatus("Displaying result...");
new ImagePlus("Watershed", outputImage).show();
}
void showAbout() {
IJ.showMessage("About Watershed_Algorithm...",
"This plug-in filter calculates the watershed of a 8-bit images.\n" +
"It uses the immersion algorithm written by Vincent and Soille (1991)\n"
);
}
}