1. 程式人生 > >python計算機視覺程式設計——基本的影象操作和處理

python計算機視覺程式設計——基本的影象操作和處理

python計算機視覺程式設計——第一章(基本的影象操作和處理)

 

第1章 基本的影象操作和處理

1.1 PIL:Python影象處理類庫

PIL(Python Imaging Library,影象處理庫)提供了通用的影象處理功能,以及大量有用的基本影象操作。PIL庫已經整合在Anaconda庫中,推薦使用Anaconda,簡單方便,常用庫都已經整合。

PIL簡明教程

  • 讀入一副影象:
from PIL import Image
from pylab import *

# 新增中文字型支援
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)
figure()

pil_im = Image.open('E:\python\Python Computer Vision\Image data\empire.jpg')
gray()
subplot(121)
title(u'原圖',fontproperties=font)
axis('off')
imshow(pil_im)

pil_im = Image.open('E:\python\Python Computer Vision\Image data\empire.jpg').convert('L')
subplot(122)
title(u'灰度圖',fontproperties=font)
axis('off')
imshow(pil_im)

show()

這裡寫圖片描述

1.1.1 轉換影象格式——save()函式

from PCV.tools.imtools import get_imlist #匯入原書的PCV模組
from PIL import Image
import os
import pickle

filelist = get_imlist('E:/python/Python Computer Vision/test jpg/') #獲取convert_images_format_test資料夾下的圖片檔名(包括字尾名)
imlist = open('E:/python/Python Computer Vision/test jpg/imlist.txt','wb+')
#將獲取的圖片檔案列表儲存到imlist.txt中
pickle.dump(filelist,imlist) #序列化
imlist.close()

for infile in filelist:
    outfile = os.path.splitext(infile)[0] + ".png" #分離檔名與副檔名
    if infile != outfile:
        try:
            Image.open(infile).save(outfile)
        except IOError:
            print ("cannot convert", infile)
  • 1
  •  

其中,test jpg資料夾是作者自己建立的資料夾,存放測試的**.jpg影象,原始碼證添加了部分程式碼以便將獲取的影象檔名儲存下來,同時將所有的影象轉化為.png格式,執行程式後的結果如下: 
這裡寫圖片描述 
 
這裡寫圖片描述

PIL中的open()函式用於建立PIL影象物件,sace()方法用於儲存如下到指定檔名的資料夾,上述過程將字尾變為.png,但檔名不變

1.1.2 建立縮圖

利用PIL可以很容易的建立縮圖,設定縮圖的大小,並用元組儲存起來,呼叫thumnail()方法即可生成縮圖。建立縮圖的程式碼見下面。 
例如建立最長邊為128畫素的縮圖,可以使用:

pil_im.thumbnail((128,128))
  • 1

1.1.3 複製並貼上影象區域

呼叫crop()方法即可從一幅影象中進行區域拷貝,拷貝出區域後,可以對區域進行旋轉等變換。

box=(100,100,400,400)
region=pil_im.crop(box)
  • 1
  • 2

目標區域由四元組來指定,座標依次為(左,上,右,下),PIL中指定座標系的左上角座標為(0,0),可以旋轉後利用paste()放回去,具體實現如下:

region=region.transpose(Image.ROTATE_180)
pil_im.paste(region,box)
  • 1
  • 2

1.1.4 調整尺寸和旋轉

  • 調整尺寸:利用resize()方法,引數是一個元組,用來指定新影象的大小:
out=pil_im.resize((128,128))
  • 1
  • 旋轉:利用rotate()方法,逆時針方式表示角度
out=pil_im.rotate(45)
  • 1

上述操作的程式碼如下:

from PIL import Image
from pylab import *

# 新增中文字型支援
from matplotlib.font_manager import FontProperties

font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)
figure()

# 顯示原圖
pil_im = Image.open('E:/python/Python Computer Vision/Image data/empire.jpg')
print(pil_im.mode, pil_im.size, pil_im.format)
subplot(231)
title(u'原圖', fontproperties=font)
axis('off')
imshow(pil_im)

# 顯示灰度圖
pil_im = Image.open('E:/python/Python Computer Vision/Image data/empire.jpg').convert('L')
gray()
subplot(232)
title(u'灰度圖', fontproperties=font)
axis('off')
imshow(pil_im)

# 複製並貼上區域
pil_im = Image.open('E:/python/Python Computer Vision/Image data/empire.jpg')
box = (100, 100, 400, 400)
region = pil_im.crop(box)
region = region.transpose(Image.ROTATE_180)
pil_im.paste(region, box)
subplot(233)
title(u'複製貼上區域', fontproperties=font)
axis('off')
imshow(pil_im)

# 縮圖
pil_im = Image.open('E:/python/Python Computer Vision/Image data/empire.jpg')
size = 128, 128
pil_im.thumbnail(size)
print(pil_im.size)
subplot(234)
title(u'縮圖', fontproperties=font)
axis('off')
imshow(pil_im)
pil_im.save('E:/python/Python Computer Vision/Image data/empire thumbnail.jpg')# 儲存縮圖

