计算机视觉-OpenCV-Android---Mat与Bitmap对象(创建、初始化、使用与转换---附大量demo)

571 阅读18分钟

内容提要: 图像深度:表示每个通道灰度值所占的大小,大小即占几个位,是什么数据类型图像类型:基于图像深度表述的信息,多了一个尾缀Cx,表示图像的通道数x个;

####1. Mat对象

  • Mat是OpenCV中用来存储图像信息的内存对象
  • 当通过Imgcodecs.imread()方法从文件读入一个图像文件时,imread方法就会返回Mat对象实例;
  • 或者通过Utils.bitmatToMat()方法由Bitmap对象转换得到Mat对象实例。

下图形象地展示了一张图像中的各个像素点数据是如何存储的, 因为图像本身的像素点比较多,下图显示的图像像素数据只是图片左上角20×20大小的部分数据:

#####1.1 加载图片与读取基本信息

  • 从Android系统中选择一张图像时,可以使用如下代码将图像文件加载为Mat对象:
Mat src = Imgcodecs.imread(fileUri.getPath()); 

- OpenCV通过imread来加载图像,```默认加载```的是三通道顺序为```BGR```的彩色图像; 还可以通过以下代码来指定加载为彩色图像:```(比上一句多了第二个参数)``` ``` Mat src = Imgcodecs.imread(fileUri.getPath(), Imgcodecs.IMREAD_COLOR) ``` 如上这句代码, 第一个参数表示文件路径; 第二个参数表示```加载图像类型```,最常见的类型有如下几种: - IMREAD_UNCHANGED= -1,表示```不改变加载图像类型```,可能包含```透明通道```。 - IMREAD_GRAYSCALE= 0,表示加载图像为```灰度图像```。 - IMREAD_COLOR= 1,表示加载图像为```彩色图像```。
使用如下代码从```Mat对象```中得到图像的```宽、高、维度、通道数、深度、类型信息```: ``` int width = src.cols(); int height = src.rows(); int dims = src.dims(); int channels = src.channels(); int depth = src.depth(); int type = src.type(); ``` 其中要特别关注```通道数、图像深度与图像类型、OpenCV加载的Mat类型图像对象```。 - ```常见的通道数目```有```1、3、4```,分别对应于```单通道、三通道、四通道```,其中```四通道```中通常会有```透明通道的数据```; - ```图像深度```表示```每个通道灰度值所占的大小```,图像深度与类型密切相关; OpenCV中常见的几种```图像深度```:![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/70ccd17d07c04626850def4c519a6f41~tplv-k3u1fbpfcp-zoom-1.image) - ```U```表示```无符号整型```; - ```S```表示```符号整型```; - ```F```表示```浮点数```; 这些类型在```CvType```中可以自己查看。OpenCV中常见的```图像类型```如下:![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e7bf1bf1d7d54e2a9367acf048d0e89d~tplv-k3u1fbpfcp-zoom-1.image)![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b84528eb01144f81a7807a821d8b05c3~tplv-k3u1fbpfcp-zoom-1.image)

当调用imread函数时, 如果只使用文件路径参数读入加载一张图像,则默认值三通道CV_8UC3图像深度CV_8U

其中:

  • CV表示计算机视觉;
  • 8表示八位;
  • UC表示无符号char;
  • 3表示三个通道。

在如上的七行类型表中,每个类型都可以做类似的解读; 也可以看出CV_8U就是图像深度,所以图像类型与深度之间是有直接关系的。

#####1.2 Mat创建与初始化

  • 综上,Mat对象中包含了图像的各种基本信息与图像像素数据;
  • Mat是由头部数据部分组成的,其中头部还包含一个指向数据的指针
  • 在OpenCV4Android的接口封装中,因为Java层面没有指针对象,因此全部用数组来替代;
  • 但是,当我们需要把Mat对象传到JNI层的时候, 可以通过getNativeObjAddr()方法来实现Mat对象从Java层C++层指针传递;

如图是Mat在内存中的结构:

  • 创建Mat对象的方法有很多种,如下几种最常见: 1)通过create方法创建:
