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

React Native Fast OpenCV中如何创建屏幕/原图角落的PointVector?

实现透视变换的完整指导

1. 创建目标区域的PointVector

由于你对图像执行了ROTATE_90_CLOCKWISE旋转,原图像的宽高会互换。基于旋转后的尺寸创建代表屏幕/目标区域四个角落的PointVector:

// 旋转后图像的宽高(原height转width,原width转height)
const rotatedWidth = frameDimensions.value!.height;
const rotatedHeight = frameDimensions.value!.width;

// 创建目标PointVector容器
const targetPoints = OpenCV.createObject(ObjectType.PointVector);
// 逐个创建四个角落点并加入容器
const topLeft = OpenCV.createObject(ObjectType.Point, 0, 0);
const topRight = OpenCV.createObject(ObjectType.Point, rotatedWidth, 0);
const bottomRight = OpenCV.createObject(ObjectType.Point, rotatedWidth, rotatedHeight);
const bottomLeft = OpenCV.createObject(ObjectType.Point, 0, rotatedHeight);

OpenCV.invoke('push_back', targetPoints, topLeft);
OpenCV.invoke('push_back', targetPoints, topRight);
OpenCV.invoke('push_back', targetPoints, bottomRight);
OpenCV.invoke('push_back', targetPoints, bottomLeft);

2. 适配图像旋转调整源点坐标

图像旋转后,文档角落的原始坐标需要同步转换为旋转后的坐标系,否则透视变换会错位:

// 90度顺时针旋转的坐标转换公式:(x,y) → (y, 原width - x)
const adjustPointRotation = (point, originalWidth) => {
  const newX = point.y;
  const newY = originalWidth - point.x;
  return OpenCV.createObject(ObjectType.Point, newX, newY);
};

// 处理文档角落的源点
const originalWidth = frameDimensions.value!.width;
const sourcePointsRotated = OpenCV.createObject(ObjectType.PointVector);
const sourcePointsJS = OpenCV.toJSValue(largest_contour).array;

// 确保源点是4个(文档四个角),否则跳过变换
if (sourcePointsJS.length !== 4) return;

sourcePointsJS.forEach(point => {
  const adjustedPoint = adjustPointRotation(point, originalWidth);
  OpenCV.invoke('push_back', sourcePointsRotated, adjustedPoint);
});

3. 生成透视变换矩阵并应用

使用OpenCV的getPerspectiveTransform生成变换矩阵,再用warpPerspective将变换应用到旋转后的图像:

// 创建3x3单精度矩阵存储透视变换参数
const transformMatrix = OpenCV.createObject(ObjectType.Mat, 3, 3, DataTypes.CV_32F);
// 生成变换矩阵(源点必须和目标点顺序一一对应)
OpenCV.invoke('getPerspectiveTransform', sourcePointsRotated, targetPoints, transformMatrix);

// 创建输出图像容器
const warpedImage = OpenCV.createObject(ObjectType.Mat, rotatedHeight, rotatedWidth, DataTypes.CV_8UC3);
// 应用透视变换
OpenCV.invoke(
  'warpPerspective',
  image, // 已旋转的原图
  warpedImage,
  transformMatrix,
  OpenCV.createObject(ObjectType.Size, rotatedWidth, rotatedHeight),
  InterpolationFlags.INTER_LINEAR, // 插值方式,保证图像平滑
  BorderTypes.BORDER_CONSTANT,
  OpenCV.createObject(ObjectType.Scalar, 0, 0, 0) // 边界填充黑色
);

4. 整合到现有代码

将上述代码插入到图像旋转之后、设置保存图像之前的位置,替换原有的setSavedImage逻辑:

if (largest_contour) {
  // --- 原有的边缘绘制代码保持不变 ---
  let n_points = OpenCV.toJSValue(largest_contour).array.length;
  for (let i = 0; i < n_points - 1; i++) {
    OpenCV.invoke(
      'line',
      image,
      OpenCV.copyObjectFromVector(largest_contour, i),
      OpenCV.copyObjectFromVector(largest_contour, i + 1),
      OpenCV.createObject(ObjectType.Scalar, 0, 255, 0),
      3,
      LineTypes.FILLED
    );
  }
  OpenCV.invoke(
    'line',
    image,
    OpenCV.copyObjectFromVector(largest_contour, n_points - 1),
    OpenCV.copyObjectFromVector(largest_contour, 0),
    OpenCV.createObject(ObjectType.Scalar, 0, 255, 0),
    3,
    LineTypes.FILLED
  );

  OpenCV.invoke('rotate', image, image, RotateFlags.ROTATE_90_CLOCKWISE);

  // --- 新增透视变换代码开始 ---
  const rotatedWidth = frameDimensions.value!.height;
  const rotatedHeight = frameDimensions.value!.width;
  const originalWidth = frameDimensions.value!.width;

  // 创建目标PointVector
  const targetPoints = OpenCV.createObject(ObjectType.PointVector);
  const topLeft = OpenCV.createObject(ObjectType.Point, 0, 0);
  const topRight = OpenCV.createObject(ObjectType.Point, rotatedWidth, 0);
  const bottomRight = OpenCV.createObject(ObjectType.Point, rotatedWidth, rotatedHeight);
  const bottomLeft = OpenCV.createObject(ObjectType.Point, 0, rotatedHeight);
  OpenCV.invoke('push_back', targetPoints, topLeft);
  OpenCV.invoke('push_back', targetPoints, topRight);
  OpenCV.invoke('push_back', targetPoints, bottomRight);
  OpenCV.invoke('push_back', targetPoints, bottomLeft);

  // 调整源点坐标
  const sourcePointsRotated = OpenCV.createObject(ObjectType.PointVector);
  const sourcePointsJS = OpenCV.toJSValue(largest_contour).array;
  if (sourcePointsJS.length !== 4) {
    // 非四边形轮廓,跳过变换
    setSavedImage(OpenCV.toJSValue(image).base64);
  } else {
    sourcePointsJS.forEach(point => {
      const newX = point.y;
      const newY = originalWidth - point.x;
      const adjustedPoint = OpenCV.createObject(ObjectType.Point, newX, newY);
      OpenCV.invoke('push_back', sourcePointsRotated, adjustedPoint);
    });

    // 生成变换矩阵并应用
    const transformMatrix = OpenCV.createObject(ObjectType.Mat, 3, 3, DataTypes.CV_32F);
    OpenCV.invoke('getPerspectiveTransform', sourcePointsRotated, targetPoints, transformMatrix);

    const warpedImage = OpenCV.createObject(ObjectType.Mat, rotatedHeight, rotatedWidth, DataTypes.CV_8UC3);
    OpenCV.invoke(
      'warpPerspective',
      image,
      warpedImage,
      transformMatrix,
      OpenCV.createObject(ObjectType.Size, rotatedWidth, rotatedHeight),
      InterpolationFlags.INTER_LINEAR,
      BorderTypes.BORDER_CONSTANT,
      OpenCV.createObject(ObjectType.Scalar, 0, 0, 0)
    );

    // 设置变换后的图像
    setSavedImage(OpenCV.toJSValue(warpedImage).base64);

    // 清理临时对象
    OpenCV.deleteObject(warpedImage);
    OpenCV.deleteObject(transformMatrix);
  }

  // 清理剩余临时对象
  OpenCV.deleteObject(targetPoints);
  OpenCV.deleteObject(sourcePointsRotated);
  OpenCV.deleteObject(topLeft);
  OpenCV.deleteObject(topRight);
  OpenCV.deleteObject(bottomRight);
  OpenCV.deleteObject(bottomLeft);
  // --- 新增透视变换代码结束 ---

  OpenCV.clearBuffers();
}

关键注意事项

  • 必须保证largest_contour是4个点的四边形,否则getPerspectiveTransform会失效,建议添加长度判断
  • 源点和目标点的顺序必须一致(均为顺时针或逆时针排列),否则变换后图像会扭曲
  • 所有通过createObject创建的临时对象,需要用deleteObject手动清理,避免内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 17:22:08