仿射变换,又称仿射映射,是指在几何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间。 仿射变换能够保持图像的“平直性”,包括旋转,缩放,平移,错切操作。一般而言,仿射变换矩阵为23的矩阵,第三列的元素起着平移的作用,前面两列的数字对角线上是缩放,其余为旋转或者错切的作用。
仿射变换是一种二维坐标(x, y)到二维坐标(u, v)的线性变换。数学表达式如下:
对应的齐次坐标系如下
仿射变换保持了二维图形的“平直性”(直线经仿射变换后依然为直线)和“平行性”(直线之间的相对位置关系保持不变,平行线经仿射变换后依然为平行线,且直线上点的位置顺序不会发生变化)。非共线的三对对应点确定一个唯一的仿射变换。
public static Mat getRotationMatrix2D(Point center, double angle, double scale)
生成的旋转矩阵与旋转角度和旋转中心的关系。
举例说明,若scale为2,angle为90度,则m1为旋转矩阵
public static Mat getAffineTransform(MatOfPoint2f src, MatOfPoint2f dst)
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 | 不关心感兴趣区域之外的部分 |
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
}
}
https://github.com/onlyloveyd/LearningAndroidOpenCV