Mat m1 = new Mat();
m1.create(new Size(3, 3), CvType.CV_8UC3);
Mat m2 = new Mat();
m2.create(3, 3, CvType.CV_8UC3);

上述代码创建两个Mat对象——m1m2,它们的大小都是3×3、类型都是三通道8位的无符号字符型。
2)通过ones、eye、zeros方法初始化创建:

Mat m3 = Mat.eye(3, 3,CvType.CV_8UC3);
Mat m4 = Mat.eye(new Size(3, 3),CvType.CV_8UC3);
Mat m5 = Mat.zeros(new Size(3, 3), CvType.CV_8UC3);
Mat m6 = Mat.ones(new Size(3, 3), CvType.CV_8UC3);

上述代码创建了m3、m4、m5、m6四个Mat对象,基于这种初始化方式来得到Mat对象是OpenCV借鉴了Matlabeye、zeros、ones三个函数实现的。
3)先定义Mat,然后通过setTo方法实现初始化:

Mat m7 = new Mat(3, 3, CvType.CV_8UC3);
m7.setTo(new Scalar(255, 255, 255));

此方法与第一种方法有点类似,区别在于第一种方法通过create初始化时没有指定颜色值。 在OpenCV中,颜色向量通常用Scalar表示,这里Scalar(255,255,255)表示白色
4)通过Mat的copyTo()clone()实现对象的创建, Mat中的克隆与拷贝方法会复制一份完全相同的数据以创建一个新的Mat对象,
克隆相关代码如下:

Mat m8 = new Mat(500, 500, CvType.CV_8UC3);
m8.setTo(new Scalar(127, 127, 127));
Mat cmat = image.clone();

拷贝的相关代码如下:

at m8 = new Mat(500, 500, CvType.CV_8UC3);
m8.setTo(new Scalar(127, 127, 127));
Mat result = new Mat();
m8.copyTo(result)


#####1.3 Mat对象保存 创建好的Mat对象经过一系列的操作之后,就可以通过OpenCV4Android的imwrite函数直接将对象保存为图像:

// 创建Mat对象并保存
Mat image = new Mat(500, 500, CvType.CV_8UC3);
image.setTo(new Scalar(127, 127, 127));
ImageSelectUtils.saveImage(image);

其中: 500表示图像的宽度与高度,vType.CV_8UC3声明图像是RGB彩色三通道图像、每个通道都是8位; 第二行代码是指定图像的每个像素点、每个通道的灰度值为127; 第三行代码是使用imwrite将图像保存到手机中的指定目录下;

saveImage方法内容如下:

File fileDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), "mybook");
if(!fileDir.exists()) {
  fileDir.mkdirs();
}
String name = String.valueOf(System.currentTimeMillis()) + "_book.jpg";
File tempFile = new File(fileDir.getAbsoluteFile()+File.separator, name);
Imgcodecs.imwrite(tempFile.getAbsolutePath(), image);

上面的前几行代码是创建目录与文件路径, 最后一行代码通过imwrite来实现文件的保存, 保存图像的格式取决于文件路径为图像指定的扩展名类型(如代码中的.jpg)。



####2. Android中的Bitmap对象 其实Android系统中有一个与Mat对象相似的对象Bitmap。 通过它可以获取图像的常见属性、像素数据,修改图像的像素数据,呈现出不同的图像显示效果,保存图像,等等。


#####2.1 图像文件与资源加载 在Android系统中, 可以把给定图像的文件路径或者图像资源ID作为参数, 通过调用API来实现文件加载,使目标图片成为一个Bitmap实例对象。

最常见的加载资源图像的方法:

Bitmap bm = BitmapFactory.decodeResource(this.getResources(), R.drawable.lena);

加载图像文件时,为了避免OOM问题,

  • 首先应该获取图像的大小,
  • 然后根据图像大小进行适当的降采样,
  • 之后再加载为Bitmap对象:
private void displaySelectedImage() {
        if(fileUri == null) return;
        ImageView imageView = (ImageView)this.findViewById(R.id.sample_img);

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;

        BitmapFactory.decodeFile(fileUri.getPath(), options);
        int w = options.outWidth;
        int h = options.outHeight;
        int inSample = 1;
        if(w > 1000 || h > 1000) {
            while(Math.max(w/inSample, h/inSample) > 1000) {
                inSample *=2;
            }
        }

        options.inJustDecodeBounds = false;
        options.inSampleSize = inSample;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;

        Bitmap bm = BitmapFactory.decodeFile(fileUri.getPath(), options);
        imageView.setImageBitmap(bm);
    }


#####2.2 读写像素 对Bitmap对象,首先可以通过相关的API查询到图像的长、宽、配置信息; 在Bitmap中,像素数据是最占内存的部分; 根据长、宽与配置信息可以计算出图像像素的大小为多少;

读取像素时,

  • 可以定义一个数组用于存储一次性读出的像素数组;
  • 也可以通过每次读取一个像素点的方式来循环读取。

Bitmap获取图像宽、高与配置信息的接口代码如下:

public final int getWidth()
public final int getHeight()
public final Config getConfig()

其中,Config是Java中的枚举类型, 当前Android支持的Bitmap像素存储类型具体如下:

Bitmap.Config.ALPHA_8;
Bitmap.Config.ARGB_4444;
Bitmap.Config.RGB_565;
Bitmap.Config.ARGB_8888;

默认情况下,Bitmap是在RGB色彩空间。 其中:

  • A表示透明通道;
  • R表示红色通道;
  • G表示绿色通道;
  • B表示蓝色通道。

其中ALPHA_8表示该图像只有透明通道而没有颜色通道,是一张透明通道图像, 这种图像通常会被用作mask图像。

上述代码参数具体分析如下:

·ARGB_4444:表示每个通道占四位,总计两个字节,表示一个像素的图像。
·ARGB_8888:表示每个通道占八位,总计四个字节,表示一个像素的图像,这个是最常见的。
·ARGB_565:表示每个通道分别占5位、6位、5位,总计两个字节,表示一个像素的图像。

