Skip to content

Commit 180bdfd

Browse files
committed
Merge branch 'master' of https://github.com/xieguigang/sciBASIC
2 parents c3542a3 + 18c07ec commit 180bdfd

File tree

8 files changed

+334
-164
lines changed

8 files changed

+334
-164
lines changed

Data_science/Mathematica/SignalProcessing/MachineVision/RANSAC/PointWithDescriptor.vb

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
Imports System.Drawing
2+
Imports Microsoft.VisualBasic.ApplicationServices.Terminal.ProgressBar
3+
Imports Microsoft.VisualBasic.Imaging
24
Imports Microsoft.VisualBasic.Imaging.Math2D
5+
Imports Microsoft.VisualBasic.Linq
6+
Imports Microsoft.VisualBasic.Math.Correlations
7+
Imports Microsoft.VisualBasic.Serialization.JSON
38
Imports std = System.Math
49

510
''' <summary>
@@ -8,11 +13,63 @@ Imports std = System.Math
813
Public Structure PointWithDescriptor
914

1015
Public Pt As PointF
16+
1117
''' <summary>
1218
''' (distance to centroid, angle)
1319
''' </summary>
1420
Public Descriptor As (r As Double, theta As Double)
1521

22+
''' <summary>
23+
''' other property that used for weight score which is generated from the raw data
24+
''' </summary>
25+
Public [properties] As Double()
26+
27+
Public Overrides Function ToString() As String
28+
Return $"({Pt.X},{Pt.Y}) radius:{Descriptor.r}, theta:{Descriptor.theta}, properties:{properties.GetJson}"
29+
End Function
30+
31+
''' <summary>
32+
''' Generates a list of candidate matches by finding the nearest neighbor in descriptor space.
33+
''' </summary>
34+
Public Shared Function GenerateCandidateMatches(ByRef sourceDesc As PointWithDescriptor(), ByRef targetDesc As PointWithDescriptor()) As List(Of (source As PointF, target As PointF))
35+
Dim matches As New List(Of (source As PointF, target As PointF))()
36+
37+
Call $"Generates a list of candidate matches by finding the nearest neighbor in descriptor space.".debug
38+
Call $"matrix size: [{sourceDesc.Length} x {targetDesc.Length}]".info
39+
40+
For Each sPt As PointWithDescriptor In Tqdm.Wrap(sourceDesc, wrap_console:=App.EnableTqdm)
41+
Dim minDist As Double = Double.PositiveInfinity
42+
Dim bestMatch As PointWithDescriptor
43+
44+
For Each tPt As PointWithDescriptor In targetDesc
45+
' Simple Euclidean distance in descriptor space (r, theta)
46+
' We might want to weight angle more than distance, but this is a start.
47+
Dim dr = sPt.Descriptor.r - tPt.Descriptor.r
48+
Dim dtheta = sPt.Descriptor.theta - tPt.Descriptor.theta
49+
Dim pd As Double = If(sPt.properties.IsNullOrEmpty OrElse tPt.properties.IsNullOrEmpty, 0, sPt.properties.SquareDistance(tPt.properties))
50+
51+
' Normalize angle difference
52+
While dtheta > std.PI : dtheta -= 2 * std.PI : End While
53+
While dtheta < -std.PI : dtheta += 2 * std.PI : End While
54+
55+
Dim distSq = dr * dr + dtheta * dtheta + pd
56+
57+
If distSq < minDist Then
58+
minDist = distSq
59+
bestMatch = tPt
60+
End If
61+
Next
62+
63+
If minDist <> Double.PositiveInfinity Then
64+
matches.Add((sPt.Pt, bestMatch.Pt))
65+
End If
66+
Next
67+
68+
Call $"find {matches.Count} candidate matches!".debug
69+
70+
Return matches
71+
End Function
72+
1673
''' <summary>
1774
''' Computes a simple (distance, angle) descriptor for each point relative to the polygon's centroid.
1875
''' </summary>
@@ -39,4 +96,31 @@ Public Structure PointWithDescriptor
3996
}
4097
Next
4198
End Function
99+
100+
Public Shared Iterator Function ComputeDescriptors(Of T As Layout2D)(shape As IEnumerable(Of T), getMetadata As Func(Of T, Double())) As IEnumerable(Of PointWithDescriptor)
101+
Dim pool As T() = shape.SafeQuery.ToArray
102+
103+
If pool.Length = 0 Then
104+
Return
105+
End If
106+
107+
Dim poly As New Polygon2D(pool.X, pool.Y)
108+
' Calculate centroid (using only valid points)
109+
Dim centroid As PointF = poly.centroid
110+
111+
' Compute descriptor for each point
112+
For i As Integer = 0 To poly.length - 1
113+
Dim pt As PointF = poly(i)
114+
Dim dx = pt.X - centroid.X
115+
Dim dy = pt.Y - centroid.Y
116+
Dim r = std.Sqrt(dx * dx + dy * dy)
117+
Dim theta = std.Atan2(dy, dx)
118+
119+
Yield New PointWithDescriptor With {
120+
.Pt = pt,
121+
.Descriptor = (r, theta),
122+
.properties = getMetadata(pool(i))
123+
}
124+
Next
125+
End Function
42126
End Structure

Data_science/Mathematica/SignalProcessing/MachineVision/RANSAC/RANSACPointAlignment.vb

Lines changed: 59 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,11 @@
5858
#End Region
5959

6060
Imports System.Drawing
61+
Imports System.Runtime.CompilerServices
6162
Imports Microsoft.VisualBasic.ApplicationServices.Terminal.ProgressBar
6263
Imports Microsoft.VisualBasic.ApplicationServices.Terminal.ProgressBar.Tqdm
6364
Imports Microsoft.VisualBasic.ComponentModel.Algorithm.base
65+
Imports Microsoft.VisualBasic.Imaging
6466
Imports Microsoft.VisualBasic.Imaging.Math2D
6567
Imports Microsoft.VisualBasic.Language
6668
Imports 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

Comments
 (0)