C++之OpenCV入門到提高003:矩陣的掩膜(Mask)處理

可均可可發表於2024-11-01

一、介紹
    今天是這個系列《C++之 Opencv 入門到提高》得第三篇文章。今天這篇文章也不難,主要介紹如何使用 Opencv 對影像進行掩膜處理,提高影像的對比度。在這個過程中,我們可以學到如何獲取影像指標、如何處理畫素值越界等問題。我們一步一個腳印的走,收穫就會越來越多。雖然操作很簡單,但是要下功夫理解每個技術點,把基礎打紮實,才能讓以後得學習過程更順利一點。OpenCV 具體的簡介內容,我就不多說了,網上很多,大家可以自行腦補。
    OpenCV 的官網地址:https://opencv.org/,元件下載地址:https://opencv.org/releases/
    OpenCV 官網學習網站:https://docs.opencv.ac.cn/4.10.0/index.html

    我需要進行說明,以防大家不清楚,具體情況我已經羅列出來。
        作業系統:Windows Professional 10(64位)
        開發元件:OpenCV – 4.10.0
        開發工具:Microsoft Visual Studio Community 2022 (64 位) - Current版本 17.8.3
        開發語言:C++(VC16)

二、例項學習
    這一節的內容不多,介面 API 其實也是挺簡單的。但是我們學習介面,不能冷冰冰的只是學API、一些基礎知識也是要掌握的。
    影像中的掩膜(Mask)是什麼?在影像處理中,掩膜(Mask)是一種用於控制影像處理區域或處理過程的特殊影像。它通常是一個與原始影像同樣大小的二維矩陣,用於選擇性地遮蓋或顯示影像的特定區域。掩膜可以用於多種影像處理任務,如影像分割、特徵提取、增強等。
    在數字影像處理中,掩膜通常是一個二進位制影像,其中畫素值為1的區域表示要保留的區域,畫素值為0的區域表示要排除的區域。透過將掩膜與原始影像進行邏輯運算,可以建立新的影像,其中只有掩膜中標記為1的區域被保留,其他區域被排除。
    掩膜在影像處理中有多種應用。例如,在影像分割中,掩膜可用於選擇性地突出顯示感興趣的區域,以便進一步處理或分析。在特徵提取中,掩膜可用於提取影像中的特定形狀或結構。此外,掩膜還可以用於影像增強,例如透過模糊或銳化特定區域來改善影像質量。

    數字影像處理中,掩模為二維矩陣陣列,有時也用多值影像,影像掩模主要用於:
      ①提取感興趣區,用預先製作的感興趣區掩模與待處理影像相乘,得到感興趣區影像,感興趣區內影像值保持不變,而區外影像值都為0。
      ②遮蔽作用,用掩模對影像上某些區域作遮蔽,使其不參加處理或不參加處理引數的計算,或僅對遮蔽區作處理或統計。
      ③結構特徵提取,用相似性變數或影像匹配方法檢測和提取影像中與掩模相似的結構特徵。
      ④特殊形狀影像的製作。

    什麼是點陣圖與掩膜的與運算?
      其實就是原圖中的每個畫素和掩膜中的每個對應畫素進行與運算。比如1 & 1 = 1;1 & 0 = 0;
      比如一個3 * 3的影像與3 * 3的掩膜進行運算,得到的結果影像就是:
    
      說白了,我們使用掩膜(mask)點陣圖來選擇哪個畫素允許複製,哪個畫素不允許複製。如果mask畫素的值是非0的,我就複製它,也就是保留下,否則不複製,不保留。

    在所有影像基本運算的操作函式中,凡是帶有掩膜(mask)的處理函式,其掩膜都參與運算(輸入影像運算完之後再與掩膜影像或矩陣運算)。

 1 #include <opencv2/opencv.hpp>
 2 #include <iostream>
 3 #include <math.h>
 4 
 5 using namespace std;
 6 using namespace cv;
 7 
 8 
 9 int main()