#調整影象尺寸
pil_im=Image.open('E:/python/Python Computer Vision/Image data/empire thumbnail.jpg')
pil_im=pil_im.resize(size)
print(pil_im.size)
subplot(235)
title(u'調整尺寸後的影象',fontproperties=font)
axis('off')
imshow(pil_im)

#旋轉影象45°
pil_im=Image.open('E:/python/Python Computer Vision/Image data/empire thumbnail.jpg')
pil_im=pil_im.rotate(45)
subplot(236)
title(u'旋轉45°後的影象',fontproperties=font)
axis('off')
imshow(pil_im)

show()
  •  
  •  

執行結果如下: 
這裡寫圖片描述

1.2 Matplotlib庫

當在處理數學及繪圖或在影象上描點、畫直線、曲線時,Matplotlib是一個很好的繪相簿,它比PIL庫提供了更有力的特性。

matplotlib教程

1.2.1 畫圖、描點和線

from PIL import Image
from pylab import *

# 新增中文字型支援
from matplotlib.font_manager import FontProperties

font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)

# 讀取影象到陣列中
im = array(Image.open('E:/python/Python Computer Vision/Image data/empire.jpg'))
figure()

# 繪製有座標軸的
subplot(121)
imshow(im)
x = [100, 100, 400, 400]
y = [200, 500, 200, 500]

# 使用紅色星狀標記繪製點
plot(x, y, 'r*')


# 繪製連線兩個點的線(預設為藍色)
plot(x[:2], y[:2])
title(u'繪製empire.jpg', fontproperties=font)

# 不顯示座標軸的
subplot(122)
imshow(im)
x = [100, 100, 400, 400]
y = [200, 500, 200, 500]

plot(x, y, 'r*')
plot(x[:2], y[:2])
axis('off')
title(u'繪製empire.jpg', fontproperties=font)

show()
# show()命令首先開啟圖形使用者介面(GUI),然後新建一個視窗,該圖形使用者介面會迴圈阻斷指令碼,然後暫停,
# 直到最後一個影象視窗關閉。每個腳本里,只能呼叫一次show()命令,通常相似指令碼的結尾呼叫。

這裡寫圖片描述

繪圖時還有很多可選的顏色和樣式,如表1-1,1-2,1-3所示,應用例程如下:

plot(x,y)          #預設為藍色實線
plot(x,y,'go-')    #帶有圓圈標記的綠線
plot(x,y,'ks:')    #帶有正方形標記的黑色虛線
  •  

表1-1 用PyLab庫繪圖的基本顏色格式命令

 

符號 顏色
‘b’ 藍色
‘g’ 綠色
‘r’ 紅色
‘c’ 青色
‘m’ 品紅
‘y’ 黃色
‘k’ 黑色
‘w’ 白色

 

表1-2 用PyLab庫繪圖的基本線型格式命令

 

符號 線型
‘-‘ 實線
‘–’ 虛線
‘:’ 點線

 

表1-3 用PyLab庫繪圖的基本繪製標記格式命令

 

符號 標記
‘.’
‘o’ 圓圈
’s’ 正方形
‘*’ 星型
‘+’ 加號
‘*’ 叉號

1.2.2 影象輪廓和直方圖

from PIL import Image
from pylab import *

# 新增中文字型支援
from matplotlib.font_manager import FontProperties

font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)
# 開啟影象,並轉成灰度影象
im = array(Image.open('E:/python/Python Computer Vision/Image data/empire.jpg').convert('L'))

# 新建一個影象
figure()
subplot(121)
# 不使用顏色資訊
gray()
# 在原點的左上角顯示輪廓影象
contour(im, origin='image')
axis('equal')
axis('off')
title(u'影象輪廓圖', fontproperties=font)

subplot(122)
# 利用hist來繪製直方圖
# 第一個引數為一個一維陣列
# 因為hist只接受一維陣列作為輸入,所以要用flatten()方法將任意陣列按照行優先準則轉化成一個一維陣列
# 第二個引數指定bin的個數
hist(im.flatten(), 128)
title(u'影象直方圖', fontproperties=font)
# plt.xlim([0,250])
# plt.ylim([0,12000])

show()

這裡寫圖片描述

1.2.3 互動式標註

有時候使用者需要和應用進行互動,比如在影象中用點做標識,或者在一些訓練資料中進行註釋,PyLab提供了一個很簡介好用的函式gitput()來實現互動式標註。

from PIL import Image
from pylab import *

im = array(Image.open('E:/python/Python Computer Vision/Image data/empire.jpg'))
imshow(im)

print('Please click 3 points')
x = ginput(3)
print('you clicked:', x)
show()

輸出:

you clicked: 
[(118.4632306896458, 177.58271393177051), 
(118.4632306896458, 177.58271393177051),
(118.4632306896458, 177.58271393177051)]

上面程式碼先讀取empire.jpg影象,顯示讀取的影象,然後用ginput()互動註釋,這裡設定的互動註釋資料點設定為3個,使用者在註釋後,會將註釋點的座標打印出來。

