OpenCV(三) Mat像素操作

像素读写

  • Mat作为图像容器,其数据部分存储了图像的像素数据,我们可以通过相关的API来获取图像的数据部分。在获取图像数据的时候,知道Mat的类型与通道数目至关重要,根据Mat的类型与通道数目,开辟适当大小的内存空间,然后通过get方法就可以循环实现每个像素点值的读取、修改,然后再通过put方法修改与Mat对应的数据部分即可

  • 常见的Mat的像素读写get与put方法支持如下表。表中是当前OpenCV支持读取图像的方法、将像素值写入到Mat对象中使用与每个get方法相对于的put方法即可。根据开辟的缓存区域data数据的大小,读写像素即可以每次从Mat中读取一个像素点数据。或者可以每次从Mat中读取一行像素数据,还可以一次从Mat中读取所有全部数据。

    方法 支持类型
    double[] get(int row,int col) 以下全部
    int get(int row,int col,double[] data) CV_64FC1~CV_64FC4
    int get(int row,int col,float[] data) CV_32FC1~CV_32FC4
    int get(int row,int col,int[] data) CV_32SC1~CV_32SC4
    int get(int row,int col,short[] data) CV_16SC1~CV_16SC1
    int get(int row,int col,byte[] data) CV8UC1~CV_8UC1
  • 从Mat 中读取一个像素点数据

     byte[] bytes =new byte[channels];
            int b,g,r;
            for (int i = 0; i 
  • 从Mat中每次读取一行像素数据

    byte[] data = new byte[channels*width];
    int pv;
    for(int i=0;i<height;i++){
        src.get(i,0,data);
        for(int j=0;col<data.length;j++){
            pv = data[j]&0xff;
            pv  = 255-pv;
            data[j]=(byte)pv;
        }
        src.put(i,0,data)
    }
    //相比第一种方式有所提高,但是内存增加
    
    
  • 从Mat中一次读取全部数据,完整数据长度T=图像宽 * 图像高 * 通道数

     byte[] data = new byte[width*height*channels];
            int pv;
            mRgba.get(0,0,data);
            for (int i = 0; i <data.length ; i++) {
                pv = data[i]&0xFF;
                data[i]=(byte) (250-pv);
            }
            mRgba.put(0,0,data);
    //调用jni次数少,效率也高,但是对于高分辨图像容易OOM
    

图像通道与均值方差计算

  • 图像中通道数目的多少可以通过Mat对象channels()进行查询获取。对于多通道图像,Mat提供Api方法可以把它分为多个单通道图像;同样对于多个单通道的数据也可以组合成一个多通道的图像。此外,OpenCV还提供了计算机图像每个通道像素值与标准方差的API方法,通过它们可以计算得到图像的像素平均值与方差,根据平均值可以实现基于平均值的二值图像分割,根据标准方差可以找到空白图像或者无效图像。

图像通道分离与合并

  • 图像通道数通过Mat的channels()获取,如果通道数目大于1,那么久调用split方法就可以实现通道分离,通过merge方法就可以实现通道合并。

    split(Mat m,List<Mat> mv)//通道分离
    //m:表示输入多通道图像
    //mv:表示分离之后多个单通道图像,mv的长度与m的通道数目一致。
        
    merge(List<Mat>mv,Mat dst)//通道合并
    //mv:表示多个待合并的单通道图像
    //dst:表示合并之后生成的多通道图像    
        
    //上面两个方法都来自Core模块,Core模块只要包含一些Mat操作与基础矩阵数学功能。    
    

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

  • OpenCV Core模块中实现了这类API

    meanStdDev(Mat src,MatOfDouble mean,MatOfDouble stddev)
    //src:表示输入Mat图像 
    //mean:表示计算出各个通道的均值,数组长度与通道数目一致
    //stddev:表示计算出各个通道的标准方差,数组长度与通道数目一致  
    
        
    meanStdDev(Mat src,MatOfDouble mean,MatOfDouble stddev,Mat mask)
    //mask 当mask中对应位置的像素值不等于零的时候,src中相同位置的像素点才参与计算均值与标准方差。    
    
  • 基于均值实现图像二值分隔

    String path = Environment.getExternalStorageDirectory()+File.separator+"dex"+File.separator+"aaa.jpg";
            Mat imread = Imgcodecs.imread(path);
            if (imread.empty()){
                Log.i(TAG, "loadImg: 图片加载失败");
                return;
            }
    
            Mat gray = new Mat();
            Imgproc.cvtColor(imread,gray,Imgproc.COLOR_BGR2GRAY);
    
            //计算均值与标准方差
            MatOfDouble means = new MatOfDouble();
            MatOfDouble stddevs = new MatOfDouble();
            Core.meanStdDev(gray,means,stddevs);
    
            double[] doubles = means.toArray();
            double[] doubles1 =  stddevs.toArray();
    
            Log.i(TAG, "disposeImg: doubles="+doubles[0]);
            Log.i(TAG, "disposeImg: doubles1="+doubles1[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) doubles[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);
            Bitmap bitmap =      Bitmap.createBitmap(gray.width(),gray.height(),Bitmap.Config.ARGB_8888);
            Utils.matToBitmap(gray,bitmap);
            dsc.setImageBitmap(bitmap);
            gray.release();
            imread.release();
    
    

  • 当stddev[0] 的值小于5,那么基本上图像可以看成是无效图像或者空白图像,因此标准方差越小则说明图像各个像素的差异越小,图像本身携带有效信息越少。在图像处理中,我们可以利用上述结论来提取和过滤质量不高的扫描或者打印图像。

##算数操作与调整图像的亮度和对比度

  • OpenCV 的Core模块支持Mat对象的加、减、乘、除算数运算,这些算数运算都处于Mat对象层次,可以在任意两个Mat 之间实现上述算数操作,以得到结果。

算数操作API

  • OpenCV 中 Mat 的加减乘除运算既可以在两个Mat对象之间,也可以在Mat对象与Scalar之间进行。Mat对象之间的加减乘除运算常见的方法如下:

    add(Mat src1,Mat src2,Mat dst)
    subtract(Mat src1,Mat src2,Mat dst)
    multiply(Mat src1,Mat src2,Mat dst)  
    divide(Mat src1,Mat src2,Mat dst) 
    
    //src1: 表示输入的第一Mat图像对象
    //src2: 表示输入的第二个Mat图像对象
    //src3:表示算数操作输出的Mat对象
    //此外,src2的类型还可以是Scalar类型。这个时候表示图像的每个像素点都与Scalar中的每个向量来完成指定算数运算。需要注意的是,在使用算数运算时候,当src1、src2均为Mat对象的时候,它们的大小与类型必须一致,默认的输出图像类型与输入图像类型一致。   
    
  • 使用加法将连个Mat对象叠加:

     String path = Environment.getExternalStorageDirectory()+File.separator+"dex"+File.separator+"aaa.jpg";
            Mat imread = Imgcodecs.imread(path);
            if (imread.empty()){
                Log.i(TAG, "loadImg: 图片加载失败");
                return;
            }
    
            Mat moon = Mat.zeros(imread.rows(),imread.cols(),imread.type());
            int cx = imread.cols()/2-60;
            int cy = imread.rows()/2-60;
    
            Imgproc.circle(moon,new Point(cx,cy),120,new Scalar(152,110,100),-1,8,0);
            Mat dsc = new Mat();
            Core.add(imread,moon,dsc);
    
            Bitmap bitmap = Bitmap.createBitmap(dsc.width(),dsc.height(),Bitmap.Config.ARGB_8888);
            Mat bit = new Mat();
            Imgproc.cvtColor(dsc,bit,Imgproc.COLOR_BGR2RGB);
            Utils.matToBitmap(bit,bitmap);
            dscImage.setImageBitmap(bitmap);
            imread.release();
            dsc.release();
            moon.release();
            bit.release();
    

调整图像的亮度与对比度

  • 图像的亮度和对比度是图像的两个基本属性,对RGB色彩图像来说,亮度越高,像素点对应的RGB值越大,越接近255;反之亮度越低,其像素点对应RGB值应该越小,越接近0,。所以在RGB色彩空间中,调整图像亮度可以简单地通过对图像进行加法与减法操作与实现。图像对比度主要用来描述图像颜色与亮度之间的差异感知,对比度越大,图像的每个像素与周围差异性也就越大,整个图像的细节就越显著;反之亦然。通过对图像进行乘法或者除法操作来扩大或者缩小图像像素直接的差值,这样我们就达到了调整图像对比度的目的。基于Mat与Scalar的算法操作,实现图像亮度或者对比度调整。

    String path = Environment.getExternalStorageDirectory()+File.separator+"dex"+File.separator+"aaa.jpg";
            Mat imread = Imgcodecs.imread(path);
            if (imread.empty()){
                Log.i(TAG, "loadImg: 图片加载失败");
                return;
            }
    
            Mat dsc = new Mat();
            Core.add(imread,new Scalar(100,100,100),dsc);//亮度处理
    		
    		//Core.multiply(imread,new Scalar(3,3,3),dsc);//对比度处理
    
            Mat bit = new Mat();
            Imgproc.cvtColor(dsc,bit,Imgproc.COLOR_BGR2RGB);
            Bitmap bitMap = createBitMap(bit);
            Utils.matToBitmap(bit,bitMap);
            dscImage.setImageBitmap(bitMap);
    
            imread.release();
            dsc.release();
            bit.release();
    
    //Core.add(imread,new Scalar(a,a,a),dsc);
    //Core.multiply(imread,new Scalar(b,b,b),dsc);
    //a表示亮度参数,b表示对比度参数
    //a取值为负值时,表示调低亮度;为正数时表示高亮度。
    //b的取值是浮点数,使用经验值范围一般为0~3.0
    //b的取值小于1时,表示降低对比度,大于1时表示提示对比度
    

基于权重的图像叠加

  • 对图像进行简单的相加方法有时候并不能满足我们的需求,我们希望可以通过参数来调整输入图像在最终叠加之后的图像中所占的权重比,比实现基于权重方式的、更加灵活的图像调整方式。
Core.addWeighted(mat src1,double alpha,Mat src2,double bate,double gamma,Mat dst)
	
 //这种方法的公式描述: dst = src1*alpha+src2*gamma  

//src1:表示输入的第一个Mat对象
//alpha:表示混合时候第一个Mat对象所占的权重大小。
//src2:表示输入的第二个Mat对象
//beta: 表示混合时候第二个Mat对象所占的权重大小
//gamma:表示混合之后是否进行亮度矫正(提升或降低)
//dst:表示输出权重叠加之后的Mat对象    

     
  • 对于正常图像:

    • 两个图形叠加时,权重调整需要满足alpha+beta=1.0;
    • 通常alpha = beta = 0.5,表示混合叠加后的图像中原来两幅图像的像素比值各站一半。
  • 假设src2全是黑色背景,那么这种叠加效果就是让图像src1变的更加暗,对比度变得更加低,在src2为黑色背景图像时,我们把alpha值调整为1.5,beta值为-0.5,这样最终的叠加结果就是图像的对比度得到了提升;当alpha = 1的时候,则输出原图。如果gamma不是默认值0,而是一个正整数的时候,那么这时候就会提升图像的亮度;

      String path = Environment.getExternalStorageDirectory()+File.separator+"dex"+File.separator+"aaa.jpg";
            Mat src = Imgcodecs.imread(path);
            if (src.empty()){
                Log.i(TAG, "loadImg: 图片加载失败");
                return;
            }
    
            Mat black = Mat.zeros(src.size(),src.type());
            Mat dsc = new Mat();
            Core.addWeighted(src,1.5,black,1-1.5,0,dsc);
            Mat mat = Mat.zeros(dsc.size(),dsc.type());
            showBitMap(dsc,mat,dscImage);
    
            src.release();
            black.release();
            dsc.release();
            mat.release();
    //alpha:对比度调整幅度
    //gamma:亮度调整
    

Mat的其他像素操作

  • 去反操作对二值图像说是一个常见操作,有时候需要先进行去反再进去分析

    bitwise_not(Mat src,Mat dst)//去反操作
    //src 输入图像
    //dst 去反图像    
    
  • 与操作对两张图像混合之后的输出图像有降低混合图像亮度效果,会让输出的像素小于等于对应位置的任意一张输入图像的像素值

    bitwise_and(Mat src1,Mat src2,Mat dst)//或操作
        //src1: 输入图像1
        //src2: 输入图像2
        //dst:  输出图像    
    
  • 或操作

    bitwise_or(Mat src1,Mat src2,Mat dsc)//或操作
      //src1: 输入图像1
      //src2: 输入图像2
      //dst:  操作结果     
    
  • 异或操作可以看做是对图像的叠加去反操作

    bitwise_xor(Mat src1,Mat src2,Mat dsc)//异或操作
      //src1: 输入图像1
      //src2: 输入图像2
      //dst:  操作结果      
    
  • 线性绝对值放缩变化

    convertScaleAbs(Mat src,Mat dst)
    //默认情况下会对输入Mat对象数据求得绝对值,并将器转换为CV_8UC1类型输出数据dst    
    
  • 归一化在图像处理中是经常需要的地方,比如浮点数进行计算得到输出的数据,将数据归一化到0~255后就可以作为彩色图像输出得到输出结果。

    normalize(Mat src,Mat dst,double alpha,double beta,int norm_type,int dtype,Mat mask)
    //src:表示输入图像
    //dst:表示输出图像 
    //alpha:表示归一化到指定范围低值
    //beta:表示归一化到指定范围的高值
    //dtype:表示输出的dst图像类型,默认为-1,表示类型与输入图像src相同 
    //mask:表示遮罩层,默认为Mat()    
    
    • 创建一个01的浮点数图像,然后将其归一化到0255

             Mat stc = Mat.zeros(new Size(400,400),CvType.CV_32FC3);
              float[] floats =new float[400*400*3];
              Random random = new Random();
              for (int i = 0; i 
    • 上述方法代码将成功创建一张大小为400*400的高斯噪声图像,其中归一化方法选择是最小与最大值归一化方法(NORM_MINMAX=32),数学表达式:

      dst = ((x-min)/(max-min))*(beta-alpha)+alpha
      //x:表示src的像素值,
      //min、max:表示src中像素的最小值与最大值
      

你可能感兴趣的:(Android,OpenCV,Android,OpenCV)