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等等的颜色转换其实都是针对我们的单像素中的通道值在处理。
- 单通道的,是Gray灰度图。
- 双通道的,两个通道值一个为实数,个为虚数。主要是RGB555和RGB565格式的图像,这个通道通常用来计算。
- 三通道的,图片就是彩色图像了,例如RGB,BGR,HSV,HLS等等。
- 四通道的,图片带透明度的图像了。相较于三通道多了一个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开发环境下进行的验证。
希望对通道和像素转换不太了解的小伙伴能有一些帮助。