// Catalano Imaging Library
// The Catalano Framework
//
// Copyright © Diego Catalano, 2012-2016
// diego.catalano at live.com
//
// Kai Uwe Barthel
// The original algorithm: http://rsbweb.nih.gov/ij/plugins/mean-shift.html
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
//
package Catalano.Imaging.Filters;
import Catalano.Imaging.FastBitmap;
import Catalano.Imaging.IApplyInPlace;
/**
* Mean Shift filter.
*
Mean Shift filter can be used for edge-preserving smoothing or for segmentation. Important edges of an image might be easier detected after mean shift filtering.
*
It uses a circular flat kernel and the color distance is calculated in the YIQ-color space.
* @author Diego Catalano
*/
public class MeanShift implements IApplyInPlace{
private int radius;
private float colorDistance;
/**
* Initialize a new instance of the MeanShift class.
*/
public MeanShift() {}
/**
* Initialize a new instance of the MeanShift class.
* @param radius Radius.
* @param colorDistance Color distance.
*/
public MeanShift(int radius, float colorDistance) {
this.radius = radius;
this.colorDistance = colorDistance;
}
@Override
public void applyInPlace(FastBitmap fastBitmap) {
int width = fastBitmap.getWidth();
int height = fastBitmap.getHeight();
if (fastBitmap.isRGB()) {
float[][][] pixelsF = new float[height][width][3];
int r,g,b;
for (int x = 0; x < height; x++) {
for (int y = 0; y < width; y++) {
r = fastBitmap.getRed(x, y);
g = fastBitmap.getGreen(x, y);
b = fastBitmap.getBlue(x, y);
// You can use ColorConverter.RGBtoYIQ but you need to multiply the result with 255.
// In this way its more fast because we spend less processor.
pixelsF[x][y][0] = 0.299f *r + 0.587f *g + 0.114f *b;
pixelsF[x][y][1] = 0.5957f *r - 0.2744f*g - 0.3212f *b;
pixelsF[x][y][2] = 0.2114f *r - 0.5226f*g + 0.3111f *b;
}
}
float shift;
int iters;
for (int x = 0; x < height; x++) {
for (int y = 0; y < width; y++) {
int yc = y;
int xc = x;
int xcOld, ycOld;
float YcOld, IcOld, QcOld;
float[] yiq = pixelsF[x][y];
float Yc = yiq[0];
float Ic = yiq[1];
float Qc = yiq[2];
iters = 0;
do {
xcOld = xc;
ycOld = yc;
YcOld = Yc;
IcOld = Ic;
QcOld = Qc;
float mx = 0;
float my = 0;
float mY = 0;
float mI = 0;
float mQ = 0;
int num=0;
int radius2 = radius * radius;
float colorDistance2 = colorDistance * colorDistance;
for (int rx=-radius; rx <= radius; rx++) {
int x2 = xc + rx;
if (x2 >= 0 && x2 < height) {
for (int ry=-radius; ry <= radius; ry++) {
int y2 = yc + ry;
if (y2 >= 0 && y2 < width) {
if (rx*rx + ry*ry <= radius2) {
yiq = pixelsF[x2][y2];
float Y2 = yiq[0];
float I2 = yiq[1];
float Q2 = yiq[2];
float dY = Yc - Y2;
float dI = Ic - I2;
float dQ = Qc - Q2;
if (dY*dY+dI*dI+dQ*dQ <= colorDistance2) {
mx += x2;
my += y2;
mY += Y2;
mI += I2;
mQ += Q2;
num++;
}
}
}
}
}
}
float num_ = 1f/num;
Yc = mY*num_;
Ic = mI*num_;
Qc = mQ*num_;
xc = (int) (mx*num_+0.5);
yc = (int) (my*num_+0.5);
int dx = xc-xcOld;
int dy = yc-ycOld;
float dY = Yc-YcOld;
float dI = Ic-IcOld;
float dQ = Qc-QcOld;
shift = dx*dx+dy*dy+dY*dY+dI*dI+dQ*dQ;
iters++;
}
while (shift > 3 && iters < 100);
int r_ = (int)(Yc + 0.9563f*Ic + 0.6210f*Qc);
int g_ = (int)(Yc - 0.2721f*Ic - 0.6473f*Qc);
int b_ = (int)(Yc - 1.1070f*Ic + 1.7046f*Qc);
fastBitmap.setRGB(x, y, r_, g_, b_);
}
}
}
if (fastBitmap.isGrayscale()) {
float shift;
int iters;
for (int x = 0; x < height; x++) {
for (int y = 0; y < width; y++) {
int yc = y;
int xc = x;
int xcOld, ycOld;
float YcOld;
float Yc = fastBitmap.getGray(x, y);
iters = 0;
do {
xcOld = xc;
ycOld = yc;
YcOld = Yc;
float mx = 0;
float my = 0;
float mY = 0;
int num=0;
int radius2 = radius * radius;
float colorDistance2 = colorDistance * colorDistance;
for (int rx=-radius; rx <= radius; rx++) {
int x2 = xc + rx;
if (x2 >= 0 && x2 < height) {
for (int ry=-radius; ry <= radius; ry++) {
int y2 = yc + ry;
if (y2 >= 0 && y2 < width) {
if (rx*rx + ry*ry <= radius2) {
float Y2 = fastBitmap.getGray(x2, y2);
float dY = Yc - Y2;
if (dY*dY <= colorDistance2) {
mx += x2;
my += y2;
mY += Y2;
num++;
}
}
}
}
}
}
float num_ = 1f/num;
Yc = mY*num_;
xc = (int) (mx*num_+0.5);
yc = (int) (my*num_+0.5);
int dx = xc-xcOld;
int dy = yc-ycOld;
float dY = Yc-YcOld;
shift = dx*dx+dy*dy+dY*dY;
iters++;
}
while (shift > 3 && iters < 100);
fastBitmap.setGray(x, y, (int)Yc);
}
}
}
}
}
Ads help maintain this website.