一起學opencv-python十(給影象加噪聲,模糊處理和影象銳化)
參考了https://www.bilibili.com/video/av24998616/?p=9
https://www.bilibili.com/video/av24998616/?p=10和
https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_filtering/py_filtering.html
給影象加噪聲
首先要知道有哪些常見的噪聲。參考了https://www.cnblogs.com/lytwajue/p/7381202.html
和https://blog.csdn.net/zh_jessica/article/details/77967650#%E5%AE%89%E8%A3%85skimage%E5%BA%93
這個也就是黑點或者白點隨機出現,一個點就是佔一個畫素咯。
這個其實很容易寫出來的。用兩個迴圈和一個random模組中的一個函式就可以寫出來。
注意是包含邊界的。
我是加了10%的噪聲,看到用了0.86,時間還是挺長的。
原圖:
加完椒鹽噪聲的圖片:
這裡要說的是我原來的1.jpg並沒有加上椒鹽噪聲,因為我們並沒有把a寫入1.jpg,只是讀進來的點陣圖資料a被改變了,但是a並不是和1.jpg關聯的。
然後是高斯噪聲:
功率譜是訊號處理相關的一個概念,和傅立葉變換有關,簡單來說就是各種頻率餘弦訊號所含能量的一個譜。
這是我自己寫出來的一個高斯噪聲的程式:
這裡先給大家倒個歉,以前好像說過cv2.add沒有廣播機制。
這裡這麼報錯了,說明是可以廣播的。
這是我寫的高斯噪聲的程式:
用了0.5s,這是我把畫素點減少到274*200個的結果,不然的話可能時間會比較長了,下面是出來的圖:
看到裡面是有一些噪聲的。
sigma是標準差。然後我們必須重新認識一下np.where函式,它的用處可不止前面學的那麼簡單:
可以說有了x,y引數的where才是完整的。x引數的意思就是如果前面的條件滿足,那麼對應的畫素替換為x,否則就替換為y。
r1=np.where((g+a[i,j])>255,255,(g+a[i,j]))
r2=np.where(r1<0,0,r1)
a[i,j]=np.round(r2)
上面三行操作是為了保證亮度是0-255之間的整數。
我要提醒你們一點的是,網上有很多加高斯噪聲的程式碼都是錯的。比如:https://blog.csdn.net/kaikai______/article/details/53535909
首先這個程式碼是單通道的,這個其實博主也承認了,第二點,它的程式碼的判斷其實寫的沒有一點意義,因為NoiseImg的dtype,也就是src的dtype,也就是我們預設用cv2.imread讀進來的圖片的dtype是uint8,那麼本來就是0-255,這裡NoiseImg[i,j]=NoiseImg[i,j]+random.gauss(means,sigma)本身就會自動溢位,也就是256會被認為是0,這個我們前面的文章裡面已經見識過溢位和飽和的巨大差別,影象處理我們需要的是飽和而不是溢位。我來演示一下:
看到下面的災難性後果了嘛?顏色完全變了哎,這就是溢位的結果。
上面的[187 244 253]為什麼變成了[193 250 3]呢?看起來不就是每個元素都加了6嗎?253+6=259-256=3。這已經溢位了,在溢位後面加判斷又有什麼用處呢?所以我的處理是不要讓a[i,j]去接受a[i,j]+r.gauss(0,1)的值,不然就會溢位。
所以諸君,慎重對待網上的一些程式碼,自己要動動腦子。還有一點我們需要注意:
當然其實也不難理解,這就是python的機制嘛,如果讓a=a+5,那麼a指向的地址空間就變了,因為改變的不是某一個子陣列,也就是不是內容,這個改變的是地址指向。如果是改變內容的話,地址指向不變,改變的是地址裡面存的內容了,還有一點,[:]改變的也是內容,這也可以解釋,畢竟它指代的是所有元素,而不是地址,其實按照c語言的中陣列指標來類比,a就是a列表的首地址。參看下圖。
那麼有沒有提供給我們現成的模組的呢?也是有的。
顯示讓我們安裝scikit-image,那麼我們就安裝這個。
mode是模式,輸入是字串,,預設是'gaussian'就是高斯噪聲,'s&p'是椒鹽噪聲。image將要被轉化為浮點型,這個下面我們會看到。
seed是起始點,可以不要。clip是True的話會對模式是高斯,泊松和斑點分佈應用在影象上之後進行修正,反之不會,預設就是真。mean就是分佈的均值是浮點數,用在高斯和斑點分佈中,預設是0。var是方差,預設為0.01。有些我們沒有用到的就先不介紹了。amount是噪聲的比例,在椒鹽,鹽和胡椒噪聲中用到。salt_vs_pepper用於s&p,是鹽(白色)和胡椒(黑色)的比例,預設是相等的0.5。
來加一個椒鹽試一試。
速度挺快的,但是顏色不知道為什麼不是黑白的。而且型別是float64,出來的都是在[0,1]之間。0是白色,1是黑色。然後是高斯分佈,用預設的均值0和方差0.01:
最後一行忽略掉,那個是我後來測試其它東西打上去的。
均值濾波
這裡面是有預設的卷積核的,blur用的就是均值濾波的卷積核。
ksize是卷積核大小。anchor是錨,也就是卷積結果放置的位置,(-1,-1)是預設的,代表核中心。bordertype是邊界補充型別,參考了https://blog.csdn.net/qianqing13579/article/details/42323397
沒錯在copyMakeBorder裡面我們就見過這幾個。
可以比較明顯的看出來,均值濾波對於高斯噪聲的效果比較好,但是對於椒鹽噪聲就不太行了。這是因為我們加的高斯噪聲的方差0.01很小,而且均值為0,很大概率高斯噪聲造成畫素點的顏色變化很小,一取平均的話就可以消除。
如果我們增大方差到0.1的話,可以看到明顯消除噪聲的效果也是非常的差。
中值濾波
中值濾波是比較難寫出對應的卷積核,不過演算法還是比較好寫的,就是取對應矩形內元素的中值作為卷積的結果。
這個ksize只需要填一個大於1的奇數就可以。而blur需要填的是元組。
看到了錯誤原因是is not a tuple。下面我們試一試中值濾波:
這個原因可能是加完噪聲之後陣列元素變成小數造成的。那麼我們就用我們上面自己寫的程式碼唄。首先我覺得既然加噪聲比較常用,我們就把它封裝成一個模組。需要注意的是模組的路徑一定要在sys.path裡面。sys.path.append可以新增路徑。
好,然後我們測試中值濾波:
我們本來設想的應該是a不變的啊,這為什麼a有噪聲呢?這是因為
沒錯,a是個陣列,在函式裡面對某一個元素賦值時會牽連的。所以我們需要改幾個地方:
我選擇改imnoise模組是因為這樣時一勞永逸。
為什麼3圖加高斯噪聲的基本看不出來效果呢,是因為標準差太小了。我設的預設值是1。
改成20可以看到明顯的效果。100的效果更明顯。
如果means是正的,會增加圖片亮度。100的效果是很顯著的。
從上面我們可以看出:中值濾波對於椒鹽噪聲和sigma較小,mean=0的高斯噪聲可以很好的處理,這是因為中值濾波卷積的結果是要取中值的,而椒鹽噪聲是0或者255,基本不可能被取到,除非有很多0或者255,也就是有很多黑色和白色,但是這樣加進去的噪聲要麼是相當於沒有加進去,黑裡加黑或者白里加白,要麼是黑裡加白或者白里加黑,這種是很容易過濾掉的。
對於sigma比較大或者means比較大的,中值濾波就比較乏力了,因為噪聲影響到的畫素比較多,取一個區域中值的時候,如果有太多畫素點的亮度偏離原來的數值太多,這個中值也會偏離比較大。
中間插播一個小知識:參考了http://lib.csdn.net/article/python/64659
高斯模糊
其實上面我們也看到了,濾波處理是有模糊的功能的,因為邊界看起來真的模糊多了,這主要還是因為卷積核的選取的原因,上一講也有可以讓邊界更清晰的卷積核,這個下面會有。
下面的圖中有一個5階的高斯卷積核,這個卷積核是根據半徑和sigma=1計算出來的。
上面還有快速計算高斯卷積的一種方法,就是用先後用兩個一維的去卷積,為什麼一維的是1,2,1呢?其實這都是預設sigma=1,然後對照下面左邊的圖,1對應的函式值大概就是0.2,最中間是0.4,所以就是1,2,1。而其實[]1,2,1]*[1;2;1]就是三階的核,只不過沒有除以16。
這個可以分開的原因是兩個維度是垂直的,也就是是獨立的,也就是引數ρ等於0。
那麼
加入ρ=0,二維的邊緣概率密度就等於兩個一維的概率密度之積。這裡稍微涉及了一點概率論的知識。
ksize引數有兩種選擇,就是寬和高都是正奇數或者它們都給0,根據sigma來計算它們?這裡我懷疑是用3σ原理。
如果σx和σy都是0,那麼就按照kszie來計算,我懷疑是直接用的σ=1。不過上面有說到getGaussianKernel是給了我們一個公式的:sigma = 0.3*((ksize-1)*0.5 - 1) + 0.8。
這個公式其實還就是根據3σ來的:參考https://blog.csdn.net/kuaile20/article/details/17606235
那麼理論就先到這裡,實戰開始了。
這是我們加了sigma是50的高斯模糊,再進行高斯過濾,似乎效果不是太好,那麼我們加大高斯卷積核的大小到11。
效果明顯更好,雖然圖片的輪廓也更模糊了,為什麼圖片的輪廓更模糊了呢?因為我們用了更大的卷積核,考慮了更多邊緣周圍畫素點的情況,那麼最後的結果就是邊緣的過渡更平滑,邊緣也就更加模糊了。增大卷積核還更加細分了權值,可以想象,是會更能消除高斯噪聲的。
但是對於椒鹽噪聲的效果其實不太理想,這是因為椒鹽噪聲非0即255嘛,但是增加核數效果還是會好,這是因為椒鹽噪聲被“人海淹沒了”,雖然它們很’“偏激’,但是如果周圍很多正常人,一加權平均,就顯得整個團體沒有那麼”偏激“。
影象銳化
由於opencv似乎沒有直接提供影象銳化的API,我們需要用自定義卷積核來自己實現銳化。
用的就是filter2D函式來自定義卷積核。ddepth是資料深度,我們就照著填-1吧,kernel就是我們的卷積核,如果想各個通道的卷積核不一樣,哪那麼就先把影象用cv2.split分為三通道,然後每個通道分別用。delta是在輸出之前,卷積之後要整體加的一個數。
好的,那麼我們回顧一下之前的銳化的核是什麼。
5階的,上面已經基本上說明了原理了,如果不是邊緣,這個值一般和原來差別不大,因為周圍畫素點的顏色差別不大,差別很大說明是邊緣;如果是邊緣,那麼它周圍的畫素點的顏色肯定差別比較大了,一邊是和邊緣點的顏色相近的,還有一邊是差別較大的,那麼我覺得邊緣出來的效果就有可能是亮度比較高或者比較低。
效果還是比較明顯的,有噪聲的話,噪聲也會更加明顯。
我們最後來學一下什麼叫做depth,-1又是什麼意思?參考了https://blog.csdn.net/sz76211822/article/details/47278185
還有https://blog.csdn.net/yiyuehuan/article/details/43703067
我想-1應該是跟隨其它輸入的意思。上面我們填-1也就是跟隨a的dtype了,
改一下就會跟著變。
我們先到這裡。