图像平滑滤波
卷積與濾波概念
離散卷積
丟兩個骰子,求點數加起來為 ttt 的概率是多少?
兩個骰子加起來為4的概率:
f(1)g(3)+f(2)g(2)+f(3)g(1)f(1)g(3) + f(2)g(2) + f(3)g(1)f(1)g(3)+f(2)g(2)+f(3)g(1)
寫成卷積標準形式為:
(f?g)(4)=∑i=13f(i)g(4?i)(f *g)(4) = \sum_{i=1}^3f(i)g(4-i) (f?g)(4)=i=1∑3?f(i)g(4?i)
進一步,兩個骰子加起來為 ttt 的概率,就是二個骰子的概率密度函數的卷積:
(f?g)(t)=∑i=1t?1f(i)g(t?i)(f *g)(t) = \sum_{i=1}^{t-1}f(i)g(t-i) (f?g)(t)=i=1∑t?1?f(i)g(t?i)
與1維卷積類似,圖像(二維)卷積定義:
(f?g)(x,y)=1NM∑i=0N?1∑j=0M?1f(i,j)g(x?i,y?j)(f *g)(x,y) = \frac{1}{NM}\sum_{i=0}^{N-1}\sum_{j=0}^{M-1}f(i,j)g(x-i,y-j) (f?g)(x,y)=NM1?i=0∑N?1?j=0∑M?1?f(i,j)g(x?i,y?j)
ggg 稱為濾波器
圖像濾波如何計算
- 濾波器 g(x,y)g(x,y)g(x,y) 左右、上下反轉:得到 g(?x,?y)g(-x,-y)g(?x,?y);
- 按照前述公式計算卷積(先乘后累加)
圖像平滑濾波與去噪
圖像濾波由卷積定義,基本知識:
- 4-領域與8-領域
圖像平滑:
- 平均濾波:在一個小區域內(通常3*3)像素值平均
g(x,y)=1M∑i,jf(i,j)g(x,y) = \frac{1}{M}\sum_{i,j}f(i,j) g(x,y)=M1?i,j∑?f(i,j)
其中MMM表示像素值個數,濾波器采用4-領域時M=5M = 5M=5;濾波器采用8-領域時M=9M = 9M=9;如果不考慮中間像素M=8M = 8M=8。 - 加權平均濾波:在一個小區域內像素值加權平均
g(x,y)=∑i,jwijf(i,j)g(x,y) = \sum_{i,j}w_{ij}f(i,j) g(x,y)=i,j∑?wij?f(i,j)
其中wijw_{ij}wij?表示所有像素值之和的倒數。 - 高斯濾波器:元素值分布根據位置呈現高斯函數的特點
116[121242121]\frac{1}{16} \left[ \begin{array}{ccc} 1 & 2 & 1\\\\ 2 & 4 & 2\\\\ 1 & 2 & 1\\\\ \end{array} \right] 161??????????121?242?121?????????? - 雙邊濾波器:元素值不僅與位置有關,還和源圖像在該位置的像素值有關。其是空域核和值域核的疊加,通過雙邊濾波器,可以在使圖像平滑的同時,比較好的保留邊緣。
- 中值濾波:確定窗口及位置(含有奇數個像素),窗口內像素按灰度大小排序,取中間值代替原窗口中心像素值。對椒鹽噪聲有效。
總結:
數學形態學濾波
數學形態學基本操作——膨脹和腐蝕
A?BA\bigoplus BA?B表示集合 AAA 用結構元素 BBB 膨脹(dilate),定義為:
A?B=∪(A)b(b∈B)A\bigoplus B = \cup(A)_b (b\in B) A?B=∪(A)b?(b∈B)
表示,將 AAA 按照 BBB 中的所有元素進行平移,將所有平移的結果取并集。
A?BA\bigoplus BA?B表示集合 AAA 用結構元素 BBB 腐蝕(erode),定義為:
A?B=∩(A)?b(b∈B)A\bigodot B = \cap(A)_{-b} (b\in B) A?B=∩(A)?b?(b∈B)
表示,將 AAA 按照 BBB 中的所有元素進行反方向平移,將所有平移的結果取并集。
直觀表現是,腐蝕使黑色區域變大了,膨脹使白色區域變大了。
圖像形態學操作——開閉運算
- 膨脹和腐蝕并不互為逆運算,二者級聯使用可生成新的形態學操作
- 開運算:先腐蝕后膨脹(島嶼分開了):
(A?B)?B(A\bigodot B)\bigoplus B (A?B)?B - 閉運算:先膨脹后腐蝕(島嶼閉合了):
(A?B)?B(A\bigoplus B)\bigodot B (A?B)?B - 先開后閉:可有效去除噪聲
數學形態學去噪方法
實戰演練:圖像平滑濾波對比
OpenCV實現濾波及平滑去噪各函數
- 圖像濾波
- 平均濾波
- 高斯平滑濾波
- 中值濾波
- 雙邊濾波
python代碼:
import cv2 as cv import numpy as npdef gauss_noise(image, mean = 0, var = 0.001):"""添加高斯噪聲mean: 均值var: 方差"""image = np.array(image/255, dtype = float)noise = np.random.normal(mean, var ** 0.5, image.shape)out = image + noiseif out.min() < 0:low_clip = -1.else:low_clip = 0.out = np.clip(out, low_clip, 1.0)out = np.uint8(out*255)# cv.imshow("Gauss_noise", out)return outfilename = 'C:/python/img/lena.jpg' img = cv.imread(filename) img = gauss_noise(img) #原圖像加入高斯噪聲blur = cv.blur(img, (5, 5)) #平均濾波 gauss = cv.GaussianBlur(img, (5, 5), 0) #高斯濾波 median = cv.medianBlur(img, 5) #中值濾波 bilateral = cv.bilateralFilter(img, 5, 150, 150) #雙邊濾波cv.imshow('Image', img) cv.imshow('Blurred', blur) cv.imshow('Gauss', gauss) cv.imshow('Median filtered', median) cv.imshow('Bilateral filtered', bilateral)cv.waitKey() cv.destroyAllWindows()c++添加高斯噪聲:
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> #include <cstdlib> #include <limits> #include <cmath> using namespace cv; using namespace std; double generateGaussianNoise(double mu, double sigma) {//定義一個特別小的值const double epsilon = numeric_limits<double>::min();//返回目標數據類型能表示的最逼近1的正數和1的差的絕對值static double z; //局部變量,這樣可以在不同的文件中定義同名函數和同名變量,而不用擔心命名沖突double u1, u2;//構造隨機變量do{u1 = rand()*(1.0 / RAND_MAX);u2 = rand()*(1.0 / RAND_MAX);} while (u1 <= epsilon);//構造高斯隨機變量Xz = sqrt(-2.0*log(u1))*sin(2 * CV_PI * u2);return z * sigma + mu; } //為圖像添加高斯噪聲 Mat addGaussianNoise(Mat& srcImage) {Mat resultImage = srcImage.clone(); //深拷貝,克隆int channels = resultImage.channels(); //獲取圖像的通道int nRows = resultImage.rows; //圖像的行數int nCols = resultImage.cols*channels; //圖像的總列數//判斷圖像的連續性if (resultImage.isContinuous()) //判斷矩陣是否連續,若連續,我們相當于只需要遍歷一個一維數組 {nCols *= nRows;nRows = 1;}for (int i = 0; i < nRows; i++){for (int j = 0; j < nCols; j++){ //添加高斯噪聲int val = resultImage.ptr<uchar>(i)[j] + generateGaussianNoise(2, 0.8) * 32;if (val < 0)val = 0;if (val > 255)val = 255;resultImage.ptr<uchar>(i)[j] = (uchar)val;}}return resultImage; } int main() {Mat srcImage = imread("lena.jpg", 1);if (!srcImage.data)return -1;imshow("srcImage", srcImage);Mat resultImage = addGaussianNoise(srcImage);imshow("resultImage", resultImage);waitKey(0);return 0; }該方法用于報告矩陣是否連續。
如果矩陣元素在每行末尾連續存儲而沒有間隙,則方法返回true。 否則,它返回false。 顯然,對于1x1或1xN矩陣總是連續的。一般 用Mat :: create創建的矩陣總是連續的。
2. Mat 數據類型指針ptr 的使用
這里的 depth_.ptr(y)[x] 就是指向depth _ 的第y 行的第x個數據,數據類型為無符號的短整型。
char 是有符號的 ,uchar(unsigned char) 是無符號的,8-bit無符號整形數據,里面全是正數 。
兩者都作為字符用的話是沒有區別的,但當整數用時有區別:
char 整數范圍為-128到127( 0x80__0x7F) ;
而unsigned char 整數范圍為0到255( 0__0xFF ) 有時候想把整數數值限在255范圍內,也用unsigned char。
而本例中resultImage.ptr<uchar>(i)[j]是指向resultImage第 i 行第 j 個元素,限制數值位0-255的整數。
補充一個關于介紹滑動條的小程序:使用滑動條做調色板
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp>using namespace cv;//參數1:滑動條位置;參數2:傳入的參數 void nothing(int pos, void *) { }int main() {Mat img = Mat::zeros(300, 512, CV_8UC3);namedWindow("image");//createTrackbar函數:為窗口創建滑動條createTrackbar("B", "image", 0, 255, nothing);createTrackbar("G", "image", 0, 255, nothing);createTrackbar("R", "image", 0, 255, nothing);createTrackbar("0:off\n1:on", "image", 0, 1, nothing);while (1){imshow("image", img);if (waitKey(1) == 27)break;//getTrackbarPos函數作用:獲取滑動條的位置的值int b = getTrackbarPos("B", "image");int g = getTrackbarPos("G", "image");int r = getTrackbarPos("R", "image");int s = getTrackbarPos("0:off\n1:on", "image");if (s == 0)img = Scalar(0, 0, 0);else//opencv中顏色按BGR排列,numpy、matplotlib常用的第三方庫RGB排列img = Scalar(b, g, r); //修改Mat類對象顏色的方法}waitKey(0); }總結
- 上一篇: OpenCV与图像处理学习十三——Har
- 下一篇: Django3 --- ASGI