在Bitmap中循环```读取每个像素每个通道```并```修改```的代码如下: ``` public void getBitmapInfo() { Bitmap bm = BitmapFactory.decodeResource(this.getResources(), R.drawable.lena); int width = bm.getWidth(); int height = bm.getHeight(); Bitmap.Config config = bm.getConfig();
    int a=0, r=0, g=0, b=0;
    for(int row=0; row<height; row++) {
        for(int col=0; col<width; col++) {
            // 读取像素
            int pixel = bm.getPixel(col, row);
            a = Color.alpha(pixel);
            r = Color.red(pixel);
            g = Color.green(pixel);
            b = Color.blue(pixel);
            // 修改像素
            r = 255 - r;
            g = 255 - g;
            b = 255 - b;
            // 保存到Bitmap中
            bm.setPixel(col, row, Color.argb(a, r, g, b));
        }
    }
这种方式每次只```读取```一个像素点的颜色值,然后```修改``````设置```的方法,
会造成对Bitmap对象的```频繁访问,效率低下```。

<br>
在```DVM内存不紧张```的时候,应该选择:
- ```开辟一块像素缓冲区```,
- ```一次性读取全部像素作为数组```,
- 然后```循环数组```,```访问```每个像素点,
- ```修改```完成之后再```重新设回```Bitmap对应的像素数据中,

这种方法```速度很快```,也更为```常见```。
实现代码如下:

private void scanPixelsDemo() { Bitmap bm = BitmapFactory.decodeResource(this.getResources(), R.drawable.lena).copy(Bitmap.Config.ARGB_8888, true); int width = bm.getWidth(); int height = bm.getHeight(); Bitmap.Config config = bm.getConfig();

    int[] pixels = new int[width*height];
    bm.getPixels(pixels, 0, width, 0, 0, width, height);
    int a=0, r=0, g=0, b=0;
    int index = 0;
    for(int row=0; row<height; row++) {
        for(int col=0; col<width; col++) {
            // 读取像素
            index = width*row + col;
            a=(pixels[index]>>24)&0xff;
            r=(pixels[index]>>16)&0xff;
            g=(pixels[index]>>8)&0xff;
            b=pixels[index]&0xff;
            // 修改像素
            r = 255 - r;
            g = 255 - g;
            b = 255 - b;
            // 保存到Bitmap中
            pixels[index] = (a << 24) | (r << 16) | (g << 8) | b;
        }
    }

    bm.setPixels(pixels, 0, width, 0, 0, width, height);

    ImageView iv = (ImageView)this.findViewById(R.id.matInfo_imageView);
    iv.setImageBitmap(bm);
    bm.recycle();

}
>关于上述代码读取保存部分代码的补充解析:
>- 初定义时,```itmap.Config.ARGB_8888```,也即每个像素点有```8 * 4 = 32个bit```;
其中ARGB四个通道各用8个bit表示,依序排列;
>- ```0xff```,刚好为```8bit```,因为```每位16进制数等于4位bit```;
>- 类似```a=(pixels[index]>>24)&0xff;``````右移```操作,
意义在于```截取``````8 * 4 = 32```个bit中各自的```8bit有效位```,
接着同0xff```相与```1位为1保留,0位为0保留;![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0542f49bf4794edb86c23a5ef66e5b07~tplv-k3u1fbpfcp-zoom-1.image)
>
>- 各个通道各自的8bit有效位```计算(修改)```完毕之后,
再将各通道相与结果```左移```对应的位数,
最后统一```相或```,则得到```修改后的一个像素点的32个bit```;![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/46d34a45b2fc46ae90f23e4cab82f915~tplv-k3u1fbpfcp-zoom-1.image)


<br>

---
#####2.3 释放内存
- ```创建```与使用```Bitmap```对象完成```读写```像素数据操作之后,
需要调用```bm.recycle()```释放已经不再需要使用Bitmap对象的内存空间;

- 对创建的Mat对象来说,当使用完之后,需要调用```release()```来释放内存,
否则在进行批量图像处理或者视频处理时,
会很容易因为Mat对象的大量创建而不释放导致内存问题与APP崩溃。

<br>

---
####3.  基础形状绘制与填充
- 使用OpenCV做```对象检测、对象识别```程序开发,很多场景下,需要在```输出图像上``````处理结果```加上```醒目的轮廓```或者以```边框矩形绘制```或者```颜色填充```,这个就需要学会图形绘制相关API的使用。
- 常见的绘制包括矩形、圆形、椭圆、直线、还有文本文字。
- 无论是```Android Canvas```还是```OpenCV SDK```,它们本身都已经提供了这些简单绘制API的支持。
---
#####3.0 **首先是OpenCV是在Mat图像上绘制与填充**
```OpenCV2.xAndroid SDK```图形绘制是在```Core```模块中,
到了```OpenCV3.x```中,图形绘制就已经移到```Imgproc```这个模块中了。
---
#####3.1 在Mat上绘制基本几何形状与文本
```Mat```上绘制的基本几何形状包括```矩形、直线、圆、椭圆```,还有```文本文字```。
下面是绘制这几个形状```相关的API```说明:

1. ```line(Mat img,Point pt1,Point pt2,Scalar color,int thickness,int lineType,int shift)```表示绘制```直线```,
- ```最后三个参数```可以不填,```默认值```分别为```1、8、0```,表示```绘制宽度```1个像素、```绘制方法```8邻域、```位置偏移```0```后面的API方法若无特别解释,则最后的这三个参数基本含义都是一样的。```
- ```前面的四个参数```分别解释如下:
```img```:传入一个Mat对象,表示绘制对象是在Mat图像上,```后面几个API方法同理``````pt1```:表示直线```起始点```的屏幕坐标。
```pt2```:表示直线```终点```的屏幕坐标。
```color```:表示```直线的颜色```,假设三通道的顺序为BGR,则new Scalar(00255)表示红色。
---
2. ```rectangle(Mat img,Point pt1,Point pt2,Scalar color,int thickness,int lineType,int shift)```
绘制矩形跟绘制直线的方法参数极其类似,主要是两个坐标点参数含义不一样:
```pt1```:表示矩形```左上角点```的屏幕坐标;
```pt2```:表示矩形```右下角点```的屏幕坐标;
---
3. ```circle(Mat img,Point center,int radius,Scalar color,int thickness,int lineType,int shift)```
```img```:同上。
```center```:表示```圆的中心点```位置的```屏幕坐标```,单位是```像素``````radius```:表示```圆的半径大小```,单位是```像素``````color```:表示圆的```颜色```。
---
4. ```ellipse(Mat img,Point center,Size axes,double angle,double startAngle,double endAngle,Scalar color,int thickness,int lineType,int shift)```
- 绘制椭圆与上述API相比多了几个参数,绘制```椭圆```或者```弧长```的时候需要指定```开始与结束的角度``````长轴与短轴大小``````中心位置```等信息;
```img```:同上;
```center```:表示椭圆的```中心位置点```屏幕坐标。
```axes```:表示椭圆的```长轴``````短轴```大小,单位是```像素```;
    需传入的是一个Size数据对,如new Size(100, 50);
```angle```:表示```旋转角度```,通常```angle = endAngle – startAngle``````startAngle``````开始角度```大小。
```endAngle``````结束角度```大小。
```color```:表示椭圆的```颜色```。
---
5. ```putText(Mat img,String text,Point org,int fontFace,double fontScale,Scalar color,int thickness)```
- 表示在Mat图像上绘制文本文字,
```OpenCV```的默认情况是```不支持中文文本绘制显示```的,如果想要显示中文信息,可以```切换``````Bitmap```对象然后```绘制``````img```:同上。
```text```:表示要显示的```文本``````org```:表示```开始位置点```屏幕坐标。
```fontFace```:表示```字体类型``````fontScale```:表示```字体大小``````color```:表示文字的```颜色``````thickness```:表示文字绘制的宽度,默认大小为1。
---
另外补充:
- OpenCV会根据```thickness的值```来决定是进行```填充```还是只做```描边绘制```;
在上述矩形、圆、椭圆的绘制方法中,如果想要把绘制方式改为```填充```,只需要设置参数```thickness=-1```即可;

- 参数```lineType```则表示```绘制线段类型```,默认情况下是8,表示八邻域绘制方式;
```lineType```共有三种方式分别如下。

·LINE_4:表示绘制线段的时候使用四邻域填充方法。 ·LINE_8:表示绘制线段的时候使用八邻域填充方法。 ·LINE_AA:表示绘制线段的时候使用反锯齿填充方法。


下面创建一个```500×500px```大小的Mat对象,类型是```CV_8UC3```,
然后在上面的API实际操作练习一下:

private void basicDrawOnMat() { //创建Mat对象 Mat src = Mat.zeros(500, 500, CvType.CV_8UC3);

    //开始绘制
    Imgproc.ellipse(src, new Point(250, 250), new Size(100, 50),
            360, 0, 360, new Scalar(0, 0, 255), 2, 8, 0);

    Imgproc.putText(src, "Basic Drawing Demo", new Point(20, 20),
            Core.FONT_HERSHEY_PLAIN, 1.0, new Scalar(0, 255, 0), 1);
    Rect rect = new Rect();
    rect.x = 50;
    rect.y = 50;
    rect.width = 100;
    rect.height = 100;
    Imgproc.rectangle(src, rect.tl(), rect.br(), //矩形
            new Scalar(255, 0, 0), 2, 8, 0);
    Imgproc.circle(src, new Point(400, 400), 50,
            new Scalar(0, 255, 0), 2, 8, 0);
    Imgproc.line(src, new Point(10, 10), new Point(490, 490),
            new Scalar(0, 255, 0), 2, 8, 0);
    Imgproc.line(src, new Point(10, 490), new Point(490, 10),
            new Scalar(255, 0, 0), 2, 8, 0);
    //绘制完毕

    //创建一个同Mat一样大小Bitmap对象
    Bitmap bm = Bitmap.createBitmap(src.cols(), src.rows(), Bitmap.Config.ARGB_8888);

    Mat dst = new Mat();//准备一个Mat缓冲变量
    Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2RGBA);//把三通道的Mat对象(即src)转化成四通道的Mat对象赋到dst上
    Utils.matToBitmap(dst, bm);//dst转换成Bitmap对象

    ImageView iv = (ImageView)this.findViewById(R.id.matInfo_imageView);
    iv.setImageBitmap(bm);
}
运行效果如下:
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f92d02f03e07496986b5397f406a8a03~tplv-k3u1fbpfcp-zoom-1.image)
---
#####3.2 在Canvas上绘制基本几何形状与文本
Android中在```Bitmap```上绘制```几何形状与文本对象```,要借助```Canvas```相关API实现;
- 首先准备好一个```Bitmap对象```;
- 再用准备好的```Bitmap对象```作为```构造函数的参数```构造出一个```Canvas对象```,
- 然后使用Canvas的绘制API完成```颜色``````风格```的设置,
- Canvas绘制颜色与风格设置都是通过```Paint```对象来完成的;

像这样首先创建Paint实例,然后设置颜色与风格:

Paint p = new Paint(); p.setColor(Color.GREEN); p.setStyle(Paint.Style.STROKE)

常见的```风格```还包括如下几种:

·Paint.Style.STROKE:描边。 ·Paint.Style.FILL:填充。 ·Paint.Style.FILL_AND_STROKE:填充与描边。


设置好```Paint```之后就可以开始绘制了:

// 绘制直线 canvas.drawLine(10, 10, 490, 490, p); canvas.drawLine(10, 490, 490, 10, p); // 绘制矩形 android.graphics.Rect rect = new android.graphics.Rect(); rect.set(50, 50, 150, 150); // 矩形左上角点,与右下角点坐标 canvas.drawRect(rect, p); // 绘制圆 p.setColor(Color.GREEN); canvas.drawCircle(400, 400, 50, p); // 绘制文本 p.setColor(Color.RED); canvas.drawText("Basic Drawing on Canvas", 40, 40, p);

绘制方法全文(```canvas绘制的内容都会映射在绑定的Bitmap上```):

private void basicDrawOnCanvas() { // 创建Bitmap对象 Bitmap bm = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888);

    // 创建画布与画笔风格
    Canvas canvas = new Canvas(bm);

    Paint p = new Paint();
    p.setColor(Color.BLUE);
    p.setStyle(Paint.Style.FILL_AND_STROKE);

    // 绘制直线
    canvas.drawLine(10, 10, 490, 490, p);
    canvas.drawLine(10, 490, 490, 10, p);

    // 绘制矩形
    android.graphics.Rect rect = new android.graphics.Rect();
    rect.set(50, 50, 150, 150); // 矩形左上角点,与右下角点坐标
    canvas.drawRect(rect, p);

    // 绘制圆
    p.setColor(Color.GREEN);
    canvas.drawCircle(400, 400, 50, p);

    // 绘制文本
    p.setColor(Color.RED);
    canvas.drawText("Basic Drawing on Canvas", 40, 40, p);

    // 显示结果
    ImageView iv = (ImageView)this.findViewById(R.id.matInfo_imageView);
    iv.setImageBitmap(bm);
    bm.recycle();//!!!!!!!!!!!!!!!!!!!!!!!!!!
}
>综上,
Android中提供的基于```Canvas的API```完整地实现了图形绘制功能,
当用OpenCV在Android中做开发时,若需绘制```复杂的几何图形或中文文字```,
优先选择本地```Canvas API```来完成。

<br><br>

---
####4. Mat与Bitmap的使用与转换
- 在Android中使用OpenCV来完成应用开发时经常需要在```Mat对象``````Bitmap对象```之间```相互切换```;
- ```Bitmap``````Android```中的```图像对象``````Ma```t作为```OpenCV```中表示图像的```内存容器```;
---
#####4.1 Mat与Bitmap相互转换
第一种情况:
- 通过```图像对象通道```,即OpenCV的```imread()```读取得到```Mat对象```;
- 或者通过```Mat类```初始化创建的```Mat对象```;

将这样的```Mat对象```转换为```Bitmap对象```的情况;

可以参考以下实例代码处理这种情况:
private void mat2BitmapDemo(int index) {
    Mat src = Imgcodecs.imread(fileUri.getPath());//通过imread读取返回的Mat对象
    int width = src.cols();
    int height = src.rows();

    Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);将```图像Bitmap```加载为```ARGB_8888```方式,

    Mat dst = new Mat();//准备一个Mat缓冲变量
    Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2RGBA);//把三通道的Mat对象(即src)转化成四通道的Mat对象赋到dst上
    Utils.matToBitmap(dst, bm);//dst转换成Bitmap对象
    dst.release();//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    ImageView iv = (ImageView)this.findViewById(R.id.matInfo_imageView);
    iv.setImageBitmap(bm);
}
>其中:
>- ```Utils.matToBitmap()```来自```OpenCV4Android SDK``````Util```包,
包中还有另外一个与它相对应的方法```Utils.bitmapToMat()```,
通过它们就可以实现```Bitmap与Mat的相互转换```。
>
>- ```Bitmap的类型``````ARGB_8888```,
而```OpenCV``````加载图像默认的类型``````BGR```,
所以需要通过```cvtColor()```转换为```RGBA四通道图像```之后,
再调用mat与Bitmap的相互转换方法(```matToBitmap()```)。
>
>否则的出现```通道顺序不正确```,会导致```图像显示颜色异常```。

第二种情况更为```常见```:
通常地,
- 通过Android本地的API```创建```或者```初始化加载图像``````Bitmap对象```;
(为简化起见,《OpenCV Android 开发实战》一书中默认加载Bitmap对象类型为ARGB_8888),
- ```Bitmap对象```传递到```OpenCV```中转换为```Mat对象```;
- 处理完成之后再将这```Mat对象```重新转回```Bitmap对象```;
- 最后通过ImageView显示。

可以参考以下实例代码处理这种情况:
private void bitmap2MatDemo() {
    Bitmap bm = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888);//将```图像Bitmap```加载为```ARGB_8888```方式,

    Mat m = new Mat();
    Utils.bitmapToMat(bm, m);

    Imgproc.circle(m, new Point(m.cols()/2, m.rows()/2), 50,
            new Scalar(255, 0, 0, 255), 2, 8, 0);

    Utils.matToBitmap(m, bm);

    ImageView iv = (ImageView)this.findViewById(R.id.matInfo_imageView);
    iv.setImageBitmap(bm);
}
---
#####4.2 内存与显示
- 在Android系统中,将```图像资源文件直接加载为OpenCV中的Mat对象```,可以避免```Bitmap加载大图像```出现的```OOM问题```;
- 使用```Mat对象```对图像完成```操作```之后,所有的```临时Mat对象```都应该```调用release()释放内存```,
避免在```JNI层面```发生```内存泄漏```问题;

示例代码:

Mat dst = new Mat(); Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2RGBA); Utils.matToBitmap(dst, bm); dst.release();//及时释放临时Mat对象内存空间

---
#####4.3 通道数、通道顺序与透明通道问题
######(1)默认通道数与顺序
使用OpenCV4Android SDK```创建图像的时候```最好将其```指定为三通道默认的BGR顺序```,
这也是```OpenCV加载图像文件``````Mat对象```的时候使用的```默认通道数与通道顺序```######(2)透明通道
- 在OpenCV中做图像处理,如果需要处理```透明通道```,则需要将```图像Bitmap```加载为```ARGB_8888```方式,
(```如以上4.1 例子中的创建Bitmap时的代码```)
- 然后转换为```Mat对象```,此时Mat对象为```四通道```,含有透明通道数据,
- 这样就可以进行透明通道混合等```操作```了,
- 完成操作以后再通过Utils包中的方法```转换回```Bitmap对象即可。

######(3)灰度与二值图像
- 当```Mat``````灰度```或者```二值图像```的时候,
- 需要首先通过```cvtColor```指定```转换类型``````COLOR_GRAY2RGBA```,
- 之后才可以把```Mat对象```转换为```Bitmap图像```。

<br>
#####参考资料
- 《OpenCV Android 开发实战》(贾志刚 著)
- [关于本书作者的GitHub项目](https://github.com/gloomyfish1998/opencv4android/tree/master/samples/OpencvDemo)
- [基于作者GitHub维护的APP](https://gitee.com/wanchuanxy/MyOpenCVLearn/tree/master/OpenCVMyTest)