1.3 NumPy庫

NumPy線上文件 
NumPy是Python一個流行的用於科學計算包。它包含了很多諸如向量、矩陣、影象等其他非常有用的物件和線性代數函式。

1.3.1 影象陣列表示

在前面影象的示例中,我們將影象用array()函式轉為NumPy陣列物件,但是並沒有提到它表示的含義。陣列就像列表一樣,只不過它規定了陣列中的所有元素必須是相同的型別,除非指定以外,否則資料型別灰按照資料型別自動確定。 
舉例如下:

from PIL import Image
from pylab import *

im = array(Image.open('E:/python/Python Computer Vision/Image data/empire.jpg'))
print (im.shape, im.dtype)
im = array(Image.open('E:/python/Python Computer Vision/Image data/empire.jpg').convert('L'),'f')
print (im.shape, im.dtype)

輸出:

(800, 569, 3) uint8
(800, 569) float32

解釋:

第一個元組表示影象陣列大小(行、列、顏色通道)
第二個字串表示陣列元素的資料型別,因為影象通常被編碼為8位無符號整型;
1. uint8:預設型別
2. float32:對影象進行灰度化,並添加了'f'引數,所以變為浮點型
  • 陣列元素如何訪問——使用下標訪問
value=im[i,j,k]
  • 多個數組元素如何發給我——使用陣列切片方式訪問,返回的是以指定間隔下標訪問該陣列的元素值
im[i,:] = im[j,:]     #將第j行的數值賦值給第i行
im[:,j] = 100         #將第i列所有數值設為100
im[:100,:50].sum()    #計算前100行、前50列所有數值的和
im[50:100,50:100]     #50~100行,50~100列,不包含第100行和100列
im[i].mean()          #第i行所有數值的平均值
im[:,-1]              #最後一列
im[-2,:]/im[-2]       #倒數第二行

1.3.2 灰度變換

將影象讀入NumPy陣列物件後,我們可以對它們執行任意數學操作,一個簡單的例子就是影象的灰度變換,考慮任意函式ff,它將0~255對映到自身,也就是輸出區間和輸入區間相同。 
舉例如下:

from PIL import Image
from numpy import *
from pylab import *

im=array(Image.open('E:/python/Python Computer Vision/Image data/empire.jpg').convert('L'))
print(int(im.min()),int(im.max()))

im2=255-im               #對影象進行反向處理
print(int(im2.min()),int(im2.max())) #檢視最大/最小元素

im3=(100.0/255)*im+100   #將影象畫素值變換到100...200區間
print(int(im3.min()),int(im3.max()))

im4=255.0*(im/255.0)**2  #對畫素值求平方後得到的影象
print(int(im4.min()),int(im4.max()))

figure()
gray()
subplot(131)
imshow(im2)
axis('off')
title(r'$f(x)=255-x$')

subplot(132)
imshow(im3)
axis('off')
title(r'$f(x)=\frac{100}{255}x+100$')

subplot(133)
imshow(im4)
axis('off')
title(r'$f(x)=255(\frac{x}{255})^2$')

show()

輸出:

3 255
0 252
101 200
0 255

這裡寫圖片描述

  • array變換的相反操作可以利用PIL的fromarray()函式來完成
pil_im=Image.fromarray(im)
  • 1
  • 如果之前的操作將”uint8”資料型別轉化為其他型別,則在建立PIL影象之前,需要將資料型別轉換回來:
pil_im=Image.fromarray(uint8(im))

1.3.3 影象縮放

NumPy陣列將成為我們對影象及資料進行處理的最主要工具,但是調整矩陣大小並沒有一種簡單的方法。我們可以用PIL影象物件轉換寫一個簡單的影象尺寸調整函式:

def imresize(im,sz):
    """    Resize an image array using PIL. """
    pil_im = Image.fromarray(uint8(im))

    return array(pil_im.resize(sz))

上面定義的調整函式,在imtools.py中你可以找到它。

1.3.4 直方圖均衡化

直方圖均衡化指將一幅影象的灰度直方圖變平,使得變換後的影象中每個灰度值的分佈概率都相同,該方法是對灰度值歸一化的很好的方法,並且可以增強影象的對比度。

  • 變換函式:影象中畫素值的累積分佈函式(cdf),將畫素值的範圍對映到目標範圍的歸一化操作

下面的函式是直方圖均衡化的具體實現:

def histeq(im,nbr_bins=256):
  """ 對一幅灰度影象進行直方圖均衡化"""

  # 計算影象的直方圖
  imhist,bins = histogram(im.flatten(),nbr_bins,normed=True)
  cdf = imhist.cumsum()      # 累積分佈函式
  cdf = 255 * cdf / cdf[-1]  # 歸一化
  # 此處使用到累積分佈函式cdf的最後一個元素(下標為-1),其目的是將其歸一化到0~1範圍

  # 使用累積分佈函式的線性插值,計算新的畫素值
  im2 = interp(im.flatten(),bins[:-1],cdf)

  return im2.reshape(im.shape), cdf

