5858# End Region
5959
6060Imports System.Drawing
61+ Imports System.Runtime.CompilerServices
6162Imports Microsoft.VisualBasic.ApplicationServices.Terminal.ProgressBar
6263Imports Microsoft.VisualBasic.ApplicationServices.Terminal.ProgressBar.Tqdm
6364Imports Microsoft.VisualBasic.ComponentModel.Algorithm.base
65+ Imports Microsoft.VisualBasic.Imaging
6466Imports Microsoft.VisualBasic.Imaging.Math2D
6567Imports Microsoft.VisualBasic.Language
6668Imports Microsoft.VisualBasic.Linq
@@ -81,6 +83,41 @@ Public Module RANSACPointAlignment
8183 Return assignMap
8284 End Function
8385
86+ ''' <summary>
87+ ''' Aligns a source polygon to a target polygon using RANSAC.
88+ ''' </summary>
89+ ''' <param name="sourcePoly">The polygon to be transformed.</param>
90+ ''' <param name="targetPoly">The polygon to align to.</param>
91+ ''' <param name="iterations">The number of RANSAC iterations.</param>
92+ ''' <param name="distanceThreshold">The distance threshold to consider a point an inlier.</param>
93+ ''' <returns>The best-fit Transform object.</returns>
94+ Public Function AlignPolygons( Of T As Layout2D)(sourcePoly As T(),
95+ targetPoly As T(),
96+ properties As Func( Of T, Double ()),
97+ Optional iterations As Integer = 1000 ,
98+ Optional distanceThreshold As Double = 0.1 ) As AffineTransform
99+ ' Pre-check: need at least 3 points
100+ If sourcePoly.Length < 2 OrElse targetPoly.Length < 2 Then
101+ Return New AffineTransform
102+ End If
103+
104+ ' 1. Compute descriptors for all points in both polygons
105+ Dim sourceDescriptors = PointWithDescriptor.ComputeDescriptors(sourcePoly, properties).ToArray
106+ Dim targetDescriptors = PointWithDescriptor.ComputeDescriptors(targetPoly, properties).ToArray
107+
108+ ' 2. Generate candidate matches based on descriptor similarity
109+ Dim candidateMatches As (source As PointF, target As PointF)() = PointWithDescriptor _
110+ .GenerateCandidateMatches(sourceDescriptors, targetDescriptors) _
111+ .ToArray
112+
113+ If candidateMatches.Length < 3 Then
114+ ' Not enough candidate matches to proceed
115+ Return New AffineTransform
116+ Else
117+ Return candidateMatches.MakeAlignment(iterations, distanceThreshold)
118+ End If
119+ End Function
120+
84121 ''' <summary>
85122 ''' Aligns a source polygon to a target polygon using RANSAC.
86123 ''' </summary>
@@ -104,13 +141,26 @@ Public Module RANSACPointAlignment
104141 Dim targetDescriptors = PointWithDescriptor.ComputeDescriptors(targetPoly).ToArray
105142
106143 ' 2. Generate candidate matches based on descriptor similarity
107- Dim candidateMatches = GenerateCandidateMatches(sourceDescriptors, targetDescriptors)
144+ Dim candidateMatches As (source As PointF, target As PointF)() = PointWithDescriptor _
145+ .GenerateCandidateMatches(sourceDescriptors, targetDescriptors) _
146+ .ToArray
108147
109- If candidateMatches.Count < 3 Then
148+ If candidateMatches.Length < 3 Then
110149 ' Not enough candidate matches to proceed
111150 Return New AffineTransform
151+ Else
152+ Return candidateMatches.MakeAlignment(iterations, distanceThreshold)
112153 End If
154+ End Function
113155
156+ ''' <summary>
157+ ''' Aligns a source polygon to a target polygon using RANSAC.
158+ ''' </summary>
159+ ''' <param name="iterations">The number of RANSAC iterations.</param>
160+ ''' <param name="distanceThreshold">The distance threshold to consider a point an inlier.</param>
161+ ''' <returns>The best-fit Transform object.</returns>
162+ <Extension>
163+ Private Function MakeAlignment(candidateMatches As (source As PointF, target As PointF)(), iterations As Integer , distanceThreshold As Double ) As AffineTransform
114164 Dim bestTransform As New AffineTransform
115165 Dim maxInliers As Integer = 0
116166 Dim thresholdSq = distanceThreshold * distanceThreshold
@@ -119,24 +169,23 @@ Public Module RANSACPointAlignment
119169
120170 ' RANSAC 迭代
121171 For Each iter As Integer In TqdmWrapper.Range( 0 , iterations, bar:=bar, wrap_console:=App.EnableTqdm)
122- ' Randomly select 3 different matches from the candidate list
123- If candidateMatches.Count < 3 Then Exit For
124-
172+ ' make sampling of 3 data points from the generated candidate matches
125173 Dim matches = candidateMatches.OrderBy( Function (x) rand.NextDouble()).Take( 3 ).ToArray()
126174
127175 Dim p1 = matches( 0 ).source, q1 = matches( 0 ).target
128176 Dim p2 = matches( 1 ).source, q2 = matches( 1 ).target
129177 Dim p3 = matches( 2 ).source, q3 = matches( 2 ).target
130178
131179 ' Compute a transform hypothesis from these 3 matches
132- Dim hypothesisTransform = ComputeAffineFrom3Pairs(p1, p2, p3, q1, q2, q3)
133-
180+ Dim hypothesisTransform As AffineTransform = ComputeAffineFrom3Pairs(p1, p2, p3, q1, q2, q3)
134181 ' Count inliers for this hypothesis across ALL candidate matches
135182 Dim currentInliers As Integer = 0
136- For Each pair In candidateMatches
183+
184+ For Each pair As (source As PointF, target As PointF) In candidateMatches
137185 Dim transformedSourcePt = hypothesisTransform.ApplyToPoint(pair.source)
138186 Dim dx = transformedSourcePt.X - pair.target.X
139187 Dim dy = transformedSourcePt.Y - pair.target.Y
188+
140189 If dx * dx + dy * dy <= thresholdSq Then
141190 currentInliers += 1
142191 End If
@@ -159,45 +208,6 @@ Public Module RANSACPointAlignment
159208 Return bestTransform
160209 End Function
161210
162- ''' <summary>
163- ''' Generates a list of candidate matches by finding the nearest neighbor in descriptor space.
164- ''' </summary>
165- Private Function GenerateCandidateMatches( ByRef sourceDesc As PointWithDescriptor(), ByRef targetDesc As PointWithDescriptor()) As List( Of (source As PointF, target As PointF))
166- Dim matches As New List( Of (source As PointF, target As PointF))()
167-
168- Call $"Generates a list of candidate matches by finding the nearest neighbor in descriptor space." .debug
169- Call $"matrix size: {sourceDesc.Length}x{targetDesc.Length}" .info
170-
171- For Each sPt As PointWithDescriptor In Tqdm.Wrap(sourceDesc, wrap_console:=App.EnableTqdm)
172- Dim minDist As Double = Double .PositiveInfinity
173- Dim bestMatch As PointWithDescriptor
174-
175- For Each tPt As PointWithDescriptor In targetDesc
176- ' Simple Euclidean distance in descriptor space (r, theta)
177- ' We might want to weight angle more than distance, but this is a start.
178- Dim dr = sPt.Descriptor.r - tPt.Descriptor.r
179- Dim dtheta = sPt.Descriptor.theta - tPt.Descriptor.theta
180- ' Normalize angle difference
181- While dtheta > std.PI : dtheta -= 2 * std.PI : End While
182- While dtheta < -std.PI : dtheta += 2 * std.PI : End While
183-
184- Dim distSq = dr * dr + dtheta * dtheta
185- If distSq < minDist Then
186- minDist = distSq
187- bestMatch = tPt
188- End If
189- Next
190-
191- If minDist <> Double .PositiveInfinity Then
192- matches.Add((sPt.Pt, bestMatch.Pt))
193- End If
194- Next
195-
196- Call $"find {matches.Count} candidate matches!" .debug
197-
198- Return matches
199- End Function
200-
201211 ''' <summary>
202212 ''' Computes an affine transform from exactly three point pairs.
203213 ''' </summary>
@@ -235,12 +245,12 @@ Public Module RANSACPointAlignment
235245 ''' <summary>
236246 ''' Refines the transformation using all inliers with a least-squares fit for an affine transform.
237247 ''' </summary>
238- Private Function RefineTransformWithLeastSquares(candidateMatches As List( Of ( source As PointF, target As PointF)), initialTransform As AffineTransform, threshold As Double ) As AffineTransform
248+ Private Function RefineTransformWithLeastSquares(candidateMatches As ( source As PointF, target As PointF)( ), initialTransform As AffineTransform, threshold As Double ) As AffineTransform
239249 Dim inlierPairs As New List( Of (source As PointF, target As PointF))
240250 Dim thresholdSq = threshold * threshold
241251 Dim errors As New List( Of Double )
242252
243- For Each pair In candidateMatches
253+ For Each pair As (source As PointF, target As PointF) In candidateMatches
244254 Dim transformedSourcePt = initialTransform.ApplyToPoint(pair.source)
245255 Dim dx = transformedSourcePt.X - pair.target.X
246256 Dim dy = transformedSourcePt.Y - pair.target.Y
0 commit comments