第11章 CustomView Matrix與座標變換

一、矩陣運算

矩陣的加法與減法:

1.運算規則

設矩陣

A\pm B=

簡言之,兩個矩陣相加減,即它們相同位置的元素相加減。

注意:只有對於兩個行數、列數分別相等(同型矩陣),加減法運算纔有意義,即加減法運算是可行的。

2.運算性質

交換律:A+B=B+A

結合律:(A+B)+C=A+(B+C)

矩陣與數的乘法:

1.運算規則

\gamma乘以矩陣A,就是將數\gamma乘以矩陣A中的每一個元素,記爲\gammaA或A\gamma

特別地,稱-AA=(a_{ij})_{m\times s}的負矩陣。

2.運算性質

結合律:(\gamma \mu)A=\gamma(\mu A); (\gamma+\mu)A=\gamma A+\mu A

分配律:\gamma (A+B)=\gamma A + \gamma B

例:已知兩個矩陣A=\begin{bmatrix} 3& -1& 2\\ 1& 5& 7\\ 2& 4& 5 \end{bmatrix}B=\begin{bmatrix} 7& 5& -2\\ 5& 1& 9\\ 4& 2& 1 \end{bmatrix},滿足矩陣方程A+2X=B,求未知矩陣X。

解:

X=\frac{1}{2}(B-A)=\frac{1}{2}(\begin{bmatrix} 7 & 5 &-2 \\ 5 & 1 &9 \\ 4 & 2 &1 \end{bmatrix}-\begin{bmatrix} 3 &-1 & 2\\ 1 & 5& 7\\ 2 & 4& 5 \end{bmatrix}) =\frac{1}{2}\begin{bmatrix} 4 & 6 & -4\\ 4 & -4 & 2\\ 2 & -2 & -4 \end{bmatrix}=\begin{bmatrix} 2 & 3 & -2\\ 2 & -2 & 1\\ 1 & -1 & -2 \end{bmatrix}

矩陣與矩陣的乘法:

1.運算規則

A=(a_{ij})_{m\times s},B=(b_{ij})_{s\times n}

則A與B的乘積C是這樣一個矩陣:

(1)行數與(左矩陣)A相同,列數與(右矩陣)B相同;

(2)C的第i行與第j列的元素c_{ij}由A的第i行元素與B的第j列元素對應相乘,現取乘積之和。

定義:設A爲m\times p的矩陣,B爲p\times n的矩陣,那麼稱m\times n的矩陣C爲矩陣A與B的乘積,記作C=AB,其中矩陣C中的第i行和第j列元素可以表示爲:(AB)_{ij}=\sum_{k=1}^{p}a_{ik}b_{kj}=a_{i1}b_{1j}+a_{i2}b_{2j}+...+a_{ip}b_{pj}

如下所示:

C=AB=\bigl(\begin{smallmatrix} 1 & 2 & 3\\ 4 & 5 & 6 \end{smallmatrix}\bigr)\begin{pmatrix} 1 & 4\\ 2 & 5\\ 3 & 6 \end{pmatrix}=\bigl(\begin{smallmatrix} 1\times 1+ 2\times 2+3\times3& 1\times4+2\times5+3\times6\\ 4\times1+5\times2+6\times3& 4\times4+5\times5+6\times6 \end{smallmatrix}\bigr)=\bigl(\begin{smallmatrix} 14 & 32\\ 32 & 77 \end{smallmatrix}\bigr)

D=BA=\begin{pmatrix} 1 & 4\\ 2 & 5\\ 3 & 6 \end{pmatrix}\bigl(\begin{smallmatrix} 1 & 2 & 3\\ 4 & 5 & 6 \end{smallmatrix}\bigr)=\begin{pmatrix} 1\times1+4\times4 &1\times2+4\times5 & 1\times3+4\times6\\ 2\times1+5\times4 & 2\times2+5\times5 & 2\times3+5\times6\\ 3\times1+6\times4 & 3\times2+6\times5 & 3\times3+6\times6 \end{pmatrix}=\begin{pmatrix} 17 & 22 & 27\\ 22 & 29 & 36\\ 27 & 36 & 45 \end{pmatrix}

