一文弄明白 OpenCV Mat 中通道channels的作用

2023-07-14 11:06:26 浏览数 (2)

1. 介绍

openCV 是使用 Mat 进行存储图片,记录各种像素信息。那么 Mat 中的像素是如何记录和获取的呢?

在网上找到有很多是C语言写的。在这里我想使用java的语法给大家介绍一下。

如何通过Mat获取到指定区域的像素。RGB,BGR,HSV,GRAY等格式数据的获取。

2. channels 通道

当我们使用Mat.channels() 方法,能够得到当前 Mat 的通道数。 通常返回结果值为:1,2,3,4 这四个结果。

那么这个通道是什么东西呢?

我们知道,所有的图像都是由一个个像素点堆积而成的。而一个像素点,又是由RGB颜色混合而成的。

每一种颜色就是一种通道。每个像素点是多个通道颜色的混合结果。

PS:知识点,RGB三原色可以混淆所有我们肉眼可以见到的颜色。

所以,当我们弄明白通道之后就能明白如何获取Mat中指定坐标的颜色值了。

mat.rows() 是Y轴长度。 mat.cols() 是X轴长度。

示例:

代码语言:javascript复制
Mat rgba ;// 假如我有一个 rgba的Mat对象
int channels = rgba.channels(); // channels 的长度是4
double[] temp = rgba.get(rgba.rows() / 2, rgba.cols() / 2); //取中间点颜色值
//temp 的数组的长度就是通道数,所以它的length=4

当我们遍历一遍temp的结果会得到:

代码语言:javascript复制
StringBuffer stringBuffer = new StringBuffer();
for (double s : temp) {
   stringBuffer.append(s).append(",");
}
Log.e("TAG", "颜色值:"   stringBuffer);

//输出:
颜色值:3.0,0.0,4.0,255.0,

数据的结果实际情况是:

  • r:3.0
  • g:0.0
  • b:4.0
  • a:255 (透明度,0表示透明,255表示不透明)

知识点,OpenCV 中的颜色顺序不是 BGR 格式么?这个顺序不针对 Mat 中的颜色,而是我们使用 Scalar 的时候传入的颜色顺序是 BGR 顺序而已。

代码语言:javascript复制
new Scalar(10,255,255); //颜色顺序是 B,G,R

我们如果是一个 BGR 格式的 Mat 对象那么颜色值会怎么显示呢?

还是使用上面的 Mat 我们进行转换之后,看看同一个点输出的结果:

代码语言:javascript复制
Mat bgr=new Mat();
Imgproc.cvtColor(rgba, bgr, Imgproc.COLOR_RGB2BGR);// 将RGB格式转为BGR格式
int channels = bgr.channels(); // channels 的长度是3
double[] temp1 = bgr.get(bgr.rows() / 2, bgr.cols() / 2); //取中间点颜色值
StringBuffer stringBuffer1 = new StringBuffer();
for (double s : temp1) {
   stringBuffer1.append(s).append(",");
}
Log.e("TAG", "颜色值:"   stringBuffer1);

//输出:
颜色值:4.0,0.0,3.0

数据的结果实际情况是:

  • b:4.0
  • g:0.0
  • r:3.0

就会出现颜色的通道数变化。

不知道注意到了没有,我上面是将rbga直接转成了BGR。

在高位转换的情况下,A通道会被直接丢弃。体现在图像上就会没有透明效果了。

我们如果想确保A通道也转换,可以使用:

代码语言:javascript复制
Imgproc.cvtColor(rgba, bgra, Imgproc.COLOR_RGBA2BGRA);

2.1 Gray 灰度图转换

当我们将RGBA或者BGR等彩色图像转换为GRAY灰色的时候,Mat的通道数就会被压制为单通道G了。效果如下:

代码语言:javascript复制
int channels = rgba.channels();
double[] temp = rgba.get(rgba.rows() / 2, rgba.cols() / 2); //取中间点颜色值
// temp的长度就是 channels的值,所以temp的结果就是4
StringBuffer stringBuffer = new StringBuffer();
for (double s : temp) {
    stringBuffer.append( s).append(",");
}
Log.e("TAG", "通道数:"   channels   " 颜色值:"   stringBuffer);

Imgproc.cvtColor(rgba, rgba, Imgproc.COLOR_RGBA2GRAY);  //将RGBA转GRAY
channels = rgba.channels();
temp = rgba.get(rgba.rows() / 2, rgba.cols() / 2); //取中间点颜色值
stringBuffer = new StringBuffer();
for (double s : temp) {
    stringBuffer.append(s).append(",");
}
Log.e("TAG", "变换后:通道数:"   channels   " 颜色值:"   stringBuffer);

//输出结果:
E/TAG: 通道数:4 颜色值:43.0,48.0,69.0,255.0,
E/TAG: 变换后:通道数:1 颜色值:49.0,

会发现灰色图是只有一个通道的。

按照Gray = R*0.299 G*0.587 B*0.114 公式进行的转换。

这个公式叫做Luminosity(亮度算法)。这个算法中RGB的各占比例。都是一个经验值。也就是说没有科学道理。纯粹经验出发调试出来的一个比例。

PS:所以有一个小常识,RGB转Gray,然后再Gray转换回RGB会出现色差。因为在转换过程中避免不了信息丢失。

