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

内容提要:
图像深度:表示每个通道灰度值所占的大小,大小即占几个位,是什么数据类型
图像类型:基于图像深度表述的信息,多了一个尾缀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中常见的几种图像深度
  • U表示无符号整型
  • S表示符号整型
  • F表示浮点数
    这些类型在CvType中可以自己查看。OpenCV中常见的图像类型如下:

当调用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对象的频繁访问,效率低下



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保留;

  • 各个通道各自的8bit有效位计算(修改)完毕之后,
    再将各通道相与结果左移对应的位数,
    最后统一相或,则得到修改后的一个像素点的32个bit



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

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



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(0,0,255)表示红色。

  1. rectangle(Mat img,Point pt1,Point pt2,Scalar color,int thickness,int lineType,int shift)
    绘制矩形跟绘制直线的方法参数极其类似,主要是两个坐标点参数含义不一样:
    pt1:表示矩形左上角点的屏幕坐标;
    pt2:表示矩形右下角点的屏幕坐标;

  1. circle(Mat img,Point center,int radius,Scalar color,int thickness,int lineType,int shift)
    img:同上。
    center:表示圆的中心点位置的屏幕坐标,单位是像素
    radius:表示圆的半径大小,单位是像素
    color:表示圆的颜色

  1. 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:表示椭圆的颜色

  1. 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);
    }

运行效果如下:



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来完成。




4. Mat与Bitmap的使用与转换

  • 在Android中使用OpenCV来完成应用开发时经常需要在Mat对象Bitmap对象之间相互切换;
  • BitmapAndroid中的图像对象Mat作为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 SDKUtil包,
    包中还有另外一个与它相对应的方法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图像


参考资料
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容