我们通过摄像头拍摄时,除非是俯视图拍摄,否则都会出现变形。离摄像头进的地方大,离摄像头远的地方小。
因为空间感,就和我们人眼看物体一样,近大远小。
例如下图所示:
在相机中,真实世界中的标准矩形,变成了梯形。我们如果要获取其中某个坐标点的位置,也会因为这个偏移而发生错误。
而针对这种情况下,我们要计算相机中的坐标,并转换为真实坐标。有两种方法,一种是实现透视变化,一种是计算相机坐标和世界坐标的转换。
实现方法简单,不需要知道摄像机参数或者平面位置的任何信息。只需要标注四个对应点为。和转换后的四个对应点位。
就能直接进行线性方程运算,将图片进行拉伸。透视变换则是在三维空间中视角的变化。
通过Imgproc.getPerspectiveTransform
得到变形矩阵数据,然后在通过Imgproc.warpPerspective
将效果绘制而成就可以了。
Imgproc.getPerspectiveTransform(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);
Imgproc.INTER_NEAREST
最近邻插值,和Imgproc.INTER_LINEAR
线性插值,Core.BORDER_CONSTANT
指定常数填充 或者Core.BORDER_REPLICATE
复制边缘像素填充).下面结合示例来看看效果吧。
第一个需求,我想将手机拍摄的梯形,矫正为矩形。效果就是上面示例图的效果:
第一步,就是将获取坐标点,可以通过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 dss = new Mat();
Imgproc.warpPerspective(mat, dss, m, new Size(640, 480));
然后将dss对象转为Bitmap 并进行显示就可以了。
你将会得到:
将原图mat中标注的坐标srcPoint区域的图片,进行截取。并拉伸平铺到desPoit尺寸的区域内进行显示。然后这个尺寸区域将会绘制在dss的Mat中,该mat的值为设置的new Size(640,480)。
大家实际操作一遍就能明白代码逻辑了。能够将摄像机拍摄倾斜的区域,矫正为真实世界上的俯视图效果。
在调用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