2.2 小结

当我们弄明白通道数的概念之后。就能够弄明白cvtColor中的各种转换了

Luv,Lab,HSV,RGB,BGR,HLS,YUV,GRAY等等的颜色转换其实都是针对我们的单像素中的通道值在处理。

  1. 单通道的,是Gray灰度图。
  2. 双通道的,两个通道值一个为实数,个为虚数。主要是RGB555和RGB565格式的图像,这个通道通常用来计算。
  3. 三通道的,图片就是彩色图像了,例如RGB,BGR,HSV,HLS等等。
  4. 四通道的,图片带透明度的图像了。相较于三通道多了一个alpha通道,也就是表示透明度。

我们在使用OpenCV时,新手经常出现Mat错误,就在于通道转换了。因为OpenCV有些算法是必须单通道的。而我们一不小心传了3通道的。或者,Mat是三通道的。与另一个单通道的Mat进行比较处理时,出现通道错误等等。

注意:

我们使用Imgproc.cvtColor方法进行转换的时候。输入的Imgproc.COLOR_RGBA2GRAY等等值是很重要的。需要根据我们的Mat的实际情况进行选择。 我们如果Mat是BGR格式的,我们却选择使用Imgproc.COLOR_RGB2HSV_FULL转换,虽然结果是转换了。但是实际情况是不对的。 因为Imgproc会按照RGB的顺序从double[]数组中提取参数进行计算处理,而不是按照BGR的格式进行提取转换。

2.2.1 Imgproc.COLOR_XXXX解释

简单介绍下ImgProc.COLOR_XXX的信息。其实说破很简单:

代码语言:javascript复制
ImgProc.COLOR_BGR2BGRA;// BGR to BGRA 也就是BGR格式转BGRA格式
ImgProc.COLOR_RGB2RGBA;// RGB to RGBA 也就是RGB格式转RGBA格式
ImgProc.COLOR_BGRA2BGR;// BGRA to BGR 格式  
ImgProc.COLOR_RGBA2BGR;// RGBA to BGR 格式
ImgProc.COLOR_BGRA2GRAY;// BGRA to GRAY 格式
ImgProc.COLOR_RGB2HSV;// RGB to HSV格式  H的范围值是0-180
ImgProc.COLOR_RGB2HSV_FULL;// RGB to HSV_FULL格式  H的范围值是0-360 (PS:FULL获取更大的精度,消耗更多的内存)

其他的就不介绍了,命名中的2代表to。然后将左边的颜色格式转为右边的颜色格式而已。

3. 通道分解和合成

我们如果想操作通道。有很多方法,最简单的是我们直接遍历然后修改通道参数:

代码语言:javascript复制
Mat det;
double [] temp;
for(int y=0;y<det.cols();  y){
   for(int x=0;x<det.rows();  x){
        temp = det.get(x,y);
       //根据通道情况,修改值
   }
}

或者,我们直接修改指定位置的颜色值:mat.put(x,y,temp);

而我们如果想批量针对通道进行操作,可以使用OpenCV提供的算法:

代码语言:javascript复制
Core.split(); //分解通道
Core.merge(); //合成通道
Core.mixChannels(); //通道拆分复制 上面两个分解和合成是该函数的一种特例场景。

下面来介绍这三个方法的传值:

代码语言:javascript复制
Core.split(Mat m, List<Mat> mv) 
//Mat m :需要进行通道分解的源Mat
//List<Mat> mv: 将源Mat的每个通道拆解为单通道的Mat,我们如果直接将该List中的Mat进行显示将会全部是灰色的(因为是单通道了)

使用:

代码语言:javascript复制
List<Mat> defList=new ArrayList<>();
Core.split(rgba,defList);
for(Mat mat:defList){
  //我们得到的都是单通道的Mat。 如果直接转Bitmap显示 将只会看到灰度图 
}

我们如果想只想看到Mat中的红色通道的效果,而不是看灰度图。该怎么处理?

需要结合split方法和merge方法一起使用

代码语言:javascript复制
Core.merge(List<Mat> mv, Mat dst)
//List<Mat> mv: 需要合并的Mat的集合,会将全部的mat的通道合并到dst中去 List中的Mat 必须宽高相同,
//dst:输出的Mat:它的宽高必须和List中的Mat的宽高相同。而通道数会是List中所有Mat的通道数的总和

使用:将上面split拆解的Mat进行合并

代码语言:javascript复制
//创建单通道 CvType.CV_8UC1
Mat blackMat = new Mat(rgba.size(), CvType.CV_8UC1, new Scalar(0)); //绘制一个全黑的Mat
List<Mat> mergeList = new ArrayList<>();
//创建一个3通道的 Mat对象
Mat dst = new Mat(rgba.size(), CvType.CV_8UC3);
mergeList.add(blackMat); //B 通道黑色
mergeList.add(blackMat); //G 通道黑色
mergeList.add(defList.get(0));// R通道
Core.merge(mergeList, dst);

最终我们得到的dst就是一个三通道的图像了,只有R通道有值。(图片是BGR的顺序存储的)

4. 总结

到这里关于通道的介绍就结束了。以上内容基于自己的理解和验证。在openCV4.6 SDK版本,java开发环境下进行的验证。

希望对通道和像素转换不太了解的小伙伴能有一些帮助。

0 人点赞