ITKeyword,专注技术干货聚合推荐

注册 | 登录

Android图像处理之色彩特效处理(学习笔记)

xiaoguiyixue 分享于

2020腾讯云双十一活动,全年最低!!!(领取3500元代金券),
地址https://cloud.tencent.com/act/cps/redirect?redirect=1073

2020阿里云最低价产品入口,含代金券(新老用户有优惠),
地址https://www.aliyun.com/minisite/goods

推荐:图像处理学习笔记之一

IplImage Mat cvMat 三大容器 。在opencv中Mat是更偏向于计算 以下转载 Key_Ky 转: CvMat,Mat和IplImage之间的转化和拷贝 1、CvMat之间的复制 //注意:深拷贝 -

彩色特效处理

1.色彩矩阵分析

在色彩处理中,通常用以下三个角度来描述一个图像。

  • 色调——物体传播的颜色
  • 饱和度——颜色的纯度,从0(灰)到100%(饱和)来进行描述
  • 亮度——颜色的相对明暗程度
    在Android中,系统使用一个颜色矩阵——ColorMatrix,来处理图像的这些色彩效果。Android中的颜色矩阵是一个4×5的数字矩阵,它用来对图片的色彩进行处理。而对于每个像素点,都有一个颜色分量矩阵用来保存颜色的RGBA值,如下图所示:
    A=∣∣∣∣∣∣afkpbglqchmrdinsejot∣∣∣∣∣∣      C=∣∣∣∣∣∣∣RGBA1∣∣∣∣∣∣∣
    在这个4×5的颜色矩阵中按以下方式划分。
  • 第一行的abcde值用来决定新的颜色值中的R——红色
  • 第二行的fghij值用来决定新的颜色值中的G——绿色
  • 第三行的klmno值用来决定新的颜色值中的B——蓝色
  • 第四行的pqrst值用来决定新的颜色值中的A——透明度
  • 矩阵A中的第五列——ejot值分别用来决定每个分量重的offset,即偏移量。

1.1 改变偏移量

A=∣∣∣∣∣∣10000100001000010000∣∣∣∣∣∣      A=∣∣∣∣∣∣100001000010000110010000∣∣∣∣∣∣
在这个矩阵中修改了R,G所对应的颜色偏移量,那么最后的处理结果就是图像的红色,绿色分量增加了100.而我们知道,红色混合绿色会得到黄色,所以使得整个图像的色调偏黄色。

1.2 改变颜色系数

A=∣∣∣∣∣∣10000200001000010000∣∣∣∣∣∣
在这个矩阵中,改变了G分量所对应的系数g,这样的矩阵运算后G分量会变成以前的两倍,最终效果就是图像的色调更加偏绿。

1.3 改变色光属性

图像的色调,饱和度,亮度这三个属性在图像处理中的使用非常多,因此颜色矩阵中,也封装了一些API来快速调用这些参数,而不用每次都去计算矩阵的值。
在Android中,系统封装了一个类——ColorMatrix,也就是说前面的颜色矩阵。通过这个类,可以很方便地改变矩阵值来处理颜色效果。

ColorMatrix colorMatrix = new ColorMatrix();
  • 色调
    Android系统提供了setRotate(int axis, float degree)来帮助我们设置颜色的色调。第一个参数,系统分别使用0、1、2来代表Red、Green、Blue三种颜色的处理;而第二个参数,就是需要处理的值,代码如下:
ColorMatrix hueMatrix = new ColorMatrix();
hueMatrix .setRotate(0,hue0);
hueMatrix .setRotate(1,hue1);
hueMatrix .setRotate(2,hue2);

通过这样的方法可以为RGB三种颜色分量分别重新设置不同的色调值。

  • 饱和度
    Android系统提供了setSaturation(float sat)方法来设置颜色的饱和度,参数代表设置颜色饱和度的值,当饱和度为0时,图像就变成灰度图像了。
ColorMatrix saturationMatrix=new ColorMatrix(); saturationMatrix.setSaturation(saturation);
  • 亮度
    当三原色以相同的比例进行混合的时候,就会显示出白色,系统正式使用这个原理来改变一个图像的亮度的,代码如下,当亮度为0时,图像就变成全黑了。
ColorMatrix lumMatrix=new ColorMatrix(); lumMatrix.setScale(lum,lum,lum,1);

