【從零學習OpenCV 4】影像像素統計

  • 2019 年 11 月 22 日
  • 筆記

經過幾個月的努力,小白終於完成了市面上第一本OpenCV 4入門書籍《從零學習OpenCV 4》。為了更讓小夥伴更早的了解最新版的OpenCV 4,小白與出版社溝通,提前在公眾號上連載部分內容,請持續關注小白。

我們可以將數字影像理解成一定尺寸的矩陣,矩陣中每個元素的大小表示了影像中每個像素的亮暗程度,因此統計矩陣中的最大值,就是尋找影像中灰度值最大的像素,計算平均值就是計算影像像素平均灰度,可以用來表示影像整體的亮暗程度。因此針對矩陣數據的統計工作在影像像素中同樣具有一定的意義和作用。在OpenCV 4中集成了求取影像像素最大值、最小值、平均值、均方差等眾多統計量的函數,接下來將詳細介紹這些功能的相關函數。

1

01

尋找影像像素最大值與最小值

OpenCV 4提供了尋找影像像素最大值、最小值的函數minMaxLoc(),該函數的原型在程式碼清單3-7中給出。

程式碼清單3-7 minMaxLoc()函數原型  1.  void cv::minMaxLoc(InputArray src,  2.                      double * minVal,  3.                      double * maxVal = 0,  4.                      Point * minLoc = 0,  5.                      Point * maxLoc = 0,  6.                      InputArray mask = noArray()  7.                        )
  • src:需要尋找最大值和最小值的影像或者矩陣,要求必須是單通道矩陣
  • minVal:影像或者矩陣中的最小值。
  • maxVal:影像或者矩陣中的最大值。
  • minLoc:影像或者矩陣中的最小值在矩陣中的坐標。
  • maxLoc:影像或者矩陣中的最大值在矩陣中的坐標。
  • mask:掩模,用於設置在影像或矩陣中的指定區域尋找最值。

這裡我們見到了一個新的數據類型Point,該數據類型是用於表示影像的像素坐標,由於影像的像素坐標軸以左上角為坐標原點,水平方向為x軸,垂直方向為y軸,因此Point(x,y)對應於影像的行和列表示為Point(列數,行數)。在OpenCV中對於2D坐標和3D坐標都設置了多種數據類型,針對2D坐標數據類型定義了整型坐標cv::Point2i(或者cv::Point)、double型坐標cv::Point2d、浮點型坐標cv::Point2f,對於3D坐標同樣定義了上述的坐標數據類型,只需要將其中的數字「2」變成「3」即可。對於坐標中x、y、z軸的具體數據,可以通過變數的x、y、z屬性進行訪問,例如Point.x可以讀取坐標的x軸數據。

該函數實現的功能是尋找影像中特定區域內的最值,函數第一個參數是輸入單通道矩陣,需要注意的是,該變數必須是一個單通道的矩陣數據,如果是多通道的矩陣數據,需要用cv::Mat::reshape()將多通道變成單通道,或者分別尋找每個通道的最值,然後再進行比較尋找到全局最值。對於cv::Mat::reshape()的用法,在程式碼清單3-8中給出。第二到第五個參數分別是指向最小值、最大值、最小值位置和最大值位置的指針,如果不需要尋找某一個參數,可以將該參數設置為NULL,函數最後一個參數是尋找最值得掩碼矩陣,用於標記尋找上述四個值的範圍,參數默認值為noArray(),表示尋找範圍是矩陣中所有數據。

程式碼清單3-8 Mat::reshape()函數原型  1.  Mat cv::Mat::reshape(int  cn,  2.                         int  rows = 0  3.                           )
  • cn:轉換後矩陣的通道數。
  • rows:轉換後矩陣的行數,如果參數為零,則轉換後行數與轉換前相同。

注意

如果矩陣中存在多個最大值或者最小值時,minMaxLoc()函數輸出最值的位置為按行掃描從左向右第一次檢測到最值的位置,同時輸入參數時一定要注意添加取地址符。

為了讓讀者更加了解minMaxLoc()函數的原理和使用方法,在程式碼清單3-9中給出尋找矩陣最值的示常式序,在圖3-6中給出了程式運行的最終結果,在圖3-7給出了創建的兩個矩陣和通道變換後的矩陣在Image Watch中查看的內容,。

程式碼清單3-9 myfindMinAndMax.cpp尋找矩陣中的最值  1.  #include <opencv2opencv.hpp>  2.  #include <iostream>  3.  #include <vector>  4.  5.  using namespace std;  6.  using namespace cv;  7.  8.  int main()  9. {  10.    system("color F0"); //更改輸出介面顏色  11.    float a[12] = { 1, 2, 3, 4, 5, 10, 6, 7, 8, 9, 10, 0 };  12.    Mat img = Mat(3, 4, CV_32FC1, a); //單通道矩陣  13.    Mat imgs = Mat(2, 3, CV_32FC2, a); //多通道矩陣  14.    double minVal, maxVal; //用於存放矩陣中的最大值和最小值  15.    Point minIdx, maxIdx; ////用於存放矩陣中的最大值和最小值在矩陣中的位置  16.  17.                 /*尋找單通道矩陣中的最值*/  18.    minMaxLoc(img, &minVal, &maxVal, &minIdx, &maxIdx);  19.    cout << "img中最大值是:" << maxVal << " " << "在矩陣中的位置:" << maxIdx << endl;  20.    cout << "img中最小值是:" << minVal << " " << "在矩陣中的位置:" << minIdx << endl;  21.  22.    /*尋找多通道矩陣中的最值*/  23.    Mat imgs_re = imgs.reshape(1, 4); //將多通道矩陣變成單通道矩陣  24.    minMaxLoc(imgs_re, &minVal, &maxVal, &minIdx, &maxIdx);  25.    cout << "imgs中最大值是:" << maxVal << " " << "在矩陣中的位置:" << maxIdx << endl;  26.    cout << "imgs中最小值是:" << minVal << " " << "在矩陣中的位置:" << minIdx << endl;  27.    return 0;  28.  }

