我在MATLAB中组装了一个算法,并且使用统计区域合并作为其中的一部分。我在MATLAB和斐济(ImageJ)中有这个版本,两者都包含在下面。两个版本都基于Nock和Nielson模型,看起来很相似(虽然我对Java /斐济不太熟悉)。当我在MATLAB中使用1500x500 2d灰度图像运行时,处理大约需要3秒钟,而斐济需要不到一秒钟。我尽可能地尽量减少了MATLAB代码,但仍然遇到了这个速度问题。造成这种时间差异的原因是什么?感谢
% Statistical Region Merging
%
% Nock, Richard and Nielsen, Frank 2004. Statistical Region Merging. IEEE Trans. Pattern Anal. Mach. Intell. 26, 11 (Nov. 2004), 1452-1458.
% DOI= http://dx.doi.org/10.1109/TPAMI.2004.110
%Segmentation parameter Q; Q small few segments, Q large may segments
function [maps,images]=srm3(image,Qlevels)
% Smoothing the image, comment this line if you work on clean or synthetic images
h=fspecial('gaussian',[3 3],1);
image=imfilter(image,h,'symmetric');
smallest_region_allowed=10;
size_image=size(image);
n_pixels=size_image(1)*size_image(2);
% Compute image gradient
[Ix,Iy]=srm_imgGrad(image(:,:,:));
Ix=max(abs(Ix),[],3);
Iy=max(abs(Iy),[],3);
normgradient=sqrt(Ix.^2+Iy.^2);
Ix(:,end)=[];
Iy(end,:)=[];
[~,index]=sort(abs([Iy(:);Ix(:)]));
n_levels=numel(Qlevels);
maps=cell(n_levels,1);
images=cell(n_levels,1);
im_final=zeros(size_image);
Q=256;
map=reshape(1:n_pixels,size_image(1:2));
% gaps=zeros(size(map)); % For future release
treerank=zeros(size_image(1:2));
size_segments=ones(size_image(1:2));
image_seg=image;
%Building pairs
n_pairs=numel(index);
idx2=reshape(map(:,1:end-1),[],1);
idx1=reshape(map(1:end-1,:),[],1);
pairs1=[ idx1;idx2 ];
pairs2=[ idx1+1;idx2+size_image(1) ];
for i=1:n_pairs
C1=pairs1(index(i));
C2=pairs2(index(i));
%Union-Find structure, here are the finds, average complexity O(1)
while (map(C1)~=C1 ); C1=map(C1); end
while (map(C2)~=C2 ); C2=map(C2); end
% Compute the predicate, region merging test
g=256;
logdelta=2*log(6*n_pixels);
dR=(image_seg(C1)-image_seg(C2))^2;
logreg1 = min(g,size_segments(C1))*log(1.0+size_segments(C1));
logreg2 = min(g,size_segments(C2))*log(1.0+size_segments(C2));
dev1=((g*g)/(2.0*Q*size_segments(C1)))*(logreg1 + logdelta);
dev2=((g*g)/(2.0*Q*size_segments(C2)))*(logreg2 + logdelta);
dev=dev1+dev2;
predicat=( (dR<dev) );
if (((C1~=C2)&&predicat) || xor(size_segments(C1)<=smallest_region_allowed, size_segments(C2)<=smallest_region_allowed))
% Find the new root for both regions
if treerank(C1) > treerank(C2)
map(C2) = C1; reg=C1;
elseif treerank(C1) < treerank(C2)
map(C1) = C2; reg=C2;
elseif C1 ~= C2
map(C2) = C1; reg=C1;
treerank(C1) = treerank(C1) + 1;
end
if C1~=C2
% Merge region
nreg=size_segments(C1)+size_segments(C2);
size_segments(C1);
size_segments(C2);
image_seg(reg)=(size_segments(C1)*image_seg(C1)+size_segments(C2)*image_seg(C2))/nreg;
size_segments(reg)=nreg;
end
end
end
while 1
map_ = map(map) ;
if isequal(map_,map) ; break ; end
map = map_ ;
end
im_final(:,:,1)=image_seg(map+(1-1)*n_pixels);
images{1}=im_final;
Fiji:
* Statistical Region Merging.
* %%
* Copyright (C) 2009 - 2013 Johannes Schindelin.
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are
* those of the authors and should not be interpreted as representing official
* policies, either expressed or implied, of any organization.
* #L%
*/
import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.gui.GenericDialog;
import ij.plugin.filter.PlugInFilter;
import ij.process.ByteProcessor;
import ij.process.FloatProcessor;
import ij.process.ImageProcessor;
import ij.process.ShortProcessor;
import java.util.Arrays;
/*
* The Statistical Region Merging algorithm is described in
*
* R. Nock, F. Nielsen: Statistical Region Merging.
* IEEE Trans. Pattern Anal. Mach. Intell. 26(11): 1452-1458 (2004)
*/
public class SRM_ implements PlugInFilter {
ImagePlus image;
public int setup(String arg, ImagePlus image) {
this.image = image;
return DOES_8G | NO_CHANGES;
}
public void run(ImageProcessor ip) {
boolean isStack = image.getStackSize() > 1;
GenericDialog gd = new GenericDialog("SRM");
gd.addNumericField("Q", Q, 2);
gd.addCheckbox("showAverages", true);
if (isStack)
gd.addCheckbox("3d", true);
gd.showDialog();
if (gd.wasCanceled())
return;
Q = (float)gd.getNextNumber();
boolean showAverages = gd.getNextBoolean();
boolean do3D = isStack ? gd.getNextBoolean() : false;
if (do3D)
srm3D(showAverages).show();
else
srm2D(ip, showAverages).show();
}
final float g = 256; // number of different intensity values
protected float Q = 25; //25; // complexity of the assumed distributions
protected float delta;
/*
* The predicate: is the difference of the averages of the two
* regions R and R' smaller than
*
* g sqrt(1/2Q (1/|R| + 1/|R'|) ln 2/delta)
*
* Instead of calculating the square root all the time, we calculate
* the factor g^2 / 2Q ln 2/delta, and compare
*
* (<R> - <R'>)^2 < factor (1/|R| + 1/|R'|)
*
* instead.
*/
protected float factor, logDelta;
/*
* For performance reasons, these are held in w * h arrays
*/
float[] average;
int[] count;
int[] regionIndex; // if < 0, it is -1 - actual_regionIndex
/*
* The statistical region merging wants to merge regions in a specific
* order: for all neighboring pixel pairs, in ascending order of
* intensity differences.
*
* In that order, it is tested if the regions these two pixels belong
* to (by construction, the regions must be distinct) should be merged.
*
* For efficiency, we do it by bucket sorting, because there are only
* g + 1 possible differences.
*
* After sorting, for each difference the pixel pair with the largest
* index is stored in neighborBuckets[difference], and every
* nextNeighbor[index] points to the pixel pair with the same
* difference and the next smaller index (or -1 if there is none).
*
* The pixel pairs are identified by
*
* 2 * (x + (w - 1) * y) + direction
*
* where direction = 0 means "right neighbor", and direction = 1 means
* " lower neighbor". (We do not need "left" or "up", as the order
* within the pair is not important.)
*
* In n dimensions, it must be n * pixel_count, and "direction"
* specifies the Cartesian unit vector (axis) determining the neighbor.
*/
int[] nextNeighbor, neighborBucket;
protected ImagePlus srm3D(boolean showAverages) {
int w = image.getWidth(), h = image.getHeight();
int d = image.getStackSize();
delta = 1f / (6 * w * h * d);
/*
* This would be the non-relaxed formula:
*
* factor = g * g / 2 / Q * (float)Math.log(2 / delta);
*
* The paper claims that this is more prone to oversegmenting.
*/
factor = g * g / 2 / Q;
logDelta = 2f * (float)Math.log(6 * w * h * d);
IJ.showStatus("Initializing regions");
initializeRegions3D(w, h, d);
IJ.showStatus("Initializing neighbors");
initializeNeighbors3D(w, h, d);
IJ.showStatus("Merging neighbors");
mergeAllNeighbors3D(w, h);
IJ.showStatus("Making stack");
ImageStack stack = new ImageStack(w, h);
if (showAverages)
for (int k = 0; k < d; k++) {
int off = k * w * h;
float[] p = new float[w * h];
for (int i = 0; i < w * h; i++)
p[i] = average[getRegionIndex(i + off)];
stack.addSlice(null, new FloatProcessor(w, h,
p, null));
}
else {
int regionCount = consolidateRegions();
if (regionCount > 1<<16)
IJ.showMessage("Found " + regionCount
+ " regions, which does not fit"
+ " in 16-bit.");
for (int k = 0; k < d; k++) {
ImageProcessor ip;
int off = k * w * h;
if (regionCount > 1<<8) {
short[] p = new short[w * h];
for (int i = 0; i < p.length; i++)
p[i] = (short)regionIndex[i
+ off];
ip = new ShortProcessor(w, h, p, null);
}
else {
byte[] p = new byte[w * h];
for (int i = 0; i < p.length; i++)
p[i] = (byte)regionIndex[i
+ off];
ip = new ByteProcessor(w, h, p, null);
}
stack.addSlice(null, ip);
}
}
IJ.showStatus("");
String title = image.getTitle() + " (SRM3D Q=" + Q + ")";
return new ImagePlus(title, stack);
}
protected ImagePlus srm2D(ImageProcessor ip, boolean showAverages) {
int w = ip.getWidth(), h = ip.getHeight();
delta = 1f / (6 * w * h);
/*
* This would be the non-relaxed formula:
*
* factor = g * g / 2 / Q * (float)Math.log(2 / delta);
*
* The paper claims that this is more prone to oversegmenting.
*/
factor = g * g / 2 / Q;
logDelta = 2f * (float)Math.log(6 * w * h);
byte[] pixel = (byte[])ip.getPixels();
initializeRegions2D(pixel, ip.getWidth(), ip.getHeight());
initializeNeighbors2D(pixel, w, h);
mergeAllNeighbors2D(w);
if (showAverages) {
for (int i = 0; i < average.length; i++)
average[i] = average[getRegionIndex(i)];
ip = new FloatProcessor(w, h, average, null);
}
else {
int regionCount = consolidateRegions();
if (regionCount > 1<<8) {
if (regionCount > 1<<16)
IJ.showMessage("Found " + regionCount
+ " regions, which does not fit"
+ " in 16-bit.");
short[] pixel16 = new short[w * h];
for (int i = 0; i < pixel16.length; i++)
pixel16[i] = (short)regionIndex[i];
ip = new ShortProcessor(w, h, pixel16, null);
}
else {
pixel = new byte[w * h];
for (int i = 0; i < pixel.length; i++)
pixel[i] = (byte)regionIndex[i];
ip = new ByteProcessor(w, h, pixel, null);
}
}
String title = image.getTitle() + " (SRM Q=" + Q + ")";
return new ImagePlus(title, ip);
}
void initializeRegions2D(byte[] pixel, int w, int h) {
average = new float[w * h];
count = new int[w * h];
regionIndex = new int[w * h];
for (int i = 0; i < average.length; i++) {
average[i] = pixel[i] & 0xff;
count[i] = 1;
regionIndex[i] = i;
}
}
void initializeRegions3D(int w, int h, int d) {
average = new float[w * h * d];
count = new int[w * h * d];
regionIndex = new int[w * h * d];
for (int j = 0; j < d; j++) {
byte[] pixel =
(byte[])image.getStack().getProcessor(j
+ 1).getPixels();
int offset = j * w * h;
for (int i = 0; i < w * h; i++) {
average[offset + i] = pixel[i] & 0xff;
count[offset + i] = 1;
regionIndex[offset + i] = offset + i;
}
}
}
protected void addNeighborPair(int neighborIndex,
byte[] pixel, int i1, int i2) {
int difference = Math.abs((pixel[i1] & 0xff)
- (pixel[i2] & 0xff));
nextNeighbor[neighborIndex] = neighborBucket[difference];
neighborBucket[difference] = neighborIndex;
}
void initializeNeighbors2D(byte[] pixel, int w, int h) {
nextNeighbor = new int[2 * w * h];
// bucket sort
neighborBucket = new int[256];
Arrays.fill(neighborBucket, -1);
for (int j = h - 1; j >= 0; j--)
for (int i = w - 1; i >= 0; i--) {
int index = i + w * j;
int neighborIndex = 2 * index;
// vertical
if (j < h - 1)
addNeighborPair(neighborIndex + 1,
pixel, index, index + w);
// horizontal
if (i < w - 1)
addNeighborPair(neighborIndex,
pixel, index, index + 1);
}
}
protected void addNeighborPair(int neighborIndex,
byte[] pixel, byte[] nextPixel, int i) {
int difference = Math.abs((pixel[i] & 0xff)
- (nextPixel[i] & 0xff));
nextNeighbor[neighborIndex] = neighborBucket[difference];
neighborBucket[difference] = neighborIndex;
}
void initializeNeighbors3D(int w, int h, int d) {
nextNeighbor = new int[3 * w * h * d];
// bucket sort
neighborBucket = new int[256];
Arrays.fill(neighborBucket, -1);
byte[] nextPixel = null;
for (int k = d - 1; k >= 0; k--) {
byte[] pixel =
(byte[])image.getStack().getProcessor(k
+ 1).getPixels();
for (int j = h - 1; j >= 0; j--)
for (int i = w - 1; i >= 0; i--) {
int index = i + w * j;
int neighborIndex =
3 * (index + k * w * h);
// depth
if (nextPixel != null)
addNeighborPair(neighborIndex
+ 2, pixel,
nextPixel, index);
// vertical
if (j < h - 1)
addNeighborPair(neighborIndex
+ 1, pixel,
index, index + w);
// horizontal
if (i < w - 1)
addNeighborPair(neighborIndex,
pixel,
index, index + 1);
}
nextPixel = pixel;
}
}
// recursively find out the region index for this pixel
int getRegionIndex(int i) {
i = regionIndex[i];
while (i < 0)
i = regionIndex[-1 - i];
return i;
}
// should regions i1 and i2 be merged?
boolean predicate(int i1, int i2) {
float difference = average[i1] - average[i2];
/*
* This would be the non-relaxed predicate mentioned in the
* paper.
*
* return difference * difference <
factor * (1f / count[i1] + 1f / count[i2]);
*
*/
float log1 = (float)Math.log(1 + count[i1])
* (g < count[i1] ? g : count[i1]);
float log2 = (float)Math.log(1 + count[i2])
* (g < count[i2] ? g : count[i2]);
return difference * difference <
.1f * factor * ((log1 + logDelta) / count[i1]
+ ((log2 + logDelta) / count[i2]));
}
void mergeAllNeighbors2D(int w) {
for (int i = 0; i < neighborBucket.length; i++) {
int neighborIndex = neighborBucket[i];
while (neighborIndex >= 0) {
int i1 = neighborIndex / 2;
int i2 = i1
+ (0 == (neighborIndex & 1) ? 1 : w);
i1 = getRegionIndex(i1);
i2 = getRegionIndex(i2);
if (predicate(i1, i2))
mergeRegions(i1, i2);
neighborIndex = nextNeighbor[neighborIndex];
}
}
}
void mergeAllNeighbors3D(int w, int h) {
for (int i = 0; i < neighborBucket.length; i++) {
int neighborIndex = neighborBucket[i];
IJ.showProgress(i, neighborBucket.length);
while (neighborIndex >= 0) {
int i1 = neighborIndex / 3;
int i2 = i1
+ (0 == (neighborIndex % 3) ? 1 :
(1 == (neighborIndex % 3) ? w :
w * h));
i1 = getRegionIndex(i1);
i2 = getRegionIndex(i2);
if (i1 != i2 && predicate(i1, i2))
mergeRegions(i1, i2);
neighborIndex = nextNeighbor[neighborIndex];
}
}
IJ.showProgress(neighborBucket.length, neighborBucket.length);
}
void mergeRegions(int i1, int i2) {
if (i1 == i2)
return;
int mergedCount = count[i1] + count[i2];
float mergedAverage = (average[i1] * count[i1]
+ average[i2] * count[i2]) / mergedCount;
// merge larger index into smaller index
if (i1 > i2) {
average[i2] = mergedAverage;
count[i2] = mergedCount;
regionIndex[i1] = -1 - i2;
}
else {
average[i1] = mergedAverage;
count[i1] = mergedCount;
regionIndex[i2] = -1 - i1;
}
}
int consolidateRegions() {
/*
* By construction, a negative regionIndex will always point
* to a smaller regionIndex.
*
* So we can get away by iterating from small to large and
* replacing the positive ones with running numbers, and the
* negative ones by the ones they are pointing to (that are
* now guaranteed to contain a non-negative index).
*/
int count = 0;
for (int i = 0; i < regionIndex.length; i++)
if (regionIndex[i] < 0)
regionIndex[i] =
regionIndex[-1 - regionIndex[i]];
else
regionIndex[i] = count++;
return count;
}
}