【從零學習OpenCV 4】影像中添加椒鹽雜訊

  • 2019 年 12 月 24 日
  • 筆記

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

椒鹽雜訊又被稱作脈衝雜訊,它會隨機改變影像中的像素值,是由相機成像、影像傳輸、解碼處理等過程產生的黑白相間的亮暗點雜訊,其樣子就像在影像上隨機的撒上一些鹽粒和黑椒粒,因此被稱為椒鹽雜訊。目前為止OpenCV 4中沒有提供專門用於為影像添加椒鹽雜訊的函數,需要使用者根據自己需求去編寫生成椒鹽雜訊的程式,本小節將會帶領讀者一起實現在影像中添加椒鹽雜訊。

考慮到椒鹽雜訊會隨機產生在影像中的任何一個位置,因此對於椒鹽雜訊的生成需要使用到OpenCV 4中能夠產生隨機數的函數rand(),為了能夠生成不同數據類型的隨機數,該函數擁有多種演變形式,在程式碼清單5-3中給出了這幾種形式的函數原型。

程式碼清單5-3 隨機數函數原型  1.  int cvflann::rand()  2.  3.  double cvflann::rand_double(double  high = 1.0,  4.                                    double  low = 0  5.                                    )  6.  7.  int cvflann::rand_int(int  high = RAND_MAX,  8.                            int  low = 0  9.                            )
  • high:輸出隨機數的最大值
  • low:輸出隨機數的最小值

這三個函數都可以用來生成隨機數,區別在於第一個函數rand()不需要輸入任何的參數,返回的隨機數為int類型;第二個函數rand_double()需要輸入隨機數的上下邊界,默認狀態下生成的隨機數在0到1之間,返回的隨機數為double類型;第三個函數rand_int()也需要輸入隨機數的上下邊界,不同的是該函數默認狀態下的最大值為RAND_MAX,這是一個由系統定義的宏變數,在筆者的電腦中這個變數表示的是整數32767,該函數會返回的隨機數為int類型。這三個函數的功能和使用方式上都比較簡單,這裡有個小技巧,rand()函數雖然沒有給出隨機數的取值範圍,但是可以採用求取餘數的方式來實現對隨機數範圍的設置,例如使用rand()函數隨機生成一個0到100之間的整數,可以使用「int a = rand()%100」語句來實現,因為無論任何數除以100後的餘數一定在0到100之間。

注意

該函數與之前所有的函數不相同之處在於該函數並不在cv的命名空間中,而是在cvflann類中,因此在使用的時候一定要在函數前添加前綴,如cvflann::rand()。有些讀者在使用rand()函數時不添加cvflann命名空間的前綴也可以使用,是因為該函數不僅在OpenCV 4中有,在stdlib.h頭文件中同樣有這個函數,只有在函數前面添加了命名空間前綴時使用的才是OpenCV 4中的隨機數生成函數。

了解隨機函數之後,在影像中添加椒鹽雜訊大致分為以下4個步驟

Step1:確定添加椒鹽雜訊的位置。根據椒鹽雜訊會隨機出現在影像中任何一個位置的特性,我們可以通過隨機數函數生成兩個隨機數,分別用於確定椒鹽雜訊產生的行和列。

Step2:確定雜訊的種類。不僅椒鹽雜訊的位置是隨機的,雜訊點是黑色的還是白色的也是隨機的,因此可以再次生成的隨機數,通過判斷隨機數的奇偶性確定該像素是黑色雜訊點還是白色雜訊點。

Step3:修改影像像素灰度值。判斷影像通道數,通道數不同的影像中像素表示白色的方式也不相同。也可以根據需求只改變多通道影像中某一個通道的數值。

Step4:得到含有椒鹽雜訊的影像。

依照上述思想,在程式碼清單5-4中給出在影像中添加椒鹽雜訊的示常式序,程式中判斷了輸入影像是灰度圖還是彩色圖,但是沒有對彩色影像的單一顏色通道產生椒鹽雜訊。如果需要對某一通道產生椒鹽雜訊,只需要單獨處理彩色影像每個通道即可。程式在影像中添加椒鹽雜訊的結果如圖5-6、圖5-7所示,由於椒鹽雜訊是隨機添加的,因此每次運行結果會有所差異。

程式碼清單5-4 mySaltAndPepper.cpp影像中添加椒鹽雜訊  1.  #include <opencv2opencv.hpp>  2.  #include <iostream>  3.  4.  using namespace cv;  5.  using namespace std;  6.  7.  //鹽雜訊函數  8.  void saltAndPepper(cv::Mat image, int n)  9. {  10.    for (int k = 0; k<n / 2; k++)  11.    {  12.      //隨機確定影像中位置  13.      int i, j;  14.      i = std::rand() % image.cols; //取餘數運算,保證在影像的列數內  15.      j = std::rand() % image.rows; //取餘數運算,保證在影像的行數內  16.      int write_black = std::rand() % 2; //判定為白色雜訊還是黑色雜訊的變數  17.      if (write_black == 0) //添加白色雜訊  18.      {  19.        if (image.type() == CV_8UC1) //處理灰度影像  20.        {  21.          image.at<uchar>(j, i) = 255; //白色雜訊  22.        }  23.        else if (image.type() == CV_8UC3) //處理彩色影像  24.        {  25.          image.at< Vec3b>(j, i)[0] = 255; //Vec3b為opencv定義的3個值的向量類型  26.          image.at<Vec3b>(j, i)[1] = 255; //[]指定通道,B:0,G:1,R:2  27.          image.at<Vec3b>(j, i)[2] = 255;  28.        }  29.      }  30.      else  //添加黑色雜訊  31.      {  32.        if (image.type() == CV_8UC1)  33.        {  34.          image.at<uchar>(j, i) = 0;  35.        }  36.        else if (image.type() == CV_8UC3)  37.        {  38.          image.at< Vec3b>(j, i)[0] = 0; //Vec3b為opencv定義的3個值的向量類型  39.          image.at<Vec3b>(j, i)[1] = 0; //[]指定通道,B:0,G:1,R:2  40.          image.at<Vec3b>(j, i)[2] = 0;  41.        }  42.      }  43.  44.    }  45.  }  46.  47.  int main()  48. {  49.    Mat lena = imread("lena.png");  50.    Mat equalLena = imread("equalLena.png", IMREAD_ANYDEPTH);  51.    if (lena.empty()||equalLena.empty())  52.    {  53.      cout << "請確認影像文件名稱是否正確" << endl;  54.      return -1;  55.    }  56.    imshow("lena原圖", lena);  57.    imshow("equalLena原圖", equalLena);  58.    saltAndPepper(lena, 10000); //彩色影像添加椒鹽雜訊  59.    saltAndPepper(equalLena, 10000); //灰度影像添加椒鹽雜訊  60.    imshow("lena添加雜訊", lena);  61.    imshow("equalLena添加雜訊", equalLena);  62.    waitKey(0);  63.    return 0;  64.  }

圖5-6 mySaltAndPepper.cpp程式中灰度圖添加椒鹽雜訊結果

圖5-7 mySaltAndPepper.cpp程式中彩色圖添加椒鹽雜訊結果