解釋:

  1. 該函式有兩個引數

    • 灰度影象
    • 直方圖中使用的bin的數目
  2. 函式返回值

    • 均衡化後的影象
    • 用來做畫素值對映的累積分佈函式

程式實現:

from PIL import Image
from pylab import *
from PCV.tools import imtools

# 新增中文字型支援
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)

im = array(Image.open('E:/python/Python Computer Vision/Image data/empire.jpg').convert('L'))
# 開啟影象,並轉成灰度影象
#im = array(Image.open('../data/AquaTermi_lowcontrast.JPG').convert('L'))
im2, cdf = imtools.histeq(im)

figure()
subplot(2, 2, 1)
axis('off')
gray()
title(u'原始影象', fontproperties=font)
imshow(im)

subplot(2, 2, 2)
axis('off')
title(u'直方圖均衡化後的影象', fontproperties=font)
imshow(im2)

subplot(2, 2, 3)
axis('off')
title(u'原始直方圖', fontproperties=font)
#hist(im.flatten(), 128, cumulative=True, normed=True)
hist(im.flatten(), 128, normed=True)

subplot(2, 2, 4)
axis('off')
title(u'均衡化後的直方圖', fontproperties=font)
#hist(im2.flatten(), 128, cumulative=True, normed=True)
hist(im2.flatten(), 128, normed=True)

show()
  •  
  •  

結果: 
這裡寫圖片描述

這裡寫圖片描述

1.3.5 影象平均

對影象取平均是一種影象降噪的簡單方法,經常用於產生藝術效果。假設所有的影象具有相同的尺寸,我們可以對影象相同位置的畫素相加取平均,下面是一個演示對影象取平均的例子:

def compute_average(imlist):
  """ 計算影象列表的平均影象"""

  # 開啟第一幅影象,將其儲存在浮點型陣列中
  averageim = array(Image.open(imlist[0]), 'f')

  for imname in imlist[1:]:
    try:
      averageim += array(Image.open(imname))
    except:
      print imname + '...skipped'
  averageim /= len(imlist)

  # 返回uint8 型別的平均影象
  return array(averageim, 'uint8')

這裡寫圖片描述 
注意:有可能因為某些影象打不開而導致平均的結果只是某一幅自身或某兩幅影象的平均

1.3.5 對影象進行主成分分析

PCA(Principal Component Analysis,主成分分析)是一個非常有用的降維技巧。它可以在使用盡可能少維數的前提下,儘量多地保持訓練資料的資訊,在此意義上是一個最佳技巧。即使是一幅 100×100 畫素的小灰度影象,也有 10 000 維,可以看成 10 000 維空間中的一個點。一兆畫素的影象具有百萬維。由於影象具有很高的維數,在許多計算機視覺應用中,我們經常使用降維操作。PCA 產生的投影矩陣可以被視為將原始座標變換到現有的座標系,座標系中的各個座標按照重要性遞減排列。

為了對影象資料進行 PCA 變換,影象需要轉換成一維向量表示。我們可以使用 NumPy 類庫中的flatten() 方法進行變換。

將變平的影象堆積起來,我們可以得到一個矩陣,矩陣的一行表示一幅影象。在計算主方向之前,所有的行影象按照平均影象進行了中心化。我們通常使用 SVD(Singular Value Decomposition,奇異值分解)方法來計算主成分;但當矩陣的維數很大時,SVD 的計算非常慢,所以此時通常不使用 SVD 分解。

下面就是 PCA 操作的程式碼:

from PIL import Image
from numpy import *

def pca(X):
  """ 主成分分析:
    輸入:矩陣X ,其中該矩陣中儲存訓練資料,每一行為一條訓練資料
    返回:投影矩陣(按照維度的重要性排序)、方差和均值"""

  # 獲取維數
  num_data,dim = X.shape

  # 資料中心化
  mean_X = X.mean(axis=0)
  X = X - mean_X

if dim>num_data:
  # PCA- 使用緊緻技巧
  M = dot(X,X.T) # 協方差矩陣
  e,EV = linalg.eigh(M) # 特徵值和特徵向量
  tmp = dot(X.T,EV).T # 這就是緊緻技巧
  V = tmp[::-1] # 由於最後的特徵向量是我們所需要的,所以需要將其逆轉
  S = sqrt(e)[::-1] # 由於特徵值是按照遞增順序排列的,所以需要將其逆轉
  for i in range(V.shape[1]):
    V[:,i] /= S
else:
  # PCA- 使用SVD 方法
  U,S,V = linalg.svd(X)
  V = V[:num_data] # 僅僅返回前nun_data 維的資料才合理

# 返回投影矩陣、方差和均值
return V,S,mean_X

該函式首先通過減去每一維的均值將資料中心化,然後計算協方差矩陣對應最大特徵值的特徵向量,此時可以使用簡明的技巧或者 SVD 分解。這裡我們使用了 range() 函式,該函式的輸入引數為一個整數 n,函式返回整數 0…(n-1) 的一個列表。你也可以使用 arange() 函式來返回一個數組,或者使用 xrange() 函式返回一個產生器(可能會提升速度)。我們在本書中貫穿使用range() 函式。

