翻譯自:http://scipy-lectures.github.com/advanced/image_processing/index.htmlhtml
做者:Emmanuelle Gouillart, Gaël Varoquauxpython
圖像 = 2-D 數值數組 (或者 3-D: CT, MRI, 2D + 時間; 4-D, ...) 這裏 圖像 == Numpy數組 np.array
這個教程中使用的工具:git
numpy:基本數組操做github
scipy:scipy.ndimage
子模塊致力於圖像處理(n維圖像)。參見http://docs.scipy.org/doc/scipy/reference/tutorial/ndimage.htmlweb
from scipy import ndimage
一些例子用到了使用np.array的特殊的工具箱:api
scikit-learnapp
圖像中的常見問題有:dom
輸入/輸出,呈現圖像ide
基本操做:裁剪、翻轉、旋轉……
圖像濾鏡:消噪,銳化
圖像分割:不一樣對應對象的像素標記
更有力和完整的模塊:
目錄
將一個數組寫入文件:
In [1]: from scipy import misc In [2]: l = misc.lena() In [3]: misc.imsave('lena.png', l) # uses the Image module (PIL) In [4]: import pylab as pl In [5]: pl.imshow(l) Out[5]: <matplotlib.image.AxesImage at 0x4118110>
從一個圖像文件建立數組:
In [7]: lena = misc.imread('lena.png') In [8]: type(lena) Out[8]: numpy.ndarray In [9]: lena.shape, lena.dtype Out[9]: ((512, 512), dtype('uint8'))
8位圖像(0-255)的dtype是uint8
打開一個raw文件(相機, 3-D圖像)
In [10]: l.tofile('lena.raw') # 建立一個raw文件 In [14]: lena_from_raw = np.fromfile('lena.raw', dtype=np.int64) In [15]: lena_from_raw.shape Out[15]: (262144,) In [16]: lena_from_raw.shape = (512, 512) In [17]: import os In [18]: os.remove('lena.raw')
須要知道圖像的shape和dtype(如何區分隔數據字節)
對於大數據,使用np.memmap
進行內存映射:
In [21]: lena_memmap = np.memmap('lena.raw', dtype=np.int64, shape=(512,512))
(數據從文件讀取,而不是載入內存)
處理一個列表的圖像文件:
In [22]: for i in range(10): ....: im = np.random.random_integers(0, 255, 10000).reshape((100, 100)) ....: misc.imsave('random_%02d.png' % i, im) ....: In [23]: from glob import glob In [24]: filelist = glob('random*.png') In [25]: filelist.sort()
使用matplotlib
和imshow
將圖像呈如今matplotlib圖像(figure)中:
In [29]: l = misc.lena() In [30]: import matplotlib.pyplot as plt In [31]: plt.imshow(l, cmap=plt.cm.gray) Out[31]: <matplotlib.image.AxesImage at 0x4964990>
經過設置最大最小之增長對比:
In [33]: plt.imshow(l, cmap=plt.cm.gray, vmin=30, vmax=200) Out[33]: <matplotlib.image.AxesImage at 0x50cb790> In [34]: plt.axis('off') # 移除axes和ticks Out[34]: (-0.5, 511.5, 511.5, -0.5)
繪製等高線:1
ln[7]: plt.contour(l, [60, 211])
更好地觀察強度變化,使用interpolate=‘nearest’
:
In [7]: plt.imshow(l[200:220, 200:220], cmap=plt.cm.gray) Out[7]: <matplotlib.image.AxesImage at 0x3bbe610> In [8]: plt.imshow(l[200:220, 200:220], cmap=plt.cm.gray, interpolation='nearest') Out[8]: <matplotlib.image.AxesImage at 0x3ed3250>
其它包有時使用圖形工具箱來可視化(GTK,Qt):2
In [9]: import skimage.io as im_io In [21]: im_io.use_plugin('gtk', 'imshow') In [22]: im_io.imshow(l)
3-D可視化:Mayavi
圖形平面工具
等值面
……
圖像是數組:使用整個numpy
機理。
>>> lena = misc.lena() >>> lena[0, 40] 166 >>> # Slicing >>> lena[10:13, 20:23] array([[158, 156, 157], [157, 155, 155], [157, 157, 158]]) >>> lena[100:120] = 255 >>> >>> lx, ly = lena.shape >>> X, Y = np.ogrid[0:lx, 0:ly] >>> mask = (X - lx/2)**2 + (Y - ly/2)**2 > lx*ly/4 >>> # Masks >>> lena[mask] = 0 >>> # Fancy indexing >>> lena[range(400), range(400)] = 255
>>> lena = scipy.lena() >>> lena.mean() 124.04678344726562 >>> lena.max(), lena.min() (245, 25)
np.histogram
>>> lena = scipy.lena() >>> lx, ly = lena.shape >>> # Cropping >>> crop_lena = lena[lx/4:-lx/4, ly/4:-ly/4] >>> # up <-> down flip >>> flip_ud_lena = np.flipud(lena) >>> # rotation >>> rotate_lena = ndimage.rotate(lena, 45) >>> rotate_lena_noreshape = ndimage.rotate(lena, 45, reshape=False)
局部濾鏡:用相鄰像素值的函數替代當前像素的值。
相鄰:方形(指定大小),圓形, 或者更多複雜的_結構元素_。
scipy.ndimage
中的_高斯濾鏡_:
>>> from scipy import misc >>> from scipy import ndimage >>> lena = misc.lena() >>> blurred_lena = ndimage.gaussian_filter(lena, sigma=3) >>> very_blurred = ndimage.gaussian_filter(lena, sigma=5)
均勻濾鏡
>>> local_mean = ndimage.uniform_filter(lena, size=11)
銳化模糊圖像:
>>> from scipy import misc >>> lena = misc.lena() >>> blurred_l = ndimage.gaussian_filter(lena, 3)
經過增長拉普拉斯近似增長邊緣權重:
>>> filter_blurred_l = ndimage.gaussian_filter(blurred_l, 1) >>> alpha = 30 >>> sharpened = blurred_l + alpha * (blurred_l - filter_blurred_l)
向lena增長噪聲:
>>> from scipy import misc >>> l = misc.lena() >>> l = l[230:310, 210:350] >>> noisy = l + 0.4*l.std()*np.random.random(l.shape)
_高斯濾鏡_平滑掉噪聲……還有邊緣:
>>> gauss_denoised = ndimage.gaussian_filter(noisy, 2)
大多局部線性各向同性濾鏡都模糊圖像(ndimage.uniform_filter
)
_中值濾鏡_更好地保留邊緣:
>>> med_denoised = ndimage.median_filter(noisy, 3)
中值濾鏡:對直邊界效果更好(低曲率):
>>> im = np.zeros((20, 20)) >>> im[5:-5, 5:-5] = 1 >>> im = ndimage.distance_transform_bf(im) >>> im_noise = im + 0.2*np.random.randn(*im.shape) >>> im_med = ndimage.median_filter(im_noise, 3)
其它排序濾波器:ndimage.maximum_filter
,ndimage.percentile_filter
其它局部非線性濾波器:維納濾波器(scipy.signal.wiener
)等
非局部濾波器
_總變差(TV)_消噪。找到新的圖像讓圖像的總變差(正態L1梯度的積分)變得最小,當接近測量圖像時:
>>> # from skimage.filter import tv_denoise >>> from tv_denoise import tv_denoise >>> tv_denoised = tv_denoise(noisy, weight=10) >>> # More denoising (to the expense of fidelity to data) >>> tv_denoised = tv_denoise(noisy, weight=50)
總變差濾鏡tv_denoise
能夠從skimage
中得到,(文檔:http://scikit-image.org/docs/dev/api/skimage.filter.html#denoise-tv),可是爲了方便咱們在這個教程中做爲一個_單獨模塊_導入。
參見:http://en.wikipedia.org/wiki/Mathematical_morphology
結構元素:
>>> el = ndimage.generate_binary_structure(2, 1) >>> el array([[False, True, False], [ True, True, True], [False, True, False]], dtype=bool) >>> el.astype(np.int) array([[0, 1, 0], [1, 1, 1], [0, 1, 0]])
腐蝕 = 最小化濾鏡。用結構元素覆蓋的像素的最小值替代一個像素值:
>>> a = np.zeros((7,7), dtype=np.int) >>> a[1:6, 2:5] = 1 >>> a array([[0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 1, 1, 0, 0], [0, 0, 1, 1, 1, 0, 0], [0, 0, 1, 1, 1, 0, 0], [0, 0, 1, 1, 1, 0, 0], [0, 0, 1, 1, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0]]) >>> ndimage.binary_erosion(a).astype(a.dtype) array([[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0]]) >>> #Erosion removes objects smaller than the structure >>> ndimage.binary_erosion(a, structure=np.ones((5,5))).astype(a.dtype) array([[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0]])
膨脹:最大化濾鏡:
>>> a = np.zeros((5, 5)) >>> a[2, 2] = 1 >>> a array([[ 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0.], [ 0., 0., 1., 0., 0.], [ 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0.]]) >>> ndimage.binary_dilation(a).astype(a.dtype) array([[ 0., 0., 0., 0., 0.], [ 0., 0., 1., 0., 0.], [ 0., 1., 1., 1., 0.], [ 0., 0., 1., 0., 0.], [ 0., 0., 0., 0., 0.]])
對灰度值圖像也有效:
>>> np.random.seed(2) >>> x, y = (63*np.random.random((2, 8))).astype(np.int) >>> im[x, y] = np.arange(8) >>> bigger_points = ndimage.grey_dilation(im, size=(5, 5), structure=np.ones((5, 5))) >>> square = np.zeros((16, 16)) >>> square[4:-4, 4:-4] = 1 >>> dist = ndimage.distance_transform_bf(square) >>> dilate_dist = ndimage.grey_dilation(dist, size=(3, 3), \ ... structure=np.ones((3, 3)))
開操做:腐蝕+膨脹:
應用:移除噪聲
>>> square = np.zeros((32, 32)) >>> square[10:-10, 10:-10] = 1 >>> np.random.seed(2) >>> x, y = (32*np.random.random((2, 20))).astype(np.int) >>> square[x, y] = 1 >>> open_square = ndimage.binary_opening(square) >>> eroded_square = ndimage.binary_erosion(square) >>> reconstruction = ndimage.binary_propagation(eroded_square, mask=square)
閉操做:膨脹+腐蝕
許多其它數學分形:擊中(hit)和擊不中(miss)變換,tophat等等。
合成數據:
>>> im = np.zeros((256, 256)) >>> im[64:-64, 64:-64] = 1 >>> >>> im = ndimage.rotate(im, 15, mode='constant') >>> im = ndimage.gaussian_filter(im, 8)
使用_梯度操做(Sobel)_來找到搞強度的變化:
>>> sx = ndimage.sobel(im, axis=0, mode='constant') >>> sy = ndimage.sobel(im, axis=1, mode='constant') >>> sob = np.hypot(sx, sy)
canny濾鏡
Canny濾鏡能夠從skimage
中獲取(文檔),可是爲了方便咱們在這個教程中做爲一個_單獨模塊_導入:
>>> #from skimage.filter import canny >>> #or use module shipped with tutorial >>> im += 0.1*np.random.random(im.shape) >>> edges = canny(im, 1, 0.4, 0.2) # not enough smoothing >>> edges = canny(im, 3, 0.3, 0.2) # better parameters
須要調整幾個參數……過分擬合的風險
基於_直方圖_的分割(沒有空間信息)
>>> n = 10 >>> l = 256 >>> im = np.zeros((l, l)) >>> np.random.seed(1) >>> points = l*np.random.random((2, n**2)) >>> im[(points[0]).astype(np.int), (points[1]).astype(np.int)] = 1 >>> im = ndimage.gaussian_filter(im, sigma=l/(4.*n)) >>> mask = (im > im.mean()).astype(np.float) >>> mask += 0.1 * im >>> img = mask + 0.2*np.random.randn(*mask.shape) >>> hist, bin_edges = np.histogram(img, bins=60) >>> bin_centers = 0.5*(bin_edges[:-1] + bin_edges[1:]) >>> binary_img = img > 0.5
自動閾值:使用高斯混合模型:
>>> mask = (im > im.mean()).astype(np.float) >>> mask += 0.1 * im >>> img = mask + 0.3*np.random.randn(*mask.shape) >>> from sklearn.mixture import GMM >>> classif = GMM(n_components=2) >>> classif.fit(img.reshape((img.size, 1))) GMM(...) >>> classif.means_ array([[ 0.9353155 ], [-0.02966039]]) >>> np.sqrt(classif.covars_).ravel() array([ 0.35074631, 0.28225327]) >>> classif.weights_ array([ 0.40989799, 0.59010201]) >>> threshold = np.mean(classif.means_) >>> binary_img = img > threshold
使用數學形態學來清理結果:
>>> # Remove small white regions >>> open_img = ndimage.binary_opening(binary_img) >>> # Remove small black hole >>> close_img = ndimage.binary_closing(open_img)
練習
參看重建(reconstruction)操做(腐蝕+傳播(propagation))產生比開/閉操做更好的結果:
>>> eroded_img = ndimage.binary_erosion(binary_img) >>> reconstruct_img = ndimage.binary_propagation(eroded_img, mask=binary_img) >>> tmp = np.logical_not(reconstruct_img) >>> eroded_tmp = ndimage.binary_erosion(tmp) >>> reconstruct_final = np.logical_not(ndimage.binary_propagation(eroded_tmp, mask=tmp)) >>> np.abs(mask - close_img).mean() 0.014678955078125 >>> np.abs(mask - reconstruct_final).mean() 0.0042572021484375
練習
檢查首次消噪步驟(中值濾波,總變差)如何更改直方圖,而且查看是否基於直方圖的分割更加精準了。
_基於圖像_的分割:使用空間信息
>>> from sklearn.feature_extraction import image >>> from sklearn.cluster import spectral_clustering >>> l = 100 >>> x, y = np.indices((l, l)) >>> center1 = (28, 24) >>> center2 = (40, 50) >>> center3 = (67, 58) >>> center4 = (24, 70) >>> radius1, radius2, radius3, radius4 = 16, 14, 15, 14 >>> circle1 = (x - center1[0])**2 + (y - center1[1])**2 < radius1**2 >>> circle2 = (x - center2[0])**2 + (y - center2[1])**2 < radius2**2 >>> circle3 = (x - center3[0])**2 + (y - center3[1])**2 < radius3**2 >>> circle4 = (x - center4[0])**2 + (y - center4[1])**2 < radius4**2 >>> # 4 circles >>> img = circle1 + circle2 + circle3 + circle4 >>> mask = img.astype(bool) >>> img = img.astype(float) >>> img += 1 + 0.2*np.random.randn(*img.shape) >>> # Convert the image into a graph with the value of the gradient on >>> # the edges. >>> graph = image.img_to_graph(img, mask=mask) >>> # Take a decreasing function of the gradient: we take it weakly >>> # dependant from the gradient the segmentation is close to a voronoi >>> graph.data = np.exp(-graph.data/graph.data.std()) >>> labels = spectral_clustering(graph, k=4, mode='arpack') >>> label_im = -np.ones(mask.shape) >>> label_im[mask] = labels
合成數據:
>>> n = 10 >>> l = 256 >>> im = np.zeros((l, l)) >>> points = l*np.random.random((2, n**2)) >>> im[(points[0]).astype(np.int), (points[1]).astype(np.int)] = 1 >>> im = ndimage.gaussian_filter(im, sigma=l/(4.*n)) >>> mask = im > im.mean()
鏈接成分分析
標記鏈接成分:ndimage.label
>>> label_im, nb_labels = ndimage.label(mask) >>> nb_labels # how many regions? 23 >>> plt.imshow(label_im) <matplotlib.image.AxesImage object at ...>
計算每一個區域的尺寸,均值等等:
>>> sizes = ndimage.sum(mask, label_im, range(nb_labels + 1)) >>> mean_vals = ndimage.sum(im, label_im, range(1, nb_labels + 1))
計算小的鏈接成分:
>>> mask_size = sizes < 1000 >>> remove_pixel = mask_size[label_im] >>> remove_pixel.shape (256, 256) >>> label_im[remove_pixel] = 0 >>> plt.imshow(label_im) <matplotlib.image.AxesImage object at ...>
如今使用np.searchsorted
從新分配標籤:
>>> labels = np.unique(label_im) >>> label_im = np.searchsorted(labels, label_im)
找到關注的封閉對象區域:3
>>> slice_x, slice_y = ndimage.find_objects(label_im==4)[0] >>> roi = im[slice_x, slice_y] >>> plt.imshow(roi) <matplotlib.image.AxesImage object at ...>
其它空間測量:ndiamge.center_of_mass
,ndimage.maximum_position
等等。
能夠在分割應用限制範圍以外使用。
示例:塊平均(block mean):
m scipy import misc >>> l = misc.lena() >>> sx, sy = l.shape >>> X, Y = np.ogrid[0:sx, 0:sy] >>> regions = sy/6 * (X/4) + Y/6 # note that we use broadcasting >>> block_mean = ndimage.mean(l, labels=regions, index=np.arange(1, ... regions.max() +1)) >>> block_mean.shape = (sx/4, sy/6)
當區域不是正則的4塊狀時,使用stride技巧更有效(示例:fake dimensions with strides)
非正則空間(Non-regular-spaced)區塊:徑向平均:
>>> sx, sy = l.shape >>> X, Y = np.ogrid[0:sx, 0:sy] >>> r = np.hypot(X - sx/2, Y - sy/2) >>> rbin = (20* r/r.max()).astype(np.int) >>> radial_mean = ndimage.mean(l, labels=rbin, index=np.arange(1, rbin.max() +1))
其它測量
相關函數,傅里葉/小波譜等。
一個使用數學形態學的例子:粒度(http://en.wikipedia.org/wiki/Granulometry_%28morphology%29)
>>> def disk_structure(n): ... struct = np.zeros((2 * n + 1, 2 * n + 1)) ... x, y = np.indices((2 * n + 1, 2 * n + 1)) ... mask = (x - n)**2 + (y - n)**2 <= n**2 ... struct[mask] = 1 ... return struct.astype(np.bool) ... >>> >>> def granulometry(data, sizes=None): ... s = max(data.shape) ... if sizes == None: ... sizes = range(1, s/2, 2) ... granulo = [ndimage.binary_opening(data, \ ... structure=disk_structure(n)).sum() for n in sizes] ... return granulo ... >>> >>> np.random.seed(1) >>> n = 10 >>> l = 256 >>> im = np.zeros((l, l)) >>> points = l*np.random.random((2, n**2)) >>> im[(points[0]).astype(np.int), (points[1]).astype(np.int)] = 1 >>> im = ndimage.gaussian_filter(im, sigma=l/(4.*n)) >>> >>> mask = im > im.mean() >>> >>> granulo = granulometry(mask, sizes=np.arange(2, 19, 4))