La visión por computadora transforma píxeles en información sobre formas, posiciones y movimiento. Vectores y matrices permiten modelar la cámara y relacionar el mundo tridimensional con la imagen.
Una cámara reduce una escena tridimensional a una imagen bidimensional. El objetivo de la visión por computadora es recuperar información útil a partir de esa proyección: objetos, profundidad, movimiento o posición.
El álgebra lineal aparece en cada etapa: representa puntos, cambia sistemas de coordenadas, proyecta la escena y permite estimar parámetros a partir de datos.
La posición de un píxel se expresa como (u, v). Además de su posición, un punto visual puede describirse mediante un vector de intensidad, color, orientación o textura.
const puntoImagen = { u: 320, v: 240 };
const caracteristica = [0.2, 0.8, 0.1, 0.6];
const norma = Math.hypot(...caracteristica);
const descriptorNormalizado = caracteristica.map(x => x / norma);
console.log(puntoImagen, descriptorNormalizado);
Normalizar descriptores permite comparar su dirección sin que domine su escala.
Un punto 2D (u, v) se escribe en coordenadas homogéneas como (su, sv, s), con s distinto de cero. Todos los múltiplos no nulos representan el mismo punto.
function deshomogeneizar([x, y, w]) {
if (Math.abs(w) < 1e-12) throw new Error("Punto en el infinito");
return [x / w, y / w];
}
console.log(deshomogeneizar([960, 720, 3])); // [320, 240]
Esta notación permite expresar proyecciones y transformaciones afines mediante multiplicaciones de matrices.
El modelo pinhole ideal supone que los rayos de luz atraviesan un único centro de proyección. Si un punto de la cámara es (X, Y, Z), su posición en un plano focal de distancia f es:
function proyectarPinhole([X, Y, Z], f) {
if (Z <= 0) return null;
return [f * X / Z, f * Y / Z];
}
console.log(proyectarPinhole([2, 1, 10], 800)); // [160, 80]
Dos puntos sobre el mismo rayo producen el mismo píxel; por eso una sola imagen no determina directamente la profundidad.
La matriz intrínseca K convierte coordenadas normalizadas de cámara en píxeles. Incluye distancias focales en píxeles, punto principal y, en el modelo general, sesgo entre ejes.
function pixelDesdeCamara([X, Y, Z], K) {
const normalizado = [X / Z, Y / Z, 1];
const h = K.map(fila =>
fila.reduce((suma, valor, i) => suma + valor * normalizado[i], 0)
);
return [h[0] / h[2], h[1] / h[2]];
}
const K = [[800, 0, 320], [0, 800, 240], [0, 0, 1]];
console.log(pixelDesdeCamara([1, 0.5, 5], K)); // [480, 320]
Los parámetros extrínsecos sitúan la cámara respecto del mundo. Una rotación R y una traslación t convierten un punto mundial Xw en coordenadas de cámara:
function transformarPunto(R, t, punto) {
return R.map((fila, i) =>
fila.reduce((suma, valor, j) => suma + valor * punto[j], 0) + t[i]
);
}
const R = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
console.log(transformarPunto(R, [0, 0, 5], [1, 2, 3])); // [1, 2, 8]
Es importante declarar la convención: t no siempre representa la posición de la cámara. En esta ecuación, el centro mundial de la cámara es C = −RTt.
Los parámetros internos y externos se reúnen en una matriz P de 3 × 4:
El símbolo ∼ indica igualdad salvo un factor de escala. X es un punto homogéneo 3D y p es el punto homogéneo de la imagen.
function proyectar(P, X) {
const p = P.map(fila =>
fila.reduce((suma, valor, i) => suma + valor * X[i], 0)
);
return [p[0] / p[2], p[1] / p[2]];
}
const P = [[800, 0, 320, 0], [0, 800, 240, 0], [0, 0, 1, 0]];
console.log(proyectar(P, [1, 0.5, 5, 1])); // [480, 320]
Una cámara real presenta distorsión radial y tangencial. La calibración estima K, los coeficientes de distorsión y, para cada imagen de referencia, la pose de la cámara.
El error de reproyección es la distancia entre el píxel detectado y la proyección predicha por el modelo.
function errorReproyeccion(observados, estimados) {
const suma = observados.reduce((total, p, i) =>
total + (p[0] - estimados[i][0]) ** 2 +
(p[1] - estimados[i][1]) ** 2, 0);
return Math.sqrt(suma / observados.length);
}
console.log(errorReproyeccion([[10, 10], [20, 20]], [[11, 10], [20, 22]]));
// aproximadamente 1.58 píxeles
En el plano proyectivo, una línea se representa mediante l = (a, b, c), y un punto p pertenece a ella cuando lTp = 0.
const cruz = (a, b) => [
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0]
];
const linea = cruz([1, 1, 1], [4, 3, 1]);
console.log(linea); // [-2, 3, -1]
Para reconocer el mismo punto en dos imágenes se comparan descriptores. Son frecuentes la distancia euclídea y la similitud coseno.
function distancia(a, b) {
return Math.hypot(...a.map((valor, i) => valor - b[i]));
}
function coseno(a, b) {
const producto = a.reduce((s, x, i) => s + x * b[i], 0);
return producto / (Math.hypot(...a) * Math.hypot(...b));
}
console.log(distancia([1, 2, 3], [1, 2, 4])); // 1
console.log(coseno([1, 0], [0, 1])); // 0
Una correspondencia cercana no siempre es correcta; suelen aplicarse pruebas de ambigüedad y verificación geométrica.
El gradiente aproxima cuánto cambia la intensidad en las direcciones x e y. Su magnitud ayuda a detectar bordes y su ángulo indica la dirección del cambio.
function gradienteCentral(imagen, x, y) {
const Ix = (imagen[y][x + 1] - imagen[y][x - 1]) / 2;
const Iy = (imagen[y + 1][x] - imagen[y - 1][x]) / 2;
return { Ix, Iy, magnitud: Math.hypot(Ix, Iy) };
}
const I = [[0, 0, 0], [0, 10, 20], [0, 20, 40]];
console.log(gradienteCentral(I, 1, 1));
El flujo óptico estima el desplazamiento aparente entre fotogramas. Bajo cambios pequeños y brillo aproximadamente constante se obtiene la restricción:
Una ecuación no basta para hallar las dos componentes (u, v). Métodos como Lucas-Kanade reúnen un vecindario y resuelven un sistema sobredeterminado por mínimos cuadrados.
function resolver2x2(a, b, c, d, e, f) {
const det = a * d - b * c;
if (Math.abs(det) < 1e-12) return null;
return [(e * d - b * f) / det, (a * f - e * c) / det];
}
// Ecuaciones normales de un pequeño vecindario.
console.log(resolver2x2(5, 1, 1, 4, 11, 9)); // [1.842..., 1.789...]
Una homografía H es una matriz 3 × 3 que relaciona puntos de dos vistas de un mismo plano, o imágenes tomadas por una cámara que solo rota.
function aplicarHomografia(H, [x, y]) {
const p = H.map(fila => fila[0] * x + fila[1] * y + fila[2]);
if (Math.abs(p[2]) < 1e-12) return null;
return [p[0] / p[2], p[1] / p[2]];
}
const H = [[1, 0.2, 30], [0.1, 1, 20], [0.001, 0, 1]];
console.log(aplicarHomografia(H, [100, 50]));
Se emplea para rectificar documentos, crear panoramas, insertar gráficos sobre superficies y comparar planos vistos desde diferentes posiciones.
Las correspondencias incluyen valores atípicos. RANSAC selecciona muestras mínimas, estima un modelo y cuenta cuántos datos son compatibles con él.
function contarInliers(errores, umbral) {
return errores.filter(error => error <= umbral).length;
}
const errores = [0.4, 1.1, 0.8, 25, 0.3, 17];
console.log(contarInliers(errores, 2)); // 4
RANSAC no garantiza el mejor resultado, pero es eficaz cuando hay suficientes correspondencias correctas y un umbral adecuado.
Con dos cámaras, un punto de la primera imagen determina una línea epipolar en la segunda. La correspondencia correcta debe encontrarse sobre esa línea.
La matriz fundamental F, de 3 × 3 y rango 2, resume la geometría proyectiva entre dos cámaras no calibradas. Para cámaras calibradas se utiliza la matriz esencial E.
function multiplicarMatrizVector(M, v) {
return M.map(fila => fila.reduce((s, x, i) => s + x * v[i], 0));
}
const F = [[0, 0, 0], [0, 0, -1], [0, 1, 0]];
const p = [2, 3, 1];
const lineaEpipolar = multiplicarMatrizVector(F, p);
console.log(lineaEpipolar); // [0, -1, 3]
Si un punto se observa en dos cámaras conocidas, se intersectan aproximadamente sus rayos para recuperar su posición 3D. Debido al ruido, los rayos rara vez se cruzan exactamente.
En un par estéreo rectificado, la profundidad se relaciona con la disparidad d = uizq − uder:
function profundidadEstereo(focalPixeles, baseMetros, disparidad) {
if (disparidad <= 0) return Infinity;
return focalPixeles * baseMetros / disparidad;
}
console.log(profundidadEstereo(800, 0.12, 24)); // 4 metros
La calibración, la homografía y la triangulación producen sistemas con más ecuaciones que incógnitas. Se busca una solución que minimice el error total.
En implementaciones numéricas se prefieren factorizaciones QR o SVD a formar explícitamente ATA, porque suelen ser más estables. La SVD también permite imponer restricciones de rango.
function errorCuadratico(A, x, b) {
return A.reduce((total, fila, i) => {
const estimado = fila.reduce((s, valor, j) => s + valor * x[j], 0);
return total + (estimado - b[i]) ** 2;
}, 0);
}
console.log(errorCuadratico([[1, 0], [0, 1], [1, 1]], [2, 3], [2, 3, 5])); // 0
Estos modelos se utilizan en realidad aumentada, lectura de documentos, robótica, navegación, inspección industrial, reconstrucción 3D y seguimiento de objetos.
| Aplicación | Herramientas algebraicas |
|---|---|
| Panoramas | Características, homografías y RANSAC |
| Realidad aumentada | Calibración, pose y proyección |
| Visión estéreo | Geometría epipolar y triangulación |
| Seguimiento | Gradientes, flujo óptico y mínimos cuadrados |
Errores frecuentes son mezclar filas con columnas, confundir coordenadas mundiales y de cámara, olvidar dividir por la coordenada homogénea, aceptar puntos detrás de la cámara, ignorar la distorsión, utilizar datos sin normalizar o estimar un modelo sin rechazar correspondencias falsas.
En sistemas reales conviene emplear bibliotecas optimizadas, tipos numéricos consistentes y pruebas con escenas cuya geometría sea conocida.
La visión por computadora utiliza el álgebra lineal para construir un puente entre píxeles y geometría. Las matrices describen cámaras y transformaciones; los vectores representan observaciones, y los métodos de estimación convierten mediciones imperfectas en modelos útiles.
En el próximo tema aplicaremos estas ideas a la ciencia de datos, donde matrices y vectores permiten organizar, transformar y analizar grandes conjuntos de observaciones.