像素读写:
  • Mat作为图像容器,其数据部分存储了图像的像素数据,可以通过相关API获取图像数据部分,常见的Mat的像素读写get与put方法如下:

方法

支持类型

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~CV32FC4

int get(int row, int col, int[] data)

CV_32SC1~CV32SC4

int get(int row, int col, short[] data)

CV_16SC1~CV_16SC4

int get(int row, int col, byte[] data)

CV_8UC1~CV_8UC4

  • 默认情况下,imread方式将Mat对象类型加载为CV_8UC3,下面代码对Mat对象中的每个对象点的值都进行反操作,并且分别用三种方法实现像素操作:
//从Mat对象中每次读取一个像素点数据
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);
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);
}
}


//从Mat中每次获取一行数据
byte[] data1 = new byte[channels * width];
int b1 = 0, g1 = 0, r1 = 0;
int pv = 0;
for(int row = 0;row<height;row++){
src.get(row, 0, data);
for(int col = 0;col<data1.length;col++){
//读取
pv = data1[col]&0xff;
//修改
pv = 255 - pv;
data1[col] = (byte)pv;
}
//写入
src.put(row, 0, data1);
}


//从Mat中一次性获取全部像素数据
int pv1 = 0;
byte[] data2 = new byte[channels*width*height];
src.get(0, 0, data2);
for(int i = 0;i<data2.length;i++){
pv1 = data2[i]&0xff;
pv1 = 255 - pv1;
data2[i] = (byte)pv1;
}
src.put(0, 0,data2);
  • 上述三种方法的比较:
第一种方法频繁调用JNI导致效率低下,但是内存需求最小
只适用于随即少量的像素读写场合

第二种每次读取一行,频率略有下降,但是内存占用比第一种下降


第三种一次读取全部的像素数据,在内存中访问最快,通过JNi调用OpenCV底层次数减少,效率提高
但是对于高分辨率的图片这种效果显然占用的内存过高,容易导致OOM问题

图像通道与均值方差计算:

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

图像通道分离与合并:
  • 图像通道数通过Mat的channels()获取之后,如果通道数目大于1,那么根据需要调用split方法就可以实现通道分离,通过merge方法就可以实现通道合并
//mat表示输入多通道图像
//mv表示分离之后的多个单通道的图像,mv的长度与m的通道数目一致
public split(Mat mat, List<Mat> mv)


//mv表示多个待合并的单通道图像
//dst表示合并之后生成的多通道图像
public merge(List<Mat> mv, Mat dst)

eg: 图像通道的分离与合并:

//分离
List<Mat> mv= new ArrayList<>();
Core.split(src, mv);
for(Mat m : mv){
int pv = 0;
int channels = m.channels();
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;
}
src.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();
均值与标准方差的计算与应用:
  • 我们需要读取每一个像素点的值并计算每个通道的均值和标准方差
//src表示输入的图像
//mean表示计算出各个通道的均值,数组长度与通道数目一致
//stddev表示计算出各个通道的标准方差,数组长度与通道数目一致
meanStdDev(Mat src, MatOfDouble mean, MatOfDouble stddev);


//mask:表示只有当mask中对应位置的像素值不等于零的时候,src中相同位置的像素点才参与计算均值和标准方差
//其他参数与上述相同
meanStdDev(Mat src, MatOfDouble mean, MatOfDouble stddev, Mat mask);

eg: 基于均值实现图像二至分割:

public void meanAndDev(){
//加载图像
Mat src = Imgcodecs.imread(fileUri.getPath());
if(src.empty()){
return;
}
//转换为灰度图
Mat gray = new Mat();
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGRA2GRAY);
//计算均值与标准方差
MatOfDouble means = new MatOfDouble();
MatOfDouble stddevs = new MatOfDouble();
Core.meanStdDev(gray, means, stddevs);




//显示均值和标准方差
double[] mean = means.toArray();
double[] stddev = stddevs.toArray();




//读取像素数组
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);


Bitmap bm = Bitmap.createBitmap(gray.cols(), gray.rows(), Bitmap.Config.ARGB_8888);
Mat dst = new Mat();
Imgproc.cvtColor(gray, dst, Imgproc.COLOR_GRAY2RGBA);
Utils.matToBitmap(dst, bm);
ImageView iv = (ImageView)this.findViewById(R.id.chapter3_imageView);
iv.setImageBitmap(bm);
dst.release();
gray.release();
src.release();
}
算术操作与调整图像的亮度和对比度
  • 算术操作API
  • OpenCV中Mat的加减乘除运算既可以在两个Mat对象之间,也可以在Mat对象与Scalar之间进行,Mat对象之间的加减乘除运算最常用的方法如下:
//src1表示输入的第一个Mat图像对象
//src2表示输入的第二个Mat图像对象,可以是Scalar类型
//dst表示算术操作输出的Mat对象
//两个Mat对象进行运算的时候,src1和src2两者的大小和类型必须相同,默认输出的图像类型与输入类型一致

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);

eg:

public void matArithmeticDemo(){
//输入图像
Mat src = Imgcodecs.imread(fileUri.getPath());
if(src.empty()){
return;
}
//输入图像src2
Mat moon = Mat.zeros(src.rows(), src.cols(), src.type());
int cx = src.cols() - 60;
int cy = 60;
Imgproc.circle(moon, new Point(cs, cy), 50, new Scalar(90, 95, 234), -1, 8, 0);


Mat dst = new Mat();
Core.add(src, moon, dst);



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);
ImageView iv = (ImageView)this.findViewById(R.id.chapter3_imageView);
iv.setImageBitmap(bm);
}
调整图像的亮度和对比度
  • 图像的亮度和对比度试图像的两个基本属性,对RGB色彩图像来说,亮度越高,像素点对应的RGB值应该越大,越接近255,反之亮度越低,,其像素点对应的RGB值应该越小,越接近 0,所以在RGB色彩空间中,调整图像亮度可以简单地通过对图像进行加法和减法操作来实现,图像对比度主要是用来描述图像颜色与亮度之间的差异感知,对比度越大,图像的每个像素与周围的差异性也就越大,整个图像的细节就越显著,反之亦然,通过对图像进行乘法或者出发操作来扩大或者缩小图像像素之间的差值,这样我们就达到调整图像对比度的目的
// 输入图像src1
Mat 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);

// 转换为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);

ImageView iv = (ImageView)this.findViewById(R.id.chapter3_imageView);
iv.setImageBitmap(bm);
  • b表示亮度参数,c表示对比度参数,其中,b的取值为负数时,表示调低亮度;为正数时,表示调高亮度。参数c取值是浮点数,使用经验值范围为0~3.0,c取值小于1时,表示降低对比度,大于1时表示提升对比度

基于权重的图像叠加:

//src1表示输入的第一个Mat对象
//aplpha表示湖和的时候带一个Mat对象所占的权重大小
//src2:表示第二个权重对象
//beta:表示混合时第二个Mat对象所占的权重大小
//gammw;表示混合之后是否进行亮度校正9提升或者降低)
dst表示输出权重叠加之后的Mat对象
public void addWeighted(Mat src1, double alpha, Mat src2, double beta, double gamma, Mat dst);
  • 通常在进行两个图像的叠加操作的时候 ,权重满足的条件是alpha + beta = 1.0,通常为0.5