2.運算性質(假設運算都是可行的)

(1)結合律:(AB)C=A(BC)

(2)分配律:A(B\pm C)=AB\pm AC(左分配律);(B\pm C)A=BA\pm CA(右分配律)

(3)(\gamma A)B=\gamma (AB)=A(\gamma B)

二、ColorMatrix色彩變換

對於色彩的存儲,Bitmap類使用一個32位的數值來保存,紅、綠、藍及透明度各佔8位,每個色彩分量的取值範圍是0~255。透明度爲0表示完全透明,爲255時色彩完全可見。

1.色彩信息的矩陣表示

由於一個色彩信息包含R、G、B、Alpha信息,所以,我們必然要使用一個四階色彩變換矩陣來修改色彩的每一個分量值。

注意:對於色彩變換矩陣,這裏的色彩順序是R、G、B、A,而不是A、R、G、B。

如果想將色彩(0,255,0,255)更改爲半透明,則可以使用下面的矩陣運算來表示:

上面使用四階矩陣完全可以改變圖片的RGBA值,但考慮一種情況:如果我們只想在原有的R色上增加一些分量呢?
這時,我們就得再多加一階來表示平移變換。所以,一個既包含線性變換又包含平移變換的組合變換稱爲仿射變換。使用四階色彩變換矩陣來修改色彩,只能對色彩的每個分量值進行乘(除)運算。如果要對這些分量值進行加減法運算(平移變換),則只能通過五階矩陣來完成。
考慮下面這個變換:
(1)紅色分量值更改爲原來的2倍。(2)綠色分量值增加100。
這個變換使用四階矩陣的乘法無法實現。所以,應該在四階色彩變換矩陣上增加一個“啞元座標”,來實現所列的矩陣運算。

  (在這個矩陣中,分量值用的是100)

2.Android中的色彩變換矩陣

在Android中,色彩變換矩陣的表示形式也是五階的。所以,在默認情況下,色彩變換矩陣的形式如下:

Android中的色彩變換矩陣是用ColorMatrix類來表示的。使用ColorMatrix類的方法如下:

ColorMatrix colorMatrix = new ColorMatrix(new float[]{
    1, 0, 0,   0, 0,
    0, 1, 0,   0, 0,
    0, 0, 1,   0, 0,
    0, 0, 0, 0.5, 0
}) ;
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));

3.示例:彩色圖片的藍色通道輸出

public class MyView extends View {
    private Paint mPaint = new Paint();
    private Bitmap bitmap;// 位圖
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint.setAntiAlias(true);
        // 獲取位圖
        bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.dog);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 繪製原始位圖
        canvas.drawBitmap(bitmap, null, new Rect(0, 0, 500, 500 * bitmap.getHeight() / bitmap.getWidth()), mPaint);
        canvas.translate(510, 0);
        // 生成色彩變換矩陣
        ColorMatrix colorMatrix = new ColorMatrix(new float[] {
        	0,0,0,0,0,
        	0,0,0,0,0,
        	0,0,1,0,0,
        	0,0,0,1,0
        });
        mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
        canvas.drawBitmap(bitmap, null, new Rect(0, 0, 500, 500 * bitmap.getHeight() / bitmap.getWidth()), mPaint);
    }
}

這裏分兩次繪製了一個Bitmap,先繪製了一個原始圖像,然後利用ColorMatrix類生成了一個僅包含藍色的圖像。用過Photoshop的讀者應該很清楚,這跟Photoshop中藍色通道的效果是一致的。 效果如下圖所示。

注意:不要在onDraw()函數裏面創建Paint對象,否則會造成內存回收,嚴重影響性能。

色彩的幾種運算方式:

1.色彩的平移運算

1)增加色彩飽和度

色彩的平移運算實際上就是色彩的加法運算,其實就是在色彩變換矩陣的最後一列加上某個值,這樣可以增加特定色彩的飽和度。

