Android Matrix之图像处理

1 概述

Matrix是一个矩阵类,Matrix就是用于坐标变换的,而且是一个3x3的矩阵.假如你对线性代数还有印象,理解它应该不难.由于我们以下大部分的计算都是基于矩阵乘法规则,如果你已经把线性代数还给了老师,请参考一下这里: 维基百科-矩阵乘法

2 原理

matrix的矩阵是这样子的:

20160518113919200.jpg

Matrix可以操作的基础变换可分为四种:
Scale:缩放
Skew:错切
Rotate:旋转
Translate:平移
persp代表透视(官方文档中,也没有详细讲解,这里不做介绍)
最后一行的三个参数分别为0,0,1。是固定值,其实这个在线代里面叫做齐次方程,简而言之就是为了方便计算。
这里可以把矩阵根据他们的作用划分为4块:

20160518114250373.jpg

这个矩阵是如何影响图像的?

20160518114718678.jpg
我们的屏幕,就像是一个窗口,透过它,我们看到了屏幕后面的世界,那里面有各种物体,我们看到的是映射在x,y平面上的一个投射图像。屏幕就像是一个镜头一样,将里面的物体映射到x,y平面上,成为一个二维的图像。
其实,在屏幕上显示的像素,不仅仅有x,y坐标,其实还有z轴的影响。所以这里对应的像素描述由一个3行一列的矩阵来表示:

20160518115357609.jpg
x,y分别代表x,y轴上的坐标,而1代表屏幕在z轴上的坐标为默认的。如果将1变大,那么屏幕会拉远, 图形会变小。

现在我们来看看matrix怎么作用于每个像素的值。这里需要用到矩阵的乘法,首先需要明确的是,矩阵的前乘和后乘是不相同的,也就是说不满足乘法交换律。

这里我们通过一个旋转变换来看看原理,其实一张图片围绕一个点旋转,也就是所有的点都围绕一个点旋转,所以只需要关注一个点的情况即可:

假定有一个点 ,相对坐标原点顺时针旋转后的情形,同时假定P点离坐标原点的距离为r,如下图:

20160518115922551.jpg
那么就有:

20160518120101349.jpg
换做矩阵运算就如下图:

20160518120125962.jpg
从这里就可以看出,矩阵中的值,是如何作用于像素点的x,y坐标以及z轴远近。

同时,可以看到,上面的矩阵四块区域的切分也是因为矩阵乘法的操作决定的,由于这里的乘法运算中,左上角的四个值,可以和x,y值做乘法运算,所以可以影响到旋转等操作,而右上角的模块,只能做加法,所以只能影响到平移。右下角的模块主要管z轴,自然就可以进行等比的缩放了,左下角的模块一般不去动他,否则会把x,y值加入到z轴中来,会不可控。

3 基本方法解析

(1)构造函数


public Matrix()
public Matrix(Matrix src)

构造函数有两个,第一个是直接创建一个单位矩阵,第二个是根据提供的矩阵创建一个新的矩阵(采用deep copy,可以理解为新的matrix和src是两个对象,但内部数值完全相同)

(2) isIdentity与isAffine

public boolean isIdentity()//判断是否是单位矩阵
public boolean isAffine()//判断是否是仿射矩阵

单位矩阵很简单,就不做讲解了。先了解下仿射变换,仿射变换其实就是二维坐标到二维坐标的线性变换,保持二维图形的“平直性”(即变换后直线还是直线不会打弯,圆弧还是圆弧)和“平行性”(指保持二维图形间的相对位置关系不变,平行线还是平行线,而直线上点的位置顺序不变),可以通过一系列的原子变换的复合来实现,原子变换就包括:平移、缩放、翻转、旋转和错切。这里除了透视可以改变z轴以外,其他的变换基本都是上述的原子变换,所以,只要最后一行是0,0,1则是仿射矩阵。

(3) rectStaysRect

public boolean rectStaysRect()

判断该矩阵是否可以将一个矩形依然变换为一个矩形。当矩阵是单位矩阵,或者只进行平移,缩放,以及旋转90度的倍数的时候,返回true。

(4) reset

public void reset()

重置矩阵为单位矩阵。

(5) setTranslate

public void setTranslate(float dx, float dy)

设置平移效果,参数dx,dy分别是x,y上的平移量。
例如:

Matrix matrix = new Matrix();
canvas.drawBitmap(bitmap, matrix, paint);

matrix.setTranslate(100, 1000);
canvas.drawBitmap(bitmap, matrix, paint);