除了单独使用上面三种方式来进行颜色效果的处理之外,Android系统还封装了矩阵的乘法运算。它提供了postConcat()方法来将矩阵的作用效果混合,从而叠加处理效果,代码如下:

ColorMatrix imageMatrix=new ColorMatrix();
imageMatrix.posConcat(hueMatrix);
imageMatrix.posConcat(saturationMatrix);
imageMatrix.posConcat(lumMatrix);

最后通过paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix))设置给paint,并使用这个画笔来绘制原来的图像,从而将颜色矩阵作用到原图上。

paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix));
canvas.drawBitmap(bitmap,0,0,paint);

2. 常用图像颜色矩阵处理效果

2.1 灰度效果

0.33f,0.59f,0.11f,0, 0,
0.33f,0.59f,0.11f,0, 0,
0.33f,0.59f,0.11f,0, 0,
0,    0,    0,    1, 0,

2.2 图像反转

-1, 0, 0, 1, 1,  0,-1, 0, 1, 1,
 0, 0,-1, 1, 1,
 0, 0, 0, 1, 0,

2.3 怀旧效果

0.393f,0.769f,0.189f,0, 0,
0.349f,0.686f.0.168f,0, 0,
0.242f,0.534f,0.131f,0, 0,
0,     0,     0,     0, 0,

2.4 去色效果

1.5f, 1.5f, 1.5f, 0, -1,
1.5f, 1.5f, 1.5f, 0, -1,
1.5f, 1.5f, 1.5f, 0, -1,
0,    0,    0,    1,  0,

2.5 高饱和度

 1.438f,-0.122f,-0.016f, 0,-0.03f,
-0.062f, 1.378f,-0.016f, 0, 0.05f,
-0.062f,-0.122f, 1.483f, 0,-0.02f
 0,      0,      0,      1, 0,

3. 像素点分析

作为更加精确的图像处理方式,可以通过改变每个像素点的具体ARGB值,来达到处理一张图像效果的目的。需要注意的是,传递进来的原始图片是不能修改的,一般根据原始图片生成一张新的图片来修改。
在Android中,系统提供了Bitmap.getPixels()方法来帮我们提取整个Bitmap中的像素点,并保存到一个数组中。

bitmap.getPixels(pixels,offset,stride,x,y,width,height);

这几个参数的含义如下:

  • pixels——接收位图颜色值的数组
  • offset——写入到pixels[]中的第一个像素索引值
  • stride——pixels[]中的行间距
  • x——从位图中读取的第一个像素的x坐标值
  • y——从位图中读取的第一个像素的y坐标值
  • width——从每一行中读取的像素宽度
  • height——读取的行数
    通常情况下,可以使用如下代码:
bitmap.getPixels(oldPx,0,bm.getWidth(),0,0,width,height);

接下来就可以获取每个像素具体的ARGB了,如下:

color=oldPx[i];
r=Color.red(color);
g=Color.green(color);
b=Color.blue(color);
a=Color.alpha(color);

当获取到具体的颜色值后,就可以通过相应的算法来修改它的ARGB值。如下老照片效果效果:

r1=(int)(0.393*r+0.769*g+0.189*b);
g1=(int)(0.349*r+0.686*g+0.168*b);
b1=(int)(0.272*r+0.534*g+0.131*b);

再通过如下代码将新的RGBA值合成像素点:

newPx[i]=Color.argb(a,r1,g1,b1);

最后通过如下代码,将处理后的像素点数组重新set给我们的Bitmap,从而达到图像处理的目的。

bitmap.setPixels(newPx,0,width,0,0,width,height);

4. 常用像素像素点处理效果

4.1 底片效果

若存在ABC3个像素点,要求B点对应的底片效果算法,代码如下:

B.r=255-B.r;
B.g=255-B.g;
B.b=255-B.b;

实现代码如下:

public static Bitmap handleImageNegative(Bitmap bm){
    int width = bm.getWidth();
    int height - bm.getHeight();
    int color;
    int r,g,b,a;

    Bitmap bmp=Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888);

    int[]oldPx=new int[width * height];
    int[]newPx=new int[width * height];
    bm.getPixels(oldPx,0,width,0,0,width,height);

    for(int i=0;i<width * height;i++){
        color=oldPx[i];
        r=Color.red(color);
        g=Color.green(color);
        b=Color.blue(color);
        a=Color.alpha(color);
        //
        r=255-r;
        g=255-g;
        b=255-b;

        if(r>255){
            r=255;
        }else if(r<0){
            r=0;
        }   
        if(g>255){
            g=255;
        }else if(g<0){
            g=0;
        }
        if(b>255){
            b=255;
        }else if(b<0){
            b=0;
        }
        newPx[i]=Color.argb(a,r,g,b);
    }
    bmp.setPixels(newPx,0,width,0,0,widht,height);
    return bmp;
}