如果資料個數小於向量的維數,我們不用 SVD 分解,而是計算維數更小的協方差矩陣 XXT 的特徵向量。通過僅計算對應前 k(k 是降維後的維數)最大特徵值的特徵向量,可以使上面的 PCA 操作更快。由於篇幅所限,有興趣的讀者可以自行探索。矩陣 V 的每行向量都是正交的,並且包含了訓練資料方差依次減少的座標方向。

我們接下來對字型影象進行 PCA 變換。fontimages.zip 檔案包含採用不同字型的字元 a 的縮圖。所有的 2359 種字型可以免費下載 2。假定這些影象的名稱儲存在列表 imlist 中,跟之前的程式碼一起儲存傳在 pca.py 檔案中,我們可以使用下面的指令碼計算影象的主成分:

import pickle
from PIL import Image
from numpy import *
from pylab import *
from PCV.tools import imtools,pca

# Uses sparse pca codepath

# 獲取影象列表和尺寸
imlist=imtools.get_imlist('E:/python/Python Computer Vision/Image data/fontimages/a_thumbs')
# open ont image to get the size
im=array(Image.open(imlist[0]))
# get the size of the images
m,n=im.shape[:2]
# get the number of images
imnbr=len(imlist)
print("The number of images is %d" % imnbr)

# create matrix to store all flattened images
immatrix = array([array(Image.open(imname)).flatten() for imname in imlist],'f')

# PCA降維
V,S,immean=pca.pca(immatrix)

# 儲存均值和主成分
#f = open('../ch01/font_pca_modes.pkl', 'wb')
#pickle.dump(immean,f)
#pickle.dump(V,f)
#f.close()

# Show the images (mean and 7 first modes)
# This gives figure 1-8 (p15) in the book.

figure()
gray()
subplot(241)
axis('off')
imshow(immean.reshape(m,n))
for i in range(7):
    subplot(2,4,i+2)
    imshow(V[i].reshape(m,n))
    axis('off')

show()

注意,這些影象在拉成一維表示後,必須用reshape()函式將它重新轉換回來。執行上面程式碼,可得原書P15 Figure1-8中的結果,即: 
這裡寫圖片描述

1.3.6 Pickle模組

如果想要儲存一些結果或者資料以方便後續使用,Python 中的 pickle 模組非常有用。pickle模組可以接受幾乎所有的 Python 物件,並且將其轉換成字串表示,該過程叫做封裝(pickling)。從字串表示中重構該物件,稱為拆封(unpickling)。這些字串表示可以方便地儲存和傳輸。

我們來看一個例子。假設想要儲存上一節字型影象的平均影象和主成分,可以這樣來完成:

# 儲存均值和主成分資料
f = open('font_pca_modes.pkl','wb')
pickle.dump(immean,f)
pickle.dump(V,f)
f.close()

在上述例子中,許多物件可以儲存到同一個檔案中。pickle 模組中有很多不同的協議可以生成 .pkl 檔案;如果不確定的話,最好以二進位制檔案的形式讀取和寫入。在其他 Python 會話中載入資料,只需要如下使用 load() 方法:

# 載入均值和主成分資料
f = open('font_pca_modes.pkl','rb')
immean = pickle.load(f)
V = pickle.load(f)
f.close()

注意,載入物件的順序必須和先前儲存的一樣。Python 中有個用 C 語言寫的優化版本,叫做cpickle 模組,該模組和標準 pickle 模組完全相容。關於 pickle 模組的更多內容,參見pickle 模組文件頁 http://docs.python.org/library/pickle.html

在本書接下來的章節中,我們將使用 with 語句處理檔案的讀寫操作。這是 Python 2.5 引入的思想,可以自動開啟和關閉檔案(即使在檔案開啟時發生錯誤)。下面的例子使用 with() 來實現儲存和載入操作:

# 開啟檔案並儲存
with open('font_pca_modes.pkl', 'wb') as f:
  pickle.dump(immean,f)
  pickle.dump(V,f)

# 開啟檔案並載入
with open('font_pca_modes.pkl', 'rb') as f:
  immean = pickle.load(f)
  V = pickle.load(f)

上面的例子乍看起來可能很奇怪,但 with() 確實是個很有用的思想。如果你不喜歡它,可以使用之前的 open 和 close 函式。

作為 pickle 的一種替代方式,NumPy 具有讀寫文字檔案的簡單函式。如果資料中不包含複雜的資料結構,比如在一幅影象上點選的點列表,NumPy 的讀寫函式會很有用。儲存一個數組 x 到檔案中,可以使用:

savetxt('test.txt',x,'%i')

最後一個引數表示應該使用整數格式。類似地,讀取可以使用:

x = loadtxt('test.txt')

可以從線上文件瞭解更多

最後,NumPy 有專門用於儲存和載入陣列的函式,線上文件中可以檢視關於 save()和 load() 的更多內容。

1.4 SciPy

