staffTecnologia

Medidor Laser basado en WebCam

Nota Importante

Este es un proyecto basado en un proyecto basado en otro proyecto y les dejo tal cual fue operativo y funcional en Abril del 2014. Solo que lo transcribí en Español.

 

Recuerda que todo lo que está publicado lleva propositos educativos y cada publicación si la combinas con otros proyectos obtendrás el desarrollo de productos mejores; Aqui y en todo mi sitio web muestro algunos principos y detallitos que pueden hacer las cosas mucho más fácil.

Introduction

Hay muchos componentes de búsqueda disponibles en el mercado, incluidos telémetros ultrasónicos, infrarrojos e incluso láser. Todos estos dispositivos funcionan bien, pero en el campo de la robótica aérea, el peso es una preocupación primordial. Es deseable obtener la mayor funcionalidad posible de cada componente que se agrega a una estructura de avión. Por ejemplo, un helicóptero robótico en miniatura puede transportar unos 100 gramos de carga útil. Es posible realizar tareas de visión artificial, como la identificación y evitación de obstáculos, mediante el uso de una cámara web (o una mini cámara inalámbrica conectada a una computadora mediante un adaptador USB). Mejor aún, dos cámaras web pueden proporcionar visión artificial estéreo, mejorando así la evitación de obstáculos porque se puede determinar la profundidad. El inconveniente de esto, por supuesto, es el peso añadido de una segunda cámara. Esta publicación describe cómo se puede configurar un mini puntero láser junto con una sola cámara para proporcionar visión monomáquina con información de alcance.

Este proyecto se basa en gran medida en un tutorial encontrado. AQUI

Teoría de Operación

El siguiente diagrama muestra cómo al proyectar un punto láser sobre un objetivo que se encuentra en el campo de visión de una cámara, se puede calcular la distancia a ese objetivo. Las matemáticas son muy simples, por lo que esta técnica funciona muy bien para aplicaciones de visión artificial que necesitan ejecutarse rápidamente.

Pues así es como funciona. Se proyecta un rayo láser sobre un objeto en el campo de visión de una cámara. Lo ideal es que este rayo láser esté paralelo al eje óptico de la cámara. La cámara captura el punto del láser junto con el resto de la escena. Se ejecuta un algoritmo simple sobre la imagen buscando los píxeles más brillantes. Suponiendo que el láser es el área más brillante de la escena (lo que parece ser cierto para el puntero láser de mi tienda de un dólar en interiores), se conoce la posición de los puntos en el marco de la imagen. Luego necesitamos calcular el alcance del objeto en función de dónde cae este punto láser a lo largo del eje y de la imagen. Cuanto más cerca del centro de la imagen, más lejos estará el objeto.

Como podemos ver en el diagrama anterior en esta sección, la distancia (D) se puede calcular:

Por supuesto, para resolver esta ecuación, necesitas saber h, que es una constante fijada como la distancia entre el puntero láser y la cámara, y theta. Theta se calcula:

Con las dos ecuaciones anteriores, obtenemos:

Ok, entonces el número de píxeles del centro del plano focal que aparece el punto láser se puede contar desde la imagen. ¿Qué pasa con los otros parámetros en esta ecuación? Necesitamos realizar una calibración para derivarlos.

Para calibrar el sistema, recopilaremos una serie de medidas en las que conozco el rango del objetivo, así como la cantidad de píxeles, el punto es del centro de la imagen cada vez. Estos datos están a continuación:

Datos de Calibración
pixels desde el centro actual D (cm)
103 29
81 45
65 58
55 71
49 90
45 109
41 127
39 159
37 189
35 218

Usando la siguiente ecuación, podemos calcular el ángulo real en función del valor de H y la distancia real para cada punto de datos.

Ahora que tenemos un theta_Actual para cada valor, podemos encontrar una relación que nos permite calcular Theta a partir del número de píxeles desde el centro de imágenes. Usé una relación lineal (por lo tanto, se necesita una ganancia y un desplazamiento). Esto parece funcionar bien a pesar de que no tiene en cuenta el hecho de que el plano focal es un plano en lugar de curvado en un radio constante alrededor del centro de la lente.

De loss datos de calibración, se obtuvo:

Offset (ro) = -0.056514344 radians

Gain (rpc) = 0.0024259348 radians/pixel

Usando:

Resolviendo las distancias calculadas, así como el error desde la distancia real desde los datos de calibración:

Datos Actuales y Rango Calculados
pixels desde el centro calc D (cm) actual D (cm) % error
103 29.84 29 2.88
81 41.46 45 -7.87
65 57.55 58 -0.78
55 75.81 71 6.77
49 93.57 90 3.96
45 110.85 109 1.70
41 135.94 127 7.04
39 153.27 159 -3.60
37 175.66 189 -7.06
35 205.70 218 -5.64

