You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何结合Canvas与相机计算物体真实尺寸并处理透视畸变

利用相机与Canvas计算平面物体真实尺寸(透视畸变处理方案)

问题背景

通过相机拍摄带物体的平面,用jsqr识别参考二维码的坐标,在视频上方的Canvas图层绘制线条估算尺寸,但相机角度变化产生的透视畸变导致误差较大,需要精准的处理方案。

已获取的二维码坐标

{
  "topRightCorner": {
    "x": 872.5086679615921,
    "y": 810.804619872336
  },
  "topLeftCorner": {
    "x": 1153.5646618728827,
    "y": 815.8786697172964
  },
  "bottomRightCorner": {
    "x": 889.327694934698,
    "y": 604.2159830226265
  },
  "bottomLeftCorner": {
    "x": 1159.2881868528104,
    "y": 607.9004011299469
  },
  "topRightFinderPattern": {
    "x": 900.5,
    "y": 791
  },
  "topLeftFinderPattern": {
    "x": 1127.5,
    "y": 795
  },
  "bottomLeftFinderPattern": {
    "x": 1133,
    "y": 626.5
  },
  "bottomRightAlignmentPattern": {
    "x": 934,
    "y": 640
  }
}

当前实现代码

function pixelToCm (lineLength, refSize, location) {
  try {
    const refWidth = Math.max(
      Math.abs(location.topRightCorner.x - location.topLeftCorner.x),
      Math.abs(location.topRightCorner.y - location.topLeftCorner.y)
    )

    const refHeight = Math.max(
      Math.abs(location.bottomLeftCorner.x - location.topLeftCorner.x),
      Math.abs(location.bottomLeftCorner.y - location.topLeftCorner.y),
    )

    const pixelSize = refSize / Math.max(refWidth, refHeight)


    const lineLengthCm = lineLength * pixelSize

    console.log(lineLength, refSize, refWidth, refHeight, pixelSize, lineLengthCm)

    return _.round(Math.abs(lineLengthCm), 2)
  } catch {
    return 0
  }
}

拍摄场景示例


最佳实现方案:基于单应性矩阵的透视变换

现有代码直接用像素距离比例换算,未处理透视畸变,仅适用于相机正对平面的场景。要解决角度变化问题,必须通过**单应性矩阵(Homography Matrix)**将图像中的透视坐标映射到真实世界的正交平面坐标,再计算尺寸。

核心步骤

  1. 建立对应点对
    已知二维码的真实边长(比如refSize=5cm),定义两组对应点:

    • 真实世界坐标(正交平面,单位cm):设二维码左上角为(0,0),右上角(refSize,0),左下角(0,refSize),右下角(refSize,refSize)
    • 图像坐标:jsqr识别出的二维码四个角点topLeftCorner、topRightCorner、bottomLeftCorner、bottomRightCorner
  2. 计算单应性矩阵
    单应性矩阵可实现透视坐标到正交坐标的转换。手动实现需解线性方程组,推荐用成熟的计算机视觉库(如OpenCV.js)计算,避免数值误差。

  3. 坐标转换与尺寸计算
    将Canvas上绘制的线条端点(图像坐标)通过单应性矩阵转换为真实世界坐标,再计算两点间的欧氏距离,即为物体的真实尺寸。

代码实现示例

1. 引入OpenCV.js简化计算(推荐)

如果允许引入外部库,用OpenCV.js的findHomography方法直接计算单应性矩阵:

// 初始化OpenCV后使用
function getHomography(location, refSize) {
  // 图像点:二维码四个角的图像坐标
  const imagePoints = [
    [location.topLeftCorner.x, location.topLeftCorner.y],
    [location.topRightCorner.x, location.topRightCorner.y],
    [location.bottomLeftCorner.x, location.bottomLeftCorner.y],
    [location.bottomRightCorner.x, location.bottomRightCorner.y]
  ];
  // 真实世界点:二维码四个角的真实坐标
  const worldPoints = [
    [0, 0],
    [refSize, 0],
    [0, refSize],
    [refSize, refSize]
  ];

  // 转换为OpenCV的Mat格式
  const srcMat = new cv.Mat(4, 1, cv.CV_32FC2, imagePoints.flat());
  const dstMat = new cv.Mat(4, 1, cv.CV_32FC2, worldPoints.flat());
  
  // 计算单应性矩阵
  const homographyMat = cv.findHomography(srcMat, dstMat);
  // 将Mat转换为二维数组
  const homography = [];
  for (let i = 0; i < 3; i++) {
    homography.push([
      homographyMat.data[i*3],
      homographyMat.data[i*3+1],
      homographyMat.data[i*3+2]
    ]);
  }
  
  srcMat.delete();
  dstMat.delete();
  homographyMat.delete();
  
  return homography;
}

// 将图像点转换为真实世界点
function imageToWorld(point, homography) {
  const [x, y] = point;
  const denominator = homography[2][0] * x + homography[2][1] * y + homography[2][2];
  const X = (homography[0][0] * x + homography[0][1] * y + homography[0][2]) / denominator;
  const Y = (homography[1][0] * x + homography[1][1] * y + homography[1][2]) / denominator;
  return [X, Y];
}

// 计算线条的真实长度
function calculateRealLength(lineStart, lineEnd, homography) {
  const [X1, Y1] = imageToWorld([lineStart.x, lineStart.y], homography);
  const [X2, Y2] = imageToWorld([lineEnd.x, lineEnd.y], homography);
  return _.round(Math.sqrt(Math.pow(X2-X1, 2) + Math.pow(Y2-Y1, 2)), 2);
}

2. 手动实现单应性矩阵计算(无外部库)

如果不想引入库,可手动解线性方程组,注意数值稳定性:

// 计算单应性矩阵(图像点→真实世界点)
function computeHomography(imagePoints, worldPoints) {
  const A = [];
  const b = [];
  // 构建8个方程
  for (let i = 0; i < 4; i++) {
    const [x, y] = imagePoints[i];
    const [X, Y] = worldPoints[i];
    // 对应X的方程
    A.push([x, y, 1, 0, 0, 0, -X*x, -X*y]);
    b.push(X);
    // 对应Y的方程
    A.push([0, 0, 0, x, y, 1, -Y*x, -Y*y]);
    b.push(Y);
  }
  
  // 求解线性方程组Ax = b(此处为简化示例,实际需实现稳定的高斯消元逻辑)
  const solution = solveLinearSystem(A, b);
  // 拼接成3x3的单应性矩阵,H[2][2] = 1
  return [
    [solution[0], solution[1], solution[2]],
    [solution[3], solution[4], solution[5]],
    [solution[6], solution[7], 1]
  ];
}

// 简化的高斯消元求解线性方程组(仅适用于8x8矩阵)
function solveLinearSystem(A, b) {
  // 此处需实现完整的高斯消元逻辑,可参考公开的数值计算代码片段
  // 示例返回单位矩阵参数,实际需替换为正确解
  return [1,0,0,0,1,0,0,0];
}

使用流程

  1. 每帧识别二维码角点后,计算单应性矩阵:
const refSize = 5; // 二维码真实边长(单位:cm)
const homography = getHomography(location, refSize);
  1. 当用户在Canvas上绘制线条后,计算真实长度:
// 假设lineStart和lineEnd是Canvas上的两个端点坐标
const realLength = calculateRealLength(lineStart, lineEnd, homography);
console.log("真实长度:", realLength, "cm");

注意事项

  • 平面约束:必须保证物体和二维码在同一平面上,否则透视变换结果会有误差
  • 角点精度:jsqr返回的角点坐标越准确,单应性矩阵的计算精度越高,最终尺寸结果越可靠
  • 实时更新:相机角度变化时,需要每帧重新识别二维码并计算单应性矩阵,保证尺寸计算的实时性
  • 库选择:手动实现单应性矩阵容易出现数值不稳定的问题,优先选择OpenCV.js这类成熟库

内容的提问来源于stack exchange,提问作者crapthings

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 14:34:55