Android OpenCV(十):图像透视变换

2,804 阅读2分钟

图像透视变换

透视变换(Perspective Transformation)是指利用透视中心、像点、目标点三点共线的条件,按透视旋转定律使承影面(透视面)绕迹线(透视轴)旋转某一角度,破坏原有的投影光线束,仍能保持承影面上投影几何图形不变的变换。透视变换是按照物体成像投影规律进行变换,即将物体重新投影到新的成像平面。透视变换常用于机器人视觉导航研究中,由于相机视场与地面存在倾斜角使得物体成像产生畸变,通常通过透视变换实现对物体图像的校正。

原理

1
1
2
2
3
3
4
4

透视变换的方程组有8个未知数,所以要求解就需要找到4组映射点,四个点就刚好确定了一个三维空间。

API

求取变换矩阵

public static Mat getPerspectiveTransform(Mat src, Mat dst, int solveMethod)
  • 参数一:src,原图像中的四个像素坐标
  • 参数二:dst,目标图像中的四个像素坐标
  • 参数三:solveMethod,选择计算透视变换矩阵方法的标志,默认情况下选择的是最佳主轴元素的高斯消元法DECOMP_LU
enum DecompTypes {
    DECOMP_LU       = 0,
    DECOMP_SVD      = 1,
    DECOMP_EIG      = 2,
    DECOMP_CHOLESKY = 3,
    DECOMP_QR       = 4,
    DECOMP_NORMAL   = 16
};
标志位 作用
DECOMP_LU 0 最佳主轴元素的高斯消元法
DECOMP_SVD 1 奇异值分解(SVD)方法
DECOMP_EIG 2 特征值分解法
DECOMP_CHOLESKY 3 Cholesky分解法
DECOMP_QR 4 QR分解法
DECOMP_NORMAL 16 使用正规方程公式,可以去前面的标志一起使用

透视变换

public static void warpPerspective(Mat src, Mat dst, Mat M, Size dsize, int flags, int borderMode, Scalar borderValue) 
  • 参数一:src,原图

  • 参数二:dst,透视变换后输出图像,与src数据类型相同,但是尺寸与dsize相同

  • 参数三:M,3*3变换矩阵

  • 参数四:dsize,输出图像的尺寸

  • 参数五:flags,插值方法标志

    // C++: enum InterpolationFlags
    public static final int
            INTER_NEAREST = 0,
            INTER_LINEAR = 1,
            INTER_CUBIC = 2,
            INTER_AREA = 3,
            INTER_LANCZOS4 = 4,
            INTER_LINEAR_EXACT = 5,
            INTER_MAX = 7,
            WARP_FILL_OUTLIERS = 8,
            WARP_INVERSE_MAP = 16;
    
  • 参数六:borderMode,像素边界外推方法的标志。BORDER_CONSTANT 或者BORDER_REPLICATE

  • 参数七:borderValue,填充边界使用的数值,默认情况下为0

操作

下面代码实现将相机视线不垂直于马路平面拍摄的图像经过透视变换变成相机视线垂直于马路平面拍摄的图像。

class PerspectiveTransformationActivity : AppCompatActivity() {

    private lateinit var mBinding: ActivityPerspectiveTransformationBinding
    private lateinit var mRgb: Mat
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_perspective_transformation)

        val bgr = Utils.loadResource(this, R.drawable.road)
        mRgb = Mat()
        Imgproc.cvtColor(bgr, mRgb, Imgproc.COLOR_BGR2RGB)
        showMat(mBinding.ivRoad, mRgb)

        doPerspectiveTransform()
    }

    private fun showMat(view: ImageView, source: Mat) {
        val bitmap = Bitmap.createBitmap(source.width(), source.height(), Bitmap.Config.ARGB_8888)
        Utils.matToBitmap(source, bitmap)
        view.setImageBitmap(bitmap)
    }

    private fun getPerspectiveTransform(): Mat {
        val srcPoints = ArrayList<Point>()
        val p1 = Point(325.0000, 374.0000)
        srcPoints.add(p1)
        val p2 = Point(597.0000, 374.0000)
        srcPoints.add(p2)
        val p3 = Point(960.0000, 505.0000)
        srcPoints.add(p3)
        val p0 = Point(0.0000, 505.0000)
        srcPoints.add(p0)
        val srcMat = Converters.vector_Point2f_to_Mat(srcPoints)

        val resultWidth = 500.0
        val resultHeight = 500.0
        val dstPoints = ArrayList<Point>()
        val p5 = Point(0.0, 0.0)
        dstPoints.add(p5)
        val p6 = Point(resultWidth, 0.0)
        dstPoints.add(p6)
        val p7 = Point(resultWidth, resultHeight)
        dstPoints.add(p7)
        val p4 = Point(0.0, resultHeight)
        dstPoints.add(p4)
        val dstMat = Converters.vector_Point2f_to_Mat(dstPoints)
        return Imgproc.getPerspectiveTransform(srcMat, dstMat)
    }

    private fun doPerspectiveTransform() {
        val transform = getPerspectiveTransform()
        val dst = Mat()
        Imgproc.warpPerspective(
            mRgb,
            dst,
            transform,
            Size(500.0, 500.0),
            Imgproc.INTER_NEAREST
        )
        showMat(mBinding.ivResult, dst)
    }

    override fun onDestroy() {
        mRgb.release()
        super.onDestroy()
    }
}

效果

透视变换
透视变换

源码

github.com/onlyloveyd/…

本文使用 mdnice 排版