10 {
11     Mat src, dst;
12     src = imread("D:\\360MoveData\\Users\\Administrator\\Desktop\\TestImage\\4.jpg", IMREAD_UNCHANGED);
13     if (!src.data)
14     {
15         cout << "圖片載入錯誤!!!" << endl;
16         return -1;
17     }
18 
19     namedWindow("原始影像", WINDOW_AUTOSIZE);
20     imshow("原始影像", src);
21 
22     //1、獲取圖形畫素指標
23     //CV_Assert(myImage.depth()==CV_8U);
24     //Mat.ptr<uchar>(int i=0)獲取畫素矩陣的指標,索引 i 表示第幾行,從0開始計數。
25     //獲取當前行指標 const uchar* current=myImage.ptr<uchar>(row);
26     //獲取當前畫素點 p(row,col) 的畫素值 p(row,col)=current[col];
27 
28     //2、畫素範圍處理 saturate_cast<uchar>
29     //saturate_cast<uchar>(-100),返回 0.
30     //saturate_cast<uchar>(288),返回 255,
31     //saturate_cast<uchar>(100),返回 100.
32     // 這個函式的功能是確保 RGB 值的範圍在 0-255 之間。
33 
34     double startDate = getTickCount();
35 
36     //第一種實現對比度
37     /*int cols = (src.cols - 1) * src.channels();
38     int offerts = src.channels();
39     int rows = src.rows;
40 
41     dst = Mat(src.size(), src.type());
42 
43     for (int row = 1; row < (rows - 1); row++)
44     {
45         const uchar* previous = src.ptr<uchar>(row - 1);
46         const uchar* current = src.ptr<uchar>(row);
47         const uchar* next = src.ptr<uchar>(row + 1);
48         uchar* output = dst.ptr<uchar>(row);
49         for (int col = offerts; col < cols; col++)
50         {
51             output[col] = saturate_cast<uchar>(5 * current[col] - (current[col - offerts] + current[col + offerts] + previous[col] + next[col]));
52         }
53     }*/
54 
55     //3、定義掩膜
56     // Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
57     //filter2D(src, dst, src.depth(), kernel):src 是原圖,dst 是目標圖,src.depth() 表示點陣圖深度,有 32,24,8 等。
58 
59     //第二種實現對比度
60     Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
61     filter2D(src, dst, src.depth(), kernel);
62 
63     double totalTime = (getTickCount() - startDate) / getTickFrequency();
64 
65     cout << "消費時間:"<< totalTime << endl;
66 
67     namedWindow("對比度影像", WINDOW_AUTOSIZE);
68     imshow("對比度影像", dst);
69 
70 
71     waitKey(0);
72 
73     return 0;
74 }

    生成的效果圖如下:

    

    當然了,在原始碼中,提供了兩種實現,效果都是一樣的。一種是自己實現的,一種是透過呼叫介面實現的。這也說明了一個問題,只要你掌握的夠深入,和介面效果一樣的問題,你也可以寫得出。

    如果我們吧註釋的程式碼開啟,把【第二種實現】註釋掉,原始碼:

 1 int cols = (src.cols - 1) * src.channels();
 2 int offerts = src.channels();
 3 int rows = src.rows;
 4 
 5 dst = Mat(src.size(), src.type());
 6 
 7 for (int row = 1; row < (rows - 1); row++)
 8 {
 9     const uchar* previous = src.ptr<uchar>(row - 1);
10     const uchar* current = src.ptr<uchar>(row);
11     const uchar* next = src.ptr<uchar>(row + 1);
12     uchar* output = dst.ptr<uchar>(row);
13     for (int col = offerts; col < cols; col++)
14     {
15         output[col] = saturate_cast<uchar>(5 * current[col] - (current[col - offerts] + current[col + offerts] + previous[col] + next[col]));
16     }
17 }

    而且,自己寫的效能更好。對比如圖:

    

    再看看我們自己寫的執行時間:

    

    我們自己寫實現的效果:

    


    
三、總結
    這是 C++ 使用 OpenCV 的第三篇文章,概念挺難懂的,其實操作起來也沒那麼難,當然了,這只是我們入門的開始,那就繼續吧。皇天不負有心人,不忘初心,繼續努力,做自己喜歡做的,開心就好。

相關文章