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
相关产品推荐
相关产品推荐