ColorMatrix colorMatrix = new ColorMatrix(new float[] {
	1, 0, 0, 0, 0,
	0, 1, 0, 0, 50,
	0, 0, 1, 0, 0,
	0, 0, 0, 1, 0
});

在綠色值上添加增量50,即增大綠色的飽和度。效果如下:

在應用ColorMatrix類後,圖片中每個像素的綠色值都增加了50,從小狗的臉上也可以看出來。

2)色彩反轉/反相功能

ColorMatrix colorMatrix = new ColorMatrix(new float[] {
   -1,  0,  0, 0, 255,
    0, -1,  0, 0, 255,
    0,  0, -1, 0, 255,
    0,  0,  0, 1, 0
});

2.色彩的縮放運算

1)調節高度

色彩的縮放運算其實就是色彩的乘法運算。將色彩變換矩陣對角線上分別代表R、G、B、A的幾個值分別乘以指定的值,就是所謂的縮放運算,如下圖所示。

我們可以針對某個顏色值進行放大/縮小運算。但是當對R、G、B、A同時進行放大/縮小運算時,就是對亮度進行調節。

將亮度增大1.2倍的代碼:

ColorMatrix colorMatrix = new ColorMatrix(new float[] {
	1.2f,    0,    0,    0, 0,
	   0, 1.2f,    0,    0, 50,
	   0,    0, 1.2f,    0, 0,
	   0,    0,    0, 1.2f, 0
});

2)通道輸出

由於在色彩變換矩陣中對角線上的數的取值範圍爲0~1,所以,當取0時,這個色彩就完全不顯示;當R、G都取0,而獨有B取1時,就只顯示藍色,所形成的圖像也就是我們通常所說的藍色通道。看一下幾個通道輸出的效果圖,如下圖所示。

紅色通道矩陣:

// 紅色通道矩陣
ColorMatrix colorMatrix = new ColorMatrix (new float [] {
	1, 0, 0, 0, 0,
	0, 0, 0, 0, 0,
	0, 0, 0, 0, 0,
	0, 0, 0, 1, 0
});
// 綠色通道矩陣
ColorMatrix colorMatrix = new ColorMatrix (new float [] {
	0, 0, 0, 0, 0,
	0, 1, 0, 0, 0,
	0, 0, 0, 0, 0,
	0, 0, 0, 1, 0
});
// 藍色通道矩陣
ColorMatrix colorMatrix = new ColorMatrix (new float [] {
	0, 0, 0, 0, 0,
	0, 0, 0, 0, 0,
	0, 0, 1, 0, 0,
	0, 0, 0, 1, 0
});

3.色彩的旋轉運算

RGB色是如何旋轉的呢?首先用R、G、B三色建立立體座標系,如下圖所示。

所以,我們可以把一個色彩值看成三維空間裏的一個點,色彩值的三個分量可以看成該點的座標(三維座標)。我們先不考慮在三個維度綜合情況下是怎麼旋轉的,來看看將某個軸作爲Z軸,在另外兩個軸形成的平面上旋轉的情況。下圖分析了將藍色軸作爲Z軸,僅在紅一綠平面上旋轉a度的情況。

(1)繞藍色軸旋轉\theta度。

 對應的色彩變換矩陣:

(2)繞紅色軸旋轉\theta度。

 對應的色彩變換矩陣:

(3)繞綠色軸旋轉\theta度。

 對應的色彩變換矩陣:

當圍繞紅色軸進行色彩旋轉時,由於當前紅色軸的色彩是不變的,而僅利用三角函數來動態變更綠色和藍色的顏色值,這種改變就叫作色相調節。當圍繞紅色軸旋轉時,是對圖片進行紅色色相的調節;當圍繞藍色軸旋轉時,是對圖片進行藍色色相的調節;當圍繞綠色軸旋轉時,是對圖片進行綠色色相的調節。

4.色彩的投射運算

先回過頭看看色彩變換矩陣運算的公式,如下:

在上式中,把紅色運算單獨標記出來,在運算中,它們就是利用G、B、A的顏色值的分量來增加紅色值的。
來看具體的運算:

