侧边栏壁纸
  • 累计撰写 372 篇文章
  • 累计创建 60 个标签
  • 累计收到 109 条评论

目 录CONTENT

文章目录

Android OpenCV 透视变换,梯形区域转矩形

Z同学
2022-10-14 / 0 评论 / 3 点赞 / 161 阅读 / 1,693 字
温馨提示:
本文最后更新于 2022-10-14,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

1. 介绍

我们通过摄像头拍摄时,除非是俯视图拍摄,否则都会出现变形。离摄像头进的地方大,离摄像头远的地方小。

因为空间感,就和我们人眼看物体一样,近大远小。

例如下图所示:

image-1665735557647

在相机中,真实世界中的标准矩形,变成了梯形。我们如果要获取其中某个坐标点的位置,也会因为这个偏移而发生错误。

而针对这种情况下,我们要计算相机中的坐标,并转换为真实坐标。有两种方法,一种是实现透视变化,一种是计算相机坐标和世界坐标的转换。

  • 透视变化:只需要标注4个对应点,不用摄像机或者其他参数。(简单)
  • 相机坐标转换:需要知道相机内参信息,相机的俯仰角度等,需要的前置参数较多。(复杂)可以参考:https://www.guyuehome.com/36095

2. 透视变换

实现方法简单,不需要知道摄像机参数或者平面位置的任何信息。只需要标注四个对应点为。和转换后的四个对应点位。

就能直接进行线性方程运算,将图片进行拉伸。透视变换则是在三维空间中视角的变化。

通过Imgproc.getPerspectiveTransform 得到变形矩阵数据,然后在通过Imgproc.warpPerspective 将效果绘制而成就可以了。

Imgproc.getPerspectiveTransform(Mat src, Mat dst, int solveMethod)
  • Mat src: 输入图形的四边形顶点坐标
  • Mat dst:输出图形的四边形顶点坐标
  • int solveMethod:可选项,默认值为Core.DECOMP_LU,变换的计算方法,cv::solve 会需要该计算值。

上面的方法就能得到一个透视矩阵的变换函数,Mat对象。这个矩阵是一个3*3的变形矩阵

然后我们再通过Imgproc.warpPerspective 将要透视变换的值,扔进去进行透视变换。可以将坐标扔进去进行变换,也可以将图片扔进行做透视变换。

Imgproc.warpPerspective(Mat src, Mat dst, Mat M, Size dsize, int flags, int borderMode, Scalar borderValue);
  • Mat src:输入对象,需要变换的坐标或者图片
  • Mat dst:输出对象,变换结束后的效果
  • Mat M:3*3尺寸大小的转换矩阵,openCV将会按照这个转换矩阵将输入src转为输出dst。
  • Size dsize:输出图像的大小。
  • int flags:可选参数,插值方法的组合。一般是使用Imgproc.INTER_NEAREST 最近邻插值,和Imgproc.INTER_LINEAR线性插值,
  • int borderMode:可选参数像,素外推方法 (Core.BORDER_CONSTANT 指定常数填充 或者Core.BORDER_REPLICATE复制边缘像素填充).
  • Scalar borderValue:可选参数,固定边缘情况下使用的值,默认值是0 也就是黑色。

下面结合示例来看看效果吧。

2.1 示例

第一个需求,我想将手机拍摄的梯形,矫正为矩形。效果就是上面示例图的效果:

第一步,就是将获取坐标点,可以通过OpenCV的轮廓识别获取坐标点(精度准确),也可以手动触摸提取坐标点(精度偏移较大)

我这里就简单点了,直接提取触摸点的方法来实现了。获取ImageView控件和Bitmap的矩阵偏移值。

matrix = new Matrix();
binding.image.getImageMatrix().invert(matrix);
matrix.postTranslate(binding.image.getScrollX(), binding.image.getScrollY());

因为我的图片并不是完整填充ImageView,所以我们需要先从ImageView中得到ImageView对象和图片实际之间的偏差。

 binding.image.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    //执行坐标转换
                    final int index = e.getActionIndex();
        			final float[] coords = new float[]{e.getX(index), e.getY(index)};
       				 matrix.mapPoints(coords);
        			float x = coords[0];
        			float y = coords[1];
                }
                return false;
            }
        });

如果我们点击图片,就能够得到实际图片的坐标值了。其中的关键方法:matrix.mapPoins()。进行的转换。

当我们获取了坐标值之后,进行透视变换的矩形数据生成。

中间的获取相机,再将相机的imageProxy转Mat这里就不做介绍,步骤简单。

将得到的Mat 先执行getPerspectiveTransform:

 MatOfPoint2f srcPoint = new MatOfPoint2f();
        srcPoint.fromArray(point1, point2, point3, point4);

        MatOfPoint2f desPoit = new MatOfPoint2f();
        desPoit.fromArray(new Point(0, 0), new Point(640,0), new Point(0,480), new Point(640,480));
        //得到换算结果
        Mat m = Imgproc.getPerspectiveTransform(srcPoint, desPoit);

参数不对就会错误哦。

到这里,只是得到了转换关系的矩阵对象。下面要对图片进行转换操作了。

先介绍一下参数:

  • mat:是我们相机拍摄得到的ImageProxy转换的Mat对象,
  • dss:是转换后,我们要显示的Mat对象。
  • m:是上一步getPerspectiveTransform之后得到的Mat对象。
  • size: 是我们dss对象的尺寸大小。
Mat dss = new Mat();
Imgproc.warpPerspective(mat, dss, m, new Size(640, 480));

然后将dss对象转为Bitmap 并进行显示就可以了。

你将会得到:

将原图mat中标注的坐标srcPoint区域的图片,进行截取。并拉伸平铺到desPoit尺寸的区域内进行显示。然后这个尺寸区域将会绘制在dss的Mat中,该mat的值为设置的new Size(640,480)。

大家实际操作一遍就能明白代码逻辑了。能够将摄像机拍摄倾斜的区域,矫正为真实世界上的俯视图效果。

3 错误

3.1 getPerspectiveTransform 坐标错误

在调用getPerspectiveTransform 方法的时候出现崩溃异常: 说坐标点需要时CV_32F。

E/cv::error(): OpenCV(4.6.0) Error: Assertion failed (src.checkVector(2, CV_32F) == 4 && dst.checkVector(2, CV_32F) == 4) in getPerspectiveTransform, file /home/ci/opencv/modules/imgproc/src/imgwarp.cpp, line 3392

原先的写法为:

 MatOfPoint srcPoint = new MatOfPoint2f();
        srcPoint.fromArray(point1, point2, point3, point4);

       MatOfPoint desPoit = new MatOfPoint();
        desPoit.fromArray(new Point(0, 0), new Point(640,0), new Point(0,480), new Point(640,480));
        //得到换算结果
        Mat m = Imgproc.getPerspectiveTransform(srcPoint, desPoit);

修改为:

        MatOfPoint2f srcPoint = new MatOfPoint2f();
        srcPoint.fromArray(point1, point2, point3, point4);
      MatOfPoint2f desPoit = new MatOfPoint2f();
        desPoit.fromArray(new Point(0, 0), new Point(640,0), new Point(0,480), new Point(640,480));
        //得到换算结果
        Mat m = Imgproc.getPerspectiveTransform(srcPoint, desPoit);

也就是修改坐标点的类型,从MatOfPoint改为MatOfPoint2f类型就可以了。

参考资料:

https://blog.csdn.net/liuweiyuxiang/article/details/86510191

https://www.guyuehome.com/36095

https://zhuanlan.zhihu.com/p/64025334

https://blog.csdn.net/baidu_36669549/article/details/97825291

3

评论区