Has anyone done any linear regression algorithms in VB.NET?
I have a laser sensor which streams distance measurements via TCP. The minimum dimension is 700mm and the maximum is 3000mm. It takes measurements every 0.25degrees, from 55 degrees to 125 degrees - a total of 70 degrees => 280 measurements per scan. Sometimes the laser will not take a true reading, and will return 0mm.
When the laser has performed its scan, I need to evaluate all the measurements to find two "edges" which are perpendicular to each other. Once I have found these lines and proved that they are perpendicular, I can calculate the X and Y offset, and rotation, of the two edges. This information will then be passed by serial to a robot to perform a pick-and-place operation.
The point where the two "edges" meet will always (in theory) be the closest point to the laser centre.
Here's a totally stripped down version - the sample set of measurements is in the next post. A screenshot (with lines removed) is also attached.
The problem I have is that I am not sure how to do the linear regression lines without including some of the obviously invalid points. This is where I could do with some help (which would be GREATLY appreciated )
Code:
Option Explicit On
Option Strict On
Imports System.Drawing.Drawing2D
Public Class Form1
Structure MEASUREDVALUE
Public Distance As Integer
Public X As Integer
Public Y As Integer
End Structure
Public MeasuredPoints(280) As MEASUREDVALUE
Private iSmallestMeasurementFromLeft As Integer = Integer.MaxValue
Private iSmallestMeasurementScanNumberLeft As Integer = 0
Private iSmallestMeasurementFromRight As Integer = Integer.MaxValue
Private iSmallestMeasurementScanNumberRight As Integer = 0
Public Sub New()
' This call is required by the Windows Form Designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
Me.SetStyle(ControlStyles.UserPaint, True)
Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
Me.WindowState = FormWindowState.Maximized
For i As Integer = 1 To 280
MeasuredPoints(i).Distance = 0
MeasuredPoints(i).X = 0
MeasuredPoints(i).Y = 0
Next
SetupSampleData()
End Sub
Private Sub SetupSampleData()
'see next post
End Sub
Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
With e.Graphics
Dim mYFlip As Matrix = New Matrix(1, 0, 0, -1, 0, 0)
.Transform = mYFlip
.TranslateTransform(CSng(Me.ClientRectangle.Width / 2), 0 - (Me.ClientRectangle.Height - 100))
.ScaleTransform(0.2, 0.2)
.DrawEllipse(Pens.Black, -25, -25, 50, 50)
.DrawLine(Pens.Black, CSng(0 + (700 * Math.Cos(DTR(90 + 35)))), CSng(0 + (700 * Math.Sin(DTR(90 + 35)))), CSng(0 + (3000 * Math.Cos(DTR(90 + 35)))), CSng(0 + (3000 * Math.Sin(DTR(90 + 35)))))
.DrawLine(Pens.Black, CSng(0 - (700 * Math.Cos(DTR(90 + 35)))), CSng(0 + (700 * Math.Sin(DTR(90 + 35)))), CSng(0 - (3000 * Math.Cos(DTR(90 + 35)))), CSng(0 + (3000 * Math.Sin(DTR(90 + 35)))))
.DrawArc(Pens.Black, 0 - 3000S, 0 - 3000S, 3000S * 2, 3000S * 2, 90 - 35, 70)
.DrawArc(Pens.Black, 0 - 700S, 0 - 700S, 700S * 2, 700S * 2, 90 - 35, 70)
Dim Angle As Single = 0
For i As Integer = 1 To UBound(MeasuredPoints)
Angle = CSng((140 - i) * 0.25)
If MeasuredPoints(i).Distance >= 700 AndAlso MeasuredPoints(i).Distance <= 3000 Then
.DrawLine(Pens.Black, CSng(0 - (MeasuredPoints(i).Distance * Math.Cos(DTR(90 - Angle)))), CSng(0 + (MeasuredPoints(i).Distance * Math.Sin(DTR(90 - Angle)))), CSng(0 - (MeasuredPoints(i).Distance * Math.Cos(DTR(90 - Angle)))), CSng(0 + (MeasuredPoints(i).Distance * Math.Sin(DTR(90 - Angle)))) + 1)
End If
'if you want to identify a measured point, add a NumericUpDown control
'and set its minimum value to 1 and its maximum value to 280
If i = NumericUpDown1.Value Then
If MeasuredPoints(i).Distance >= 700 AndAlso MeasuredPoints(i).Distance <= 3000 Then
.DrawLine(Pens.Red, 0, 0, CSng(0 - (MeasuredPoints(i).Distance * Math.Cos(DTR(90 - Angle)))), CSng(0 + (MeasuredPoints(i).Distance * Math.Sin(DTR(90 - Angle)))) + 1)
Else
.DrawLine(Pens.Red, 0, 0, CSng(0 - (3000 * Math.Cos(DTR(90 - Angle)))), CSng(0 + (3000 * Math.Sin(DTR(90 - Angle)))) + 1)
End If
End If
Next
'find the smallest measurement working from left to right
iSmallestMeasurementFromLeft = Integer.MaxValue
iSmallestMeasurementScanNumberLeft = 0
For i As Integer = 1 To 280
If MeasuredPoints(i).Distance >= 700 AndAlso MeasuredPoints(i).Distance <= 3000 AndAlso MeasuredPoints(i).Distance < iSmallestMeasurementFromLeft Then
iSmallestMeasurementFromLeft = MeasuredPoints(i).Distance
iSmallestMeasurementScanNumberLeft = i
End If
Next
Angle = CSng((140 - iSmallestMeasurementScanNumberLeft) * 0.25)
.DrawLine(Pens.Green, 0, 0, CSng(0 - (MeasuredPoints(iSmallestMeasurementScanNumberLeft).Distance * Math.Cos(DTR(90 - Angle)))), CSng(0 + (MeasuredPoints(iSmallestMeasurementScanNumberLeft).Distance * Math.Sin(DTR(90 - Angle)))) + 1)
'TODO: calculate the linear regression line to the left
'find the smallest measurement working from left to right
iSmallestMeasurementFromRight = Integer.MaxValue
iSmallestMeasurementScanNumberRight = 0
For i As Integer = 280 To 1 Step -1
If MeasuredPoints(i).Distance >= 700 AndAlso MeasuredPoints(i).Distance <= 3000 AndAlso MeasuredPoints(i).Distance < iSmallestMeasurementFromRight Then
iSmallestMeasurementFromRight = MeasuredPoints(i).Distance
iSmallestMeasurementScanNumberRight = i
End If
Next
Angle = CSng((140 - iSmallestMeasurementScanNumberRight) * 0.25)
.DrawLine(Pens.Green, 0, 0, CSng(0 - (MeasuredPoints(iSmallestMeasurementScanNumberRight).Distance * Math.Cos(DTR(90 - Angle)))), CSng(0 + (MeasuredPoints(iSmallestMeasurementScanNumberRight).Distance * Math.Sin(DTR(90 - Angle)))) + 1)
'TODO: calculate the linear regression line to the right
End With
End Sub
Private Function DTR(ByVal whatDegrees As Double) As Double
DTR = (whatDegrees * Math.PI) / 180
End Function
Private Function RTD(ByVal whatRadians As Double) As Double
RTD = (whatRadians * 180) / Math.PI
End Function
Private Sub NumericUpDown1_ValueChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles NumericUpDown1.ValueChanged
Me.Invalidate()
End Sub
End Class
Since this is apparently resolved, I'm curious how you picked out the two linear sections of the graph above. It seems like that would be difficult, though I guess with the condition you gave, that the meeting point would be the closest one to the sensor, you could start do linear regressions starting at that point and including more and more points to the left of that closest point and perform regressions until you add an outlier point (perhaps one that increases your R^2 error), at which time you know you've added all of the data points connected to the left surface. You'd do the same for the right side afterwards.
The time you enjoy wasting is not wasted time. Bertrand Russell
I didnt actually start from the nearest point - I found a better way. Here's how I achieved it.
Starting from the left point, I kept adding points to a list and performing a linear regression on the list, until the sigma value went into (and then out of) acceptable limits. I then took the last point away from the list, so I had a list of points that made one acceptable linear regression line.
I repeated this until all points had been evaluated. This gave me one, two or several acceptable regression lines.
Lines with a small number of points were then eliminated as 'random lines' (say, for example, the grouping on the left). As the lines are meant to be perpendicular, I then evaluated the slopes of all the lines to find the best matched pair.
It's working fine, and tracking the edges perfectly