SciPy(http://scipy.org/) 是建立在 NumPy 基礎上,用於數值運算的開源工具包。SciPy 提供很多高效的操作,可以實現數值積分、優化、統計、訊號處理,以及對我們來說最重要的影象處理功能。

1.4.1 影象模糊

影象的高斯模糊是非常經典的影象卷積例子。本質上,影象模糊就是將(灰度)影象II 和一個高斯核進行卷積操作: 

Iδ=I∗GδIδ=I∗Gδ

 

其中,*表示卷積,GδGδ表示標準差為δδ的卷積核

  • 濾波操作模組——scipy.ndimage.filters

該模組可以使用快速一維分離的方式來計算卷積,使用方式如下:

from PIL import Image
from numpy import *
from pylab import *
from scipy.ndimage import filters

# 新增中文字型支援
from matplotlib.font_manager import FontProperties
font=FontProperties(fname=r"c:\windows\fonts\SimSun.ttc",size=14)

im=array(Image.open('E:/python/Python Computer Vision/Image data/empire.jpg').convert('L'))

figure()
gray()
axis('off')
subplot(141)
axis('off')
title(u'原圖',fontproperties=font)
imshow(im)

for bi,blur in enumerate([2,5,10]):
    im2=zeros(im.shape)
    im2=filters.gaussian_filter(im,blur)
    im2=np.uint8(im2)
    imNum=str(blur)
    subplot(1,4,2+bi)
    axis('off')
    title(u'標準差為'+imNum,fontproperties=font)
    imshow(im2)

#如果是彩色影象,則分別對三個通道進行模糊
#for bi, blur in enumerate([2, 5, 10]):
#  im2 = zeros(im.shape)
#  for i in range(3):
#    im2[:, :, i] = filters.gaussian_filter(im[:, :, i], blur)
#  im2 = np.uint8(im2)
#  subplot(1, 4,  2 + bi)
#  axis('off')
#  imshow(im2)

show()

這裡寫圖片描述

上面第一幅圖為待模糊影象,第二幅用高斯標準差為2進行模糊,第三幅用高斯標準差為5進行模糊,最後一幅用高斯標準差為10進行模糊。關於該模組的使用以及引數選擇的更多細節,可以參閱SciPy scipy.ndimage文件

1.4.2 影象導數

在很多應用中影象強度的變化情況是非常重要的資訊。強度的變化可以用灰度影象 II(對於彩色影象,通常對每個顏色通道分別計算導數)的 xx 和 yy方向導數 IxIx 和IyIy 進行描述。

  • 影象的梯度向量為∇I=[Ix,Iy]T∇I=[Ix,Iy]T,描述影象在每個畫素點上強度變化最大的方向。
  • 梯度有兩個重要的屬性: 
    1. 梯度的大小: 
      |∇I|=I2x+I2y−−−−−−√|∇I|=Ix2+Iy2
    2. 梯度的方向: 
      α=arctan2(Ix,Iy)α=arctan2(Ix,Iy)

NumPy中的arctan2()函式返回弧度表示的有符號角度,角度的變化區間為[−π,π][−π,π]

我們可以用離散近似的方式來計算影象的導數。影象導數大多數可以通過卷積簡單地實現: 
Ix=I∗DxIx=I∗Dx,Iy=I∗DyIy=I∗Dy 
對於,通常選擇prewitt濾波器或sobel濾波器 
這些導數濾波器可以使用scipy.ndimage.filters模組的標準卷積操作來簡單實現

from PIL import Image
from pylab import *
from scipy.ndimage import  filters
import numpy

# 新增中文字型支援
from matplotlib.font_manager import FontProperties
font=FontProperties(fname=r"c:\windows\fonts\SimSun.ttc",size=14)

im=array(Image.open('E:/python/Python Computer Vision/Image data/empire.jpg').convert('L'))
gray()

subplot(141)
axis('off')
title(u'(a)原圖',fontproperties=font)
imshow(im)

# sobel derivative filters
imx=zeros(im.shape)
filters.sobel(im,1,imx)
subplot(142)
axis('off')
title(u'(b)x方向差分',fontproperties=font)
imshow(imx)

imy=zeros(im.shape)
filters.sobel(im,0,imy)
subplot(143)
axis('off')
title(u'(c)y方向差分',fontproperties=font)
imshow(imy)

mag=255-numpy.sqrt(imx**2+imy**2)
subplot(144)
title(u'(d)梯度幅值',fontproperties=font)
axis('off')
imshow(mag)

show()

這裡寫圖片描述

高斯差分:

from PIL import Image
from pylab import *
from scipy.ndimage import filters
import numpy

# 新增中文字型支援
#from matplotlib.font_manager import FontProperties
#font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)

def imx(im, sigma):
    imgx = zeros(im.shape)
    filters.gaussian_filter(im, sigma, (0, 1), imgx)
    return imgx


def imy(im, sigma):
    imgy = zeros(im.shape)
    filters.gaussian_filter(im, sigma, (1, 0), imgy)
    return imgy


def mag(im, sigma):
    # there's also gaussian_gradient_magnitude()
    #mag = numpy.sqrt(imgx**2 + imgy**2)
    imgmag = 255 - numpy.sqrt(imgx ** 2 + imgy ** 2)
    return imgmag


im = array(Image.open('E:/python/Python Computer Vision/Image data/empire.jpg').convert('L'))
figure()
gray()

sigma = [2, 5, 10]

for i in  sigma:
    subplot(3, 4, 4*(sigma.index(i))+1)
    axis('off')
    imshow(im)
    imgx=imx(im, i)
    subplot(3, 4, 4*(sigma.index(i))+2)
    axis('off')
    imshow(imgx)
    imgy=imy(im, i)
    subplot(3, 4, 4*(sigma.index(i))+3)
    axis('off')
    imshow(imgy)
    imgmag=mag(im, i)
    subplot(3, 4, 4*(sigma.index(i))+4)
    axis('off')
    imshow(imgmag)

show()

這裡寫圖片描述

1.4.3 形態學:物件計數

形態學(或數學形態學)是度量和分析基本形狀的影象處理方法的基本框架與集合。形態學通常用於處理二值影象,但是也能夠用於灰度影象。二值影象是指影象的每個畫素只能取兩個值,通常是 0 和 1。二值影象通常是,在計算物體的數目,或者度量其大小時,對一幅影象進行閾值化後的結果。可以從 http://en.wikipedia.org/wiki/Mathematical_morphology 大體瞭解形態學及其處理影象的方式。

scipy.ndimage 中的 morphology 模組可以實現形態學操作 
scipy.ndimage 中的measurements 模組來實現二值影象的計數和度量功能

下面通過一個簡單的例子介紹如何使用它們:

from scipy.ndimage import measurements,morphology

# 載入影象,然後使用閾值化操作,以保證處理的影象為二值影象
im = array(Image.open('houses.png').convert('L'))
im = 1*(im<128)

labels, nbr_objects = measurements.label(im)
print "Number of objects:", nbr_objects
  1. 上面的指令碼首先載入該影象,通過閾值化方式來確保該影象是二值影象。通過和 1 相乘,指令碼將布林陣列轉換成二進位制表示。
  2. 然後,我們使用 label() 函式尋找單個的物體,並且按照它們屬於哪個物件將整數標籤給畫素賦值。
  3. 圖 1-12b 是 labels 陣列的影象。影象的灰度值表示物件的標籤。可以看到,在一些物件之間有一些小的連線。進行二進位制開(binary open)操作,我們可以將其移除:
# 形態學開操作更好地分離各個物件
im_open = morphology.binary_opening(im,ones((9,5)),iterations=2)

labels_open, nbr_objects_open = measurements.label(im_open)
print "Number of objects:", nbr_objects_open
  • binary_opening() 函式的第二個引數指定一個數組結構元素。

    • 該陣列表示以一個畫素為中心時,使用哪些相鄰畫素。
    • 在這種情況下,我們在 y 方向上使用 9 個畫素(上面 4 個畫素、畫素本身、下面 4 個畫素),在 x 方向上使用 5 個畫素。你可以指定任意陣列為結構元素,陣列中的非零元素決定使用哪些相鄰畫素。
    • 引數 iterations 決定執行該操作的次數。你可以嘗試使用不同的迭代次數 iterations 值,看一下物件的數目如何變化。
    • 可以在圖 1-12c 與圖 1-12d 中檢視經過開操作後的影象,以及相應的標籤影象。
  • binary_closing() 函式實現相反的操作。

    • 我們將該函式和在 morphology 和 measurements 模組中的其他函式的用法留作練習。你可以從 scipy.ndimage 模組文件 中瞭解關於這些函式的更多知識。
from PIL import Image
from numpy import *
from scipy.ndimage import measurements, morphology
from pylab import *

"""   This is the morphology counting objects example in Section 1.4.  """

# 新增中文字型支援
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)

