主題
在本節中我們将描述一種稱為圖像修複的區域填充算法。
這種圖檔修複算法的作用是可以通過使用OpenCV子產品來進行圖檔上異常劃痕或斑點等噪線、噪點的修複,而且代碼相對其他的圖檔修複算法而言要稍微簡單一些。(最後效果類似于PhotoShop)
圖像修複算法是計算機仿人視覺中的一類基本算法,算法的主要目标是填充圖像或視訊内的區域,該區域主要使用二進制掩模來進行辨別,填充通常根據需要我們來填充的區域周邊的邊界資訊來完成。
圖像修複的最常見應用是用來恢複照片中産生的一些小的噪線等,當然圖像修複還可以用于删除圖像中的小的不需要的對象,這種時候隻需要我們把不需要的對象看作一種特殊的噪線即可。
在本節中,關于圖檔的修複,我們會簡要的讨論在機器仿人視覺中較為常用的兩種修複算法,分别是INPAINT_NS和INPAINT_TELEA。
INPAINT_NS
這裡我們依靠圖檔來進行這種算法的說明,原圖如下圖左邊所示,現在假設我們圖檔破損了,破損區域如下圖右
邊所示。
那麼我們現在的問題就是我們該如何填補這個黑色區域的問題。現在我們想要的一條限制黑線,他應該有兩個這樣的特征:
(1)我們想要這樣一條限制黑線,限制黑線的起點是從下邊緣進入黑色區域,然後從上邊沿處脫離黑色區域。
(2)曲線的右邊的區域應該為藍色,而限制黑線的左邊區域應該為白色。
通過以上簡述的兩個限制條件,我們可以得知的是,這種算法要求我們得到的限制黑線有以下兩個特點:保留原有的邊緣特征,以及一條能夠繼續在平滑區域中傳播顔色資訊的限制黑線。
這個算法的創始人通過建立了一個偏微分方程來更新具有上述限制的區域内的圖像強度,算法比較複雜,是以我們就不再在這裡進行過多的描述了,這裡給出對應的論文的網站。
INPAINT_TELEA
第二種算法與之前那種算法的差別在于:它不使用拉普拉斯算子作為平滑度的估計(前面那種算法需要使用拉普拉斯算子來進行平滑度的估計)。這種算法使用的是:依靠像素的已知圖像鄰域邊上的權重平均值來對已經破損的圖像進行補繪。這裡的補繪指的是用我們已知的鄰域像素和圖像梯度來幫忙估計要修複的像素的顔色,通過這種估計可以來進行圖像的修複。
提出這種算法的論文如下所示,感興趣的讀者可以自行前往如下網址。
cv2.inpaint函數
在我們進行圖像的修複的時候,我們需要使用到的函數為:cv2.inpaint,這個函數的文法如下所示:
dst = cv2.inpaint(src, inpaintMask, inpaintRadius, flags)
·src:我們要修複的圖像。
·inpaintMask:二進制的掩碼,這裡指的是我們要修複的像素。
·inpaintRadius:表示我們要對圖檔進行修複的半徑。
·flags:我們要選用的修複算法,這裡我們使用的主要有上面說過的兩種:
(1)cv2.INPAINT_NS
(2)cv2. INPAINT_TELEA
·dst:最後我們得到的結果圖像。
INPAINT_NS修複
首先我們先來進行cv2.INPAINT_NS的修複,我們的原圖如代碼下方圖中最左邊的所示,示例代碼如下所示。
import numpy as np
import cv2
class Sketcher:
def __init__(self, windowname, dests, colors_func):
self.prev_pt = None
self.windowname = windowname
self.dests = dests
self.colors_func = colors_func
self.dirty = False
self.show()
cv2.setMouseCallback(self.windowname, self.on_mouse)
def show(self):
cv2.imshow(self.windowname, self.dests[0])
cv2.imshow(self.windowname + ": mask", self.dests[1])
# 設定相對應的滑鼠事件
def on_mouse(self, event, x, y, flags, param):
pt = (x, y)
if event == cv2.EVENT_LBUTTONDOWN:
self.prev_pt = pt
elif event == cv2.EVENT_LBUTTONUP:
self.prev_pt = None
if self.prev_pt and flags & cv2.EVENT_FLAG_LBUTTON:
for dst, color in zip(self.dests, self.colors_func()):
cv2.line(dst, self.prev_pt, pt, color, 5)
self.dirty = True
self.prev_pt = pt
self.show()
def main():
# 讀取照片
img = cv2.imread("1.jpg")
# 如果沒有打開圖檔,直接傳回
if img is None:
return
# 創造一個原圖的複制出來,友善後面顯示
img_mask = img.copy()
# 建立一個黑色的掩膜
inpaintMask = np.zeros(img.shape[:2], np.uint8)
sketch = Sketcher('image', [img_mask, inpaintMask], lambda : ((255, 255, 255), 255))
while True:
ch = cv2.waitKey()
if ch == ord('q'):
break
if ch == ord('n'):
# 使用圖像修複算法
res = cv2.inpaint(src=img_mask, inpaintMask=inpaintMask, inpaintRadius=3, flags=cv2.INPAINT_NS)
cv2.imshow('output', res)
if ch == ord('r'):
img_mask[:] = img
inpaintMask[:] = 0
sketch.show()
print('Completed')
if __name__ == '__main__':
main()
cv2.destroyAllWindows()
運作上述代碼,我們先左鍵拖動我們的滑鼠,通過滑鼠事件來在我們的圖檔上進行畫圖(将我們需要修複的部分用白色部分覆寫)我們滑鼠拖動的白色部分其實也就是我們的掩膜,是以會顯示在mask的視窗上,(每次進行算法前必須都提前準備好與我們的噪線相對應的掩膜)
通過滑鼠拖動将損壞區域完全覆寫以後,我們可以按下q鍵來進行畫布的退出,也可以按下n鍵來進行NS算法的畫面修補,如果需要再檢視原圖,也按下r鍵來檢視;下圖中間的部分表示的是已經被我們畫出的白色掩膜包含的圖檔,下圖右側的圖檔表示的是通過NS修補後的圖檔。
可以看到的是,經過NS算法修複之後,原本圖中上方的藍色劃線字樣已經完全消失了。
注意:如果在運作程式時将噪線完全覆寫後運作NS算法還有殘留部分,那麼可以增大在原圖中殘留部分周邊的白色部分的面積,通過增大掩膜的方式增大計算,進而做到更好效果的NS修改算法效果。
INPAINT_TELEA修複
在上面的例子中我們已經對有“劃痕”的圖檔進行了INPAINT_NS算法的修複,下面我們來嘗試INPAINT_TELEA的修複算法并将兩種算法來進行一些簡單的比較。我們還是采用上圖來進行修複,示例代碼如下所示。
import numpy as np
import cv2
class Sketcher:
def __init__(self, windowname, dests, colors_func):
self.prev_pt = None
self.windowname = windowname
self.dests = dests
self.colors_func = colors_func
self.dirty = False
self.show()
cv2.setMouseCallback(self.windowname, self.on_mouse)
def show(self):
cv2.imshow(self.windowname, self.dests[0])
cv2.imshow(self.windowname + ": mask", self.dests[1])
# 設定相對應的滑鼠事件
def on_mouse(self, event, x, y, flags, param):
pt = (x, y)
if event == cv2.EVENT_LBUTTONDOWN:
self.prev_pt = pt
elif event == cv2.EVENT_LBUTTONUP:
self.prev_pt = None
if self.prev_pt and flags & cv2.EVENT_FLAG_LBUTTON:
for dst, color in zip(self.dests, self.colors_func()):
cv2.line(dst, self.prev_pt, pt, color, 5)
self.dirty = True
self.prev_pt = pt
self.show()
def main():
# 讀取照片
img = cv2.imread("1.jpg")
# 如果沒有打開圖檔,直接傳回
if img is None:
return
# 創造一個原圖的複制出來,友善後面顯示
img_mask = img.copy()
# 建立一個黑色的掩膜
inpaintMask = np.zeros(img.shape[:2], np.uint8)
sketch = Sketcher('image', [img_mask, inpaintMask], lambda : ((255, 255, 255), 255))
while True:
ch = cv2.waitKey()
if ch == ord('q'):
break
if ch == ord('t'):
res=cv2.inpaint(src=img_mask,inpaintMask=inpaintMask,inpaintRadius=3, flags=cv2.INPAINT_TELEA)
cv2.imshow('output', res)
if ch == ord('r'):
img_mask[:] = img
inpaintMask[:] = 0
sketch.show()
print('Completed')
if __name__ == '__main__':
main()
cv2.destroyAllWindows()
與前面一樣的,這裡我們可以使用q鍵來進行畫布的退出,可以按下t鍵來進行TELEA算法的畫面修補,按下r鍵可以檢視我們的原圖,運作上述代碼後,與之前NS算法一樣的操作之後,我們可以得到如下兩張圖所示的效果,其分别對應:掩膜圖以及TELEA修複圖。
這裡我們可以通過嘗試分别在兩塊代碼中加入以下代碼顯示各自修複的時間來進行比較兩種修複算法的修複快慢。
Start=time.time()
#中間過程
......
print(time.time()-Start)
這個時候我們可以發現的是,就上面兩個例子而言NS算法所耗費的時間略比TELEA算法的時間要短一些,當然這也與我們選擇的掩膜有關:可以看到的是我們在NS中畫出的白色掩膜比起我們在TELEA中的掩膜面積要小一些,這個也對我們代碼中NS算法最後得出較快的運作結果有關。
感興趣的讀者可以自行對這兩種算法進行時間上的比較,就理論上而言,TELEA算法在與NS達到相同的效果的前提下,其所消耗的時間應該要比NS算法要短一些,但我們在實際運用的過程中,我們往往會發現的是,NS算法更能節約一些時間,且在相同計算量下做得比TELEA算法更好。
注意:TELEA算法因為基于的原理是快速比對算法(Fast Marching Method based),是以我們常常也稱其為FMM算法。
總結
(前一段日子在趕着寫書,被催稿(吐)導緻的本系列咕咕咕了,之後可能還會咕,但是還是會盡量快的更新,謝謝大家支援啦!)