专栏首页李蔚蓬的专栏OpenCV | 基于Android系统详析Mat与Bitmap对象(创建、初始化、使用与转换 | 附大量demo)

OpenCV | 基于Android系统详析Mat与Bitmap对象(创建、初始化、使用与转换 | 附大量demo)

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 – startAnglestartAngle开始角度大小。 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图像
参考资料

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 计算机视觉 OpenCV Android | Mat像素操作

    下面演示对Mat对象中的每个像素点的值都进行取反操作,并且分别用这三种方法实现像素操作。

    凌川江雪
  • ASP.NET (Web) + C#算法 | 生成随机数字序列(随机数字+每个数字取随机不重复的位置和颜色)

    //n是一个递减变化的数 //intList的一个运行模拟序列: //0 1 2 3 4 n = listlength = 5,取到1 //0 4...

    凌川江雪
  • Tip | 数据类型占位 & 降采样 & 像素读取 & Bitmap & Color源码

    下面修改通道的时候使用的是位运算, 其实对比Color源码我们知道这跟调用Color的API是一样的:

    凌川江雪
  • 冒泡排序法三部曲の二冒泡排序法的优化

    在冒泡排序法三部曲の一冒泡排序法的原理之后,其实存在一些可优化的问题,首先就是假如是{1,2,3,6,4}这样的数组,经过一次冒泡之后数组变为{1,2,3...

    根究FPGA
  • Codeforces Global Round 2 C. Ramesses and Corner Inversion(思维)

    版权声明:欢迎转载,若转载,请标明出处,如有错误,请指点,也欢迎大佬们给出优化方法 https://blog.csdn.net/Charles_Zaqd...

    Ch_Zaqdt
  • Codeforces Round #535 (Div. 3) E1. Array and Segments (Easy version)(思维+暴力)

    题目链接:http://codeforces.com/contest/1108/problem/E1

    Ch_Zaqdt
  • 动态规划算法-背包问题

    温安适
  • 迷 宫

    Description Karles 和朋友到迷宫玩耍,没想到遇上了 10000000 年一次的大洪水,好在 Karles 是一个喜 欢思考的人,他发现迷宫的地...

    attack
  • 2018-2019 ICPC, NEERC, Northern Eurasia Finals L. Lazyland(思维)

    题目链接:http://codeforces.com/contest/1089/problem/L

    Ch_Zaqdt
  • 1216 跳马问题

    1216 跳马问题 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 题目 ? ...

    attack

扫码关注云+社区

领取腾讯云代金券