Zhang-Suen thinning algorithm C# - c#
I am trying to write a Zhang-Suen thinning algorithm in C# following this guideline, without processing the margins.
In the function 'zhangsuen', I am reading from the image 'imgUndo' and writting to the image 'img'. The pointers dataPtrOrigin_aux inside the for cycles are used to read the 9 pixels inside a 3x3 window such that dataPtrOrigin_aux5 is the central pixel of this window, and that window will move along the whole image, moving from left to right and from top to bottom. In each pixel, if the if the statements are verified to be true, the corresponding changes are made in the image to be written by the pointer dataPtrFinal.
Note that I stored the neighbours of the current pixel inside a 8 element array. As such, they will be stored following this order:
internal static void zhangsuen(Image<Bgr, byte> img, Image<Bgr, byte> imgUndo)
{
unsafe
{
MIplImage m = img.MIplImage; //Image to be written.
MIplImage mUndo = imgUndo.MIplImage; //Image to be read.
byte* dataPtrFinal = (byte*)m.imageData.ToPointer();
byte* dataPtrUndo = (byte*)mUndo.imageData.ToPointer();
int width = img.Width; //Width of the image.
int height = img.Height; //Height of the image.
int nChan = m.nChannels; //3 channels (R, G, B).
int wStep = m.widthStep; //Total width of the image (including padding).
int padding = wStep - nChan * width; //Padding at the end of each line.
int x, y, i;
int[] neighbours = new int[8]; //Store the value of the surrounding neighbours in this array.
int step; //Step 1 or 2.
int[] sequence = { 1, 2, 4, 7, 6, 5, 3, 0, 1 };
int blackn = 0; //Number of black neighbours.
int numtransitions = 0; //Number of transitions from white to black in the sequence specified by the array sequence.
int changed = 1; //Just so it enters the while.
bool isblack = false;
int counter = 0;
while(changed > 0)
{
changed = 0;
if (counter % 2 == 0) //We want to read all the pixels in the image before going to the next step
step = 1;
else
step = 2;
for (y = 0; y < height; y++)
{
for (x = 0; x < width; x++)
{
byte* dataPtrOrigin_aux1 = (byte*)(dataPtrUndo + (y - 1) * m.widthStep + (x - 1) * m.nChannels);
byte* dataPtrOrigin_aux2 = (byte*)(dataPtrUndo + (y - 1) * m.widthStep + (x) * m.nChannels);
byte* dataPtrOrigin_aux3 = (byte*)(dataPtrUndo + (y - 1) * m.widthStep + (x + 1) * m.nChannels);
byte* dataPtrOrigin_aux4 = (byte*)(dataPtrUndo + (y) * m.widthStep + (x - 1) * m.nChannels);
byte* dataPtrOrigin_aux5 = (byte*)(dataPtrUndo + (y) * m.widthStep + (x) * m.nChannels);
byte* dataPtrOrigin_aux6 = (byte*)(dataPtrUndo + (y) * m.widthStep + (x + 1) * m.nChannels);
byte* dataPtrOrigin_aux7 = (byte*)(dataPtrUndo + (y + 1) * m.widthStep + (x - 1) * m.nChannels);
byte* dataPtrOrigin_aux8 = (byte*)(dataPtrUndo + (y + 1) * m.widthStep + (x) * m.nChannels);
byte* dataPtrOrigin_aux9 = (byte*)(dataPtrUndo + (y + 1) * m.widthStep + (x + 1) * m.nChannels);
if (x > 0 && y > 0 && x < width - 1 && y < height - 1)
{
if (dataPtrOrigin_aux5[0] == 0)
isblack = true;
if (isblack)
{
neighbours[0] = dataPtrOrigin_aux1[0];
neighbours[1] = dataPtrOrigin_aux2[0];
neighbours[2] = dataPtrOrigin_aux3[0];
neighbours[3] = dataPtrOrigin_aux4[0];
neighbours[4] = dataPtrOrigin_aux6[0];
neighbours[5] = dataPtrOrigin_aux7[0];
neighbours[6] = dataPtrOrigin_aux8[0];
neighbours[7] = dataPtrOrigin_aux9[0];
for(i = 0; i <= 7; i++)
{
if (neighbours[i] == 0)
blackn++;
if (neighbours[sequence[i]] - neighbours[sequence[i + 1]] == 255) //número de transições de branco para preto, seguindo a ordem do vector sequence
numtransitions++;
}
if ((blackn >= 2 && blackn <= 6) && numtransitions == 1)
{
if (step == 1 && (neighbours[1] == 255 || neighbours[4] == 255 || neighbours[6] == 255) && (neighbours[4] == 255 || neighbours[6] == 255 || neighbours[3] == 255))
{
dataPtrFinal[0] = 255;
dataPtrFinal[1] = 255;
dataPtrFinal[2] = 255;
changed++;
}
if (step == 2 && (neighbours[1] == 255 || neighbours[4] == 255 || neighbours[3] == 255) && (neighbours[1] == 255 || neighbours[6] == 255 || neighbours[3] == 255))
{
dataPtrFinal[0] = 255;
dataPtrFinal[1] = 255;
dataPtrFinal[2] = 255;
changed++;
}
}
}
}
dataPtrFinal += nChan;
isblack = false;
blackn = 0;
numtransitions = 0;
}
dataPtrFinal += padding;
}
dataPtrUndo = (byte*)m.imageData.ToPointer(); //Change the image to be read to the one that has just been written.
counter++;
}
}
}
As I end reading the first image and writing the changes to the image 'img' (As soon as the cycle for (y = 0; y < height; y++) ends I want the image I have just written to be the one I will read in the next cycle so that further thinning is made. I tried to accomplish this with the line
dataPtrUndo = (byte*)m.imageData.ToPointer();
Although at some value of counter that is greater than 0 (depends on the image that is read) I get an error that says that protected memory has been tried to be written which indicates I have tried to write outside of the image limits, but I don't understand why. Is it the last attribution to dataPtrUndo that I am doing erroneously?
Here is my C# implementation of Zhang-Suen thinning algorithm
public static bool[][] ZhangSuenThinning(bool[][] s)
{
bool[][] temp = s;
bool even = true;
for (int a = 1; a < s.Length-1; a++)
{
for (int b = 1; b < s[0].Length-1; b++)
{
if (SuenThinningAlg(a, b, temp, even))
{
temp[a][b] = false;
}
even = !even;
}
}
return temp;
}
static bool SuenThinningAlg(int x, int y, bool[][] s, bool even)
{
bool p2 = s[x][y - 1];
bool p3 = s[x + 1][y - 1];
bool p4 = s[x + 1][y];
bool p5 = s[x + 1][y + 1];
bool p6 = s[x][y + 1];
bool p7 = s[x - 1][y + 1];
bool p8 = s[x - 1][y];
bool p9 = s[x - 1][y - 1];
int bp1 = NumberOfNonZeroNeighbors(x, y, s);
if (bp1 >= 2 && bp1 <= 6)//2nd condition
{
if (NumberOfZeroToOneTransitionFromP9(x, y, s) == 1)
{
if (even)
{
if (!((p2 && p4) && p8))
{
if (!((p2 && p6) && p8))
{
return true;
}
}
}
else
{
if (!((p2 && p4) && p6))
{
if (!((p4 && p6) && p8))
{
return true;
}
}
}
}
}
return false;
}
static int NumberOfZeroToOneTransitionFromP9(int x, int y, bool[][]s)
{
bool p2 = s[x][y - 1];
bool p3 = s[x + 1][y - 1];
bool p4 = s[x + 1][y];
bool p5 = s[x + 1][y + 1];
bool p6 = s[x][y + 1];
bool p7 = s[x - 1][y + 1];
bool p8 = s[x - 1][y];
bool p9 = s[x - 1][y - 1];
int A = Convert.ToInt32((p2 == false && p3 == true)) + Convert.ToInt32((p3 == false && p4 == true)) +
Convert.ToInt32((p4 == false && p5 == true)) + Convert.ToInt32((p5 == false && p6 == true)) +
Convert.ToInt32((p6 == false && p7 == true)) + Convert.ToInt32((p7 == false && p8 == true)) +
Convert.ToInt32((p8 == false && p9 == true)) + Convert.ToInt32((p9 == false && p2 == true));
return A;
}
static int NumberOfNonZeroNeighbors(int x, int y, bool[][]s)
{
int count = 0;
if (s[x-1][y])
count++;
if (s[x-1][y+1])
count++;
if (s[x-1][y-1])
count++;
if (s[x][y+1])
count++;
if (s[x][y-1])
count++;
if (s[x+1][y])
count++;
if (s[x+1][y+1])
count++;
if (s[x+1][y-1])
count++;
return count;
}
bwang22's answer works. Sort of. But with two issues: It doesn't do the iterations until no more changes happen. And it does a shallow copy of the Array.. The two issues cooperate so to speak, cancelling each other out, resulting in an thinning, but not the best looking one.
Here is the corrected code, which gives a nicer looking result:
First two methods to convert from Image to bool[][] and back; the functions are not optimzed for speed; if you need that go for lockbits/unsafe..:
public static bool[][] Image2Bool(Image img)
{
Bitmap bmp = new Bitmap(img);
bool[][] s = new bool[bmp.Height][];
for (int y = 0; y < bmp.Height; y++ )
{
s[y] = new bool[bmp.Width];
for (int x = 0; x < bmp.Width; x++)
s[y][x] = bmp.GetPixel(x, y).GetBrightness() < 0.3;
}
return s;
}
public static Image Bool2Image(bool[][] s)
{
Bitmap bmp = new Bitmap(s[0].Length, s.Length);
using (Graphics g = Graphics.FromImage(bmp)) g.Clear(Color.White);
for (int y = 0; y < bmp.Height; y++)
for (int x = 0; x < bmp.Width; x++)
if (s[y][x]) bmp.SetPixel(x, y, Color.Black);
return (Bitmap)bmp;
}
Now the corrected thinning code, much of it more or less unchanged from bwang22's answer:
public static bool[][] ZhangSuenThinning(bool[][] s)
{
bool[][] temp = ArrayClone(s); // make a deep copy to start..
int count = 0;
do // the missing iteration
{
count = step(1, temp, s);
temp = ArrayClone(s); // ..and on each..
count += step(2, temp, s);
temp = ArrayClone(s); // ..call!
}
while (count > 0);
return s;
}
static int step(int stepNo, bool[][] temp, bool[][] s)
{
int count = 0;
for (int a = 1; a < temp.Length - 1; a++)
{
for (int b = 1; b < temp[0].Length - 1; b++)
{
if (SuenThinningAlg(a, b, temp, stepNo == 2))
{
// still changes happening?
if (s[a][b]) count++;
s[a][b] = false;
}
}
}
return count;
}
static bool SuenThinningAlg(int x, int y, bool[][] s, bool even)
{
bool p2 = s[x][y - 1];
bool p3 = s[x + 1][y - 1];
bool p4 = s[x + 1][y];
bool p5 = s[x + 1][y + 1];
bool p6 = s[x][y + 1];
bool p7 = s[x - 1][y + 1];
bool p8 = s[x - 1][y];
bool p9 = s[x - 1][y - 1];
int bp1 = NumberOfNonZeroNeighbors(x, y, s);
if (bp1 >= 2 && bp1 <= 6) //2nd condition
{
if (NumberOfZeroToOneTransitionFromP9(x, y, s) == 1)
{
if (even)
{
if (!((p2 && p4) && p8))
{
if (!((p2 && p6) && p8))
{
return true;
}
}
}
else
{
if (!((p2 && p4) && p6))
{
if (!((p4 && p6) && p8))
{
return true;
}
}
}
}
}
return false;
}
static int NumberOfZeroToOneTransitionFromP9(int x, int y, bool[][] s)
{
bool p2 = s[x][y - 1];
bool p3 = s[x + 1][y - 1];
bool p4 = s[x + 1][y];
bool p5 = s[x + 1][y + 1];
bool p6 = s[x][y + 1];
bool p7 = s[x - 1][y + 1];
bool p8 = s[x - 1][y];
bool p9 = s[x - 1][y - 1];
int A = Convert.ToInt32((!p2 && p3 )) + Convert.ToInt32((!p3 && p4 )) +
Convert.ToInt32((!p4 && p5 )) + Convert.ToInt32((!p5 && p6 )) +
Convert.ToInt32((!p6 && p7 )) + Convert.ToInt32((!p7 && p8 )) +
Convert.ToInt32((!p8 && p9 )) + Convert.ToInt32((!p9 && p2 ));
return A;
}
static int NumberOfNonZeroNeighbors(int x, int y, bool[][] s)
{
int count = 0;
if (s[x - 1][y]) count++;
if (s[x - 1][y + 1]) count++;
if (s[x - 1][y - 1]) count++;
if (s[x][y + 1]) count++;
if (s[x][y - 1]) count++;
if (s[x + 1][y]) count++;
if (s[x + 1][y + 1]) count++;
if (s[x + 1][y - 1]) count++;
return count;
}
I have kept the original even flag, but call it by comparing a step number. And I have saved a few characters by using the bools directly..
Finally a function to get a deep copy of the nested 2d array:
public static T[][] ArrayClone<T>(T [][] A)
{ return A.Select(a => a.ToArray()).ToArray(); }
This is how to call it, using two PictureBoxes:
pictureBox1.Image = Image.FromFile("D:\\RCdemo.png");
bool[][] t = Image2Bool(pictureBox1.Image);
t = ZhangSuenThinning(t);
pictureBox2.Image = Bool2Image(t);
I append a test image.
bwang22's answer is very slow. Try this instead:
public readonly struct ConnectivityData
{
public readonly int[] N;
public readonly int NumNeighbors;
public readonly int NumChanges;
public ConnectivityData(in int[] n, in int numNeighbors, in int numChanges)
{
N = n;
NumNeighbors = numNeighbors;
NumChanges = numChanges;
}
}
public static void ZhangSuen(in HashSet<Pixel> pixels)
{
while (true)
{
// Pass #1:
List<Pixel> mark1 = new List<Pixel>();
foreach (Pixel p in pixels)
{
ConnectivityData conn = ComputeConnectivity(p, pixels);
if (conn.NumNeighbors > 1 &&
conn.NumNeighbors < 7 &&
conn.NumChanges == 1 &&
conn.N[0] * conn.N[2] * conn.N[4] == 0 &&
conn.N[2] * conn.N[4] * conn.N[6] == 0)
{
mark1.Add(p);
}
}
//delete all marked:
foreach (Pixel p in mark1)
{
pixels.Remove(p);
}
// PASS #2:
List<Pixel> mark2 = new List<Pixel>();
foreach (Pixel p in pixels)
{
ConnectivityData conn = ComputeConnectivity(p, pixels);
if (conn.NumNeighbors > 1 &&
conn.NumNeighbors < 7 &&
conn.NumChanges == 1 &&
conn.N[0] * conn.N[2] * conn.N[6] == 0 &&
conn.N[0] * conn.N[4] * conn.N[6] == 0)
{
mark2.Add(p);
}
}
//delete all marked:
foreach (Pixel p in mark2)
{
pixels.Remove(p);
}
if (mark1.Count == 0 && mark2.Count == 0)
{
break;
}
}
}
private static ConnectivityData ComputeConnectivity(
in Pixel p,
in HashSet<Pixel> pixels)
{
// calculate #neighbors and number of changes:
int[] n = new int[8];
if (pixels.Contains(new Pixel(p.X, p.Y - 1)))
{
n[0] = 1;
}
if (pixels.Contains(new Pixel(p.X + 1, p.Y - 1)))
{
n[1] = 1;
}
if (pixels.Contains(new Pixel(p.X + 1, p.Y)))
{
n[2] = 1;
}
if (pixels.Contains(new Pixel(p.X + 1, p.Y + 1)))
{
n[3] = 1;
}
if (pixels.Contains(new Pixel(p.X, p.Y + 1)))
{
n[4] = 1;
}
if (pixels.Contains(new Pixel(p.X - 1, p.Y + 1)))
{
n[5] = 1;
}
if (pixels.Contains(new Pixel(p.X - 1, p.Y)))
{
n[6] = 1;
}
if (pixels.Contains(new Pixel(p.X - 1, p.Y - 1)))
{
n[7] = 1;
}
return new ConnectivityData(
n,
n[0] + n[1] + n[2] + n[3] + n[4] + n[5] + n[6] + n[7],
ComputeNumberOfChanges(n));
}
private static int ComputeNumberOfChanges(in int[] n)
{
int numberOfChanges = 0;
// Iterate over each location and see if it is has changed from 0 to 1:
int current = n[0];
for (int i = 1; i < 8; i++)
{
if (n[i] == 1 && current == 0)
{
numberOfChanges++;
}
current = n[i];
}
// Also consider the change over the discontinuity between n[7] and n[0]:
if (n[0] == 1 && n[7] == 0)
{
numberOfChanges++;
}
return numberOfChanges;
}
To use:
From your Bitmap etc, create a hash set of type Pixel, (which contains all the black pixels you want to thin) eg:
public class Pixel
{
public int X;
public int Y;
public Pixel(in int x, in int y)
{
X = x;
Y = y;
}
public override bool Equals(object pixel)
{
Pixel b = pixel as Pixel;
return X == b.X && Y == b.Y;
}
public override int GetHashCode()
{
//return (a.X << 2) ^ a.Y; // this is also commonly used as a pixel hash code
return X * 100000 + Y; // a bit hacky [will fail if bitmap width is > 100000]
}
}
...then call ZhangSuen(pixels). This will delete the appropriate pixels from the set.
Note that this method does not work perfectly on all images. It makes parts of some images disappear. Specifically, I am having problems with downward-right pointing diagonal lines of thickness around 11 pixels wide.
I am currently working on a way to improve this, but it performs better than the similar Staniford algorithm on most files I have tested it with (CAD files).
Related
I'm trying to do edge detection. But it works on just one image
I'm trying to create a simple edge detection filter. And as I said it works with only one image. I'm trying to create this filter with 2 steps. Blurring image (with meanfilter) Calculate ( Original image-Blurring image) The first step works well. And code of second one is simple like first one. But I see an error message: System.ArgumentOutOfRangeException: 'Parameter must be positive and < Height. Parameter name: y' Working image:https://i.hizliresim.com/dLXkbn.png My code: public void edgedetectionfilter( ) { Bitmap InputPicture,BlurredPicture, OutputPicture; InputPicture = new Bitmap(pBox_SOURCE.Image); BlurredPicture = new Bitmap(pBox_PROCESSED.Image); int PicWidth = InputPicture.Width; int PicHeight= InputPicture.Height; OutputPicture = new Bitmap(PicWidth, PicHeight); OutputPicture = InputPicture; int x, y, difR, difG, difB; Color OrgPicColoValue,BluredPicColorValue; for (x = 0; x < PicWidth; x++) { for (y = 0; y < PicWidth; y++) { BluredPicColorValue = BlurredPicture.GetPixel(x, y); OrgPicColoValue = InputPicture.GetPixel(x, y); //ERROR LINE difR = Convert.ToInt16(OrgPicColoValue.R -BluredPicColorValue.R); difG = Convert.ToInt16(OrgPicColoValue.G- BluredPicColorValue.G ); difB = Convert.ToInt16(OrgPicColoValue.B- BluredPicColorValue.B); if (difR > 255) difR = 255; if (difG > 255) difG = 255; if (difB > 255) difB = 255; if (difR < 0) difR = 0; if (difG < 0) difG = 0; if (difB < 0) difB = 0; OutputPicture.SetPixel(x, y, Color.FromArgb(difR, difG, difB)); } } pBoxMedian.Image = OutputPicture; } public void meanfilter(int p) //KERNELSIZE=P { if (sliderKernel.Value % 2 == 0) { MessageBox.Show("Enter an odd number"); return; } Color ColorValue; Bitmap InputPicture, OutputPicture; InputPicture = new Bitmap(pBox_SOURCE.Image); int PicWidth = InputPicture.Width; int PicHeight= InputPicture.Height; OutputPicture = new Bitmap(PicWidth, PicHeight); OutputPicture = InputPicture; int x, y, i, j, sumR, sumG, sumB, avgR, avgG, avgB; for (x = (KernelSize - 1) / 2; x < PicWidth - (KernelSize - 1) / 2; x++) { for (y = (KernelSize - 1) / 2; y < PicHeight - (KernelSize- 1) / 2; y++) { toplamR = 0; toplamG = 0; toplamB = 0; for (i = -((KernelSize - 1) / 2); i <= (KernelSize - 1) / 2; i++) { for (j = -((KernelSize - 1) / 2); j <= (KernelSize - 1) / 2; j++) { ColorValue= InputPicture.GetPixel(x + i, y + j); sumR = sumR + ColorValue.R; sumG = sumG + ColorValue.G; sumB = sumB + ColorValue.B; } } avgR = sumR / (KernelSize * KernelSize ); avgG = sumG / (KernelSize *KernelSize ); avgB = sumB / (KernelSize * KernelSize ); OutputPicture.SetPixel(x, y, Color.FromArgb(avgR, avgG, avgB)); } } pBox_PROCESSED.Image = OutputPicture; }
You compare y < PicWidth, whereas you probably want y < PicHeight. Is the image it worked on square, by chance?
How quickly display Minecraft Schematic to winform?
There is my Code, But drawing is very slow: int BlockWidth = 16 SourceBlueMap = new Bitmap(BlocksRect.Width, BlocksRect.Height); Graphics Gpi = Graphics.FromImage(SourceBlueMap); AlphaBlock block; string strPath = #"C:\Users\Administrator\Desktop\Blocks"; BlockInfo blockinfo; for (int x = 0; x < iXDim; x++) { for (int y = 0; y < iYDim; y++) { for (int z = 0; z < iZDim; z++) { block = Blocks.GetBlock(x, y, z); rect.X = x * BlockWidth; rect.Y = z * BlockWidth; if (BlockInfoList.Exists(info => info.ID == block.ID && info.Data == block.Data)) { blockinfo = BlockInfoList.Find(info => info.ID == block.ID && info.Data == block.Data); if (File.Exists(strPath + "\\" + blockinfo.Name + ".png")) { using (Bitmap bmp = new Bitmap(strPath + "\\" + blockinfo.Name + ".png")) { Gpi.DrawImage(bmp, rect); } } } } } } Gpi.Dispose(); Draw a 256 * 256 size map takes 6 seconds. But other programs use draw than 1 second: http://www.diamondpants.com/spritecraft/PayPalDonate.aspx
Finding The Shortest Distance Between Two 3D Line Segments
I have two Line Segments, represented by a 3D point at their beginning/end points. Line: class Line { public string Name { get; set; } public Point3D Start { get; set; } = new Point3D(); public Point3D End { get; set; } = new Point3D(); } The 3D points are just 3 doubles for coordinates X,Y and Z. 3DPoint: class Point3D { public double X { get; set; } public double Y { get; set; } public double Z { get; set; } } The Question: Can I find the distance between two 'Lines' and the endpoints of that distance 'Line'. [1 What I have: Currently, I can successfully get the distance between the two lines with this code (Adapted From Here Using the Segment To Segment Section): public double lineNearLine(Line l1, Line l2) { Vector3D uS = new Vector3D { X = l1.Start.X, Y = l1.Start.Y, Z = l1.Start.Z }; Vector3D uE = new Vector3D { X = l1.End.X, Y = l1.End.Y, Z = l1.End.Z }; Vector3D vS = new Vector3D { X = l2.Start.X, Y = l2.Start.Y, Z = l2.Start.Z }; Vector3D vE = new Vector3D { X = l2.End.X, Y = l2.End.Y, Z = l2.End.Z }; Vector3D w1 = new Vector3D { X = l1.Start.X, Y = l1.Start.Y, Z = l1.Start.Z }; Vector3D w2 = new Vector3D { X = l2.Start.X, Y = l2.Start.Y, Z = l2.Start.Z }; Vector3D u = uE - uS; Vector3D v = vE - vS; Vector3D w = w1 - w2; double a = Vector3D.DotProduct(u, u); double b = Vector3D.DotProduct(u, v); double c = Vector3D.DotProduct(v, v); double d = Vector3D.DotProduct(u, w); double e = Vector3D.DotProduct(v, w); double D = a * c - b * b; double sc, sN, sD = D; double tc, tN, tD = D; if (D < 0.01) { sN = 0; sD = 1; tN = e; tD = c; } else { sN = (b * e - c * d); tN = (a * e - b * d); if (sN < 0) { sN = 0; tN = e; tD = c; } else if (sN > sD) { sN = sD; tN = e + b; tD = c; } } if (tN < 0) { tN = 0; if (-d < 0) { sN = 0; } else if (-d > a) { sN = sD; } else { sN = -d; sD = a; } } else if (tN > tD) { tN = tD; if ((-d + b) < 0) { sN = 0; } else if ((-d + b) > a) { sN = sD; } else { sN = (-d + b); sD = a; } } if (Math.Abs(sN) < 0.01) { sc = 0; } else { sc = sN / sD; } if (Math.Abs(tN) < 0.01) { tc = 0; } else { tc = tN / tD; } Vector3D dP = w + (sc * u) - (tc * v); double distance1 = Math.Sqrt(Vector3D.DotProduct(dP, dP)); return distance1; } What I Need: Is there any way to determine the endpoints of the displacement vector 'dP' from the code above? If not, can anyone suggest a better method for finding minimum distance and the endpoints of that distance? Thank you for Reading, and Thanks in advance for any suggestions! An Enormous Thank You to #Isaac van Bakel for the theory behind this Solution Here is my code complete: Shortest distance between two lines represented by the line that connects them at that shortest distance. Classes: Sharp3D.Math : I use this reference for Vector3D, but really any 3D vector class will work. On top of that, the vectors aren't even required if you do the subtraction element by element. Point3D : My Personal Point3D class. Feel free to use as much or little as you want. class Point3D { public double X { get; set; } public double Y { get; set; } public double Z { get; set; } public Vector3D getVector() { return new Vector3D { X = this.X, Y = this.Y, Z = this.Z }; } } Line : My Personal Line class. Feel free to use as much or little as you want. class Line { public string Name { get; set; } public Point3D Start { get; set; } = new Point3D(); public Point3D End { get; set; } = new Point3D(); public double Length { get { return Math.Sqrt(Math.Pow((End.X - Start.X), 2) + Math.Pow((End.Y - Start.Y), 2)); } } } Functions: ClampPointToLine : Clamping function I wrote to clamp a point to a line. public Point3D ClampPointToLine(Point3D pointToClamp, Line lineToClampTo) { Point3D clampedPoint = new Point3D(); double minX, minY, minZ, maxX, maxY, maxZ; if(lineToClampTo.Start.X <= lineToClampTo.End.X) { minX = lineToClampTo.Start.X; maxX = lineToClampTo.End.X; } else { minX = lineToClampTo.End.X; maxX = lineToClampTo.Start.X; } if (lineToClampTo.Start.Y <= lineToClampTo.End.Y) { minY = lineToClampTo.Start.Y; maxY = lineToClampTo.End.Y; } else { minY = lineToClampTo.End.Y; maxY = lineToClampTo.Start.Y; } if (lineToClampTo.Start.Z <= lineToClampTo.End.Z) { minZ = lineToClampTo.Start.Z; maxZ = lineToClampTo.End.Z; } else { minZ = lineToClampTo.End.Z; maxZ = lineToClampTo.Start.Z; } clampedPoint.X = (pointToClamp.X < minX) ? minX : (pointToClamp.X > maxX) ? maxX : pointToClamp.X; clampedPoint.Y = (pointToClamp.Y < minY) ? minY : (pointToClamp.Y > maxY) ? maxY : pointToClamp.Y; clampedPoint.Z = (pointToClamp.Z < minZ) ? minZ : (pointToClamp.Z > maxZ) ? maxZ : pointToClamp.Z; return clampedPoint; } distanceBetweenLines : The function that returns the line that represents the shortest distance between two lines. Returns null if unsolvable. public Line distBetweenLines(Line l1, Line l2) { Vector3D p1, p2, p3, p4, d1, d2; p1 = l1.Start.getVector(); p2 = l1.End.getVector(); p3 = l2.Start.getVector(); p4 = l2.End.getVector(); d1 = p2 - p1; d2 = p4 - p3; double eq1nCoeff = (d1.X * d2.X) + (d1.Y * d2.Y) + (d1.Z * d2.Z); double eq1mCoeff = (-(Math.Pow(d1.X, 2)) - (Math.Pow(d1.Y, 2)) - (Math.Pow(d1.Z, 2))); double eq1Const = ((d1.X * p3.X) - (d1.X * p1.X) + (d1.Y * p3.Y) - (d1.Y * p1.Y) + (d1.Z * p3.Z) - (d1.Z * p1.Z)); double eq2nCoeff = ((Math.Pow(d2.X, 2)) + (Math.Pow(d2.Y, 2)) + (Math.Pow(d2.Z, 2))); double eq2mCoeff = -(d1.X * d2.X) - (d1.Y * d2.Y) - (d1.Z * d2.Z); double eq2Const = ((d2.X * p3.X) - (d2.X * p1.X) + (d2.Y * p3.Y) - (d2.Y * p2.Y) + (d2.Z * p3.Z) - (d2.Z * p1.Z)); double[,] M = new double[,] { { eq1nCoeff, eq1mCoeff, -eq1Const }, { eq2nCoeff, eq2mCoeff, -eq2Const } }; int rowCount = M.GetUpperBound(0) + 1; // pivoting for (int col = 0; col + 1 < rowCount; col++) if (M[col, col] == 0) // check for zero coefficients { // find non-zero coefficient int swapRow = col + 1; for (; swapRow < rowCount; swapRow++) if (M[swapRow, col] != 0) break; if (M[swapRow, col] != 0) // found a non-zero coefficient? { // yes, then swap it with the above double[] tmp = new double[rowCount + 1]; for (int i = 0; i < rowCount + 1; i++) { tmp[i] = M[swapRow, i]; M[swapRow, i] = M[col, i]; M[col, i] = tmp[i]; } } else return null; // no, then the matrix has no unique solution } // elimination for (int sourceRow = 0; sourceRow + 1 < rowCount; sourceRow++) { for (int destRow = sourceRow + 1; destRow < rowCount; destRow++) { double df = M[sourceRow, sourceRow]; double sf = M[destRow, sourceRow]; for (int i = 0; i < rowCount + 1; i++) M[destRow, i] = M[destRow, i] * df - M[sourceRow, i] * sf; } } // back-insertion for (int row = rowCount - 1; row >= 0; row--) { double f = M[row, row]; if (f == 0) return null; for (int i = 0; i < rowCount + 1; i++) M[row, i] /= f; for (int destRow = 0; destRow < row; destRow++) { M[destRow, rowCount] -= M[destRow, row] * M[row, rowCount]; M[destRow, row] = 0; } } double n = M[0, 2]; double m = M[1, 2]; Point3D i1 = new Point3D { X = p1.X + (m * d1.X), Y = p1.Y + (m * d1.Y), Z = p1.Z + (m * d1.Z) }; Point3D i2 = new Point3D { X = p3.X + (n * d2.X), Y = p3.Y + (n * d2.Y), Z = p3.Z + (n * d2.Z) }; Point3D i1Clamped = ClampPointToLine(i1, l1); Point3D i2Clamped = ClampPointToLine(i2, l2); return new Line { Start = i1Clamped, End = i2Clamped }; } Implementation: Line shortestDistanceLine = distBetweenLines(l1, l2); Results: So far this has been accurate in my testing. Returns null if passed two identical lines. I appreciate any feedback!
The shortest distance between two skew lines (lines which don't intersect) is the distance of the line which is perpendicular to both of them. If we have a line l1 with known points p1 and p2, and a line l2 with known points p3 and p4: The direction vector of l1 is p2-p1, or d1. The direction vector of l2 is p4-p3, or d2. We therefore know that the vector we are looking for, v, is perpendicular to both of these direction vectors: d1.v = 0 & d2.v = 0 Or, if you prefer: d1x*vx + d1y*vy + d1z*vz = 0 And the same for d2. Let's take the point on the lines l1, l2 where v is actually perpendicular to the direction. We'll call these two points i1 and i2 respectively. Since i1 lies on l1, we can say that i1 = p1 + m*d1, where m is some number. Similarly, i2 = p3 + n*d2, where n is another number. Since v is the vector between i1 and i2 (by definition) we get that v = i2 - i1. This gives the substitutions for the x,y,z vectors of v: vx = i2x - i1x = (p3x + n*d2x) - (p1x + m*d1x) and so on. Which you can now substitute back into your dot product equation: d1x * ( (p3x + n*d2x) - (p1x + m*d1x) ) + ... = 0 This has reduced our number of equations to 2 (the two dot product equations) with two unknowns (m and n), so you can now solve them! Once you have m and n, you can find the coordinates by going back to the original calculation of i1 and i2. If you only wanted the shortest distance for points on the segment between p1-p2 and p3-p4, you can clamp i1 and i2 between these ranges of coordinates, since the shortest distance will always be as close to the perpendicular as possible.
The code above return a wrong value so I take the idea from Python code (Shortest distance between two line segments) and I converted for C#. It necessary to use Numpy lib for C#: public static Tuple<NDarray, NDarray, NDarray> closestDistanceBetweenLines( NDarray a0, NDarray a1, NDarray b0, NDarray b1, bool clampAll = false, bool clampA0 = false, bool clampA1 = false, bool clampB0 = false, bool clampB1 = false) { // If clampAll=True, set all clamps to True if (clampAll) { clampA0 = true; clampA1 = true; clampB0 = true; clampB1 = true; } // Calculate denomitator NDarray A = a1 - a0; NDarray B = b1 - b0; NDarray magA = np.linalg.norm(A); NDarray magB = np.linalg.norm(B); NDarray _A = A / magA; NDarray _B = B / magB; NDarray cross = np.cross(_A, _B); double denom = Math.Pow((float)np.linalg.norm(cross), 2); // If lines are parallel (denom=0) test if lines overlap. // If they don't overlap then there is a closest point solution. // If they do overlap, there are infinite closest positions, but there is a closest distance if (denom == 0) { NDarray d0 = np.dot(_A, b0 - a0); // Overlap only possible with clamping if (clampA0 || clampA1 || clampB0 || clampB1) { NDarray d1 = np.dot(_A, b1 - a0); // Is segment B before A? if ((float)d0 <= 0F || (float)d0 >= (float)d1) { if (clampA0 && clampB1) { if ( (float)np.absolute(d0) < (float)np.absolute(d1) ) { return Tuple.Create(a0, b0, np.linalg.norm(a0 - b0)); } return Tuple.Create(a0, b1, np.linalg.norm(a0 - b1)); } } else if ((float)d0 >= (float)magA || (float)d0 <= (float)d1) { // Is segment B after A? if (clampA1 && clampB0) { if ((float)np.absolute(d0) < (float)np.absolute(d1)) { return Tuple.Create(a1, b0, np.linalg.norm(a1 - b0)); } return Tuple.Create(a1, b1, np.linalg.norm(a1 - b1)); } } } // Segments overlap, return distance between parallel segments; NDarray vuoto1 = np.array(new[] { 0 }); return Tuple.Create(vuoto1, vuoto1, np.linalg.norm(d0 * _A + a0 - b0)); } // Lines criss-cross: Calculate the projected closest points NDarray t = b0 - a0; var ArrFordetA = np.array(new float[,] { { (float)t[0], (float)t[1], (float)t[2] }, { (float)_B[0], (float)_B[1], (float)_B[2] }, { (float)cross[0], (float)cross[1], (float)cross[2] } }); NDarray detA = np.linalg.det(ArrFordetA); var ArrFordetB = np.array(new float[,] { { (float)t[0], (float)t[1], (float)t[2] }, { (float)_A[0], (float)_A[1], (float)_A[2] }, { (float)cross[0], (float)cross[1], (float)cross[2] } }); NDarray detB = np.linalg.det(ArrFordetB); var t0 = detA / denom; var t1 = detB / denom; var pA = a0 + _A * t0; // Projected closest point on segment A var pB = b0 + _B * t1; // Projected closest point on segment B // Clamp projections if (clampA0 || clampA1 || clampB0 || clampB1) { if (clampA0 && (float)t0 < 0) { pA = a0; } else if (clampA1 && (float)t0 > (float)magA) { pA = a1; } if (clampB0 && (float)t1 < 0) { pB = b0; } else if (clampB1 && (float)t1 > (float)magB) { pB = b1; } // Clamp projection A if (clampA0 && (float)t0 < 0 || clampA1 && (float)t0 > (float)magA) { NDarray dot = np.dot(_B, pA - b0); if ( clampB0 && (float)dot < 0) { dot = (NDarray)0; } else if (clampB1 && (float)dot > (float)magB) { dot = magB; } pB = b0 + _B * dot; } // Clamp projection B if ( clampB0 && (float)t1 < 0 || clampB1 && (float)t1 > (float)magB) { NDarray dot = np.dot(_A, pB - a0); if (clampA0 && (float)dot < 0) { dot = (NDarray)0; } else if (clampA1 && (float)dot > (float)magA) { dot = magA; } pA = a0 + _A * dot; } } return Tuple.Create(pA, pB, np.linalg.norm(pA - pB)); } to call this function use: private void button1_Click(object sender, EventArgs e) { NDarray a1 = np.array(new[] { 13.43, 21.77, 46.81 }); NDarray a0 = np.array(new[] { 27.83, 31.74, -26.60 }); NDarray b0 = np.array(new[] { 77.54, 7.53, 6.22 }); NDarray b1 = np.array(new[] { 26.99, 12.39, 11.18 }); Debug.WriteLine("----------------- True: -----------------"); Debug.WriteLine(closestDistanceBetweenLines(a0, a1, b0, b1, true)); Debug.WriteLine("---------------------------------------------"); Debug.WriteLine(""); Debug.WriteLine("----------------- False: -----------------"); Debug.WriteLine(closestDistanceBetweenLines(a0, a1, b0, b1, false)); Debug.WriteLine("---------------------------------------------"); Tuple<NDarray, NDarray, NDarray> RisultatoTrue = closestDistanceBetweenLines(a0, a1, b0, b1, true); var Pa = np.array(RisultatoTrue.Item1); Debug.WriteLine("Start point X:" + Pa[0].ToString()); Debug.WriteLine("Start point Y:" + Pa[1].ToString()); Debug.WriteLine("Start point Z:" + Pa[2].ToString()); var Pb = np.array(RisultatoTrue.Item2); Debug.WriteLine("End point X:" + Pb[0].ToString()); Debug.WriteLine("End point Y:" + Pb[1].ToString()); Debug.WriteLine("End point Z:" + Pb[2].ToString()); var dist = np.array(RisultatoTrue.Item3); Debug.WriteLine("Distance:" + dist.ToString()); }
C# Check for neighbours
I have a function to check neighbors of an array and if that element is equal with 1. X is for each neighbor found and v[l] is the position for each 0. I have a problem with this code each time gives me "Index was outside the bounds of the array" and i don't know what to do else. public int modificari(int i,int j,int n,int m) { int x = 0; v = new int[n]; l=0; if (mat[i, j] == 1) { if (j++ < m) { if (mat[i, j++] == 1) x++; else { v[l] = i * n + j + 2; l++; } } if (j++ < m && i++ < n) { if (mat[i++, j++] == 1) x++; else { v[l] = (i + 1) * n + j + 2; l++; } } if (i++ < n) { if (mat[i++, j] == 1) x++; else { v[l] = (i + 1) * n + j + 1; l++; } } if (j-- >= 0 && i++ < n) { if (mat[i++, j--] == 1) x++; else { v[l] = (i + 1) * n + j; l++; } } if (j-- >= 0) { if (mat[i, j--] == 1) x++; else { v[l] = i * n + j; l++; } } if (j-- >= 0 && i-- >= 0) { if (mat[i--, j--] == 1) x++; else { v[l] = (i - 1) * n + j; l++; } } if (i-- >= 0) { if (mat[i--, j] == 1) x++; else { v[l] = (i - 1) * n + j + 1; l++; } } if (j < n && i-- >= 0) { if (mat[i--, j++] == 1) x++; else { v[l] = (i - 1) * n + j + 2; l++; } } if (x < 2 && x > 3) return 1; else return random(); } return x; }
That is a total mess. It is very hard to follow, even for an experienced coder. Use of one letter variable names and inline ++ operators is usually discouraged for the sake of readability. I've quickly tried to rewrite your function from my best guess of what you're trying to achieve. I'm hoping you can see a different way to approach the problem that suits you better. NOTE: I did not test this code at all, it probably has compile errors. public struct Point { public int X; public int Y; public Point( int x, int y ) { X = x; Y = y; } } public class Whatever { // ... // Here is a list of the positions of all the neighbours whose values are // zero. List<Point> zeroPositions = new List<Point>(); // ... public int Modificari(int pointX, int pointY) { // Determine dimensions of array. int height = mat.GetLength(0); int width = mat.GetLength(1); // Find the minimum and maximum positions bounded by array size. (So we // don't try to look at cell (-1, -1) when considering the neighbours of // cell (0, 0) for instance. int left = Math.Max( pointX - 1, 0 ); int right = Math.Min( pointX + 1, width ); int top = Math.Max( pointY - 1, 0 ); int bottom = Math.Min( pointY + 1, height ); // This is the number of neighbours whose value is 1. int oneCount = 0; zeroPositions.Clear(); for( int y = top; y <= bottom; y++ ) { for( int x = left; x <= right; x++ ) { if( mat[x, y] == 1 ) { oneCount++; } else if( mat[x, y] == 0 ) { zeroPositions.Add( new Point( x, y ) ); } } } return oneCount; } //... } Also I'd really advise you to try not to do too many things in a function. Try making a different function for getting positions of ones and for returning the number of zeros.
index outside bounds of the array adaptive median
I'm trying to output a new image after applying the adaptive median filter but it only works if the maximum window size is 3*3, but it should work for all odd window sizes, and it does so if the image is so small for example 10*10 pixels, but if the image is for example 440*445 pixels it only works if the max window size is 3*3 otherwise it says index outside bounds of the array, here is my code using counting sort to sort the pixels: using System; using System.Collections.Generic; using System.Text; using System.Linq; namespace ImageFilters { public class Class2 { public byte[,] median(byte[,] array, int width, int height, int msize) { byte[,] marr = new byte[height + 1, width + 1]; int size = 3; for (int i = 0; i <height+1; i++) { marr[i, 0] = 255; } for (int j = 0; j < width+1; j++) { marr[0, j] = 255; } int n=0; int z=0; for (int k = 0; k < (height) * width; k++) { repeat: byte[] farr = new byte[size * size]; int I=0; for (int i = n; i < n+size; i++) { for (int j = z; j < z+size; j++) { if (j < width && i < height) { farr[I] = array[i, j]; I++; } else { farr[I] = 0; I++; } } } int zxy = farr[(size*size)/2]; byte[] check = new byte[size * size]; check=counting(farr,size); int median = 0; int lennn; lennn = check.Length; int min = 99999; int maxi = -1; int a1, a2; min = check.Min(); maxi = check.Max(); median = check[lennn / 2]; a1 = median - min; a2 = maxi - median; int b1, b2; b1 = zxy - min; b2 = maxi - zxy; if (a1 > 0 && a2 > 0) { if (b1 > 0 && b2 > 0) { marr[n + 1, z + 1] = Convert.ToByte(zxy); z++; if (z + (size - 1) > (width + 1)) { n++; z = 0; } } else { marr[n + 1, z + 1] = Convert.ToByte(median); z++; if (z + (size - 1) > (width + 1)) { n++; z = 0; } } } else { size = size + 2; if (size <= msize) goto repeat; else { marr[n +1, z +1] = Convert.ToByte(median); z++; if (size > 3) size = 3; if (z + (size - 1) > (width + 1)) { n++; z = 0; } } } } return marr; } public static byte[] counting(byte[] array1D, int siz) { int max = -10000000; byte[] SortedArray = new byte[siz * siz]; max = array1D.Max(); byte[] Array2 = new byte[max + 1]; //for (int i = 0; i < Array2.Length; i++) // Array2[i] = 0; // create new array ( Array2) with zeros in every index . for (int i = 0; i < (siz*siz); i++) { Array2[array1D[i]] += 1; //take the element in the index(i) of(array1d) AS the index of (Array2) // and increment the element in this index by 1 . } for (int i = 1; i <= max; i++) { Array2[i] += Array2[i - 1]; // Count every element in (array1d) and put it in (Array2). } for (int i = (siz*siz) - 1; i >= 0; i--) { SortedArray[Array2[array1D[i]] - 1] = array1D[i]; // transfer the element in index (i) of (array1d) to (SortedArray Array2[array1D[i]]--; } return SortedArray; } } } Any help will be appreciated, thank you.