计算机视觉 OpenCV Android | Mat像素操作
来源:凌川江雪     阅读:1244
曹雨灵
发布于 2019-02-01 20:46
查看主页

本文目录

1. 像素读写
2. 图像通道与均值方差计算
3. 算术操作与调整图像的亮度和比照度
4. 基于权重的图像叠加
5. Mat的其余各种像素操作





1. 像素读写

常见的Mat的像素读写get与put方法支持如下表:

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

Mat src = Imgcodecs.imread(fileUri.getPath());if(src.empty()){  return;}int channels = src.channels();int width = src.cols();int height = src.rows();

接下来便可以通过方才所述三种方式读取像素数据、修改、写入比较它们的执行时间

1.1.从Mat中每次读取一个像素点数据

对于CV_8UC3Mat类型来说,对应的数据类型byte
则先初始化byte数组data,用来存取每次读取出来的一个像素点的所有通道值
数组的长度取决于图像通道数目

完整代码如下:

byte[] data = new byte[channels];int b=0, g=0, r=0;for(int row=0; row<height; row++) {  for(int col=0; col<width; col++) {      // 读取      src.get(row, col, data);//!!!!!!!!!!!!!!!!!!!!!!!读取一个px      b = data[0]&0xff;      g = data[1]&0xff;      r = data[2]&0xff;      // 修改      b = 255 - b;      g = 255 - g;      r = 255 - r;      // 写入      data[0] = (byte)b;      data[1] = (byte)g;      data[2] = (byte)r;      src.put(row, col, data);  }}

补充诠释

  • 一个px有多个通道;
  • 一个通道配给它一个数组元素;
  • 1.2中逐行读取时的一个列(某行中的某个列其实就是一个数组元素而已)不是px,
    而只是某个px的一个channel而已;
  • 1.3 同理
  • 即1.2 以及1.3 中,data的一个元素,不是px,而只是某个px的一个channel而已;
1.2 从Mat中每次读取一行像素数据

首先需要定义每一行像素数据数组的长度,这里为图像宽度乘以每个像素的通道数目
接着循环修改每一行的数据
这里get方法第二个参数 col = 0的意思是从每一行的第一列开始获取像素数据

完整代码如下:

       // each row data        byte[] data = new byte[channels*width];//channels 是一个px的通道数;width是一个行的px的个数;        // loop        int b=0, g=0, r=0;        int pv = 0;        for(int row=0; row<height; row++) {            src.get(row, 0, data);            /*get一整行的px数据,存进data;形象地说,是以 位置是(row, 0)的第一个px的第一个channel为起始元素,获取一个data长度的数据;            数据一个元素(channel)一个元素(channel)地存进数组data, 每个元素是某个px的一个channel;*/            for(int col=0; col<data.length; col++) {//行中循环列,解决内容:修改一整行的数据                // 读取                pv = data[col]&0xff;                // 修改                pv = 255 - pv;                data[col] = (byte)pv;            }            // 至此,data蓄满一行修改好的px(channel)数据            // 写入            src.put(row, 0, data);        }

关于代码的补充诠释

  • byte[] data = new byte[channels*width];中:
    channels 是一个px的通道数;
    width是一个行的px的个数;
  • for(int row=0; row<height; row++):外层 for 循环行;
  • src.get(row, 0, data);get一整行的px数据,存进data;
    形象地说,
    是以 位置是(row, 0)第一个px第一个channel起始元素
    获取一个data长度的数据;
    数据一个元素(channel)一个元素(channel)地存进数组data
    每个元素是某个px的一个channel
  • for(int col=0; col<data.length; col++)次层 for ,
    行中循环列,解决内容:修改一整行的数据;
  • 次层for执行完毕,data蓄满一行修改好的px(channel)数据;
  • src.put(row, 0, data):数组对象引用赋给行首,交付整行数据;
    形象地说,
    是以 位置是(row, 0)第一个px第一个channel起始元素
    提交一个data长度的数据,即一整行;
1.3 从Mat中一次读取一律像素数据

完整代码如下:

// all pixelsint pv = 0;byte[] data = new byte[channels*width*height];src.get(0, 0, data);for(int i=0; i<data.length; i++) {  pv = data[i]&0xff;  pv = 255-pv;  data[i] = (byte)pv;}src.put(0, 0, data);

关于代码的补充诠释(参考1.2的补充,不难了解)

  • src.get(0, 0, data);get一律的px数据,存进data;
    形象地说,
    是以 位置是(0, 0)第一个px第一个channel起始元素
    获取一个data长度的数据;
    数据一个元素(channel)一个元素(channel)地存进数组data
    每个元素是某个px的一个channel
  • src.put(0, 0, data):数组对象引用赋给行首,交付一律数据;
    形象地说,
    是以 位置是(0, 0)第一个px第一个channel起始元素
    提交一个data长度的数据,即一律px的一律channel

上述三种方法

所以Android开发者在使用OpenCV的时候,
需要注意应根据项目需求
选择第二种或者者第三种方法实现像素读写
第一种方法只适用于随机一些像素读写的场合。





2. 图像通道与均值方差计算

2.1 图像通道分离与合并

这两个方法的详细解释具体如下:

上面两个方法都来自Core板块Core板块主要包含少量Mat操作基础矩阵数学功能

一个简单的多通道的Mat对象其分离与合并的代码演示如下:

public void channelsAndPixels() {//        Mat src = Imgcodecs.imread(fileUri.getPath());//        if(src.empty()){//            return;//        }        //*******        Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(), R.drawable.lena);        Mat ori = new Mat();        Mat src = new Mat();        Utils.bitmapToMat(bitmap, ori);        Imgproc.cvtColor(ori, src, Imgproc.COLOR_RGBA2BGR);        //*******        List<Mat> mv = new ArrayList<>();        Core.split(src, mv);        for(Mat m : mv) {            int pv = 0;            int channels = m.channels();//channels = 1,毕竟都调用了split()了//            //下面这行用来测试channels的值//            Toast.makeText(this,"The m.channels is" + channels,Toast.LENGTH_SHORT).show();            int width = m.cols();            int height = m.rows();            byte[] data = new byte[channels*width*height];            m.get(0, 0, data);            for(int i=0; i<data.length; i++) {                pv = data[i]&0xff;                pv = 255-pv;                data[i] = (byte)pv;            }            m.put(0, 0, data);        }        Core.merge(mv, src);        Bitmap bm = Bitmap.createBitmap(src.cols(), src.rows(), Bitmap.Config.ARGB_8888);        Mat dst = new Mat();        Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2RGBA);        Utils.matToBitmap(dst, bm);        ImageView iv = (ImageView)this.findViewById(R.id.chapter3_imageView);        iv.setImageBitmap(bm);        dst.release();        src.release();    }

上面的代码实现了对多通道图像分离之后取反
而后再合并
最后通过Android ImageView组件显示结果
如此便是图像通道分离与合并基本用法

2.2 .均值与标准方差计算与应用

接下来的内容是关于图像Mat像素数据的简单统计,计算均值与方差

OpenCV Core板块中已经实现了这类API,具体解释如下:

完整的基于均值实现图像二值分割的代码如下:

// 加载图像Mat src = Imgcodecs.imread(fileUri.getPath());if(src.empty()){  return;}// 转为灰度图像Mat gray = new Mat();Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);// 计算均值与标准方差MatOfDouble means = new MatOfDouble();MatOfDouble stddevs = new MatOfDouble();Core.meanStdDev(gray, means, stddevs);// 显示均值与标准方差double[] mean = means.toArray();double[] stddev = stddevs.toArray();Log.i(TAG, "gray image means:" + mean[0]);Log.i(TAG, "gray image stddev:" + stddev[0]);// 读取像素数组int width = gray.cols();int height = gray.rows();byte[] data = new byte[width*height];gray.get(0, 0, data);int pv = 0;// 根据均值进行二值分割int t = (int)mean[0];for(int i=0; i<data.length; i++) {  pv = data[i]&0xff;  if(pv > t) {      data[i] = (byte)255;        } else {      data[i] = (byte)0;  }}gray.put(0, 0, data);

最终得到的gray就是二值图像,转换为Bitmap对象之后,通过ImageView显示就可。

  • 另外,
    关于计算得到的标准方差,如上面的代码中假设stddev[0]的值小于5,那么基本上图像可以看成是无效图像或者者空白图像
    由于标准方差越小则说明图像各个像素的差异越小,图像本身携带的有效信息越少
  • 在图像解决中,可以利用这个结论来提取和过滤质量不高的扫描或者者打印图像





3. 算术操作与调整图像的亮度和比照度

3.1 算术操作API的详情

下面是一个简单的算术运算的例子,使用加法,将两个Mat对象的叠加结果输出:

// 输入图像src1Mat src = Imgcodecs.imread(fileUri.getPath());if(src.empty()){  return;}// 输入图像src2Mat moon = Mat.zeros(src.rows(), src.cols(), src.type());int cx = src.cols() - 60;int cy = 60;Imgproc.circle(moon, new Point(cx, cy), 50, new Scalar(90,95,234), -1, 8, 0);// 加法运算Mat dst = new Mat();Core.add(src, moon, dst);
3.2 调整图像的亮度和比照度

加减法只能使各个通道值保持差值(差距)去变大变小;
乘除法能放大缩小差值;

基于Mat与Scalar算术操作,实现图像亮度或者者比照度调整的代码实现如下:

// 输入图像src1Mat src = Imgcodecs.imread(fileUri.getPath());if(src.empty()){  return;}// 调整亮度Mat dst1 = new Mat();Core.add(src, new Scalar(b,b,b), dst1);// 调整比照度Mat dst2 = new Mat();Core.multiply(dst1, new Scalar(c, c, c), dst2);//至dst2,图像的两个度已经调整完毕,就差个转化类型而已// 转换为Bitmap,显示Bitmap bm = Bitmap.createBitmap(src.cols(), src.rows(),              Bitmap.Config.ARGB_8888);Mat result = new Mat();Imgproc.cvtColor(dst2, result, Imgproc.COLOR_BGR2RGBA);Utils.matToBitmap(result, bm);





4. 基于权重的图像叠加

Core板块中已经实现了这样的API函数,方法名称与各个参数的解释具体如下:

dst=src1*alpha+src2*beta+gamma

其中,
假如src2是纯黑色的背景图像,
gamma大小决定了图像的亮度
alpha大小决定了图像的比照度(由于src2纯黑色背景则基本无比照度,所以该由src1决定得多),
alpha+beta=1

基于权重叠加的图像亮度与比照度调整的完整代码实现如下:

// 加载图像Mat src = Imgcodecs.imread(fileUri.getPath());if(src.empty()){  return;}// create black imageMat black = Mat.zeros(src.size(), src.type());Mat dst = new Mat();// 像素混合 - 基于权重Core.addWeighted(src, alpha, black, 1.0-alpha, gamma, dst);// 转换为Bitmap,显示Bitmap bm = Bitmap.createBitmap(src.cols(), src.rows(),               Bitmap.Config.ARGB_8888);Mat result = new Mat();Imgproc.cvtColor(dst, result, Imgproc.COLOR_BGR2RGBA);Utils.matToBitmap(result, bm);

其中,
两个参数alpha和gamma分别表示比照度与亮度调整的幅度,这里的默认值分别为1.530
完整代码可以参考文末作者的GitHub;





5. Mat的其余各种像素操作

OpenCV除了支持图像的算术操作之外,还支持图像的逻辑操作、平方、取LOG、归一化值范围等操作,
这些操作在解决复杂场景的图像二值或者者灰度图像分析的时候非常有用。

图像逻辑操作相关的API与参数说明具体如下:

(因唯两个高值像素相与得高值像素,
高值与低值、低值与低值的结果都是低值,
于是三分之二的运算都是降低亮度的操作)

(其了解同与操作相反)

异或者操作可以看作是对输入图像的叠加取反效果

下面创立两个Mat对象,
而后对它们完成位运算——逻辑与、或者、非,
得到的结果将拼接为一张大Mat对象显示,
完整的代码演示如下:

// 创立图像Mat src1 = Mat.zeros(400, 400, CvType.CV_8UC3);Mat src2 = new Mat(400, 400, CvType.CV_8UC3);src2.setTo(new Scalar(255, 255, 255));// ROI区域定义Rect rect = new Rect();rect.x=100;rect.y=100;rect.width = 200;rect.height = 200;// 绘制矩形Imgproc.rectangle(src1, rect.tl(), rect.br(), new Scalar(0, 255, 0), -1);rect.x=10;rect.y=10;Imgproc.rectangle(src2, rect.tl(), rect.br(), new Scalar(255, 255, 0), -1);// 逻辑运算Mat dst1 = new Mat();Mat dst2 = new Mat();Mat dst3 = new Mat();Core.bitwise_and(src1, src2, dst1);Core.bitwise_or(src1, src2, dst2);Core.bitwise_xor(src1, src2, dst3);// 输出结果Mat dst = Mat.zeros(400, 1200, CvType.CV_8UC3);rect.x=0;rect.y=0;rect.width=400;rect.height=400;dst1.copyTo(dst.submat(rect));rect.x=400;dst2.copyTo(dst.submat(rect));rect.x=800;dst3.copyTo(dst.submat(rect));// 释放内存dst1.release();dst2.release();dst3.release();// 转换为Bitmap,显示Bitmap bm = Bitmap.createBitmap(dst.cols(), dst.rows(), Bitmap.Config.ARGB_8888);Mat result = new Mat();Imgproc.cvtColor(dst, result, Imgproc.COLOR_BGR2RGBA);Utils.matToBitmap(result, bm);// showImageView iv = (ImageView)this.findViewById(R.id.chapter3_imageView);iv.setImageBitmap(bm);

如上代码前文字所述,
三个输出图像分别以x = 0, 400, 800为Mat矩阵左上角点拼接到结果Mat矩阵dst中:

相关API解释如下:

(数据   只需经过   归一化   即可以变成   彩色图像  输出,划重点!!!!!!)

下面简单演示一下如何创立一个0~1的浮点数图像,
而后将其归一化到0~255,
代码实现如下:

// 创立随机浮点数图像Mat src = Mat.zeros(400, 400, CvType.CV_32FC3);float[] data = new float[400*400*3];Random random = new Random();for(int i=0; i<data.length; i++) {  data[i] = (float)random.nextGaussian();}src.put(0, 0, data);// 将值归一化到0~255之间Mat dst = new Mat();Core.normalize(src, dst, 0, 255, Core.NORM_MINMAX, -1, new Mat());// 类型转换Mat dst8u = new Mat();dst.convertTo(dst8u, CvType.CV_8UC3);

上述代码将创立一张大小为400×400高斯噪声图像
其中归一化方法选择的是最小与最大值归一化方法(NORM_MINMAX=32)
这种方法的数学表示如下:

  • 图解:如图所示,( x - min / max - min )必然是一个[0,1]的实数!

  • 另外,
    你会发现公式中,不加alpha对( 0 , 255 )这个范围的归一(即以上题境)没有什么影响,
    这是由于( x - min / max - min )光乘以(beta - alpha)不加最后的alpha只能归一到范围( 0 , beta )
    加上 最后的alpha才能归一到( alpha , beta )

其中,
x表示src的像素值,
min、max表示src中像素的最小值与最大值
src 各个通道完成上述计算就可得到最终的归一化结果

计算图像的结果有正负值,那么在显示之前调用convertScaleAbs()对负值求取绝对值图像
在后面的图像滤波与梯度计算中会用到该方法。

此外,Core中图像常见的操作还有对Mat做平方与取对数,这些操作都与实际应用场合有肯定的关系,而且使用与参数都比较简单,书中这里没再做过多的说明。

关于相关API的更多说明,我们可以查看对应的OpenCV帮助文档。

参考资料
免责声明:本文为用户发表,不代表网站立场,仅供参考,不构成引导等用途。 系统环境 服务器应用
相关推荐
『互联网架构』软件架构-zookeeper快速入门(33)
Node.js超详细零基础教程(1)—解决GET、POST请求
使用koa2+mongodb+ava构建RESTful api并测试
构造函数,实例,原型关系详解
2020年最新程序员职业发展路线指南,超详细!
首页
搜索
订单
购物车
我的