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軸長度。
示例:
Mat rgba ;// 假如我有一個 rgba的Mat對象
int channels = rgba.channels(); // channels 的長度是4
double[] temp = rgba.get(rgba.rows() / 2, rgba.cols() / 2); //取中間點顏色值
//temp 的數組的長度就是通道數,所以它的length=4
當我們遍歷一遍temp的結果會得到:
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 順序而已。
new Scalar(10,255,255); //顏色順序是 B,G,R
我們如果是一個 BGR 格式的 Mat 對象那么顏色值會怎么顯示呢?
還是使用上面的 Mat 我們進行轉換之后,看看同一個點輸出的結果:
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
數據的結果實際情況是:
就會出現顏色的通道數變化。
不知道注意到了沒有,我上面是將rbga直接轉成了BGR。
在高位轉換的情況下,A通道會被直接丟棄。體現在圖像上就會沒有透明效果了。
我們如果想確保A通道也轉換,可以使用:
Imgproc.cvtColor(rgba, bgra, Imgproc.COLOR_RGBA2BGRA);
2.1 Gray 灰度圖轉換
當我們將RGBA或者BGR等彩色圖像轉換為GRAY灰色的時候,Mat的通道數就會被壓制為單通道G了。效果如下:
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的信息。其實說破很簡單:
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. 通道分解和合成
我們如果想操作通道。有很多方法,最簡單的是我們直接遍歷然后修改通道參數:
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提供的算法:
Core.split(); //分解通道
Core.merge(); //合成通道
Core.mixChannels(); //通道拆分復制 上面兩個分解和合成是該函數的一種特例場景。
下面來介紹這三個方法的傳值:
Core.split(Mat m, List<Mat> mv)
//Mat m :需要進行通道分解的源Mat
//List<Mat> mv: 將源Mat的每個通道拆解為單通道的Mat,我們如果直接將該List中的Mat進行顯示將會全部是灰色的(因為是單通道了)
使用:
List<Mat> defList=new ArrayList<>();
Core.split(rgba,defList);
for(Mat mat:defList){
//我們得到的都是單通道的Mat。 如果直接轉Bitmap顯示 將只會看到灰度圖
}
我們如果想只想看到Mat中的紅色通道的效果,而不是看灰度圖。該怎么處理?
需要結合split方法和merge方法一起使用
Core.merge(List<Mat> mv, Mat dst)
//List<Mat> mv: 需要合并的Mat的集合,會將全部的mat的通道合并到dst中去 List中的Mat 必須寬高相同,
//dst:輸出的Mat:它的寬高必須和List中的Mat的寬高相同。而通道數會是List中所有Mat的通道數的總和
使用:將上面split拆解的Mat進行合并
//創建單通道 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開發環境下進行的驗證。