注意:最終結果的220=0.2x100+1x200,可見綠色分量在原有綠色分量的基礎上增加了紅色分量值的0.2倍。利用其他色彩分量的倍數來更改自己色彩分量的值,這種運算就叫作投射運算。

在對下圖中陰影部分的值進行修改時,所使用的增加值來自其他色彩分量的信息。

應用一:黑白圖片

ColorMatrix colorMatrix = new ColorMatrix(new float[] {
    0.213f, 0.715f, 0.072f, 0, 0,
    0.213f, 0.715f, 0.072f, 0, 0,
    0.213f, 0.715f, 0.072f, 0, 0,
    0,      0,      0,      1, 0
});

首先了解一下去色原理:只要把R、G、B三通道的色彩信息設置成一樣,即R=G=B, 圖像就變成了灰色。並且,爲了保證圖像亮度不變,同一個通道中的R+G+B=1,如0.213+0.715+0.072=1。

下面談一下0.213、0.715、0.072這三個數字的由來。
按理說應該把R、G、B平分,都是0.3333333。三個數字應該是根據色彩光波頻率及色彩心理學計算出來的。

在作用於人眼的光線中,彩色光要明顯強於無色光。如果對一張圖像按RGB平分理論給圖像去色,人眼就會明顯感覺到圖像變暗了(當然可能有心理上的原因,也有光波的科學依據)。另外,在彩色圖像中能夠識別的一些細節也可能會丟失。

所以Google最終給出的顏色值就是上面的三個數字: 0.213、 0.715、0.072。我們在給圖像去色時保留了大量的G通道信息,使得圖像不至於變暗或者綠色信息不至於丟失。

應用二:色彩反色

利用色彩變換矩陣將兩個顏色反轉,這種操作就叫作色彩反色。比如,將紅色和綠色反色(紅綠反色),代碼如下:

ColorMatrix colorMatrix = new ColorMatrix(new float [] {
	0, 1, 0, 0, 0,
	1, 0, 0, 0, 0,
	0, 0, 1, 0, 0,
	0, 0, 0, 1, 0
}) ;

從色彩變換矩陣中可以看出,紅綠反色的關鍵在於,第一行用綠色來代替紅色,第二行用紅色來代替綠色。類似的可以有紅藍反色、綠藍反色等,對應矩陣難度不大,就不再細講了。

應用三:照片變舊

ColorMatrix colorMatrix = new ColorMatrix(new float[] {
	1/2f, 1/2f, 1/2f, 0, 0,
	1/3f, 1/3f, 1/3f, 0, 0,
	1/4f, 1/4f, 1/4f, 0, 0,
	0,    0,    0,    1, 0
});

ColorMatrix函數:

在Android中,ColorMatrix自帶一些函數,用來幫助我們完成調整飽和度、色彩旋轉等操作。

1.構造函數

ColorMatrix()
ColorMatrix(float[] src)
ColorMatrix(ColorMatrix src)

在這三個構造函數中,我們已經使用過第二個構造函數;至於第三個構造函數,就是利用另一個ColorMatrix實例來複制一個一樣的ColorMatrix對象。

2.設置和重置函數

public void set(ColorMatrix src)
public void set(float[] src)
public void reset()

上面的函數是設置和重置函數,重置後,對應的數組如下:

Set this colormatrix to identity:
[1 0 0 0 0  ←red vector
 0 1 0 0 0  ←green vector
 0 0 1 0 0  ←blue vector
 0 0 0 1 0] ←alpha vector

3.setSaturation()函數——設置飽和度

我們可以通過色彩的平移運算單獨增強R、G、B其中一個分量的飽和度,但當我們需要整體增強色彩飽和度時,需要如何來做呢?ColorMatrix提供了一個函數來整體增強色彩飽和度,如下:

// 整體增強色彩飽和度,即同時增強R、G、B的色彩飽和度
public void setSaturation(float sat)
● sat:表示把當前色彩飽和度放大的倍數。取值0時,表示完全無色彩,即黑白圖像;
       當取值1時,表示色彩不變動;當取值大於1時,顯示色彩過度飽和。

舉個例子:滑塊默認在1倍的位置,向左到底是0,向右到底是20 (飽和度放大20倍)。

