仿射变换
仿射变换,又称仿射映射,是指在几何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间。 仿射变换能够保持图像的“平直性”,包括旋转,缩放,平移,错切操作。一般而言,仿射变换矩阵为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
}
}