/// <summary> /// Scans a line on the image for line segments using a hysteresis connected component algorithm. /// </summary> /// <param name="Input">Input data.</param> /// <param name="AnalyzeMask">Mask for marking visited pixels.</param> /// <param name="Height">Data height.</param> /// <param name="Width">Data width.</param> /// <param name="Rho">Distance from origin to line.</param> /// <param name="Theta">Line angle.</param> /// <param name="HighTh">Upper hysteresis threshold.</param> /// <param name="LowTh">Lower hysteresis threshold.</param> /// <param name="MaxIgnore">Maximum interblob distance.</param> /// <param name="ScanWidth">Width of the scanned area.</param> /// <param name="OX">Image data origin X coordinate.</param> /// <param name="OY">Image data origin Y coordinate.</param> /// <returns>A list of line segment detections.</returns> internal static List <LineDetection> AnalyzeLine(double[,] Input, bool[,] AnalyzeMask, int Height, int Width, double Rho, double Theta, double HighTh, double LowTh, int MaxIgnore, int ScanWidth, int OX, int OY) { /* Unit vector in the direction of the line */ Vector LineVector = new Vector() { X = Cos(Theta), Y = Sin(Theta) }; /* Origin of the line */ Vector LineOrigin = new Vector() { X = -Rho *Sin(Theta), Y = Rho * Cos(Theta) }; /* Unit vector perpendicular to the line */ Vector LONormal = new Vector() { X = -Sin(Theta), Y = Cos(Theta) }; /* Compute the intersections with the bounding box */ Vector LeftIntersect; double LDist; if (!LineIntersection.IntersectLeft(LineOrigin, LineVector, Width, Height, out LeftIntersect, out LDist)) { return(null); } Vector RightIntersect; double RDist; if (!LineIntersection.IntersectRight(LineOrigin, LineVector, Width, Height, out RightIntersect, out RDist)) { return(null); } /* Sort the intersections */ double Start = Min(LDist, RDist); double End = Max(LDist, RDist); Vector StVec, EVec; if (Start == LDist && End == RDist) { StVec = LeftIntersect; EVec = RightIntersect; } else if (Start == RDist && End == LDist) { StVec = RightIntersect; EVec = LeftIntersect; } else { throw new ApplicationException("Geometry error."); } /* Scan line for blobs */ int k; int N = (int)(End - Start); Vector pt = StVec; List <Vector> LineIntervals = new List <Vector>(); List <DetectionBlob> Blobs = new List <DetectionBlob>(); for (k = 0; k < N; k++, pt.Increment(LineVector)) { int l; Vector vl = pt; for (l = -ScanWidth; l < ScanWidth; l++, vl.Increment(LONormal)) { int X = (int)Round(vl.X); int Y = (int)Round(vl.Y); if (X < 0 || X >= Width) { continue; } if (Y < 0 || Y >= Height) { continue; } if (AnalyzeMask[Y, X]) { continue; } double Val = Input[Y, X]; if (Val > HighTh) { DetectionBlob db = BitmapFill(Input, new IntPoint() { X = X, Y = Y }, AnalyzeMask, LowTh, Theta); LineIntervals.Add(new Vector() { X = db.LineStart, Y = db.LineEnd }); Blobs.Add(db); } } } /* Merge blobs into line segments */ List <DetectionSegment> FoundSegments = new List <DetectionSegment>(); DetectionSegment cseg = default(DetectionSegment); /* Current segment */ for (k = 0; k < LineIntervals.Count; k++) { if (cseg.Blobs != null) { /* If still within threshold of current segment */ if (Min(LineIntervals[k].X, LineIntervals[k].Y) < cseg.End + MaxIgnore) { cseg.Blobs.Add(Blobs[k]); cseg.End = Max(cseg.End, Max(LineIntervals[k].Y, LineIntervals[k].X)); continue; } else { FoundSegments.Add(cseg); cseg = default(DetectionSegment); } } /* Create new current segment */ cseg.Blobs = new List <DetectionBlob>(); cseg.Angle = Theta; cseg.Blobs.Add(Blobs[k]); cseg.Start = Min(LineIntervals[k].X, LineIntervals[k].Y); cseg.End = Max(LineIntervals[k].Y, LineIntervals[k].X); } if (cseg.Blobs != null) { FoundSegments.Add(cseg); } List <LineDetection> Detections = FoundSegments.Select((x) => MergeBlobs(x, Input, OX, OY)).ToList(); return(Detections); }
/// <summary> /// Gets the connected component starting from a point on an image. /// </summary> /// <param name="Input">Input Image.</param> /// <param name="StartPoint">Starting point.</param> /// <param name="Mask">Already processed components mask.</param> /// <param name="LowThreshold">Threshold for component discrimination.</param> /// <param name="Angle">Angle of the line; used for distance projections.</param> /// <returns>The connected component blob.</returns> static DetectionBlob BitmapFill(double[,] Input, IntPoint StartPoint, bool[,] Mask, double LowThreshold, double Angle) { Queue <IntPoint> PointQ = new Queue <IntPoint>(); PointQ.Enqueue(StartPoint); List <IntPoint> DiscoveredPoints = new List <IntPoint>(); double LineMin = double.MaxValue, LineMax = double.MinValue; while (PointQ.Count > 0) { IntPoint pt = PointQ.Dequeue(); if (pt.X < 0 || pt.X >= Mask.GetLength(1)) { continue; } if (pt.Y < 0 || pt.Y >= Mask.GetLength(0)) { continue; } if (Mask[pt.Y, pt.X]) { continue; } double Val = Input[pt.Y, pt.X]; double MinMax = pt.X * Cos(Angle) + pt.Y * Sin(Angle); if (MinMax < LineMin) { LineMin = MinMax; } if (MinMax > LineMax) { LineMax = MinMax; } if (Val > LowThreshold) { Mask[pt.Y, pt.X] = true; DiscoveredPoints.Add(pt); PointQ.Enqueue(new IntPoint() { X = pt.X - 1, Y = pt.Y }); PointQ.Enqueue(new IntPoint() { X = pt.X + 1, Y = pt.Y }); PointQ.Enqueue(new IntPoint() { X = pt.X, Y = pt.Y - 1 }); PointQ.Enqueue(new IntPoint() { X = pt.X, Y = pt.Y + 1 }); } } DetectionBlob db = new DetectionBlob() { Points = DiscoveredPoints, LineStart = LineMin, LineEnd = LineMax }; return(db); }