Componentes

No hay muchas piezas en mi buscador de rango de muestra. Utilicé un pedazo de cartón para sostener un puntero láser en una cámara web para que el puntero láser apunte en una dirección que sea paralela a la de la cámara. Las piezas que se ven a continuación se colocan en una cuadrícula de una pulgada como referencia.

El Buscador de Rango ensamblado luce así:

Software

He escrito software de dos maneras, uno que usa Visual C ++ y el otro que usa Visual Basic. Probablemente encontrará que la versión Visual Basic del software es mucho más fácil de seguir que el código VC ++, pero con todo, hay una compensación. El código VC ++ se puede juntar de forma gratuita (suponiendo que tenga Visual Studio), mientras que el código VB requiere la compra de un paquete de software de terceros (también además de Visual Studio).

Visual Basic

El código Visual Basic que he escrito está disponible como un paquete llamado VB_LASER_RANGER.ZIP en la parte inferior de esta página.

Para que el codigo funcione necesitamos el Active X Video OCX instalado en la PC.

El código y las funciones que se encuentran en el formulario principal estan a continuación:


Private Sub exit_Click()
' Solo si esta corriendo...
If (Timer1.Enabled) Then

Timer1.Enabled = False ‘Detener Timer
VideoOCX.Stop
VideoOCX.Close

End If

End
End Sub

Private Sub Start_Click() ‘Init VideoOCX Control, asigna memoria y comenzar a agarrar (grabbing, tomar video)

If (Not Timer1.Enabled) Then
Start.Caption = “Stop”

‘ Deshabilita mensajes de error internos en VideoOCX
VideoOCX.SetErrorMessages False

‘ Init control
If (Not VideoOCX.Init) Then
‘ Init falló. Mostrar mensaje de error y finalizar sub
MsgBox VideoOCX.GetLastErrorString, vbOKOnly, “VideoOCX Error”
End
Else
‘ Asigna memoria para el manejo de imagen global
capture_image = VideoOCX.GetColorImageHandle
‘ result_image = VideoOCX_Processed.GetColorImageHandle

Timer1.Enabled = True ‘Inicia el Timer de Captura

‘ Start Capture mode
If (Not VideoOCX.Start) Then
‘ El inicio falló. Mostrar mensaje de error y finalizar sub
MsgBox VideoOCX.GetLastErrorString, vbOKOnly, “VideoOCX Error”
End
End If
End If
Else
Start.Caption = “Start”
Timer1.Enabled = False ‘detiene el Timer
VideoOCX.Stop
VideoOCX.Close
End If

End Sub

Private Sub Timer1_Timer()
‘ Timer for capturing – handles videoOCXTools
Dim matrix As Variant
Dim height, width As Integer
Dim r, c As Integer
Dim max_r, max_c As Integer
Dim max_red As Integer
Dim gain, offset As Variant
Dim h_cm As Variant
Dim range As Integer
Dim pixels_from_center As Integer

‘ Parámetro calibrado para la conversión de píxeles a distancia
gain = 0.0024259348
offset = -0.056514344
h_cm = 5.842

max_red = 0

‘ Captura una imagen
If (VideoOCX.Capture(capture_image)) Then

‘ VideoOCX.Show capture_image

‘ Inicialización de matriz de transformación
matrix = VideoOCX.GetMatrix(capture_image)

height = VideoOCX.GetHeight
width = VideoOCX.GetWidth

‘ Código de procesamiento de imágenes

‘ El punto láser no debe verse sobre la fila central (con una pequeña almohadilla)
For r = height / 2 – 20 To height – 1

‘ Nuestra configuración física está aproximadamente calibrada para hacer el láser
‘ Dot en las columnas intermedias … no te molestes en ver demasiado lejos
For c = width / 2 – 25 To width / 2 + 24

‘ Busque el valor de píxel rojo más grande en la escena (láser rojo)
If (matrix(c, r, 2) > max_red) Then
max_red = matrix(c, r, 2)
max_r = r
max_c = c
End If
Next c
Next r

‘ Calcule la distancia para el punto láser desde el medio del marco
pixels_from_center = max_r – 120

‘ Calcular el rango en CM basado en parámetros calibrados
range = h_cm / Tan(pixels_from_center * gain + offset)

‘ Imprime la fila de posición de puntos láser y columna a la pantalla
row_val.Caption = max_r
col_val.Caption = max_c

‘ Rango de impresión a objeto iluminado por láser a pantalla
range_val.Caption = range

‘ Dibuja una línea vertical roja para intersectar el objetivo
For r = 0 To height – 1
matrix(max_c, r, 2) = 255
Next r