4.2 老照片效果

求某像素点的老照片效果算法,代码如下:

r1=(int)(0.393*r+0.769*g+0.189*b);
g1=(int)(0.349*r+0.686*g+0.168*b);
b1=(int)(0.272*r+0.534*g+0.131*b);

4.3 浮雕效果

若存在ABC3个像素点,要求B点对应的浮雕效果算法,代码如下:

B.r=C.r-B.r+127;
B.g=C.g-B.g+127;
B.b=C.b-B.b+127;

5. 图形特效处理

5.1 Android变形矩阵——Matrix

对于图像的图形变换,Android系统也通过矩阵来进行处理。Android的图形变换矩阵是一个3×3的矩阵,如下:

A=∣∣∣∣adgbehcfi∣∣∣∣   C=∣∣∣∣XY1∣∣∣∣   R=∣∣∣∣X1Y11∣∣∣∣=AC
当使用变换矩阵去处理每一个像素点的时候,与颜色矩阵的矩阵乘法一样,计算公式如下:
X1=a×X+b×Y+cY1=d×X+e×Y+f1=g×X+h×Y+i
通常情况下,会让g=h=0,i=1,这样使1=g×X+h×Y+i恒成立。因此只需要着重关注上面几个参数就可以了。
图形初始矩阵和色彩变换初始矩阵一样:
∣∣∣∣100010001∣∣∣∣
图像的变形处理通常包含以下四类基本变换。

  • Translate——平移变换
  • Rotate——旋转变换
  • Scale——缩放变换
  • Skew——错切变换
5.1.1 平移变换
5.1.2 旋转变换
5.1.3 缩放变换
5.1.4 错切变换

矩阵变换规律:

∣∣∣∣ADGBEHCFI∣∣∣∣        ∣∣∣∣Scale_XSkew_Y0Skew_XScale_Y0Trans_XTrans_Y1∣∣∣∣

推荐:OpenCV 图像处理学习笔记(一)

解读IplImage结构 typedef struct _IplImage { int nSize;                    /* IplImage大小 */ int ID;                         /* 版本 (=0)*/ int nChann

  • A和E控制Scale——缩放变换
  • B和D控制Skew——错切变换
  • C和F控制Trans——平移变换
  • A、B、D、E共同控制Rotate——旋转变换

在图形变换矩阵中,同样是通过一个一维数组来模拟矩阵,并通过setValues()方法将一个一维数组转换为图形变换矩阵,代码如下:

float[] mImageMatrix=new float[9];
Matrix matrix=new Matrix();
matrix.setValues(mImageMatrix);

当获得变换矩阵后,就可以通过以下代码将一个图形以这个变换矩阵的形式绘制出来

canvas.drawBitmpa(mBitmap,matrix,null);

Android系统同样提供了一些API来简化矩阵的运算。Android中使用Matri类来封装矩阵,并提供了以下几个方法:

  • matri X.setRoate()——旋转变换
  • matri X.setTranslate()——平移变换
  • matri X.setScale()——缩放变换
  • matri X.setSkew()——错切变换
  • pre()和post()——提供矩阵的前乘和后乘运算

Matri类的set方法会重置矩阵中的所有值,而post和pre方法不会,这两个方法常用来实现矩阵的混合作用。不过要注意的是,矩阵运算并不满足交换律,所以矩阵乘法的前乘和后乘是两种不同的运算方式。例如:
(1)先平移到(300,100)
(2)再旋转45度
(3)最后平移到(200,200)
如果使用后乘运算,表示当前矩阵乘上参数代表的矩阵

matri X.setRotate(45);
matri X.postTranslate(200,200);

如果使用前乘运算,表示参数代表的矩阵乘上当前矩阵

matri X.setTranslate(200,200);
matri X.preRotate(45);

5.2 像素块分析

drawBitmapMesh()与操纵像素点来改变色彩的原理类似,只不过是把图像分成一个个的小块,然后通过改变每一个图像块来修改整个图像。代码如下:

drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[]verts, int vertOffset, int[]colors, int colorOffset, Paint paint)