# load image and threshold to make sure it is binary
figure()
gray()
im = array(Image.open('E:/python/Python Computer Vision/Image data/houses.png').convert('L'))
subplot(221)
imshow(im)
axis('off')
title(u'原圖', fontproperties=font)
im = (im < 128)

labels, nbr_objects = measurements.label(im)
print ("Number of objects:", nbr_objects)
subplot(222)
imshow(labels)
axis('off')
title(u'標記後的圖', fontproperties=font)

# morphology - opening to separate objects better
im_open = morphology.binary_opening(im, ones((9, 5)), iterations=2)
subplot(223)
imshow(im_open)
axis('off')
title(u'開運算後的影象', fontproperties=font)

labels_open, nbr_objects_open = measurements.label(im_open)
print ("Number of objects:", nbr_objects_open)
subplot(224)
imshow(labels_open)
axis('off')
title(u'開運算後進行標記後的影象', fontproperties=font)

show()

輸出:

Number of objects: 45
Number of objects: 48
  • 1
  • 2

這裡寫圖片描述

1.4.4 有用的SciPy模組

SciPy 中包含一些用於輸入和輸出的實用模組。下面介紹其中兩個模組:io 和 misc

1.讀寫.mat檔案

如果你有一些資料,或者在網上下載到一些有趣的資料集,這些資料以 Matlab 的 .mat 檔案格式儲存,那麼可以使用 scipy.io 模組進行讀取。

