圖像處理筆記(二)
放在最開始的話
參考的資料
參考的資料是《python圖像處理》,所以說學習的順序和書中的編排也是基本相同的。書中對原理的解析非常少(幾乎沒有),所以我決定隨着看到書中的現象,隨着把原理搞清楚,順便做成文檔發到網上,也希望大家能夠通過閱讀我的文檔有所收穫。
感謝
今天下午忙完其他的事之後,忽然發現上一篇博客的瀏覽量有20!寫了這麼久博客,還從來沒這麼多人看。昨天講的SVD有點粗糙,原因是我個人也一知半解,等我的西瓜書到了之後我在再仔細研究一下,再發一篇專門用來講他們。感謝大家的支持,我會繼續寫的。
形態學:對象計數
前一陣子看到女朋友再用ImageJ處理葉片數據,結果今天就學到了。
對象計數的過程大體可以分爲:
- 將圖像轉換成灰度圖
- 對灰度圖進行二值化
- 統計圖中值爲1的連通塊的個數
下面我們對一張圖片進行計數,用的圖片並非是網絡上的開放圖片,如果大家想要練習的話,可以從網上下一些相似的圖像自己玩一玩,我用到的圖象是這個:
大家看到的樣子,是他已經縮放到了9%的樣子,所以我們知道這張圖片的尺寸很大。首先我們將圖片以灰度圖方式讀入,並且進行二值化處理,進行觀察。
from PIL import Image
from numpy import *
import matplotlib.pyplot as plt
from scipy.ndimage import measurements, morphology
import modules
def compare(im1, im2, module = 'gray'):
"""
在一張圖裏面顯示兩個圖片
"""
fig, ax = plt.subplots(figsize=(12,8),ncols=2,nrows=1)
ax[0].imshow(im1, module)
ax[1].imshow(im2, module)
plt.show()
im = array(Image.open("C:\\Users\\wangsy\\Desktop\\learning\\cl2\\leafs.jpg").convert("L"))
temp = 1*(im>140)
compare(im, temp)
我們可以發現,葉片的形狀已經基本呈現出來了,但是比較惱火的是途中的白色處還有零星的噪點(說白了就是本來應該白的地方是黑的),這顯然不是我們想要的。這時,我們有幾條路去解決這個問題:
-
一、調整二值化閾值:
二值化是需要一個閾值的,我們這張圖是以140爲閾值去做的,我們可以嘗試調整一下這個閾值,看看能不能達到降噪的目的
from PIL import Image from numpy import * import matplotlib.pyplot as plt from scipy.ndimage import measurements, morphology import modules def compare(im1, im2, module = 'gray'): """ 在一張圖裏面顯示兩個圖片 """ fig, ax = plt.subplots(figsize=(12,8),ncols=2,nrows=1) ax[0].imshow(im1, module) ax[1].imshow(im2, module) plt.show() im = array(Image.open("C:\\Users\\wangsy\\Desktop\\learning\\cl2\\leafs.jpg").convert("L")) temp1 = 1*(im>160) temp2 = 1*(im>180) compare(temp1, temp2)
上面的圖像分別是以160、180爲閾值進行二值化的圖像,可以看出來,閾值高了也未必是一件好事,同時二值化的圖像無論如何調整閾值都是會存在一定的噪點的。
-
膨脹與腐蝕
圖像的膨脹(Dilation)和腐蝕(Erosion)是兩種基本的形態學運算,主要用來尋找圖像中的極大區域和極小區域。其中膨脹類似於“領域擴張”,將圖像中的高亮區域或白色部分進行擴張,其運行結果圖比原圖的高亮區域更大;腐蝕類似於“領域被蠶食”,將圖像中的高亮區域或白色部分進行縮減細化,其運行結果圖比原圖的高亮區域更小。——這段話來源於這篇博客
我們這裏主要用腐蝕,腐蝕的主要作用就是:
- 圖像邊界收縮
- 去噪聲
- 元素分割
他的原理是使用一個結構元掃描這張圖片中的每一個元素,如果以當前元素爲中心放置結構元的區域中全是1,那麼這裏就取1,如果不是的話,就取0。結構元是什麼呢?實際上和濾波器差不多。說白了就是,有一張圖像A,還有一個用於篩選的結構元B。我們以每個A的元素爲中心,檢查其周圍和B一樣的區域,如果裏面有一個不是1的話,那麼這個元素所在位置就是0,反之則爲1。
一般使用opencv完成這個操作,但是爲了和教材貼合,我們還是用scipy.ndimage來實現這個操作吧。
from PIL import Image from numpy import * import matplotlib.pyplot as plt from scipy.ndimage import measurements, morphology, binary_erosion import modules def compare(im1, im2, module = 'gray'): """ 在一張圖裏面顯示兩個圖片 """ fig, ax = plt.subplots(figsize=(12,8),ncols=2,nrows=1) ax[0].imshow(im1, module) ax[1].imshow(im2, module) plt.show() im = array(Image.open("C:\\Users\\wangsy\\Desktop\\learning\\cl2\\leafs.jpg").convert("L")) im = 1*(im<150) im = modules.imresize(im, (im.shape[1]//6,im.shape[0]//6)) labels, nbr_objects = measurements.label(im) im_open = binary_erosion(im, iterations =6) compare(im, im_open) labels_open, nbr_objects_open = measurements.label(im_open) print(nbr_objects, nbr_objects_open)
我們使用腐蝕功能對圖片進行腐蝕,使用measurements.label對腐蝕後的圖片做了統計,下面是兩種結果的對比:
仔細觀察你會發現,腐蝕過的葉片更加瘦小,裏面的黑洞洞也更大了,並且旁邊的噪點變少了,我們使用統計功能發現:
未腐蝕統計的連通塊數目:22
腐蝕過的區域連通塊數目:10
可以看出,腐蝕過的區域的連通塊數目更加符合我們的預期。當然,在上面的代碼中,爲了增加程序運行的效率,我使用了之前章節中我們寫過的imresize函數對原本很大的圖片進行了縮放,圖片被縮放了六倍。
當然了,這一部分的問題解決的並不完美,但是由於能力有限,暫時無法全部解決,我會在以後再回來填坑的。
一些有用的Scipy模塊
讀寫.mat文件
你可以使用Scipy來對.mat文件進行讀取和存儲,操作如下:
讀取
data = scipy.io.loadmat('test.mat')
打開的data對象包含一個字典,字典的鍵表示原來的.mat文件中的變量名,當然你也可以反過來將你創建好的字典進行保存。
保存
data = {}
data['x'] = x
scipy.io.savemat('test.mat',data)
以圖像的形式保存數組
因爲我們需要對圖像進行操作,並且需要使用數組對象來做運算,所以可以直接保存爲圖像文件。imsave()函數可以從scipy.misc模塊載入。要將數組im保存到文件中,可以使用下面的命令:
from scipy.misc import imsave
imsave('test.jpg',im)
這裏面也包含了著名的Lena測試圖像:
lena = scipy.misc.lena()
woc,這是啥,我快點打開看看。
AttributeError: module ‘scipy.misc’ has no attribute ‘lena’
百度之,發現是這張圖:
其實這張圖的完整版有點讓人大跌眼鏡,好奇的可以點這個鏈接看一下,還是比較驚豔的哈。這背後還有一段八卦,好奇的可以看這裏
高級示例:圖像去噪
告訴你個好消息,第一章快學完了。
原理
這裏使用的是ROF去噪模型。據說他的數學基礎和處理技巧都非常高深,所以書裏就沒講。但是好在,書中還是有對ROF模型的簡述:
一副灰度圖像的全變差定義爲梯度範數之和。在連續表示的情況下,全變差表示爲:
在離散表示的情況下,全變差表示爲:
這裏的就是這張圖片,相當於是一個多元函數的自變量。其中上面的式子是在所有圖像座標 上取和。在ROF模型中,目標函數爲尋找降噪後的圖像U,使下式最小:
圖形學在研究的問題都是病態的問題,用人話來說就是沒法準確表示的問題,所以在早年(AI不火的時候)圖形學問題大多都是通過最小化能量函數的方法進行求解的,上面的也是一樣。我們觀察上面的式子可以發現,這個式子表示:通過調整U,來使得後面的式子達到最小化,後面的式子分爲兩部分:
- 上過初中的兄弟萌不難把它和距離公式聯繫在一起,那麼這個就表示新的圖U與原來的圖I之間的距離,這個距離實質上描述了兩張圖的差異程度
- 這部分描述的是圖像的平滑程度,我們認爲平滑的圖像噪聲會更小,這一項小的時候噪聲會更小。
我們通過權衡這兩項來均衡圖像的失真程度以及修改後圖像的平滑程度,試想如果沒有了前一項,那麼機器直接生成一張空白的圖像就可以溜之大吉。反之,如果只有前面一項,機器直接生成一個和原圖完全一樣的圖片也可以矇混過關。當然,我們也可以通過調節兩項之間的權重來調節生成的圖片的效果。
代碼
from numpy import *
def compare(im1, im2, module = 'gray'):
import matplotlib.pyplot as plt
"""
在一張圖裏面顯示兩個圖片
"""
fig, ax = plt.subplots(figsize=(12,8),ncols=2,nrows=1)
ax[0].imshow(im1, module)
ax[1].imshow(im2, module)
plt.show()
def denoise(im, U_init, tolerance = 0.1, tau = 0.125, tv_weight = 100):
"""
ROF降噪模型
輸入:
im : 有噪聲的圖像
U_int : 降噪後圖像的初始值
輸出:
去噪和去紋理後的圖像, 紋理殘留
"""
# 獲取圖像的大小
m, n = im.shape
# 初始化變量
U = U_init
Px = im # 對偶域的x 分量
Py = im # 對偶域的y 分量
error = 1
iter = 0
while(error > tolerance):
iter+=1
if(iter > 10000):
break
if(iter %1000 ==0):
compare(im,U)
Uold = U
# 變量U梯度的x分量
GradUx = roll(U, -1, axis = 1) - U
# 變量U梯度的y分量
GradUy = roll(U, -1, axis = 0) - U
# 更新對偶變量
PxNew = Px + (tau / tv_weight) * GradUx
PyNew = Py + (tau / tv_weight) * GradUy
NormNew = maximum(1, sqrt(PxNew**2 + PyNew**2))
# 更新X、Y分量
Px = PxNew / NormNew
Py = PyNew / NormNew
# 更新原始變量
RxPx = roll(Px, 1, axis = 1)
RyPy = roll(Py, 1, axis = 0)
DivP = (Px - RxPx) + (Py - RyPy) # 對偶域的散度
U = im + tv_weight * DivP # 更新原始變量
# 更新誤差
error = linalg.norm(U - Uold) / sqrt(n*m)
return U, im - U
if __name__ == "__main__":
from numpy import random
from scipy.ndimage import filters
print(1)
# 使用噪聲創建合成圖像
im = zeros((500,500))
print(1)
im[100:400,100:400] = 128
print(1)
im[200:300,200:300] = 255
print(1)
im = im + 30*random.standard_normal((500,500))
G = filters. gaussian_filter(im, 10)
compare(im,G)
U, err = denoise(im,im)
compare(U, err)
compare(U,G)
結果
這是原來的圖片:
下面的圖片中,左邊是ROF出來的,右邊是高斯出來的:
可以看出來,兩種方法都是有降噪的作用的,但是兩者之間也有所區別:
- 高斯:更像是將圖片整體同時都進行了模糊,這樣能讓圖像的平滑都變高,但卻無法保留圖像的邊緣信息。
- ROF:相較於高斯,能夠保留圖像的邊緣信息,但是在某些內部部分它的平滑效果不如高斯好
實際應用
下面我將使用圖像平滑技術來P圖(這圖已經P過了,我再PP看看會咋樣),下面是照片:
可以看到,已經是非常好看的了。
接下來我們先嚐試着將它轉換成灰度圖並且使用ROF進行降噪。
首先先將圖片轉化成灰度圖
接下來,跑我們的ROF降噪模型:
# -*- coding: utf-8 -*-
"""
Created on Wed Apr 8 21:52:32 2020
@author: wangsy
"""
import ROF
from PIL import Image
from numpy import *
import matplotlib.pyplot as plt
from scipy.ndimage import measurements, morphology, binary_erosion
import modules
im = array(Image.open("C:\\Users\\wangsy\\Desktop\\learning\\cl2\\psc.jpg").convert("L"))
print(im.shape)
imNew,_ = ROF.denoise(im,im)
ROF.compare(im, imNew)
發現並沒有什麼變化。。。爲了證明我們這幾天的學習是有用的,我決定再給她做一個直方圖均衡!
imNew,_ = modules.histeq(imNew)
ROF.compare(im, imNew)
emmmm,不說了,這年頭進醫院有點危險,我要做好防護了。不行!一定是灰度圖的問題,我要這樣完成美化任務:
- Step1:使用RGB通道讀入圖形
- Step2:對每個通道的圖形單獨進行ROF
- Step3:用生成出來的圖片裝逼,跳
import ROF
from PIL import Image
from numpy import *
import matplotlib.pyplot as plt
from scipy.ndimage import measurements, morphology, binary_erosion
import modules
im = array(Image.open("C:\\Users\\wangsy\\Desktop\\learning\\cl2\\psc.jpg"))
imNew = zeros((im.shape))
for i in range(3):
imNew[:,:,i], _ = ROF.denoise(im[:,:,i],im[:,:,i])
imNew = imNew/255.
ROF.compare(im,imNew)
這也沒啥差別啊。。啊!你看將圖片放大後,右邊的也就是P過的圖變得更白了呢!(確信)
不行,這樣的效果不夠明顯,但是我又不會別的東西,那就在再每一層加個直方圖均衡!
你快看呢!我給你染了個奶奶灰!得了,我沒招了GG。
其他真實圖片
爲什麼對於上面的圖片,我們的P圖ROF沒有用了呢?我覺得有以下原因:
- 一、P圖軟件(輕顏相機)已經對圖像進行了降噪,反覆降噪效果不大
- 二、由於現代拍攝技術的成熟再加上拍攝場景較爲明亮,圖片本身噪點就不高
我用我ipad的原相機在漆黑的夜晚拍了一張圖片,我們用這張圖片再來看一下降噪的效果:
這張照片很大,也很糊,我們需要先把它resize一下,讓他小一點,我們好快點處理。
import ROF
from PIL import Image
from numpy import *
import matplotlib.pyplot as plt
from scipy.ndimage import measurements, morphology, binary_erosion
import modules
im = array(Image.open("C:\\Users\\wangsy\\Desktop\\learning\\cl2\\IMG_0551.JPG"))
print(im.shape)
im = modules.imresize(im,(im.shape[0]//3,im.shape[0]//3))
imNew = zeros((im.shape))
for i in range(3):
imNew[:,:,i], _ = ROF.denoise(im[:,:,i],im[:,:,i])
# imNew[:,:,i], _ = modules.histeq(imNew[:,:,i])
imNew = imNew/255.
ROF.compare(im,imNew)
觀察上面的圖片我們還是看不到太多的區別,但是我們將他們放大:
我們會發現,左邊的(原圖)噪點明顯,而右邊的圖像則較爲平滑。
另外,我想介紹另一個功能,我們對這張圖進行直方圖均衡化:
import ROF
from PIL import Image
from numpy import *
import matplotlib.pyplot as plt
from scipy.ndimage import measurements, morphology, binary_erosion
import modules
im = array(Image.open("C:\\Users\\wangsy\\Desktop\\learning\\cl2\\IMG_0551.JPG"))
print(im.shape)
im = modules.imresize(im,(im.shape[0]//3,im.shape[0]//3))
imNew = zeros((im.shape))
for i in range(3):
imNew[:,:,i], _ = ROF.denoise(im[:,:,i],im[:,:,i])
imNew[:,:,i], _ = modules.histeq(imNew[:,:,i])
imNew = imNew/255.
ROF.compare(im,imNew)
小區的雨棚、水管、樹叢、牆、路都非常清晰的顯示出來了!爲了再探索一下這個東西,我決定在漆黑的臥室裏給我拍個照片,看看用這個做會怎麼樣!
左邊是給自己拍的照片,右邊是使用直方圖均衡化後的照片,屬實nb。
- 我裝逼:bulabulabula!!!
- 女朋友:哦