关键参数如下:

  • bitmap:将要扭曲的图像
  • meshWidth:需要的横向网格数目
  • meshHeight:需要的纵向网格数目
  • verts:网格交叉点坐标数组
  • vertOffset:verts数组中开始跳过的(x,y)坐标对的数目

6. Android图像处理之画笔特效处理

6.1 PorterDuffXfermode

首先看一下API Demo中一张经典的图:
这里写图片描述
ProterDuffXfermode设置的是两个图层交集区域的显示方式,dst是先画的图形,而src是后画的图形。
经常用到DST_IN、SRC_IN模式来实现将一个矩形图片变成圆角或者圆形图片的效果。
如下:先用一个普通画笔画画一个遮罩层,再用带ProterDuffXfermode的画笔将图形画在罩层上,这样就可以通过上面所说的效果来混合两个图像了。

mBitmap=BitmapFactory.decodeResourse(getResoures(),R.drawable.test1);
mOut=Bitmap.createBitmap(mBitmap.getWidth(),mBitmap.getHeight(),Bitmap.Config.ARGB_8888);
Canvas canvas=new Canvas();
mPaint=new Paint();
mPaint.setAntiAlias(true);
canvas.drawRoundRect(0,0,mBitmap.getWidth(),mBitmap.getHeight(),80,80,mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(mBitmap,0,0,mPaint);

6.2 Shader

Shader又被称之为着色器、渲染器,它用来实现一系列的渐变,渲染效果。Android中的Shader包括以下几种。

  • BitmapShader——位图Shader
  • LinearGradient——线性Shader
  • RadialGradient——光束Shader
  • SweepGradient——梯度Shader
  • ComposeShader——混合Shader

除了第一个Shader以外,其他的Shader都比较正常,实现了名副其实的渐变,渲染效果。而与其他的Shader所产生的渐变不同,BitmapShader产生的是一个图像,这有点像Photoshop中的图像填充渐变,他的作用就是通过Paint对画布进行指定Binmap的填充,填充模式有三种:

  • CLAMP拉伸——拉伸的是图片最后的那一个像素,不断重复
  • REPEAT重复——横向,纵向不断重复
  • MIRROR镜像——横向不断翻转重复,纵向不断翻转重复
mBitmap=BitmapFactory.decodeResource(getResources(),R.drawable.test);
mBitmapShader=new BitmapShader(mBitmap,Shader.TileMode.CLAMP,Shader.TileMode.CLAMP);
mPaint=new Paint();
mPaint.setShader(mBitmapShader);
canvas.drawCircle(500,250,200,mPaint);
paint.setShader(new LinearGradient(0,0,400,400,Color.BLUE,Color.YELLOW,Shader.TileMode.REPEAT));
canvas.drawRect(0,0,400,400,paint);

6.3 PathEffect

  • CornerPathEffect——拐角处变得圆滑
  • DiscretePathEffect——线段上产生许多杂点
  • DashPathEffect——虚线
  • PathDashPathEffect——和DashPathEffect类似,在它的基础上,可以设置显示点的图形,即方形点的虚线,圆形点的虚线
  • ComposePathEffect——将任意两种路径特性组合起来形成新的效果
mEffects[0]=null;
mEffects[1]=new CornerPathEffect();
mEffects[2]=new DiscretePathEffect(); 
mEffects[3]=new DashPathEffect(); 
Path path=new Path();
path.addRect(0,0,8,8,Path.Direction.CWW);
mEffects[4]=new PathDashPathEffect(path,12,0,PathDashPathEffect.Style.TPTATE);
mEffects[5]=ComposePathEffect(mEffect[3],mEffects[1]);
for(int i=0;i<mEffects.length;i++){
    mPaint.setPathEffect(mEffects[i]);
    canvas.drawPath(mPath,mPaint);
    canvas.translate(0,200);
}

7. View的孪生兄弟——SurfaceView

7.1 SurfaceView和View的区别

View通过刷新来重绘视图,Android系统通过发出VSYNC信号来进行屏幕的重绘,刷新的间隔时间为16ms。如果在16ms内View完成你所需要执行的所有操作,那么用户在视觉上,就不会产生卡顿的感觉;而如果执行的操作逻辑太多,特别是需要频繁刷新的界面上,例如游戏界面,那么就不会不断阻塞主线程,从而导致画面卡顿。很多时候在自定义View的Log中经常会看到如下警告:

“Skipped 47 frames!The application may be doing too much work on its main thread"

这些警告的产生很多情况下就是因为在绘制过程中,处理的逻辑太多造成的。
因此,Android系统提供了SurfaceView组建来解决这个问题。SurfaceView可以说是View的孪生兄弟,但它与View还是有所不同的,它们的区别主要体现在一下几点:

  • View主要适用于主动更新的情况下,而SurfaceView主要使用与被动更新,例如频繁的刷新。
  • View在主线程中对画面进行刷新,而SurfaceView通常会通过一个子线程来进行页面的刷新。
  • View在绘图时没有使用双缓冲机制,而SurfaceView在底层实现机制中就已经实现了双缓冲机制。

7.2 SurfaceView的使用

● 创建SurfaceView

创建自定义的SurfaceView继承自SurfaceView,并实现两个接口——SurfaceHolder。Callback和Runnable,代码如下:

public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable

通过实现这两个接口没就需要在自定义的SurfaceView中实现接口的方法:

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
    }

