【從零學習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程式中彩色圖添加椒鹽雜訊結果