(6) setScale

public void setScale(float sx, float sy, float px, float py)
public void setScale(float sx, float sy)

两个方法都是设置缩放到matrix中,sx,sy代表了缩放的倍数,px,py代表缩放的中心。

(7) setRotate

public void setRotate(float degrees, float px, float py)
 public void setRotate(float degrees)

类似setScale.

(8) setSinCos

public void setSinCos(float sinValue, float cosValue, float px, float py)
public void setSinCos(float sinValue, float cosValue)

px:中心的x坐标
py:中心的y坐标
sinValue,cosValue,见如下例子,我们把图像旋转90度,那么90度对应的sinValue和cosValue分别是1和0。

Matrixmatrix = new Matrix();
matrix.setSinCos(1, 0, bitmap.getWidth() / 2, bitmap.getHeight() / 2);
canvas.drawBitmap(bitmap, matrix, paint);

(9) setSkew

public void setSkew(float kx, float ky, float px, float py)
public void setSkew(float kx, float ky)

错切,这里kx,ky分别代表了x,y上的错切因子,px,py代表了错切的中心。这里不对错切解释。

(10) setConcat

public boolean setConcat(Matrix a,Matrix b)

将当前matrix的值变为a和b的乘积。

4 进阶方法解析

上面的基本方法中,有关于变换的set方法都可以带来不同的效果,但是每个set都会把上个效果清除掉,例如依次调用了setSkew,setTranslate,那么最终只有setTranslate会起作用,那么如何才和将两种效果复合呢。实则,这就是Matrix复合原理。Matrix给我们提供了很多方法,但是主要都是2类:

preXXXX:以pre开头,例如preTranslate 
postXXXX:以post开头,例如postScale

他们分别代表了前乘,和后乘。看一段代码:

Matrix matrix = new Matrix();
matrix.setTranslate(100, 1000);
matrix.preScale(0.5f, 0.5f);

这里matrix前乘了一个scale矩阵,换算成数学式如下:

20160518144456776.jpg
从上面可以看出,最终得出的matrix既包含了缩放信息也有平移信息。
后乘自然就是matrix在后面,而缩放矩阵在前面,由于矩阵前后乘并不等价,也就导致了他们的效果不同。我们来看看后乘的结果:

20160518144820621.jpg
可以看到,结果跟上面不同,并且这也不是我们想要的结果,这里缩放没有更改,但是平移被减半了,换句话说,平移的距离也被缩放了,所以需要注意前后乘法的区别。

5 其他方法解析

matrix除了上面的方法外,还有一些其他的方法,这里依次解析比较常见的几个方法

(1) invert

public boolean invert(Matrix inverse)

反转当前矩阵,如果能反转就返回true并将反转后的值写入inverse,否则返回false。当前矩阵*inverse=单位矩阵。

(2) mapPoints

public void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex,int pointCount)
public void mapPoints(float[] dst, float[] src)
public void mapPoints(float[] pts)

映射点的值到指定的数组中,这个方法可以在矩阵变换以后,给出指定点的值。
dst:指定写入的数组
dstIndex:写入的起始索引,x,y两个坐标算作一对,索引的单位是对,也就是经过两个值才加1
src:指定要计算的点
srcIndex:要计算的点的索引
pointCount:需要计算的点的个数,每个点有两个值,x和y。
单参的方法,源码如下:

public void mapPoints(float[] pts) {
    mapPoints(pts, 0, pts, 0, pts.length >> 1);
}

(3) mapVectors

public void mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex,int vectorCount)
public void mapVectors(float[] dst, float[] src)
public void mapVectors(float[] vecs)

与上面的mapPoionts基本类似,这里是将一个矩阵作用于一个向量,由于向量的平移前后是相等的,所以这个方法不会对translate相关的方法产生反应,如果只是调用了translate相关的方法,那么得到的值和原本的一致。

(4) mapRect

public boolean mapRect(RectF dst, RectF src)
public boolean mapRect(RectF rect)

返回值即是调用的rectStaysRect(),这里把src中指定的矩形的左上角和右下角的两个点的坐标,写入dst中。
单参的方法,源码如下:

public boolean mapRect(RectF rect) {
    return mapRect(rect, rect);
}

(5) mapRadius

public float mapRadius(float radius)

返回一个圆圈半径的平均值,将matrix作用于一个指定radius半径的圆,随后返回的平均半径。

写在最后

后续会研究一些相关的demo,并做分享,,,作者Github

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值