分别对应SurfaceView的创建,改变和销毁过程。

对于Runnable接口,需要实现run()方法,代码:

    @Override
    public void run() {
    }

●初始化SurfaceView

通常需要定义以下三个成员变量,代码如下:

//SurfaceHolder
private SurfaceHolder mHolder;
//用于绘图的Canvas
private Canvas mCanvas;
//子线程标志位
private boolean mIsDrawing;

初始化方法中对SurfaceHolder初始化,并注册SurfaceHolder的回调方法。

mHolder = getHolder(); mHolder.addCallback(this);

在SurfaceView中没我们也通过Canvas来进行绘图,而另一个标志位,则是用来控制子线程的。

●使用SurfaceView

通过SurfaceHolder对象的lockCanvas()方法,就可以获得当前的Canvas绘图对象。获取到的Canvas对象还是继续上次的Canvas对象,而不是一个新的对象。因此之前的绘图操作都将被保留,如果需要擦除,则可以在绘制前,通过drawColor()方法进行清屏操作。
绘制的时候,充分利用SurfaceView的三个回调方法,在SurfaceCreate()方法中开启子线程进行绘制,而子线程使用一个while(mIsDrawing)的循环来不停的进行绘制,而在绘制的具体逻辑中,通过lockCanvas()方法获取Canvas对象进行绘制,并通过unlockCanvasAndPost(mCanvas)方法对画布内容进行提交。整个SurfaceView的末班代码如下:

package com.example.hp.dragviewgroup;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/** * Created by hp on 2016/1/20. */
public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable {
    //SurfaceHolder
    private SurfaceHolder mHolder;
    //用于绘图的Canvas
    private Canvas mCanvas;
    //子线程标志位
    private boolean mIsDrawing;

    public SurfaceViewTemplate(Context context) {
        super(context);
        initView();
    }

    public SurfaceViewTemplate(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    private void initView() {
        mHolder = getHolder();
        mHolder.addCallback(this);
        setFocusable(true);
        setFocusableInTouchMode(true);
        //控制是否在屏幕应保持修改的值。
        this.setKeepScreenOn(true);
// mHolder.setFormat(PixelFormat.OPAQUE);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mIsDrawing = true;
        new Thread(this).start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mIsDrawing = false;
    }

    @Override
    public void run() {
        while (mIsDrawing) {
            draw();
        }
    }

    private void draw() {
        try {
            mCanvas = mHolder.lockCanvas();
            //draw something
        } catch (Exception e) {

        } finally {
            if (mCanvas != null) {
                mHolder.unlockCanvasAndPost(mCanvas);
            }
        }
    }
}

推荐:c#与图像处理学习笔记(一)

http://blog.ednchina.com/cola123/203676/message.aspx# 1、  打开一幅图像 首先加入一个PictureBox 控件pictureBox1,然后打开一幅图像在该控件中显示:   pri

彩色特效处理 1.色彩矩阵分析 在色彩处理中,通常用以下三个角度来描述一个图像。 色调——物体传播的颜色 饱和度——颜色的纯度,从0(灰)到100%(饱和)来进行描述 亮度——颜色的相对明暗程

相关阅读排行


相关内容推荐

最新文章

×

×

请激活账号

为了能正常使用评论、编辑功能及以后陆续为用户提供的其他产品,请激活账号。

您的注册邮箱: 修改

重新发送激活邮件 进入我的邮箱

如果您没有收到激活邮件,请注意检查垃圾箱。