‘ Dibuja una línea horizontal roja para intersectar el objetivo
For c = 0 To width – 1
matrix(c, max_r, 2) = 255
Next c

VideoOCX.ReleaseMatrixToImageHandle (capture_image)

End If

VideoOCX.Show capture_image

End Sub

Las capturas de pantalla de este código se pueden ver a continuación:

No tengo las fotos de mi diseño, en ese entonces solo usaba una web cam de 1 Mpx y la camara de seguridad del local. Luego lo convertí en un Scaner 3D.

Visual C++

void CTripodDlg::doMyImageProcessing(LPBITMAPINFOHEADER lpThisBitmapInfoHeader)
{
// doMyImageProcessing: Aquí es donde escribiría su propio código de procesamiento de imágenes
// Task :Lea el valor de la escala de grises de un píxel y procese en consecuencia

unsigned int W, H; // Width and Height of current frame [pixels]
unsigned int row, col; // Pixel’s row and col positions
unsigned long i; // Dummy variable for row-column vector
unsigned int max_row; // Row of the brightest pixel
unsigned int max_col; // Column of the brightest pixel
BYTE max_val = 0; // Value of the brightest pixel

// Values used for calculating range from captured image data
// these values are only for a specific camera and laser setup
const double gain = 0.0024259348; // Gain Constant used for converting
// pixel offset to angle in radians
const double offset = -0.056514344; // Offset Constant
const double h_cm = 5.842; // Distance between center of camera and laser
double range; // Calculated range
unsigned int pixels_from_center; // Brightest pixel location from center
// not bottom of frame

char str[80]; // To print message
CDC *pDC; // Device context need to print message

W = lpThisBitmapInfoHeader->biWidth; // biWidth: number of columns
H = lpThisBitmapInfoHeader->biHeight; // biHeight: number of rows

for (row = 0; row < H; row++) {
for (col = 0; col < W; col++) { // Recall each pixel is composed of 3 bytes i = (unsigned long)(row*3*W + 3*col); //Si el valor de píxel actual es mayor que cualquier otro, // it is the new max pixel if (*(m_destinationBmp + i) >= max_val)
{
max_val = *(m_destinationBmp + i);
max_row = row;
max_col = col;
}
}
}
//Después de cada cuadro, reinicia el valor de píxel máximo a cero
max_val = 0;

for (row = 0; row < H; row++) {
for (col = 0; col < W; col++) { i = (unsigned long)(row*3*W + 3*col); //Dibuja una cruz blanca sobre el píxel más brillante en la pantalla de salida if ((row == max_row) || (col == max_col)) *(m_destinationBmp + i) = *(m_destinationBmp + i + 1) = *(m_destinationBmp + i + 2) = 255; } } // Calcula la distancia del píxel más brillante desde el centro en lugar de la parte inferior del marco pixels_from_center = 120 – max_row; // Calcule el rango en CM basado en la ubicación de píxeles brillantes y la configuración de constantes específicas range = h_cm / tan(pixels_from_center * gain + offset); // Imprimir Mensage en (row, column) = (75, 580) pDC = GetDC(); // Mostrar coordenadas de marco y rango calculado sprintf(str, “Max Value at x= %u, y= %u, range= %f cm “,max_col, max_row, range); pDC->TextOut(75, 580, str);
ReleaseDC(pDC);
}

El código completo para este proyecto está disponible como un paquete llamado Laserrange.zip en la parte inferior de esta página.

Nota, para ejecutar este ejecutable, deberá tener instalado tanto QCSDK como el controlador QC543 instalado en su computadora. Lo siento, pero estás solo para encontrar ambos.

A continuación se presentan dos ejemplos del buscador de rango láser basado en la cámara web en acción. Tenga en cuenta cómo parece que hay dos puntos láser en el segundo ejemplo. Esta “luz perdida” es causada por reflejos internos en la cámara.

El punto reflejado pierde la intensidad mientras rebota dentro de la cámara para que no interfiera con el algoritmo que detecta el píxel más brillante en la imagen.

Seguimiento

Según muchas preguntas y comentarios que he recibido, parece que muchas personas han tratado de duplicar este esfuerzo. Tenga en cuenta que este trabajo se realizó originalmente antes de 2004 (hace mucho tiempo). Si tuviera que hacer esto todo, usaría OpenCV para los componentes de visión.

Descargas (3)

:D Si eres de los que solo dan like para descarga y luego quitan el like, al menos quedate con el like de 30 a 40 días, eso me ayuda a llegar a más gente.

LaserRange.zip (1,9 MiB, 45 hits)


tripodDlg.cpp (9,8 KiB, 44 hits)


Vb Laser Ranger (9,2 KiB, 46 hits)