data = scipy.io.loadmat('test.mat')
  • 1

上面程式碼中,data 物件包含一個字典,字典中的鍵對應於儲存在原始 .mat 檔案中的變數名。由於這些變數是陣列格式的,因此可以很方便地儲存到 .mat 檔案中。你僅需建立一個字典(其中要包含你想要儲存的所有變數),然後使用 savemat() 函式:

data = {}
data['x'] = x
scipy.io.savemat('test.mat',data)
  • 1
  • 2
  • 3

因為上面的指令碼儲存的是陣列 x,所以當讀入到 Matlab 中時,變數的名字仍為 x。關於scipy.io 模組的更多內容,請參見線上文件

2.以影象形式儲存陣列

因為我們需要對影象進行操作,並且需要使用陣列物件來做運算,所以將陣列直接儲存為影象檔案 4 非常有用。本書中的很多影象都是這樣的建立的。

imsave() 函式:從 scipy.misc 模組中載入。要將陣列 im 儲存到檔案中,可以使用下面的命令:

from scipy.misc import imsave
imsave('test.jpg',im)
  • 1
  • 2

scipy.misc 模組同樣包含了著名的 Lena 測試影象:

lena = scipy.misc.lena()
  • 1

該指令碼返回一個 512×512 的灰度影象陣列

所有 Pylab 圖均可儲存為多種影象格式,方法是點選影象視窗中的“儲存”按鈕。

1.5 高階示例:影象去噪

我們通過一個非常實用的例子——影象的去噪——來結束本章。影象去噪是在去除影象噪聲的同時,儘可能地保留影象細節和結構的處理技術。我們這裡使用 ROF(Rudin-Osher-Fatemi)去噪模型。該模型最早出現在文獻 [28] 中。影象去噪對於很多應用來說都非常重要;這些應用範圍很廣,小到讓你的假期照片看起來更漂亮,大到提高衛星影象的質量。ROF 模型具有很好的性質:使處理後的影象更平滑,同時保持影象邊緣和結構資訊。

ROF 模型的數學基礎和處理技巧非常高深,不在本書講述範圍之內。在講述如何基於 Chambolle 提出的演算法 [5] 實現 ROF 求解器之前,本書首先簡要介紹一下 ROF 模型。

降噪綜合示例:

from pylab import *
from numpy import *
from numpy import random
from scipy.ndimage import filters
from scipy.misc import imsave
from PCV.tools import rof

""" This is the de-noising example using ROF in Section 1.5. """

# 新增中文字型支援
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)

# create synthetic image with noise
im = zeros((500,500))
im[100:400,100:400] = 128
im[200:300,200:300] = 255
im = im + 30*random.standard_normal((500,500))

U,T = rof.denoise(im,im)
G = filters.gaussian_filter(im,10)


# save the result
#imsave('synth_original.pdf',im)
#imsave('synth_rof.pdf',U)
#imsave('synth_gaussian.pdf',G)


# plot
figure()
gray()

subplot(1,3,1)
imshow(im)
#axis('equal')
axis('off')
title(u'原噪聲影象', fontproperties=font)

subplot(1,3,2)
imshow(G)
#axis('equal')
axis('off')
title(u'高斯模糊後的影象', fontproperties=font)

subplot(1,3,3)
imshow(U)
#axis('equal')
axis('off')
title(u'ROF降噪後的影象', fontproperties=font)

show()

這裡寫圖片描述

其中第一幅圖示原噪聲影象,中間一幅圖示用標準差為10進行高斯模糊後的結果,最右邊一幅圖是用ROF降噪後的影象。上面原噪聲影象是模擬出來的影象,現在我們在真實的影象上進行測試:

from PIL import Image
from pylab import *
from numpy import *
from numpy import random
from scipy.ndimage import filters
from scipy.misc import imsave
from PCV.tools import rof

""" This is the de-noising example using ROF in Section 1.5. """

# 新增中文字型支援
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)

im = array(Image.open('E:/python/Python Computer Vision/Image data/empire.jpg').convert('L'))

U,T = rof.denoise(im,im)
G = filters.gaussian_filter(im,10)


# save the result
#imsave('synth_original.pdf',im)
#imsave('synth_rof.pdf',U)
#imsave('synth_gaussian.pdf',G)


# plot
figure()
gray()

subplot(1,3,1)
imshow(im)
#axis('equal')
axis('off')
title(u'原噪聲影象', fontproperties=font)

subplot(1,3,2)
imshow(G)
#axis('equal')
axis('off')
title(u'高斯模糊後的影象', fontproperties=font)

subplot(1,3,3)
imshow(U)
#axis('equal')
axis('off')
title(u'ROF降噪後的影象', fontproperties=font)

show()

這裡寫圖片描述 
ROF降噪能夠保持邊緣和影象結構

Number of objects: 45
Number of objects: 48
  • 1
  • 2

這裡寫圖片描述