阅读 262

Android OpenCV(十一):图像仿射变换

仿射变换

仿射变换,又称仿射映射,是指在几何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间。 仿射变换能够保持图像的“平直性”,包括旋转,缩放,平移,错切操作。一般而言,仿射变换矩阵为23的矩阵,第三列的元素起着平移的作用,前面两列的数字对角线上是缩放,其余为旋转或者错切的作用。 仿射变换是一种二维坐标(x, y)到二维坐标(u, v)的线性变换。数学表达式如下:数学表达式

对应的齐次坐标系如下

齐次坐标系表达式 仿射变换保持了二维图形的“平直性”(直线经仿射变换后依然为直线)和“平行性”(直线之间的相对位置关系保持不变,平行线经仿射变换后依然为平行线,且直线上点的位置顺序不会发生变化)。非共线的三对对应点确定一个唯一的仿射变换。

API

计算旋转矩阵

public static Mat getRotationMatrix2D(Point center, double angle, double scale) 
复制代码
  • 参数一:center,图像旋转的中心位置
  • 参数二:angle,图像旋转的角度,单位为度,正值为逆时针旋转
  • 参数三:scale,两个轴的比例因子,可以实现旋转过程中的图像缩放,不缩放输入1

生成的旋转矩阵与旋转角度和旋转中心的关系。

举例说明,若scale为2,angle为90度,则m1为旋转矩阵

计算仿射矩阵(三点)

public static Mat getAffineTransform(MatOfPoint2f src, MatOfPoint2f dst)
复制代码
  • 参数一:src,原图像中的三个像素坐标
  • 参数二:dst,目标图像中的三个像素坐标
  • 返回值:2*3 的变换矩阵。透视变换是3*3的矩阵,仿射则是2*3的矩阵

仿射变换

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

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

  • 参数三:M,2*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

边界填充 作用
BORDER_CONSTANT 0 用特定值填充,如iiiiii|abcdefgh|iiiiiii
BORDER_REPLICATE 1 两端复制填充,如aaaaaa|abcdefgh|hhhhhhh
BORDER_REFLECT 2 倒叙填充,如fedcba|abcdefgh|hgfedcb
BORDER_WRAP 3 正序填充,如cdefgh|abcdefgh|abcdefg
BORDER_REFLECT_101 4 不包含边界值倒叙填充,gfedcb|abcdefgh|gfedcba
BORDER_TRANSPARENT 5 随机填充,uvwxyz|abcdefgh|ijklmno
BORDER_REFLECT101 4 与BORDER_REFLECT_101相同
BORDER_DEFAULT 4 与BORDER_REFLECT_101相同
BORDER_ISOLATED 16 不关心感兴趣区域之外的部分
  • 参数七:borderValue,填充边界使用的数值,默认情况下为0

操作

class AffineActivity : AppCompatActivity() {

private lateinit var mBinding: ActivityAffineBinding
private lateinit var mRgb: Mat

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_affine)
mRgb = Mat()
val bgr = Utils.loadResource(this, R.drawable.lena)
Imgproc.cvtColor(bgr, mRgb, Imgproc.COLOR_BGR2RGB)
bgr.release()
showMat(mBinding.ivLena, mRgb)
}


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 rotateMatrix(degree: Double, scale: Double) {
val center = Point(mRgb.width() / 2.0, mRgb.height() / 2.0)
val matrix = Imgproc.getRotationMatrix2D(center, degree, scale)
val size = mRgb.size()
val dst = Mat()
Imgproc.warpAffine(mRgb, dst, matrix, size)
showMat(mBinding.ivResult, dst)
dst.release()
}

private fun threePointsMatrix() {
val srcPoints = arrayOfNulls<Point>(3)
val dstPoints = arrayOfNulls<Point>(3)

srcPoints[0] = Point(0.0, 0.0)
srcPoints[1] = Point(0.0, mRgb.width() - 1.0)
srcPoints[2] = Point(mRgb.height() - 1.0, mRgb.width() - 1.0)

dstPoints[0] = Point(mRgb.width() * 0.11, mRgb.width() * 0.2)
dstPoints[1] = Point(mRgb.width() * 0.15, mRgb.width() * 0.7)
dstPoints[2] = Point(mRgb.width() * 0.81, mRgb.width() * 0.85)

val transform = Imgproc.getAffineTransform(
MatOfPoint2f(srcPoints[0], srcPoints[1], srcPoints[2]),
MatOfPoint2f(dstPoints[0], dstPoints[1], dstPoints[2])
)
val dst = Mat()
val size = mRgb.size()
Imgproc.warpAffine(mRgb, dst, transform, size)
showMat(mBinding.ivResult, dst)
dst.release()
}


override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_affine, menu)
return true
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
title = item.title
when (item.itemId) {
R.id.affine_rotate_scale -> rotateMatrix(120.0, 1.2)
R.id.affine_three_points -> threePointsMatrix()
}
return true
}
}
复制代码

效果

求取旋转矩阵完成仿射
求取旋转矩阵完成仿射
通过三点求取矩阵完成仿射
通过三点求取矩阵完成仿射

源码

github.com/onlyloveyd/…

本文使用 mdnice 排版