//----------------------------------------------------------------------- // // Copyright : Olivier AUGEREAU - 2011 // //----------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; using Emgu.CV; using Emgu.CV.Structure; namespace DocumentMatching { public class Ransac { /// /// RANSAC : recherche de la meilleur transformation parmis un ensemble de mise en correspondance /// /// Dictionnaire de mise en correspondance des points /// Condition d'arrêt : nombre maximal de tests /// Condition d'arrêt : si on trouve suffisament d'inliers on arrête la recherche /// public static Transformation applyRansac(Dictionary c, int NB_MAX_ITERATION = 200, int NB_SUFFISANT_INLIERS = 10000) { int nbInliers = 0; int nbTentative = 0; int maxInliers = 0; Transformation best = new Transformation(); //création d'un sous-dictionnaire contenant les match des inliers Dictionary matchs = new Dictionary(); int nbMatch = c.Count; // le nombre de combinaisons possible est : (matchedFeatures.Length * matchedFeatures.Length -1) / 2 int nbCombi = (nbMatch * nbMatch - 1) / 2; // si le nombre de combinaison est plus petit que la limite du nombre d'itération fixé on ne fera que nbCombi tests int nbMin = Math.Min(nbCombi, NB_MAX_ITERATION); //Liste stockant les tirages aléatoires List tirages = new List(); //si nbCombi n'est pas trop grand on enumère toute les possibilité puis on en tirera au hasard if (nbCombi < 200000) { for (int i = 0; i < nbMatch; i++) { for (int j = 0; j < nbMatch; j++) { if (i > j) { tirages.Add(new Point(i, j)); } } } } //si nbCombi est très suppérieur à 1000 on peut tirer des valeurs au hasard else { Random rand = new Random(); int rand1, rand2; while (tirages.Count < 1000) { rand1 = rand.Next(nbMatch); rand2 = rand.Next(nbMatch); Point randCoord = new Point(rand1, rand2); if (!tirages.Contains(randCoord)) tirages.Add(randCoord); } } //on cherche la meilleurs trasformation (celle qui a le plus d'inliers), on arrête de chercher après nbMin tentatives while (nbTentative < nbMin) { nbInliers = 0; matchs.Clear(); //on prend deux points au hasard ainsi que leurs correspondances Random rand = new Random(); int rando = rand.Next(tirages.Count); int rand1 = tirages[rando].X; int rand2 = tirages[rando].Y; tirages.RemoveAt(rando); nbTentative++; Point p1 = c.Keys.ElementAt(rand1); Point p2 = c.Keys.ElementAt(rand2); LineSegment2DF ligne_requete = new LineSegment2DF(p1, p2); LineSegment2DF ligne_model = new LineSegment2DF(c[p1], c[p2]); //test optionnel : si la longeur des vecteur est trop petite, cela risque de donner un scale et une rot faussés if (ligne_model.Length < 5 || ligne_requete.Length < 5) { nbTentative++; continue; } matchs.Add(p1, c[p1]); matchs.Add(p2, c[p2]); //calculer translation, rotation et scale float translationX = ligne_requete.P1.X - ligne_model.P1.X; float translationY = ligne_requete.P1.Y - ligne_model.P1.Y; double scale = ligne_requete.Length / ligne_model.Length; //test optionnel : si le scale est trop petit ou trop grand je saute la possibilité car dans mon cas ça ne peut pas arriver if (scale > 4 || scale < 0.2) { nbTentative++; continue; } double rotation = ligne_requete.GetExteriorAngleDegree(ligne_model); //création de la matrice de transformation Matrix matRot = new Matrix(2, 3); CvInvoke.cv2DRotationMatrix(ligne_model.P1, rotation, scale, matRot.Ptr); //pour chaque mise en correspondance for (int i = 0; i < nbMatch; i++) { if (i == rand1 || i == rand2) { continue; } // test optionnel : si le point que l'on souhaite valider est trop proche d'un point déjà validé du modèle, il n'apporte pas d'information supplémentaire on le saute // idem pour sa mise en correspondance bool skip = false; foreach (Point match in matchs.Keys) { LineSegment2DF distValid1 = new LineSegment2DF(match, c.Keys.ElementAt(i)); LineSegment2DF distValid2 = new LineSegment2DF(c[match], c[c.Keys.ElementAt(i)]); //distance min entre 2 points : 5 pixels (sift prend parfois des points très proches) if (distValid1.Length < 5 || distValid2.Length < 5) { skip = true; break; } } if (skip) continue; //on transforme le point du modèle Point pi = c.Keys.ElementAt(i); Matrix point1 = new Matrix(3, 1); point1[0, 0] = pi.X; point1[1, 0] = pi.Y; point1[2, 0] = 1; Matrix point2 = matRot * point1; point2[0, 0] += translationX; point2[1, 0] += translationY; Matrix point3 = new Matrix(2, 1); point3[0, 0] = c[pi].X; point3[1, 0] = c[pi].Y; //on calcul la distance entre le point transformé et sa correspondance double dist = Math.Sqrt((point2[0, 0] - point3[0, 0]) * (point2[0, 0] - point3[0, 0]) + (point2[1, 0] - point3[1, 0]) * (point2[1, 0] - point3[1, 0])); //s'il tombe suffisament près (ici à moins de 10 pixels) du point attendu, c'est un inliers //on peut faire varier ce seuil suivant la précision souhaité if (dist < 10) { nbInliers++; matchs.Add(pi, c[pi]); //on stock le max (correspond à la meilleur transformation) if (nbInliers > maxInliers) { maxInliers = nbInliers; best.matRotScale = matRot; best.nbInliers = nbInliers; best.rotation = rotation; best.scale = scale; best.translationX = translationX; best.translationY = translationY; Dictionary matchsCopy = new Dictionary(); foreach (Point p in matchs.Keys) { matchsCopy.Add(p, matchs[p]); } best.matchs = matchsCopy; } } //condition d'arrêt supplémentaire : si le nombre d'inliers est supérieur à un seuil fixé if (nbInliers > NB_SUFFISANT_INLIERS) { break; } } nbTentative++; } tirages.Clear(); return best; } } }