/// <summary> /// Creates a traffic sign match /// </summary> /// <param name="candidate"> candidate sign </param> /// <param name="knownSign"> known sign </param> /// <param name="matchScore"> match score </param> /// <param name="matches"> feature matches </param> /// <param name="homography"> homography matrix </param> public TrafficSignMatch(TrafficSign candidate, TrafficSign knownSign, int matchScore, VectorOfVectorOfDMatch matches, Mat homography) { Candidate = candidate; KnownSign = knownSign; MatchScore = matchScore; Matches = matches; Homography = homography; }
/// <summary> /// Detects the keypoints and computes the features for a sign /// </summary> /// <param name="sign"> Sign to update </param> private void UpdateDescriptors(TrafficSign sign) { Mat desc = new Mat(); VectorOfKeyPoint kp = new VectorOfKeyPoint(); detector.DetectAndCompute(sign.ImageGray, null, kp, desc, false); sign.Features = desc; sign.KeyPoints = kp; }
/// <summary> /// Converts contours to candidates and deny small or big contours /// </summary> /// <param name="contours"> contours of blobs </param> /// <param name="context"> original color image </param> /// <returns> Sign Candidates </returns> private List <TrafficSign> ContoursToCandidates(VectorOfVectorOfPoint contours, Image <Bgr, byte> context) { List <TrafficSign> goodCandidates = new List <TrafficSign>(); List <Rectangle> boxes = new List <Rectangle>(); // Iterate over contours for (int i = 0; i < contours.Size; i++) { Rectangle bb = CvInvoke.BoundingRectangle(contours[i]); // Deny small if (bb.Height < 20) { continue; } if (bb.Width < 20) { continue; } // Deny strange shape if ((double)bb.Width / bb.Height < 0.80) { continue; } // Merge overlapping BBs Rectangle rectToRemove = Rectangle.Empty; boxes.ForEach(other => { if (other.IntersectsWith(bb)) { bb = CvInvoke.cvMaxRect(bb, other); rectToRemove = other; } }); if (!rectToRemove.IsEmpty) { boxes.Remove(rectToRemove); } Rectangle paddedRoi = new Rectangle(bb.Location, bb.Size); paddedRoi.Inflate(CandidatePadding, CandidatePadding); boxes.Add(paddedRoi); } // Convert boxes to Candidate objects boxes.ForEach(paddedRoi => { TrafficSign newCandidate = new TrafficSign(context.Copy(paddedRoi), null); newCandidate.BoundingBoxInScene = paddedRoi; goodCandidates.Add(newCandidate); }); return(goodCandidates); }
/// <summary> /// Match this sign to an other sign /// </summary> /// <param name="otherSign"> other traffic sign </param> /// <returns> matched features as matches </returns> public VectorOfVectorOfDMatch MatchToOtherSign(TrafficSign otherSign) { if (!IsKnownSign) { throw new Exception("Only known signs support matching."); } if (otherSign.Features.Cols < 1) { throw new Exception("Other sign has no descriptors to match."); } if (Features.Cols < 1) { throw new Exception("Current sign has no features to match."); } VectorOfVectorOfDMatch matches = new VectorOfVectorOfDMatch(); matcher.KnnMatch(otherSign.features, matches, 2, null); return(matches); }
/// <summary> /// Reads known signs from folder /// </summary> public void ReadKnownSigns() { if (!Directory.Exists(KnownSignsPath)) { throw new Exception("Cannot open folder: " + KnownSignsPath); } KnownSigns = new List <TrafficSign>(); foreach (string file in Directory.EnumerateFiles(KnownSignsPath)) { try { TrafficSign sign = new TrafficSign(new Image <Bgr, byte>(file), Path.GetFileName(file)); UpdateDescriptors(sign); KnownSigns.Add(sign); } catch (Exception ex) { // Log problem Console.WriteLine(ex.Message); } } }
/// <summary> /// Computes a score for a match, higher is better /// </summary> /// <param name="homography"> homography matrix </param> /// <param name="known"> known sign </param> /// <param name="candidate"> candidate sign </param> /// <returns> score of the match </returns> public int ScoreMatch(Mat homography, TrafficSign known, TrafficSign candidate) { // Default score int score = -1; if (homography == null) { return(0); } // Create rectangle and transorm based on homography Rectangle rect = new Rectangle(Point.Empty, known.ImageGray.Size); PointF[] pts = new PointF[] { new PointF(rect.Left, rect.Top), new PointF(rect.Right, rect.Top), new PointF(rect.Right, rect.Bottom), new PointF(rect.Left, rect.Bottom) }; pts = CvInvoke.PerspectiveTransform(pts, homography); // Check transformed recatangle // Scene corners should form rectangular shape // Check relative positions left upper if ((pts[0].X > pts[1].X) || (pts[0].X > pts[2].X)) { return(0); } if ((pts[0].Y > pts[2].Y) || (pts[0].Y > pts[3].Y)) { return(0); } // Check relative positions right upper if ((pts[1].X < pts[0].X) || (pts[1].X < pts[3].X)) { return(0); } if ((pts[1].Y > pts[2].Y) || (pts[1].Y > pts[3].Y)) { return(0); } // Check relative positions right lower if ((pts[2].X < pts[0].X) || (pts[2].X < pts[3].X)) { return(0); } if ((pts[2].Y < pts[0].Y) || (pts[2].Y < pts[1].Y)) { return(0); } // Check relative positions left lower if ((pts[3].Y > pts[1].X) || (pts[3].X > pts[2].X)) { return(0); } if ((pts[3].Y < pts[0].Y) || (pts[3].Y < pts[1].Y)) { return(0); } score = 1; // Low score for strange shaped rectangles double dia1 = Math.Sqrt(Math.Pow(pts[2].X - pts[0].X, 2) + Math.Pow(pts[2].Y - pts[0].Y, 2)); double dia2 = Math.Sqrt(Math.Pow(pts[1].X - pts[3].X, 2) + Math.Pow(pts[3].Y - pts[1].Y, 2)); if (dia1 * dia2 < known.ImageGray.Size.Height / 5) { return(score); } double edgeRatioMax = 0.2; if (Math.Abs(1.0 - (dia1 / dia2)) > edgeRatioMax) { return(score); } score = 2; // Check based on template matching Image <Gray, byte> perspective = new Image <Gray, byte>(known.ImageGray.Size); Image <Gray, byte> templateRes = new Image <Gray, byte>(known.ImageGray.Size); double maxValue = 0; Point maxLoc = new Point(); double minValue = 0; Point minLoc = new Point(); CvInvoke.WarpPerspective(known.ImageGray, perspective, homography, known.ImageGray.Size, Emgu.CV.CvEnum.Inter.Linear); CvInvoke.MatchTemplate(candidate.ImageGray, perspective, templateRes, TemplateMatchingType.Ccoeff, null); CvInvoke.MinMaxLoc(templateRes, ref minValue, ref maxValue, ref minLoc, ref maxLoc); return(score + (int)maxValue); }