圖3-6 findMinAndMax.cpp程式運行結果

圖3-7 Image Watch查看findMinAndMax.cpp程式中矩陣的內容

1

02

計算影像的均值和標準方差

影像的均值表示影像整體的亮暗程度,影像的均值越大影像整體越亮。標準方差表示影像中明暗變化的對比程度,標準差越大表示影像中明暗變化越明顯。OpenCV 4提供了mean()函數用於計算影像的平均值,提供了meanStdDev()函數用於同時計算影像的均值和標準方差。接下來將詳細的介紹這兩個函數的使用方法。

程式碼清單3-10 mean()函數原型  1.  cv::Scalar cv::mean(InputArray src,  2.                        InputArray mask = noArray()  3.                          )
  • src:待求平均值的影像矩陣。
  • mask:掩模,用於標記求取哪些區域的平均值。

該函數用來求取影像矩陣的每個通道的平均值,函數的第一個參數用來輸入待求平均值的影像矩陣,其通道數目可以在1到4之間。需要注意的是,該函數的返回值是一個cv::Scalar類型的變數,函數的返回值有4位,分別表示輸入影像4個通道的平均值,如果輸入影像只有1個通道,那麼返回值的後三位都為0,例如輸入該函數一個單通道平均值為1的影像,輸出的結果為[1,0,0,0],可以通過cv::Scalar[n]查看第n個通道的平均值。該函數的第二個參數用於控制影像求取均值的範圍,在第一個參數中去除第二個參數中像素值為0的像素,計算的原理如式(3.5)所示,當不輸入第二個參數時,表示求取第一個參數全部像素的平均值。

(3.5)

其中

表示第c個通道的平均值,

表示第c個通道像素的灰度值。

meanStdDev()函數可以同時求取影像每個通道的平均值和標準方差,其函數原型在程式碼清單3-11中給出。

程式碼清單3-11 meanStdDev()函數原型  1.  void cv::meanStdDev(InputArray src,  2.                        OutputArray mean,  3.                        OutputArray stddev,  4.                        InputArray mask = noArray()  5.                          )
  • src:待求平均值的影像矩陣。
  • mean:影像每個通道的平均值,參數為Mat類型變數。
  • stddev:影像每個通道的標準方差,參數為Mat類型變數。
  • mask:掩模,用於標記求取哪些區域的平均值和標準方差。

該函數的第一個參數與前面mean()函數第一個參數相同,都可以是1-4通道的影像,不同之處在於該函數沒有返回值,影像的均值和標準方差輸出在函數的第二個和第三個參數中,區別於mean()函數,用於存放平均值和標準方差的是Mat類型變數,變數中的數據個數與第一個參數通道數相同,如果輸入影像只有一個通道,該函數求取的平均值和標準方差變數中只有一個數據。該函數計算原理如式(3.6)所示。

(3.6)

我們在程式碼清單3-12中給出了利用上面兩個函數計算程式碼清單3-9中img和imgs兩個矩陣的平均值和標準方差,並在圖3-8給出了程式運行的結果。

程式碼清單3-12 myMeanAndmearStdDev.cpp計算矩陣平均值和標準方差  1.  #include <opencv2opencv.hpp>  2.  #include <iostream>  3.  #include <vector>  4.  5.  using namespace std;  6.  using namespace cv;  7.  int main()  8. {  9.    system("color F0"); //更改輸出介面顏色  10.    float a[12] = { 1, 2, 3, 4, 5, 10, 6, 7, 8, 9, 10, 0 };  11.    Mat img = Mat(3,4, CV_32FC1, a); //單通道矩陣  12.    Mat imgs = Mat(2, 3, CV_32FC2, a); //多通道矩陣  13.  14.    cout << "/* 用meanStdDev同時求取影像的均值和標準方差 */" << endl;  15.    Scalar myMean;  16.    myMean = mean(imgs);  17.    cout << "imgs均值=" << myMean << endl;  18.    cout << "imgs第一個通道的均值=" << myMean[0] << " "  19.        << "imgs第二個通道的均值=" << myMean[1] << endl << endl;  20.  21.    cout << "/* 用meanStdDev同時求取影像的均值和標準方差 */" << endl;  22.    Mat myMeanMat, myStddevMat;  23.  24.    meanStdDev(img, myMeanMat, myStddevMat);  25.    cout << "img均值=" << myMeanMat << " " << endl;  26.    cout << "img標準方差=" << myStddevMat << endl << endl;  27.    meanStdDev(imgs, myMeanMat, myStddevMat);  28.    cout << "imgs均值=" << myMeanMat << " " << endl << endl;  29.    cout << "imgs標準方差=" << myStddevMat << endl;  30.    return 0;  31.  }

圖3-8 meanAndmearStdDev.cpp程式運行結果