public class MyActivity extends Activity {
    private SeekBar mSeekBar;
    private ImageView mImageView;
    private Bitmap mOriginBmp, mTempBmp;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mImageView = (ImageView) findViewById(R.id.img);
        mSeekBar = (SeekBar) findViewById(R.id.seekbar);
        mOriginBmp = BitmapFactory.decodeResource(getResources(), R.drawable.dog);
        mTempBmp = Bitmap.createBitmap(mOriginBmp.getWidth(), mOriginBmp.getHeight(), Bitmap.Config.ARGB_8888);

        mSeekBar.setMax(20);
        mSeekBar.setProgress(1);
        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                Bitmap bitmap = handleColorMatrixBmp(progress);
                mImageView.setImageBitmap(bitmap);
            }
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
            }
        });
    }

    private Bitmap  handleColorMatrixBmp(int progress){
        // 創建一個相同尺寸的可變的位圖區,用於繪製調色後的圖片
        Canvas canvas = new Canvas(mTempBmp); // 得到畫筆對象
        Paint paint = new Paint(); // 新建paint
        paint.setAntiAlias(true); // 設置抗鋸齒,也即是邊緣做平滑處理
        ColorMatrix mSaturationMatrix = new ColorMatrix();

        mSaturationMatrix.setSaturation(progress);

        paint.setColorFilter(new ColorMatrixColorFilter(mSaturationMatrix));// 設置顏色變換效果
        canvas.drawBitmap(mOriginBmp, 0, 0, paint); // 將顏色變化後的圖片輸出到新創建的位圖區
        // 返回新的位圖,也即調色處理後的圖片
        return mTempBmp;
    }
}

4.setScale()函數——色彩縮放

public void setScale(float rScale, float gScale, float bScale, float aScale)
● 4個參數分別對應R、G、B、A顏色值的縮放倍數。

比如,在小狗圖片中,綠色佔大部分,所以我們僅將綠色放大1.3倍。

canvas.drawBitmap(bitmap, null, new Rect(0, 0, 500, 500 * bitmap.getHeight() / bitmap.getWidth()), mPaint);
canvas.save();
canvas.translate(510, 0);
//生成色彩變換矩陣
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setScale(1, 1.3f, 1, 1);
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
canvas.drawBitmap(bitmap, null, new Rect(0, 0, 500, 500 * bitmap.getHeight() / bitmap.getWidth()), mPaint);

注:canvas.save()使得之前的位圖不會被translate(偏移)。我們知道每調用drawXXX,都產生一個新圖層。

5.setRotate()函數——色彩旋轉

上面在講解色彩旋轉運算時,列出了在色彩旋轉時的效果和原理。由於涉及正、餘弦函數的計算,而且這些公式推導起來具有一定的難度,所以Android 已經封裝好了色彩旋轉的函數。
 

public void setRotate(int axis, float degree)
● axis:將旋轉圍繞某一個顏色軸進行。
        取值有:0 圍繞紅色軸旋轉;1 圍繞綠色軸旋轉;2 圍繞藍色軸旋轉
● degree:旋轉的角度

來看一下當圍繞某一個顏色軸旋轉時色相變化的效果。

代碼與調節飽和度的代碼只有兩點不同。

(1)設置SeekBar的範圍
mSeekBar.setMax(360);
mSeekBar.setProgress(180);

(2)在handleColorRotateBmp()函數中處理當前progress
ColorMatrix colorMatirx = new ColorMatrix();
colorMatrix.setRotate(0, progress - 180);
paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));

     同理,圍繞綠色軸旋轉的效果爲:

ColorMatrix相乘:

public void setConcat(ColorMatrix matA, ColorMatrix matB)
● 函數接收兩個ColorMatrix矩陣matA和matB,乘法規則:matA×matB,將結果作爲當前ColorMatirx的值。
public void preConcat(ColorMatrix prematrix)
● 含義就是:當前矩陣A × prematrix
public void postConcat(ColorMatrix postmatrix)
● 含義就是:postmatrix × 當前矩陣A
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章