首頁 > 軟體

如何利用python正確地為影象新增高斯噪聲

2022-03-17 10:00:50

開門見山,直接使用 skimage 庫為影象新增高斯噪聲是很簡單的:

import skimage

origin = skimage.io.imread("./lena.png")
noisy = skimage.util.random_noise(origin, mode='gaussian', var=0.01)

但是如果不用庫函數而自己實現的話,有幾個問題是值得注意的。

彩圖 or 灰度圖

讀取圖片時,圖片可能是有三通道的RGB圖片,也有可能是單通道的灰度圖,甚至四通道的RGBA圖。

通道數不同會影響影象資料的 shape ,例如: (256, 256, 3) 、(256, 256)

很多人按照MATLAB的習慣,使用 np.random.randn 來生成高斯噪聲,則需要根據通道數調整引數。

# RGB
noise = sigma * np.random.randn(256, 256, 3)
# GRAY
noise = sigma * np.random.randn(256, 256)

為了通用的處理,最好使用 np.random.normal 生成高斯噪聲。

noise = np.random.normal(mean, var ** 0.5, image.shape)

前兩個引數分別為 均值和標準差,第三個引數為生成資料的 shape,直接將影象本身shape輸入進去,更加優雅。

uint8 or float64

一般遇到的影象都是8bit的,用skimage或opencv讀取後會發現資料型別是uint8的ndarray,取值範圍是 [0, 255] 。

如果手賤直接在整型資料上新增高斯噪聲,如:

image = io.imread("lena.png")
noise = np.random.normal(0, 10, image.shape)
noisy = image + noise

你會發現 plt.imshow 出來的是一片空白,或者有零星幾個噪點。

以一個畫素為例分析原因:

  • 影象本身是[0, 255]的整數:[226 137 125]
  • 生成的噪聲是浮點數:[-2.92864248 4.04786763 12.23436435]
  • 相加後最後的資料:[223.07135752 141.04786763 137.23436435]

matplotlib 的 imshow 要求輸入是 (0-1 float or 0-255 int),所以上述不倫不類的資料是無法正確顯示的(只顯示了恰好落在0-1之間的畫素)。

在很多應用中,為了方便計算,都會將影象資料轉換為浮點數,float64,取值範圍為 [0, 1]

為了轉換資料型別,最簡單的方式是直接除以255:

image = io.imread("./lena.png")
print(image.dtype)					# uint8

image = image/255
print(image.dtype)					# float64

更穩妥的做法,可以使用skimage的img_to_float()

image = img_as_float(image)

這樣再新增高斯噪聲就可以正確顯示。

方差 or 標準差

高斯噪聲符合一個均值為0,方差為 σ 2 sigma^2 σ2 的高斯分佈。

均值為0,是保證影象的亮度不會有變化,而方差大小則決定了高斯噪聲的強度。

方差/標準差越大,噪聲越強。

這裡有一點點初中數學的細節,就是在[0, 1]區間內, y = x y=sqrt{x} y=x ​ 比 y = x y=x y=x 要大。

所以,設定方差為0.1,噪聲要比設定標準差為0.1大不少。注意不要用混了就可以。

是否截斷(clip)

由於需要把噪聲疊加到原影象中,因此疊加後的資料值就可能超出對應資料型別的取值範圍

如果用matplotlib顯示超出範圍的彩色影象,則可能遇到以下提示:

Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

matplotlib自動將圖片做了截斷。

而不知為何,matplotlib並不會自動對灰度圖進行截斷,例如:

疊加噪聲之後,圖片資料的最小值和最大值分別為 -0.32 和 1.25,這明顯超過了[0, 1]的範圍。

這樣顯示出的圖片是不正確的(中間影象),更像是重新將影象縮放到了[0, 1]範圍內,就像將色階向外擴了一樣,對比度也下降了。

使用 np.clip,將影象截斷到 [0, 1]之間,如右圖所示,影象明顯正常了很多。

總結

完整的程式碼如下:

from skimage import io, img_as_float
import numpy as np

mean = 0
var = 0.01

image = io.imread("./lena.png")

image = img_as_float(image)
noise = np.random.normal(mean, var**0.5, image.shape)
noisy = image + noise
noisy = np.clip(noisy, 0.0, 1.0)

當然,上述問題在 skimage.util.random_noise 中都已解決,工程中可以直接使用。

import skimage

origin = skimage.io.imread("./lena.png")
noisy = skimage.util.random_noise(origin, mode='gaussian', var=0.01)

推薦學習skimage的原始碼

參考

https://zhuanlan.zhihu.com/p/50820267

https://www.jb51.net/article/241120.htm

https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.imshow.html

https://github.com/scikit-image/scikit-image/blob/v0.17.2/skimage/util/noise.py

到此這篇關於如何利用python正確地為影象新增高斯噪聲的文章就介紹到這了,更多相關python為影象加高斯噪聲內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com