-
Notifications
You must be signed in to change notification settings - Fork 2k
Question: Is there a Distance Transform algorithm in Accord.Net? #654
Comments
Hi there, Thanks for creating the issue. The framework doesn't offer an implementation for the (Euclidean) Distance transform yet, though I also agree that it would be very useful to have one. At some point there was one file in the repository that was supposed to be implementing it, but it was not actually included in the build. It was a leftover from an incomplete implementation. In any case, may I ask if this file implements the transform you need? https://github.com/accord-net/java/blob/master/Catalano.Android.Image/src/Catalano/Imaging/Filters/DistanceTransform.java If yes, then it should be straightforward to add it to the project. Diego (the author of that code) is also a developer here in Accord.NET. Regards, |
Diego's code seems reasonable. I like that it has other distance masks other than Euclidean. |
@DiegoCatalano would you like to try passing this algorithm to C#? 😄 |
Here's my naive approach to porting that function. It seems to work but I'm probably doing some things stupidly. :) But maybe it'll save you some time. public class DistanceTransform
{
/**
* Distance metric.
*/
public enum Distance
{
/**
* Chessboard metric.
*/
Chessboard,
/**
* Euclidean metric.
*/
Euclidean,
/**
* Manhattan metric.
*/
Manhattan
};
private Distance distance = Distance.Euclidean;
private float[,] maskDistance = new float[3, 3] {
{1.4142f,1,1.4142f},
{1,0,1},
{1.4142f,1,1.4142f}
};
private float[,] image;
/**
* Get Distance metric.
* @return Distance metric.
*/
public Distance GetDistance()
{
return distance;
}
/**
* Set Distance metric.
* @param distance Distance metric.
*/
public void SetDistance(Distance distance)
{
this.distance = distance;
switch (distance)
{
case Distance.Chessboard:
this.maskDistance = new float[3,3]{
{1,1,1},
{1,0,1},
{1,1,1}
};
break;
case Distance.Manhattan:
this.maskDistance = new float[3,3]{
{2,1,2},
{1,0,1},
{2,1,2}
};
break;
case Distance.Euclidean:
this.maskDistance = new float[3,3]{
{1.4142135f,1,1.4142135f},
{1,0,1},
{1.4142135f,1,1.4142135f}
};
break;
}
}
/**
* Get Mask distance.
* @return Mask distance.
*/
public float[,] GetMaskDistance()
{
return maskDistance;
}
/**
* Set Mask distance.
* @param maskDistance Mask distance.
*/
public void SetMaskDistance(float[,] maskDistance)
{
this.maskDistance = maskDistance;
}
/**
* Initialize a new instance of the DistanceTransform class.
* Default distance: Euclidean.
*/
public DistanceTransform() { }
/**
* Initialize a new instance of the DistanceTransform class.
* @param distance Distance metric.
*/
public DistanceTransform(Distance distance)
{
SetDistance(distance);
}
/**
* Initialize a new instance of the DistanceTransform class.
* @param maskDistance Distance mask.
*/
public DistanceTransform(float[,] maskDistance)
{
this.maskDistance = maskDistance;
}
/**
* Compute Distance Transform.
* @param fastBitmap Image to be processed.
* @return Distance map.
*/
public float[,] Compute(UnmanagedImage fastBitmap)
{
if (fastBitmap.PixelFormat == PixelFormat.Format8bppIndexed)// isGrayscale())
{
int width = fastBitmap.Width;
int height = fastBitmap.Height;
image = new float[height,width];
var colorLookup = Enum.GetValues(typeof(KnownColor))
.Cast<KnownColor>()
.Select(Color.FromKnownColor)
.ToLookup(c => c.ToArgb());
//Initialize Distance Map
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
var color = fastBitmap.GetPixel(j, i);
var pixelColors = colorLookup[color.ToArgb()];
bool matchingName = false;
foreach (var itemColor in pixelColors)
{
if (itemColor == Color.Black)
matchingName = true;
}
if (matchingName)
{
image[i,j] = 0;
}
else
{
image[i,j] = float.PositiveInfinity;
}
}
}
//Top -> Bottom - Left -> Right
for (int i = 1; i < height - 1; i++)
{
for (int j = 1; j < width - 1; j++)
{
if (image[i,j] > 0)
{
float d1 = maskDistance[1,0] + image[i,j - 1];
float d2 = maskDistance[0,0] + image[i - 1,j - 1];
float d3 = maskDistance[0,1] + image[i - 1,j];
float d4 = maskDistance[0,2] + image[i - 1,j + 1];
image[i,j] = Math.Min(d1, Math.Min(d2, Math.Min(d3, d4)));
}
}
}
//Bottom -> Top - Right -> Left
for (int i = height - 2; i > 1; i--)
{
for (int j = width - 3; j > 1; j--)
{
if (image[i,j] > 0)
{
float d1 = maskDistance[1,2] + image[i,j + 1];
float d2 = maskDistance[2,2] + image[i + 1,j + 1];
float d3 = maskDistance[2,1] + image[i + 1,j];
float d4 = maskDistance[2,0] + image[i + 1,j - 1];
image[i,j] = Math.Min(image[i,j], Math.Min(d1, Math.Min(d2, Math.Min(d3, d4))));
}
}
}
for (int i = 0; i < image.GetUpperBound(0); i++)
{
for (int j = 0; j < image.GetUpperBound(1); j++)
{
if (image[i,j] == float.PositiveInfinity)
{
image[i,j] = 0;
}
}
}
return image;
}
else
{
throw new ArgumentException("Distance Transform only works in grayscale images.");
}
}
/**
* Convert Distance map to FastBitmap.
* @return FastBitmap.
*/
public UnmanagedImage ToFastBitmap()
{
int width = image.GetUpperBound(1);
int height = image.GetUpperBound(0);
double max = 0;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
if (image[i,j] > max)
{
max = image[i,j];
}
}
}
//FastBitmap fb = new FastBitmap(width, height, FastBitmap.ColorSpace.Grayscale);
using (Bitmap bmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed))
{
UnmanagedImage fb = UnmanagedImage.FromManagedImage(bmp);
if (max > 255)
{
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
var col = (byte)Accord.Math.Tools.Scale(0, max, 0, 255, image[i, j]);
fb.SetPixel(j, i, col);
}
}
}
else
{
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
var col = (byte)Accord.Math.Tools.Scale(0, max, 0, 255, image[i, j]);
fb.SetPixel(j, i, col);
}
}
}
return fb;
}
}
} |
Thanks @fdncred! However, I think you really do not want to use the GetPixel/SetPixel functions in your loops. They will slow down your algorithm by multiple orders of magnitude. But it surely helps as a starting point ;-) thanks! |
@cesarsouza How about this one? I used your MatrixToImage and ImageToMatrix. Strange thing though, the output is different than with SetPixel(). I'm not sure which one is right. public class DistanceTransform
{
/**
* Distance metric.
*/
public enum Distance
{
/**
* Chessboard metric.
*/
Chessboard,
/**
* Euclidean metric.
*/
Euclidean,
/**
* Manhattan metric.
*/
Manhattan
};
private Distance distance = Distance.Euclidean;
private float[,] maskDistance = new float[3, 3]
{
{1.4142f,1,1.4142f},
{1,0,1},
{1.4142f,1,1.4142f}
};
private float[,] image;
/**
* Get Distance metric.
* @return Distance metric.
*/
public Distance GetDistance()
{
return distance;
}
/**
* Set Distance metric.
* @param distance Distance metric.
*/
public void SetDistance(Distance distance)
{
this.distance = distance;
switch (distance)
{
case Distance.Chessboard:
this.maskDistance = new float[3, 3]
{
{1,1,1},
{1,0,1},
{1,1,1}
};
break;
case Distance.Manhattan:
this.maskDistance = new float[3, 3]
{
{2,1,2},
{1,0,1},
{2,1,2}
};
break;
case Distance.Euclidean:
this.maskDistance = new float[3, 3]
{
{1.4142135f,1,1.4142135f},
{1,0,1},
{1.4142135f,1,1.4142135f}
};
break;
}
}
/**
* Get Mask distance.
* @return Mask distance.
*/
public float[,] GetMaskDistance()
{
return maskDistance;
}
/**
* Set Mask distance.
* @param maskDistance Mask distance.
*/
public void SetMaskDistance(float[,] maskDistance)
{
this.maskDistance = maskDistance;
}
/**
* Initialize a new instance of the DistanceTransform class.
* Default distance: Euclidean.
*/
public DistanceTransform() { }
/**
* Initialize a new instance of the DistanceTransform class.
* @param distance Distance metric.
*/
public DistanceTransform(Distance distance)
{
SetDistance(distance);
}
/**
* Initialize a new instance of the DistanceTransform class.
* @param maskDistance Distance mask.
*/
public DistanceTransform(float[,] maskDistance)
{
this.maskDistance = maskDistance;
}
/**
* Compute Distance Transform.
* @param fastBitmap Image to be processed.
* @return Distance map.
*/
public unsafe float[,] Compute(UnmanagedImage fastBitmap)
{
if (fastBitmap.PixelFormat == PixelFormat.Format8bppIndexed)// isGrayscale())
{
int width = fastBitmap.Width;
int height = fastBitmap.Height;
image = new float[height, width];
Accord.Imaging.Converters.ImageToMatrix conv = new Accord.Imaging.Converters.ImageToMatrix(min: 0, max: 1);
conv.Convert(fastBitmap, out image);
//Top -> Bottom - Left -> Right
for (int i = 1; i < height - 1; i++)
{
for (int j = 1; j < width - 1; j++)
{
if (image[i, j] > 0)
{
float d1 = maskDistance[1, 0] + image[i, j - 1];
float d2 = maskDistance[0, 0] + image[i - 1, j - 1];
float d3 = maskDistance[0, 1] + image[i - 1, j];
float d4 = maskDistance[0, 2] + image[i - 1, j + 1];
image[i, j] = Math.Min(d1, Math.Min(d2, Math.Min(d3, d4)));
}
}
}
//Bottom -> Top - Right -> Left
for (int i = height - 2; i > 1; i--)
{
for (int j = width - 3; j > 1; j--)
{
if (image[i, j] > 0)
{
float d1 = maskDistance[1, 2] + image[i, j + 1];
float d2 = maskDistance[2, 2] + image[i + 1, j + 1];
float d3 = maskDistance[2, 1] + image[i + 1, j];
float d4 = maskDistance[2, 0] + image[i + 1, j - 1];
image[i, j] = Math.Min(image[i, j], Math.Min(d1, Math.Min(d2, Math.Min(d3, d4))));
}
}
}
for (int i = 0; i < image.GetUpperBound(0) + 1; i++)
{
for (int j = 0; j < image.GetUpperBound(1) + 1; j++)
{
if (image[i, j] == 1)//float.PositiveInfinity)
{
image[i, j] = 0;
}
}
}
return image;
}
else
{
throw new ArgumentException("Distance Transform only works in grayscale images.");
}
}
/**
* Convert Distance map to FastBitmap.
* @return FastBitmap.
*/
public unsafe Bitmap ToBitmap()
{
int width = image.GetUpperBound(1) + 1;
int height = image.GetUpperBound(0) + 1;
double max = 0;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
if (image[i, j] > max)
{
max = image[i, j];
}
}
}
UnmanagedImage fb = UnmanagedImage.Create(width, height, PixelFormat.Format8bppIndexed);
Accord.Imaging.Converters.ImageToMatrix conv = new Accord.Imaging.Converters.ImageToMatrix(min: 0, max: 255);
conv.Convert(fb, out byte[,] img);
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
byte col = (byte)Vector.Scale(image[i, j], 0, max, 0, 255);
img[i, j] = col;
}
}
Accord.Imaging.Converters.MatrixToImage m2i = new Accord.Imaging.Converters.MatrixToImage(min: 0, max: 255);
m2i.Convert(img, out Bitmap output);
return output;
}
} |
I don't think either of my implementations are correct. The output image doesn't match any of these. OpenCV Stack Homepages |
Hello @fdncred, The distance you used was a very old version. The new version, it's the same algorithm used in ImageJ. I just tested and it is correct as this link below: |
Thanks @DiegoCatalano. Do you have a C# version? If not, I'll try to port it. |
I didn't change that much. /**
* Distance Transform.
*
* <p><li>Supported types: Grayscale.
* <br><li>Coordinate System: Matrix.
*
* @author Diego Catalano
*/
public class DistanceTransform
{
/**
* Distance.
*/
public enum Distance
{
/**
* Chessboard.
*/
Chessboard,
/**
* Euclidean.
*/
Euclidean,
/**
* Manhattan.
*/
Manhattan,
/**
* Squared Euclidean.
*/
SquaredEuclidean
};
private float[][] image;
private float max = 0;
private Accord.IntPoint ued;
private Distance distance = Distance.Euclidean;
/**
* Get Maximum distance from transform.
* @return Maximum distance.
*/
public float GetMaximumDistance()
{
return max;
}
/**
* Get the Ultimate eroded point.
* @return UED.
*/
public Accord.IntPoint GetUltimateErodedPoint()
{
return ued;
}
/**
* Initialize a new instance of the DistanceTransform class.
* Default distance: Euclidean.
*/
public DistanceTransform() { }
/**
* Initialize a new instance of the DistanceTransform class.
* @param distance Distance.
*/
public DistanceTransform(Distance distance)
{
this.distance = distance;
}
/**
* Compute Distance Transform.
* @param fastBitmap Image to be processed.
* @return Distance map.
*/
public float[][] Compute(UnmanagedImage fastBitmap)
{
if (fastBitmap.PixelFormat == PixelFormat.Format8bppIndexed)//fastBitmap.isGrayscale())
{
int width = fastBitmap.Width;
int height = fastBitmap.Height;
//byte[] bPixels = fastBitmap.getGrayData();
Accord.Imaging.Converters.ImageToArray conv = new Accord.Imaging.Converters.ImageToArray(min: 0, max: 255);
conv.Convert(fastBitmap, out float[] fbPixels);
byte[] bPixels = fbPixels.Select(f => (byte)f).ToArray();
float[] fPixels = new float[bPixels.Length];
for (int i = 0; i < width * height; i++)
if (bPixels[i] != 0)
fPixels[i] = float.MaxValue;
//int[][] pointBufs = new int[2][width];
int[][] pointBufs = new int[2][];
pointBufs[0] = new int[width];
pointBufs[1] = new int[width];
// pass 1 & 2: increasing y
for (int x = 0; x < width; x++)
{
pointBufs[0][x] = -1;
pointBufs[1][x] = -1;
}
for (int y = 0; y < height; y++)
EdmLine(bPixels, fPixels, pointBufs, width, y * width, y);
//pass 3 & 4: decreasing y
for (int x = 0; x < width; x++)
{
pointBufs[0][x] = -1;
pointBufs[1][x] = -1;
}
for (int y = height - 1; y >= 0; y--)
EdmLine(bPixels, fPixels, pointBufs, width, y * width, y);
//image = new float[height][width];
image = new float[height][];
int p = 0;
if (distance == Distance.Euclidean)
{
for (int i = 0; i < height; i++)
{
image[i] = new float[width];
for (int j = 0; j < width; j++)
{
if (fPixels[p] < 0f)
image[i][j] = 0;
else
image[i][j] = (float)Math.Sqrt(fPixels[p]);
if (image[i][j] > max)
{
max = image[i][j];
ued = new Accord.IntPoint(i, j);
}
p++;
}
}
}
else
{
for (int i = 0; i < height; i++)
{
image[i] = new float[width];
for (int j = 0; j < width; j++)
{
if (fPixels[p] < 0f)
image[i][j] = 0;
else
image[i][j] = fPixels[p];
if (image[i][j] > max)
{
max = image[i][j];
ued = new Accord.IntPoint(i, j);
}
p++;
}
}
}
return image;
}
else
{
throw new ArgumentException("Distance Transform only works in grayscale images.");
}
}
// Handle a line; two passes: left-to-right and right-to-left
private void EdmLine(byte[] bPixels, float[] fPixels, int[][] pointBufs, int width, int offset, int y)
{
int[] points = pointBufs[0]; // the buffer for the left-to-right pass
int pPrev = -1;
int pDiag = -1; // point at (-/+1, -/+1) to current one (-1,-1 in the first pass)
int pNextDiag;
int distSqr = Int32.MaxValue; // this value is used only if edges are not background
for (int x = 0; x < width; x++, offset++)
{
pNextDiag = points[x];
if (bPixels[offset] == 0)
{
points[x] = x | y << 16; // remember coordinates as a candidate for nearest background point
}
else
{ // foreground pixel:
float dist2 = MinDist2(points, pPrev, pDiag, x, y, distSqr, distance);
if (fPixels[offset] > dist2) fPixels[offset] = dist2;
}
pPrev = points[x];
pDiag = pNextDiag;
}
offset--; //now points to the last pixel in the line
points = pointBufs[1]; // the buffer for the right-to-left pass. Low short contains x, high short y
pPrev = -1;
pDiag = -1;
for (int x = width - 1; x >= 0; x--, offset--)
{
pNextDiag = points[x];
if (bPixels[offset] == 0)
{
points[x] = x | y << 16; // remember coordinates as a candidate for nearest background point
}
else
{ // foreground pixel:
float dist2 = MinDist2(points, pPrev, pDiag, x, y, distSqr, distance);
if (fPixels[offset] > dist2) fPixels[offset] = dist2;
}
pPrev = points[x];
pDiag = pNextDiag;
}
}
private float MinDist2(int[] points, int pPrev, int pDiag, int x, int y, int distSqr, Distance distance)
{
int p0 = points[x]; // the nearest background point for the same x in the previous line
int nearestPoint = p0;
if (p0 != -1)
{
int x0 = p0 & 0xffff; int y0 = (p0 >> 16) & 0xffff;
int dist1Sqr = CalcDistance(x, y, x0, y0, distance);
if (dist1Sqr < distSqr)
distSqr = dist1Sqr;
}
if (pDiag != p0 && pDiag != -1)
{
int x1 = pDiag & 0xffff; int y1 = (pDiag >> 16) & 0xffff;
int dist1Sqr = CalcDistance(x, y, x1, y1, distance);
if (dist1Sqr < distSqr)
{
nearestPoint = pDiag;
distSqr = dist1Sqr;
}
}
if (pPrev != pDiag && pPrev != -1)
{
int x1 = pPrev & 0xffff; int y1 = (pPrev >> 16) & 0xffff;
int dist1Sqr = CalcDistance(x, y, x1, y1, distance);
if (dist1Sqr < distSqr)
{
nearestPoint = pPrev;
distSqr = dist1Sqr;
}
}
points[x] = nearestPoint;
return (float)distSqr;
}
private int CalcDistance(int x, int y, int x0, int y0, Distance distance)
{
int v = 0;
switch (distance)
{
case Distance.Euclidean:
v = (x - x0) * (x - x0) + (y - y0) * (y - y0);
break;
case Distance.Manhattan:
v = Math.Abs(x - x0) + Math.Abs(y - y0);
break;
case Distance.Chessboard:
v = Math.Max(Math.Abs(x - x0), Math.Abs(y - y0));
break;
case Distance.SquaredEuclidean:
v = (x - x0) * (x - x0) + (y - y0) * (y - y0);
break;
}
return v;
}
/**
* Convert Distance map to FastBitmap.
* @return FastBitmap.
*/
//public FastBitmap toFastBitmap()
//{
// int width = image[0].length;
// int height = image.length;
// FastBitmap fb = new FastBitmap(width, height, FastBitmap.ColorSpace.Grayscale);
// if (max > 255)
// {
// for (int i = 0; i < height; i++)
// {
// for (int j = 0; j < width; j++)
// {
// fb.setGray(i, j, (int)Catalano.Math.Tools.Scale(0, max, 0, 255, image[i][j]));
// }
// }
// }
// else
// {
// for (int i = 0; i < height; i++)
// {
// for (int j = 0; j < width; j++)
// {
// fb.setGray(i, j, (int)image[i][j]);
// }
// }
// }
// return fb;
//}
/**
* Convert Distance map to FastBitmap.
* @return FastBitmap.
*/
public unsafe Bitmap ToBitmap()
{
int width = image[0].Length;
int height = image.Length;
UnmanagedImage fb = UnmanagedImage.Create(width, height, PixelFormat.Format8bppIndexed);
Accord.Imaging.Converters.ImageToMatrix conv = new Accord.Imaging.Converters.ImageToMatrix(min: 0, max: 255);
conv.Convert(fb, out byte[,] img);
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
byte col = (byte)Vector.Scale(image[i][j], 0, max, 0, 255);
img[i, j] = col;
}
}
Accord.Imaging.Converters.MatrixToImage m2i = new Accord.Imaging.Converters.MatrixToImage(min: 0, max: 255);
m2i.Convert(img, out Bitmap output);
return output;
}
} |
I guess the goal isn't to match OpenCV's implementation of the DT but, unless I'm doing something wrong, this updated algorithm seems pretty far off of the DT image in this tutorial. http://docs.opencv.org/3.2.0/d2/dbd/tutorial_distance_transform.html |
The error in your result was to determine the threshold, before executing the distance transform. Look how the lines are connected. I set the threshold to a value of 200, so I got those lines of separation from the cards. Now we can apply the distance transform. Apply threshold again with the value 100. In the last, we need to implement a watershed with markers !!! |
Thanks for explaining @DiegoCatalano. I'm able to reproduce your result. |
Hi there, I have just committed an implementation for the distance transform to the framework based on Diego's code. I might be able to include in the release I was planning to do this weekend. Regards, |
@DiegoCatalano @cesarsouza I was going to ask whether there was watershed in Accord. Thanks for answering before I asked. LOL. This is my code snippet of trying to match OpenCV. //Load File into a Bitmap
Bitmap CurBitmap = Accord.Imaging.Image.FromFile(sInputFileName).Clone(PixelFormat.Format24bppRgb);
//Show loaded Image
ImageBox.Show("Current Bitmap", CurBitmap);
// This is for cards.png, change all white to black
UnmanagedImage input = UnmanagedImage.FromManagedImage(CurBitmap);
int width = input.Width;
int height = input.Height;
int pixelSize = System.Drawing.Image.GetPixelFormatSize(input.PixelFormat) / 8;
int offset = input.Stride - input.Width * pixelSize;
unsafe
{
byte* src = (byte*)input.ImageData.ToPointer();
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++, src += pixelSize)
{
// Change White to Black
if (src[RGB.B] == 255 &&
src[RGB.G] == 255 &&
src[RGB.R] == 255)
{
src[RGB.B] = 0;
src[RGB.G] = 0;
src[RGB.R] = 0;
}
}
src += offset;
}
}
// Show modified cards
ImageBox.Show("Input Bitmap", input.ToManagedImage());
int[,] kernel =
{
{+0, -1, +0},
{-1, +4, -1},
{+0, -1, +0}
};
Convolution lapConvolve = new Convolution(kernel);
UnmanagedImage lap = lapConvolve.Apply(input);
// Subtract images using laplacian convolution
Subtract sub = new Subtract(lap);
sub.ApplyInPlace(input);
// Make Grayscale
UnmanagedImage GrayCurBitmap = Grayscale.CommonAlgorithms.BT709.Apply(input);
// Threshold 200 to match Diego's results
Threshold thresh = new Threshold(200);
thresh.ApplyInPlace(GrayCurBitmap);
// Distance Transform
DistanceTransform dt = new DistanceTransform(DistanceTransform.Distance.Euclidean);
// Compute the DT
float[][] dtFloat = dt.Compute(GrayCurBitmap);
// Make a bitmap
Bitmap dtBmp = dt.ToBitmap();
// Apply Threshold to match OpenCV
Threshold thresh2 = new Threshold(100);
thresh2.ApplyInPlace(dtBmp);
Dilatation3x3 dilate = new Dilatation3x3();
dilate.ApplyInPlace(dtBmp);
CurBitmap.Dispose(); |
Awesome! I'll have to try it. Thanks! |
I am able to get a little closer to the OpenCV tutorial output but not all the way there. It appears that BinaryWatershed() works a bit different than the OpenCV version. The OpenCV version takes a seed array of approximately where the markers will be. This output doesn't quite match the OpenCV Step 7 output. But I can see that it's similar. This is the bit of code that got me to this point. Accord.Imaging.Filters.DistanceTransform newDT = new Accord.Imaging.Filters.DistanceTransform();
Bitmap dtBmp = newDT.Apply(GrayCurBitmap).ToManagedImage();
Threshold thresh2 = new Threshold(100);
thresh2.ApplyInPlace(dtBmp);
//ImageBox.Show("DT Thresh Bitmap", dtBmp);
Dilatation3x3 dilate = new Dilatation3x3();
dilate.ApplyInPlace(dtBmp);
//ImageBox.Show("DT Thresh Dilate Bitmap", dtBmp);
Accord.Imaging.Filters.BinaryWatershed bw = new BinaryWatershed(1.0f, DistanceTransformMethod.Euclidean);
Bitmap wtrBmp = bw.Apply(dtBmp);
GrayscaleToRGB toRGB = new GrayscaleToRGB();
wtrBmp = toRGB.Apply(wtrBmp);
PointsMarker marker = new PointsMarker(Color.Red, 5);
marker.Points = bw.MaxPoints;
Bitmap marked = marker.Apply(wtrBmp); I have no clue how to get the step 8 output with Accord. I'm not even sure I understand how the OpenCV makes the card shaped contours. |
Well, now I understand what @DiegoCatalano said some posts ago: it would be necessary to implement a full watershed algorithm with markers in the framework. The current implementation only goes until the step where the foreground/background markers (and their maxima) are found, and does not conclude the final step of the watershed algorithm, where the objects would have finally been segmented into their pixelwise classes. This issue will remain open until the complete version of the watershed algorithm can be implemented. Anyways, thanks a lot for the investigation @fdncred: I hope that this issue can be resolved soon! Regards, |
If anyone would like to work on this issue, the framework is almost there with a full watershed implementation. As explained above, the only step missing now is the segmentation after the borders and markers have been found. |
I've grepped and looked through the code looking for a Distance Transform but I don't see one. Could you please clarify if there is one please?
This is what I'm looking for.
https://en.wikipedia.org/wiki/Distance_transform
https://homepages.inf.ed.ac.uk/rbf/HIPR2/distance.htm
The text was updated successfully, but these errors were encountered: